โš™๏ธ My First C++ Game ยท Episode 2 of 7 ยท See All Episodes
๐Ÿ Episode 2 ยท Beginner+ ยท Data Structures

Grow Long!
Snake in C++

The 1997 classic rebuilt with modern C++: a std::deque body, grid movement on a timer, food, self-collision and speed-up. This episode is secretly about data structures, the fun way.

๐Ÿ‘ถ Ages 12+ โฑ๏ธ ~1.5 Hours โš™๏ธ C++ & SFML โœ“ Free
๐Ÿ Snake logic ๐Ÿ“š std::deque ๐Ÿงญ Direction state ๐ŸŽ Food spawning ๐Ÿ’€ Self-collision โฑ๏ธ Tick timing
โญ
0 XP
Level 1
๐Ÿ”ฅ0
Your Progress 0 / 6 steps
1
๐Ÿงญ
The Grid & Snake Head
A 25ร—25 cell world with one moving segment
Active
๐ŸŽฏ
Goal for this step

Set up the grid world and a head cell that steps on a timer.

  • 1New project snake.cpp: 500ร—500 window, framerate 60. The world is 25ร—25 cells of 20 px.
  • 2A cell is a coordinate pair: struct Cell { int x, y; };, your first struct!
  • 3Movement is TICK-based: an sf::Clock accumulates time; every 0.12 s the snake takes one grid step.
  • 4Direction is a Cell too: {1, 0} means right. Head position += direction each tick.
snake.cpp
#include <SFML/Graphics.hpp>
#include <deque>

struct Cell { int x, y; };

int main() {
    sf::RenderWindow window(sf::VideoMode(500, 500), "C++ Snake");
    window.setFramerateLimit(60);

    Cell head{12, 12};
    Cell dir{1, 0};
    sf::Clock tick;

    while (window.isOpen()) {
        // events...
        if (tick.getElapsedTime().asSeconds() > 0.12f) {
            tick.restart();
            head.x += dir.x;
            head.y += dir.y;
        }
        // draw head as a 20x20 rectangle at (head.x*20, head.y*20)
    }
}
โœ๏ธ
Fill in the Blanks
+15 XP
A bundles related data, Cell holds an x and y. The snake steps once per (0.12 s), not once per frame.
๐Ÿง 
Knowledge Check
+15 XP
Why does Snake use tick-based movement instead of smooth per-frame movement?
AC++ is too slow for smooth movement
BSnake is a GRID game, whole-cell steps at fixed intervals ARE the classic feel
Csf::Clock only ticks slowly
2
๐Ÿ
The Body: std::deque
Grow at the front, shrink at the back
Locked
๐ŸŽฏ
Goal for this step

Give the snake a body using the perfect container for the job.

  • 1The body is a std::deque<Cell>, a list that is fast at BOTH ends.
  • 2The movement trick that powers every Snake ever: each tick push_front(new head) and pop_back().
  • 3The middle never moves! Only the ends change, that is why deque (double-ended queue) is the right tool.
  • 4Draw every segment; make the head brighter.
snake.cpp
std::deque<Cell> snake = { {12,12}, {11,12}, {10,12} };

// each tick:
Cell newHead{ snake.front().x + dir.x,
              snake.front().y + dir.y };
snake.push_front(newHead);
snake.pop_back();            // remove the tail

// drawing:
sf::RectangleShape seg(sf::Vector2f(18, 18));
for (auto& c : snake) {
    seg.setPosition(c.x * 20.f + 1, c.y * 20.f + 1);
    seg.setFillColor(&c == &snake.front()
        ? sf::Color(6, 214, 160) : sf::Color(4, 160, 120));
    window.draw(seg);
}
๐Ÿ’ก
Choosing the right container IS the skill here: vector (end-only), deque (both ends), map (lookups). Snake naturally teaches deque.
โŒจ๏ธ
Code Challenge
+20 XP
Move the snake one step using the two-ended trick:
snake.cpp
snake.(newHead);
snake.();
๐Ÿ’ก Hint: The new head is added at the front; the tail is removed from the back.
๐Ÿง 
Knowledge Check
+15 XP
What happens if you push_front WITHOUT pop_back?
AThe snake teleports
BThe snake grows by one segment, which is exactly how eating will work!
CA compile error
3
๐ŸŽฎ
Steering & the 180ยฐ Rule
Arrow keys change direction, but no instant reversal
Locked
๐ŸŽฏ
Goal for this step

Steer the snake, and block the fatal reverse-into-yourself move.

  • 1On arrow KeyPressed events, set dir, but ONLY if the new direction is not the exact opposite of the current one.
  • 2Opposite check: newDir.x != -dir.x || newDir.y != -dir.y.
  • 3Subtle classic bug: pressing two keys within one tick can still reverse you. Fix: store nextDir and apply it once per tick.
  • 4That two-step input buffer is real production-grade thinking.
snake.cpp
Cell nextDir = dir;

// in the event loop:
if (event.type == sf::Event::KeyPressed) {
    Cell want = dir;
    if (event.key.code == sf::Keyboard::Up)    want = { 0,-1};
    if (event.key.code == sf::Keyboard::Down)  want = { 0, 1};
    if (event.key.code == sf::Keyboard::Left)  want = {-1, 0};
    if (event.key.code == sf::Keyboard::Right) want = { 1, 0};
    if (want.x != -dir.x || want.y != -dir.y)
        nextDir = want;
}

