C++ Snake Game Project
The legendary snake: eat, grow, and don't bite yourself.
What you will build
Guide a snake around a board with the keyboard, eating food to grow longer until you crash into a wall or yourself. Building it teaches the real-time game loop — update, render, sleep, repeat — plus non-blocking keyboard input, the two ingredients of every game ever made.
What you will learn
- The update/render game loop
- Storing the snake as a deque of positions
- Non-blocking keyboard input on the console
- Grid-based collision detection
Feature checklist
- Arrow-key / WASD steering
- Food spawns at random empty cells
- Snake grows with each food eaten
- Score counter and high score
- Speed increases as you grow
- Game-over screen with final score
Starter code
#include <iostream>
#include <deque>
#include <utility>
#include <random>
struct SnakeGame {
static const int W = 30, H = 15;
std::deque<std::pair<int,int>> body{{15, 7}}; // head at front
int dx = 1, dy = 0;
std::pair<int,int> food{5, 5};
int score = 0;
bool alive = true;
void step() {
auto head = body.front();
head.first += dx;
head.second += dy;
if (head.first < 0 || head.first >= W ||
head.second < 0 || head.second >= H) { alive = false; return; }
for (auto& seg : body)
if (seg == head) { alive = false; return; }
body.push_front(head);
if (head == food) {
++score;
std::mt19937 rng(std::random_device{}());
food = { std::uniform_int_distribution<int>(0, W - 1)(rng),
std::uniform_int_distribution<int>(0, H - 1)(rng) };
} else body.pop_back();
}
};
// main loop: read key without blocking, call step(),
// redraw the grid, sleep ~120ms, repeat while alive.Full walkthrough
Step 1: Model the snake as a deque of cells
struct SnakeGame holds the whole state: a std::deque<std::pair<int,int>> body with the HEAD at the front, direction dx/dy, a food cell, score, and alive flag, on a W=30, H=15 board. A deque is the perfect snake: push_front for the new head, pop_back for the tail, both O(1). Choosing the container IS the design here.
Step 2: Write step(): move, collide, eat
step() computes the new head from body.front() + (dx, dy), then judges it: outside the walls → dead; equal to any existing segment → dead. Otherwise push_front the new head; if it landed on food, score++ and respawn food (and do NOT pop the tail — that is how the snake grows); else pop_back to slide forward.
void step() {
auto head = body.front();
head.first += dx; head.second += dy;
if (head.first < 0 || head.first >= W ||
head.second < 0 || head.second >= H) { alive = false; return; }
for (auto& seg : body) if (seg == head) { alive = false; return; }
body.push_front(head);
if (head == food) { ++score; respawnFood(); }
else body.pop_back();
}Step 3: Respawn food on an EMPTY cell
Draw random (x, y) until it is not on the body — checking membership against every segment. The starter's sketch respawns anywhere; the checklist says 'random empty cells', and food spawning inside the snake is a bug players will find in the first minute.
Step 4: Read keys without blocking (POSIX way)
Put the terminal in non-canonical, no-echo mode with termios, then poll stdin each frame: if a key is waiting, read it and set dx/dy — 'w' = (0,-1), 's' = (0,1), 'a' = (-1,0), 'd' = (1,0). Reject 180° turns (dx,dy exactly opposite to current) or the snake instantly bites itself. Restore the terminal settings on exit, always — a program that leaves the terminal in raw mode leaves the shell broken.
Step 5: Draw the board from state
Each frame: clear the screen (ANSI "\033[H\033[J"), then print the grid — '#' border, 'O' head, 'o' body, '*' food — by testing every cell against the body and food. Drawing is a pure function of state: if the picture is wrong, the state is wrong, and that separation makes bugs findable.
Step 6: Run the frame loop with rising speed
while (alive) { pollKey(); step(); draw(); sleep(frameMs); } with frameMs starting near 120 and shrinking slightly with score (say 120 - score * 2, floored at 60). Sleep-based pacing on the main thread plus non-blocking input is the whole game-loop architecture of this era of games.
Step 7: Game over and high score
On death, print the final score, read the saved high score from a file, beat-announce if beaten ('New high score!'), and save. Then offer restart: reset body to a single centre cell, direction (1,0), score 0 — a full state reset, not just alive = true, or the corpse of the last game leaks into the new one.
Common mistakes
- Allowing a 180° turn (pressing left while moving right), which makes the head collide with the neck instantly.
- Popping the tail even when food was eaten, so the snake never grows; or forgetting to pop it otherwise, so it grows forever.
- Leaving the terminal in raw mode when the program exits or crashes — restore termios on every exit path.
Compile and run
g++ -std=c++17 -o snake snake.cppReading single keystrokes without Enter needs platform code: termios on Linux/macOS (shown in the lecture) or _kbhit()/_getch() from <conio.h> on Windows. Everything else — the game state and step() logic — is identical on both.
Sample run
$ ./snake
SNAKE — steer with W A S D, don't bite yourself.
+------------------------------+
| * |
| O o o |
+------------------------------+
Score: 0 High: 5
(you steer up and eat the food...)
+------------------------------+
| O |
| o o |
| * |
+------------------------------+
Score: 1 High: 5
(three more foods eaten — the snake speeds up as it grows)
Score: 4 High: 5
(you turn too late and hit the wall)
GAME OVER — final score: 4 · high score: 5
Play again? (y/n):
> nTry it in your browser
No browser demo is included for this project. Build the C++ version below — that is the real program.