Kelick Tech Hub

Build, learn & keep up — tech, Linux & sports news included.

C++ Snake Game Project

Intermediate•2–3 days•game loop, std::deque, console rendering

The legendary snake: eat, grow, and don't bite yourself.

Share on X

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

Feature checklist

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

Compile and run

g++ -std=c++17 -o snake snake.cpp

Reading 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):
> n

Try it in your browser

No browser demo is included for this project. Build the C++ version below — that is the real program.

Related Intermediate projects

← All 60 projects · Classic site (Learn & Sandbox)

Download Fake GPS