Wired-Chaos

Snake

← Back to index

Overview

This tutorial shows how to build a Snake game on an 8x8 LED matrix using the LedControl library and an analog joystick.

You will learn:

Jump to section - [What does `void` mean?](#what-does-void-mean) - [What is a library?](#what-is-a-library) - [What you need](#what-you-need) - [Step 1: Install the library](#step-1-install-the-library) - [Step 2: Wire the hardware](#step-2-wire-the-hardware) - [Step 3: Understand the sketch structure](#step-3-understand-the-sketch-structure) - [Step 4: Pin and game variables](#step-4-pin-and-game-variables) - [Step 5: Initialize the display and inputs](#step-5-initialize-the-display-and-inputs) - [Step 6: Read joystick input and choose a direction](#step-6-read-joystick-input-and-choose-a-direction) - [Step 7: Move the snake without blocking](#step-7-move-the-snake-without-blocking) - [Step 8: Update the snake body](#step-8-update-the-snake-body) - [Step 9: Handle collisions](#step-9-handle-collisions) - [Step 10: Spawn food safely](#step-10-spawn-food-safely) - [Step 11: Show game over and restart](#step-11-show-game-over-and-restart) - [Full sketch reference](#full-sketch-reference) - [Line-by-line explanation](#line-by-line-explanation) - [Common problems and fixes](#common-problems-and-fixes)

What does void mean?

In Arduino C++, void is a function return type that means “nothing.” When a function is declared as void, it does not return any value back to the caller.

In this sketch, void setup() and void loop() are special Arduino functions that the board calls automatically:

Using void is normal for functions that perform actions like initializing hardware or updating the game state without producing a result.

What is a library?

In Arduino programming, a library is a collection of pre-written code that provides functions and tools to simplify common tasks. For example, the LedControl library handles communication with the MAX7219 LED driver chip, allowing you to control the matrix without writing low-level SPI code from scratch.

Libraries are installed via the Arduino IDE’s Library Manager and included in your sketch with #include <LibraryName.h>. They extend Arduino’s capabilities, such as controlling displays, sensors, or motors, making projects like this Snake game easier to build.

Explanation:

Try this:

What you need

Step 1: Install the library

  1. Open the Arduino IDE.
  2. Go to SketchInclude LibraryManage Libraries....
  3. Search for LedControl by Eberhard Fahle.
  4. Install the library.

Explanation:

Try this:

Step 2: Wire the hardware

LED matrix wiring

Explanation:

Try this:

Joystick wiring

Optional LED indicator

Example: verify the matrix

Use this quick test sketch to confirm the MAX7219 matrix is wired correctly:

#include <LedControl.h>

int DIN = 10;
int CLK = 13;
int CS  = 7;

LedControl lc = LedControl(DIN, CLK, CS, 1);

void setup() {
  lc.shutdown(0, false);
  lc.setIntensity(0, 5);
  lc.clearDisplay(0);
  for (int r = 0; r < 8; r++) {
    lc.setRow(0, r, 0xFF);
  }
}

void loop() {}

If the matrix lights up fully, the wiring is good. If not, check power, ground, and the DIN/CLK/CS pin connections.

Step 3: Understand the sketch structure

The sketch is divided into these sections:

  1. #include <LedControl.h> – load the matrix driver library
  2. pin and game state setup
  3. setup() to initialize the matrix and joystick pins
  4. loop() to read the joystick and move the snake
  5. helper functions for food, collisions, endgame, restart

Explanation:

Try this:

Step 4: Pin and game variables

Use these pin assignments in your sketch:

int DIN = 10;
int CLK = 13;
int CS  = 7;

int xPin = A0;
int yPin = A1;
const int buttonPin = 8;
int ledPin = 9;

LedControl lc = LedControl(DIN, CLK, CS, 1);

Game state variables:

Explanation:

Try this:

const int maxLength = 64;
int snakeLength = 3;
int snakeRow[maxLength] = {3, 3, 3};
int snakeCol[maxLength] = {3, 2, 1};
int row = 3;
int col = 3;
String direction = "right";

int foodRow = 0;
int foodCol = 0;

byte endgame[8] = {
  0x81, 0x42, 0x24, 0x18,
  0x18, 0x24, 0x42, 0x81
};

unsigned long lastMoveTime = 0;
int moveInterval = 300;
bool gameOver = false;

Step 5: Initialize the display and inputs

In setup(), wake the matrix and configure the joystick pins.

void setup() {
  lc.shutdown(0, false);
  lc.setIntensity(0, 1);
  lc.clearDisplay(0);

  pinMode(xPin, INPUT);
  pinMode(yPin, INPUT);
  pinMode(buttonPin, INPUT_PULLUP);

  randomSeed(analogRead(A2));
  spawnFood();
}

Explanation:

Try this:

Notes:

Step 6: Read joystick input and choose a direction

The joystick values are read with analogRead(). The sketch uses thresholds to decide direction.

int xValue = analogRead(xPin);
int yValue = analogRead(yPin);

if (xValue > 700 && direction != "left") direction = "right";
else if (xValue < 300 && direction != "right") direction = "left";
else if (yValue > 700 && direction != "up") direction = "down";
else if (yValue < 300 && direction != "down") direction = "up";

Explanation:

Try this:

Tip: if your joystick values are different, adjust 700 and 300 to match the center and edge readings.

Example: calibrate joystick values

Use this simple debug sketch to see the actual analogRead() values from your joystick:

int xPin = A0;
int yPin = A1;

void setup() {
  Serial.begin(9600);
}

void loop() {
  int xValue = analogRead(xPin);
  int yValue = analogRead(yPin);
  Serial.print("X=");
  Serial.print(xValue);
  Serial.print(" Y=");
  Serial.println(yValue);
  delay(200);
}

Move the joystick left, right, up, and down, then use the printed values to fine-tune the threshold numbers.

Step 7: Move the snake without blocking

The sketch uses millis() to move the snake every moveInterval milliseconds.

unsigned long currentTime = millis();
if (currentTime - lastMoveTime >= moveInterval) {
  lastMoveTime = currentTime;
  // update head position here
}

Explanation:

Try this:

This keeps the sketch responsive while reading joystick input continuously.

Example: change the game speed

To make the snake faster or slower, adjust moveInterval:

int moveInterval = 500; // slower
int moveInterval = 200; // faster

A larger value means slower movement, and a smaller value means faster movement.

Step 8: Update the snake body

Before drawing, shift the body positions down the arrays so the new head can be inserted.

for (int i = snakeLength - 1; i > 0; i--) {
  snakeRow[i] = snakeRow[i - 1];
  snakeCol[i] = snakeCol[i - 1];
}

snakeRow[0] = row;
snakeCol[0] = col;

Then draw the snake and food:

Explanation:

Try this:

lc.clearDisplay(0);
for (int i = 0; i < snakeLength; i++) {
  lc.setLed(0, snakeRow[i], snakeCol[i], true);
}
lc.setLed(0, foodRow, foodCol, true);

Step 9: Handle collisions

Wall collision

If the head goes outside 0..7, the game ends.

if (col > 7 || col < 0 || row > 7 || row < 0) {
  showEndgame();
  return;
}

Self collision

After the head moves, check if it hits any existing body segment.

for (int i = 1; i < snakeLength; i++) {
  if (snakeRow[i] == row && snakeCol[i] == col) {
    showEndgame();
    return;
  }
}

Food collision

If the head hits the food, grow the snake and place a new food item.

if (row == foodRow && col == foodCol) {
  spawnFood();
  if (snakeLength < maxLength) snakeLength++;
}

Explanation:

Try this:

Step 10: Spawn food safely

The food is placed in a random free cell. If the snake already fills the board, the game ends.

void spawnFood() {
  bool valid = false;
  int attempts = 0;

  while (!valid && attempts < 100) {
    foodRow = random(0, 8);
    foodCol = random(0, 8);
    valid = true;
    for (int i = 0; i < snakeLength; i++) {
      if (snakeRow[i] == foodRow && snakeCol[i] == foodCol) {
        valid = false;
        break;
      }
    }
    attempts++;
  }

  if (!valid) showEndgame();
}

Explanation:

Try this:

Step 11: Show game over and restart

When the player loses, the display shows an end pattern and waits for the button press.

void showEndgame() {
  lc.clearDisplay(0);
  for (int i = 0; i < 8; i++) {
    lc.setRow(0, i, endgame[i]);
    delay(10);
  }
  delay(3000);
  lc.clearDisplay(0);
  gameOver = true;
}

void checkRestart() {
  if (digitalRead(buttonPin) == LOW) {
    delay(200);
    while (digitalRead(buttonPin) == LOW);
    resetGame();
    gameOver = false;
  }
}

Explanation:

Try this:

Then reset the snake state:

void resetGame() {
  snakeLength = 3;
  row = 3;
  col = 3;
  snakeRow[0] = 3; snakeCol[0] = 3;
  snakeRow[1] = 3; snakeCol[1] = 2;
  snakeRow[2] = 3; snakeCol[2] = 1;
  direction = "right";
  lastMoveTime = millis();
  spawnFood();
}

Full sketch reference

Use the complete sketch below after you have verified wiring and library installation.

Github Repo

Line-by-line explanation

Common problems and fixes

To do and not to do

Do

Don’t

Tips


Back to index