Joystick Snake Game
Intermediate45 minThe classic snake game on an 8x8 LED screen: you steer the snake with the joystick, and when it eats the blinking food it grows one cell longer, gets a little faster and the buzzer gives a short beep. The screen is small, so the snake leaves one edge and comes back in at the other; when it runs into its own body the game is over, three falling notes play, your score shows on the screen in digits, and pressing the joystick's button starts a new game. It teaches reading an analog joystick and turning it into a direction, keeping a game in arrays (the snake's cells), setting the game's speed with millis(), and driving a MAX7219 LED matrix with a few wires. Install "LedControl" from the Library Manager.
Build it step by step in the appWhat you need
1 × Arduino Uno R3
1 × Breadboard 830 Points (MB-102)
1 × MAX7219 8x8 Dot Matrix LED Module
1 × XY Joystick Module
1 × Passive Buzzer (Piezo)
6 × Jumper Wire - Male/Male
10 × Jumper Wire - Male/Female
Wiring steps
- 1
Connect and join the power rails

Plug a red male-male jumper wire into the 5V pin on the Arduino's power header and connect its other end to the breadboard's bottom red (+) rail. Plug a black wire into the GND pin on the same header and connect it to the bottom blue (−) rail. The breadboard's top and bottom rails are not connected to each other: at the right end of the breadboard, join the bottom (+) rail to the top (+) rail with a red wire, and the bottom (−) rail to the top (−) rail with a black wire. Now all four rails carry 5V and GND.
- Arduino 5V → the bottom red (+) rail (red wire)
- Arduino GND → the bottom blue (−) rail (black wire)
- the bottom red (+) rail → the top red (+) rail (red wire)
- the bottom blue (−) rail → the top blue (−) rail (black wire)
- 2
Connect the 8x8 LED matrix

The matrix module has 5 pins on each side: the side marked DIN is the input, the other side (DOUT) is for chaining another matrix. Push the female ends of male-female wires onto the input side's pins. Connect VCC to the top (+) rail, GND to the top (−) rail, and DIN to the Arduino's pin 12, CS to 10 and CLK to 11 (DIN_PIN, CS_PIN, CLK_PIN). Set the matrix in front of you with its input pins at the bottom.
- LED matrix VCC → the top red (+) rail (red wire)
- LED matrix GND → the top blue (−) rail (black wire)
- Arduino pin 12 → LED matrix DIN (blue wire)
- Arduino pin 10 → LED matrix CS (yellow wire)
- Arduino pin 11 → LED matrix CLK (green wire)
- 3
Connect the joystick

Push the female ends of male-female wires onto the joystick module's 5 pins. Connect GND to the bottom (−) rail and +5V to the bottom (+) rail. Connect VRx (horizontal axis) to the Arduino's A0 pin, VRy (vertical axis) to A1, and SW (the button that closes when you press the stick) to pin 2. The code gives the button its resistor with the Arduino's own pull-up, so none is needed.
- joystick +5V → the bottom red (+) rail (red wire)
- joystick GND → the bottom blue (−) rail (black wire)
- Arduino A0 → joystick VRx (orange wire)
- Arduino A1 → joystick VRy (purple wire)
- Arduino pin 2 → joystick SW (white wire)
- 4
Add and wire the buzzer