// at each tick, before moving:
dir = nextDir;
โœ๏ธ
Fill in the Blanks
+15 XP
Reversing 180ยฐ would mean instant (head into neck). We buffer input in and apply it once per tick to kill the double-press bug.
๐Ÿง 
Knowledge Check
+15 XP
The double-key-press bug happens becauseโ€ฆ
AKeyboards are slow
BTwo direction changes can land between ticks: upโ†’leftโ†’down reads as legal twice but reverses overall
CSFML drops events
4
๐ŸŽ
Food & Growing
Random spawning that avoids the snake body
Locked
๐ŸŽฏ
Goal for this step

Spawn food in valid cells and grow when eaten.

  • 1Food is one Cell, drawn red. Eating: new head equals food position โ†’ skip pop_back (grow!) and respawn food.
  • 2Respawn must NOT land on the snake: loop random positions until one is free.
  • 3Seed randomness once at the top of main: srand(time(0));.
  • 4Score = snake length โˆ’ 3. Show it in the title bar with window.setTitle, no font file needed!
snake.cpp
#include <cstdlib>
#include <ctime>

Cell food{18, 12};

Cell spawnFood(const std::deque<Cell>& snake) {
    while (true) {
        Cell f{ rand() % 25, rand() % 25 };
        bool onSnake = false;
        for (auto& c : snake)
            if (c.x == f.x && c.y == f.y) onSnake = true;
        if (!onSnake) return f;
    }
}

// in the tick:
snake.push_front(newHead);
if (newHead.x == food.x && newHead.y == food.y)
    food = spawnFood(snake);     // grew! no pop_back
else
    snake.pop_back();
โŒจ๏ธ
Code Challenge
+20 XP
Grow only when food is eaten:
snake.cpp
snake.push_front(newHead);
if (newHead.x == food.x && newHead.y == )
    food = (snake);
else
    snake.();
๐Ÿ’ก Hint: Compare both coordinates; on a hit respawn the food and skip the tail removal; otherwise remove the tail as usual.
๐Ÿง 
Knowledge Check
+15 XP
Why must spawnFood check the snake body?
AFood on the snake looks bad
BFood inside the body can be unreachable or auto-eaten, on a long snake the game breaks
Crand() requires it
5
๐Ÿ’€
Death: Walls & Self-Bite
Two ways to die, one game-over state
Locked
๐ŸŽฏ
Goal for this step

End the game on wall hits and self-collision.

  • 1Wall death: new head outside 0-24 in either axis.
  • 2Self-bite: new head equals ANY body cell, loop and compare (check BEFORE pushing the new head).
  • 3On death: set a gameOver flag, stop ticking, tint the snake grey, show the score, R restarts.
  • 4The self-bite check is why Snake gets hard: the hazard is your own success.
snake.cpp
bool gameOver = false;

// in the tick, before push_front:
if (newHead.x < 0 || newHead.x > 24 ||
    newHead.y < 0 || newHead.y > 24)
    gameOver = true;

for (auto& c : snake)
    if (c.x == newHead.x && c.y == newHead.y)
        gameOver = true;

if (!gameOver) {
    snake.push_front(newHead);
    // ... food / pop_back ...
}
โœ๏ธ
Fill in the Blanks
+15 XP
The grid runs from 0 to in both axes. Self-collision compares the new head against every cell before it is pushed.
๐Ÿง 
Knowledge Check
+15 XP
What makes Snake harder the better you play?
AThe walls close in
BYour own body is the obstacle, every food eaten makes the hazard longer
CThe food gets smaller
6
โฑ๏ธ
Speed-Up & Polish
Faster ticks as you grow, plus juicy touches
Locked
๐ŸŽฏ
Goal for this step

Scale difficulty with length and add the finishing feel.

  • 1Speed from length: tickTime = std::max(0.05f, 0.12f - snake.size() * 0.002f), every meal shaves 2 ms, floored at 50 ms.
  • 2Flash the food (alternate brightness with a frame counter), and darken each body segment slightly toward the tail (index-based colour).
  • 3High score: keep a static best across restarts and show both in the title.
  • 4Ship it. You have used structs, deques, const references and range-for, real modern C++.
snake.cpp
float tickTime = std::max(0.05f,
    0.12f - (float)snake.size() * 0.002f);

if (tick.getElapsedTime().asSeconds() > tickTime) {
    // ... the whole tick ...
}

// tail fade while drawing (i = index):
int shade = 160 - std::min(100, (int)i * 4);
seg.setFillColor(sf::Color(6, shade + 54, 120 + shade/3));
โŒจ๏ธ
Code Challenge
+20 XP
Make the game speed up with length, safely floored:
snake.cpp
float tickTime = std::(0.05f,
    0.12f - (float)snake.() * 0.002f);
๐Ÿ’ก Hint: A floor needs the bigger-of-two function; the snakeโ€™s length comes from its container.
๐Ÿง 
Knowledge Check
+15 XP
The tick floor of 0.05 s exists becauseโ€ฆ
ASFML cannot go faster
BPast ~20 ticks/second humans cannot react, the floor keeps maximum difficulty humanly playable
CThe deque overflows
๐ŸŽ‰๐Ÿ†๐ŸŽฎโœจ๐ŸŽ‰
Workshop Complete!
Deques, grids and tick-based movement, Snake is a data-structures lesson in disguise and you aced it. Episode 3: Breakout!
0
Total XP
1
Level
0
Best Streak
0%
Accuracy
โ–ถ Episode 3: Breakout โ†’
โญ View My Progress & Certificates

This workshop was free and took many hours to build. If it helped you learn something new, consider supporting the project.

☕ Support on Ko-fi