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.
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.
sf::VideoMode({800, 500})sf::VideoMode({800, 500}) (curly braces), the new event loop while (const std::optional e = window.pollEvent())while (const std::optional e = window.pollEvent()) with e->is<sf::Event::Closed>()e->is<sf::Event::Closed>(), sf::Keyboard::Key::Leftsf::Keyboard::Key::Left (add ::Key::Key), and setPosition({x, y})setPosition({x, y}) / move({dx, dy})move({dx, dy}) (curly braces). The โ
complete finished code at the bottom of this page is full SFML 3 and compiles on a fresh install, use it as your reference.sf::RenderWindowsf::RenderWindow, the game loop and event polling. If you're starting here or need that boilerplate, grab it in one click: Episode 1 setup โ ยท C++ Cheatsheet โSet up the grid world and a head cell that steps on a timer.
#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)
}
}#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)
}
}
Give the snake a body using the perfect container for the job.
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);
}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);
}
snake.snake.(newHead); snake.(newHead); snake.();();
Steer the snake, and block the fatal reverse-into-yourself move.
Cell nextDir = dir;
// in the event loop:
if (const auto* kp = event->getIf<sf::Event::KeyPressed>()) {
Cell want = dir;
if (kp->code == sf::Keyboard::Key::Up) want = { 0,-1};
if (kp->code == sf::Keyboard::Key::Down) want = { 0, 1};
if (kp->code == sf::Keyboard::Key::Left) want = {-1, 0};
if (kp->code == sf::Keyboard::Key::Right) want = { 1, 0};
if (want.x != -dir.x || want.y != -dir.y)
nextDir = want;
}
// at each tick, before moving:
dir = nextDir;Cell nextDir = dir;
// in the event loop:
if (const auto* kp = event->getIf<sf::Event::KeyPressed>()) {
Cell want = dir;
if (kp->code == sf::Keyboard::Key::Up) want = { 0,-1};
if (kp->code == sf::Keyboard::Key::Down) want = { 0, 1};
if (kp->code == sf::Keyboard::Key::Left) want = {-1, 0};
if (kp->code == sf::Keyboard::Key::Right) want = { 1, 0};
if (want.x != -dir.x || want.y != -dir.y)
nextDir = want;
}
// at each tick, before moving:
dir = nextDir;
Spawn food in valid cells and grow when eaten.
#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();#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();
snake.push_front(newHead);
if (newHead.x == food.x && newHead.y == snake.push_front(newHead);
if (newHead.x == food.x && newHead.y == )
food = )
food = (snake);
else
snake.(snake);
else
snake.();();
End the game on wall hits and self-collision.
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 ...
}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 ...
}
Scale difficulty with length and add the finishing feel.
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));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));
float tickTime = std::float tickTime = std::(0.05f,
0.12f - (float)snake.(0.05f,
0.12f - (float)snake.() * 0.002f);() * 0.002f);
The whole game in one file, written in current SFML 3 (what the install step gives you today) and compiled with g++ + SFML 3.0 to confirm it builds. The step snippets above use classic SFML 2 names; this is your working reference. Build it with g++ snake.cpp -o snake -lsfml-graphics -lsfml-window -lsfml-systemg++ snake.cpp -o snake -lsfml-graphics -lsfml-window -lsfml-system then run ./snake./snake (on Windows, from the MSYS2 MinGW terminal so it finds the SFML DLLs).
// Complete C++ Snake for SFML 3. Build:
// g++ snake.cpp -o snake -lsfml-graphics -lsfml-window -lsfml-system
#include <SFML/Graphics.hpp>
#include <optional>
#include <deque>
#include <cstdlib>
int main() {
const int GRID = 25, CELL = 20;
sf::RenderWindow window(sf::VideoMode({500, 500}), "C++ Snake");
window.setFramerateLimit(60);
std::deque<sf::Vector2i> snake = {{12,12},{11,12},{10,12}};
sf::Vector2i dir = {1, 0};
sf::Vector2i food = {5, 5};
bool dead = false;
sf::Clock clock;
float acc = 0.f;
auto spawnFood = [&]() {
while (true) {
sf::Vector2i f = {std::rand() % GRID, std::rand() % GRID};
bool onSnake = false;
for (auto& c : snake) if (c == f) onSnake = true;
if (!onSnake) { food = f; return; }
}
};
spawnFood();
auto reset = [&]() {
snake = {{12,12},{11,12},{10,12}}; dir = {1,0}; dead = false; spawnFood();
};
while (window.isOpen()) {
while (const std::optional event = window.pollEvent()) {
if (event->is<sf::Event::Closed>()) window.close();
if (const auto* k = event->getIf<sf::Event::KeyPressed>()) {
using K = sf::Keyboard::Key;
if (k->code == K::Up && dir.y != 1) dir = {0,-1};
if (k->code == K::Down && dir.y != -1) dir = {0, 1};
if (k->code == K::Left && dir.x != 1) dir = {-1,0};
if (k->code == K::Right && dir.x != -1) dir = {1, 0};
if (k->code == K::Space && dead) reset();
}
}
acc += clock.restart().asSeconds();
if (!dead && acc >= 0.12f) {
acc = 0.f;
sf::Vector2i head = snake[0] + dir;
if (head.x < 0 || head.x >= GRID || head.y < 0 || head.y >= GRID) dead = true;
for (auto& c : snake) if (c == head) dead = true;
if (!dead) {
snake.push_front(head);
if (head == food) spawnFood();
else snake.pop_back();
}
}
window.clear(sf::Color(10, 14, 20));
sf::CircleShape f(8.f);
f.setPosition({food.x * (float)CELL + 2, food.y * (float)CELL + 2});
f.setFillColor(sf::Color(255, 84, 112));
window.draw(f);
for (auto& c : snake) {
sf::RectangleShape cell({CELL - 2.f, CELL - 2.f});
cell.setPosition({c.x * (float)CELL + 1, c.y * (float)CELL + 1});
cell.setFillColor(sf::Color(51, 209, 122));
window.draw(cell);
}
window.display();
}
}// Complete C++ Snake for SFML 3. Build:
// g++ snake.cpp -o snake -lsfml-graphics -lsfml-window -lsfml-system
#include <SFML/Graphics.hpp>
#include <optional>
#include <deque>
#include <cstdlib>
int main() {
const int GRID = 25, CELL = 20;
sf::RenderWindow window(sf::VideoMode({500, 500}), "C++ Snake");
window.setFramerateLimit(60);
std::deque<sf::Vector2i> snake = {{12,12},{11,12},{10,12}};
sf::Vector2i dir = {1, 0};
sf::Vector2i food = {5, 5};
bool dead = false;
sf::Clock clock;
float acc = 0.f;
auto spawnFood = [&]() {
while (true) {
sf::Vector2i f = {std::rand() % GRID, std::rand() % GRID};
bool onSnake = false;
for (auto& c : snake) if (c == f) onSnake = true;
if (!onSnake) { food = f; return; }
}
};
spawnFood();
auto reset = [&]() {
snake = {{12,12},{11,12},{10,12}}; dir = {1,0}; dead = false; spawnFood();
};
while (window.isOpen()) {
while (const std::optional event = window.pollEvent()) {
if (event->is<sf::Event::Closed>()) window.close();
if (const auto* k = event->getIf<sf::Event::KeyPressed>()) {
using K = sf::Keyboard::Key;
if (k->code == K::Up && dir.y != 1) dir = {0,-1};
if (k->code == K::Down && dir.y != -1) dir = {0, 1};
if (k->code == K::Left && dir.x != 1) dir = {-1,0};
if (k->code == K::Right && dir.x != -1) dir = {1, 0};
if (k->code == K::Space && dead) reset();
}
}
acc += clock.restart().asSeconds();
if (!dead && acc >= 0.12f) {
acc = 0.f;
sf::Vector2i head = snake[0] + dir;
if (head.x < 0 || head.x >= GRID || head.y < 0 || head.y >= GRID) dead = true;
for (auto& c : snake) if (c == head) dead = true;
if (!dead) {
snake.push_front(head);
if (head == food) spawnFood();
else snake.pop_back();
}
}
window.clear(sf::Color(10, 14, 20));
sf::CircleShape f(8.f);
f.setPosition({food.x * (float)CELL + 2, food.y * (float)CELL + 2});
f.setFillColor(sf::Color(255, 84, 112));
window.draw(f);
for (auto& c : snake) {
sf::RectangleShape cell({CELL - 2.f, CELL - 2.f});
cell.setPosition({c.x * (float)CELL + 1, c.y * (float)CELL + 1});
cell.setFillColor(sf::Color(51, 209, 122));
window.draw(cell);
}
window.display();
}
}