Plug the passive buzzer's + leg into column 15 in the top half and its other leg into column 16; the + leg is usually the longer one and the body has a + mark. Connect the Arduino's pin 8 to column 15 with an orange wire (BUZZER_PIN 8). Connect column 16 to the top (−) rail with a short black wire.
- buzzer + leg → column 15, top half
- buzzer − leg → column 16, top half
- Arduino pin 8 → column 15, top half (orange wire)
- column 16, top half → the top blue (−) rail (black wire)
Arduino sketch
// Install from the Library Manager:
// "LedControl" (Eberhard Fahle)
#include <LedControl.h>
// The MAX7219 module's DIN, CLK, CS pins
const int DIN_PIN = 12;
const int CLK_PIN = 11;
const int CS_PIN = 10;
// Joystick: two axes and its push button
const int JOY_X = A0;
const int JOY_Y = A1;
const int JOY_BUTTON = 2;
const int BUZZER_PIN = 8;
// If the snake goes the wrong way, change
// these: they flip the screen sideways or
// upside down, and swap the axes
const bool FLIP_X = false;
const bool FLIP_Y = false;
const bool SWAP_AXES = false;
// One step's time (ms): a little shorter
// after each food, MIN_DELAY at least
const int START_DELAY = 350;
const int MIN_DELAY = 120;
// 3x5 digits for the score, row by row
const byte DIGITS[10][5] = {
{7, 5, 5, 5, 7}, {2, 6, 2, 2, 7}, {7, 1, 7, 4, 7}, {7, 1, 3, 1, 7},
{5, 5, 7, 1, 1}, {7, 4, 7, 1, 7}, {7, 4, 7, 5, 7}, {7, 1, 1, 2, 2},
{7, 5, 7, 5, 7}, {7, 5, 7, 1, 7}
};
LedControl matrix(DIN_PIN, CLK_PIN, CS_PIN, 1);
// The snake's cells, head to tail
byte snakeX[64];
byte snakeY[64];
int length = 3;
// Heading (-1, 0, 1) and the next heading
int dx = 1, dy = 0;
int nextDx = 1, nextDy = 0;
byte foodX = 0, foodY = 0;
int score = 0;
int stepDelay = START_DELAY;
bool playing = false;
unsigned long lastStep = 0;
// One LED on/off (x rightwards, y down)
void plot(int x, int y, bool on) {
int col = FLIP_X ? 7 - x : x;
int row = FLIP_Y ? 7 - y : y;
matrix.setLed(0, row, col, on);
}
bool isSnake(int x, int y, int count) {
for (int i = 0; i < count; i++) {
if (snakeX[i] == x && snakeY[i] == y) return true;
}
return false;
}
// Food goes to a random free spot
void placeFood() {
do {
foodX = random(8);
foodY = random(8);
} while (isSnake(foodX, foodY, length));
}
void draw() {
matrix.clearDisplay(0);
for (int i = 0; i < length; i++) {
plot(snakeX[i], snakeY[i], true);
}
}
void newGame() {
length = 3;
for (int i = 0; i < length; i++) {
snakeX[i] = 3 - i;
snakeY[i] = 4;
}
dx = nextDx = 1;
dy = nextDy = 0;
score = 0;
stepDelay = START_DELAY;
placeFood();
draw();
playing = true;
lastStep = millis();
Serial.println("New game started.");
}
// Show the score in two-digit numbers
void showScore() {
matrix.clearDisplay(0);
int digits[2] = {score / 10 % 10, score % 10};
for (int d = 0; d < 2; d++) {
if (d == 0 && score < 10) continue;
// One digit centred, two side by side
int left = score < 10 ? 2 : 1 + d * 4;
for (int r = 0; r < 5; r++) {
for (int c = 0; c < 3; c++) {
bool on = DIGITS[digits[d]][r] & (4 >> c);
plot(left + c, r + 1, on);
}
}
}
}
void gameOver() {
playing = false;
Serial.print("Game over! Score: ");
Serial.println(score);
int notes[3] = {440, 330, 220};
for (int i = 0; i < 3; i++) {
tone(BUZZER_PIN, notes[i], 200);
delay(250);
}
showScore();
}
// Read the joystick; no turning back
void readJoystick() {
int x = analogRead(JOY_X);
int y = analogRead(JOY_Y);
if (SWAP_AXES) {
int t = x;
x = y;
y = t;
}
int jx = 0, jy = 0;
if (x < 300) jx = -1;
if (x > 700) jx = 1;
if (y < 300) jy = -1;
if (y > 700) jy = 1;
// Pushed diagonally: sideways wins
if (jx != 0) jy = 0;
if (jx == 0 && jy == 0) return;
if (jx == -dx && jy == -dy) return;
nextDx = jx;
nextDy = jy;
}
void step() {
dx = nextDx;
dy = nextDy;
// Off one edge, in at the other
int x = (snakeX[0] + dx + 8) % 8;
int y = (snakeY[0] + dy + 8) % 8;
bool ate = (x == foodX && y == foodY);
// On food the tail stays put
if (isSnake(x, y, ate ? length : length - 1)) {
gameOver();
return;
}
if (ate) length++;
for (int i = length - 1; i > 0; i--) {
snakeX[i] = snakeX[i - 1];
snakeY[i] = snakeY[i - 1];
}
snakeX[0] = x;
snakeY[0] = y;
if (ate) {
score++;
tone(BUZZER_PIN, 1800, 60);
stepDelay = max(MIN_DELAY, stepDelay - 15);
Serial.print("Food eaten, score: ");
Serial.println(score);
if (length == 64) {
gameOver();
return;
}
placeFood();
}
draw();
}
void setup() {
Serial.begin(9600);
pinMode(JOY_BUTTON, INPUT_PULLUP);
// Noise on the unconnected A5 puts food
// in different places every game
randomSeed(analogRead(A5));
matrix.shutdown(0, false);
matrix.setIntensity(0, 4);
newGame();
}
void loop() {
if (!playing) {
// Score shown; the button restarts
if (digitalRead(JOY_BUTTON) == LOW) newGame();
return;
}
readJoystick();
if (millis() - lastStep >= (unsigned long)stepDelay) {
lastStep = millis();
step();
}
// Food blinks, so it stands apart
if (playing) plot(foodX, foodY, millis() / 150 % 2);
}
What the Serial Monitor shows
New game started. Food eaten, score: 1 Food eaten, score: 2 Food eaten, score: 3 Game over! Score: 3 New game started.
What you will see
Once the code is uploaded, a 3-LED snake starts walking right across the matrix and the food blinks somewhere on the screen. Push the joystick to steer the snake: it cannot turn back, only to its left or right. Eating the food gives a short beep, the snake grows one cell and gets a little faster; the score is also printed in the Serial Monitor. The snake leaves one edge and comes back in at the other. When it runs into its own body, three falling notes play and the score shows on the screen in digits; press down on the joystick to start a new game. If the snake goes left when you push right, set FLIP_X in the code to true; if up and down are swapped, set FLIP_Y to true; if pushing right moves it up or down, set SWAP_AXES to true, then upload again. If the screen never lights up, check that the DIN, CS and CLK wires are on the matrix's input side (the side marked DIN). If a new game does not start, check that the SW wire is on pin 2.
In the app, tick off every wire, match the parts to your inventory and pick up where you left off.
Build it step by step in the app