Just a Simple Snake Game
I chose to finally learn Rust. I had tried before, mostly with courses and tutorials, and those tries were unsuccessful. I did finish some small projects at the time, but it was slow and unsatisfying.
This time I chose to avoid tutorial hell. It’s frustrating to just watch videos and write down a few examples to test. I won’t deny it helps; I had tried to learn Rust before, always on courses and YouTube, so I probably started with a good understanding of the language already.
I chose a small project: a snake game on the CLI. No engine, minimal libs. I wanted zero dependencies, and I got close: the one thing bare Rust cannot do is non-blocking keyboard input, and I went as far as the kilo raw mode chapter and the termios man pages before I folded and took crossterm. The only other crate is rand, for dropping the food.

There are things here I had never seen in other languages. VecDeque was the first: the snake is literally a queue, it gains a head at the front and loses the tail at the back, every frame. With a Vec that tail cut would shift the whole body one slot; the deque does both ends in O(1). It was my first data structure picked for a reason instead of by habit.
The other was how the board draws itself. The O and the * in the gif above are the entire rendering: one enum, three characters, and a Display impl:
#[derive(Copy, Clone, PartialEq)]
pub enum Tile {
Empty,
Snake,
Food,
}
impl fmt::Display for Tile {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let c = match self {
Tile::Empty => ' ',
Tile::Snake => 'O',
Tile::Food => '*',
};
write!(f, "{c}")
}
}
The layout stayed boring on purpose: six files (main, game, board, snake, food, utils), no architecture to speak of. I still rewrote it once. In the first scaffold the board owned everything and the game rendered an empty grid forever; the redo made the board dumb, geometry and render only, and put Snake and Food as siblings inside Game. The shape came from the design posts I read early on: Game Loop and Update Method from Game Programming Patterns, plus Catherine West’s RustConf keynote. Positions are plain (usize, usize) indices instead of references between entities, her trick for staying out of the borrow checker’s way.
The whole thing took ten days and four commits, from an empty board on May 25 to WASD movement and wraparound, then food, growth, and reset on self-collision on June 4.
Discuss on Bluesky →
Comments on Bluesky
No comments yet. Reply on Bluesky and it shows up here.