arduino-juego-snake-matriz-8x8

How to Program Snake on an 8x8 LED Matrix

  • 7 min

If you had a Nokia 3310, you know what we’re talking about. Snake is not just a game; it’s the father of mobile video games. And today, we are going to program it from scratch.

Unlike other projects where the challenge is the electrical assembly, here the challenge is the software. How do we make a row of lights “move” like a snake? How do we remember where each part of its body is? How do we detect if it has bitten its tail?

We are not going to use an LCD screen, but an 8x8 LED Matrix retro style.

This project is ideal for understanding the management of Arrays and the difference between Game Logic (what happens in memory) and Rendering (what shows on the lights).

Anatomy of a digital snake

The beginner’s mistake is to think of the screen as a grid of pixels grid[8][8] and try to move the lit points. That is a programming nightmare.

The correct way is to abstract. Our snake is not lights, it’s a list of coordinates. We will use two Arrays:

  • bodyX[]: Stores the X coordinates of each segment.
  • bodyY[]: Stores the Y coordinates of each segment.

Index 0 will always be the Head. Index 1 will be the neck. The last index will be the Tail.

Movement algorithm

To move the snake, we don’t move all the points at once. We apply the “drag” logic:

We start with the tail. The tail moves to where the previous segment was.

We repeat until we reach the neck.

Finally, we move the Head one cell in the direction indicated by the Joystick.

Components

  • [1x|blue] Arduino Uno | Or Nano
  • [1x|green] LED Matrix 8x8 Module with MAX7219 | Retro display {integrated driver}
  • [1x|orange] Analog Joystick (KY-023) | Direction control
  • [Various|gray] Dupont Wires | Connections

Using the MAX7219 chip saves us a lot of work. If you try to control 64 LEDs directly with Arduino, you’ll run out of pins and patience. This chip does it all with just 3 wires.

Connection Diagram

We have two peripherals: the input (Joystick) and the output (Display).

LED Matrix 8x8

The matrix with MAX7219 uses three control signals plus power.

[ { “from”: “Matrix VCC”, “to”: “Arduino 5V”, “color”: “red” }, { “from”: “Matrix GND”, “to”: “Arduino GND”, “color”: “black” }, { “from”: “Matrix DIN”, “to”: “Pin 12”, “color”: “green”, “note”: “Data for LedControl” }, { “from”: “Matrix CS”, “to”: “Pin 10”, “color”: “blue”, “note”: “Chip Select” }, { “from”: “Matrix CLK”, “to”: “Pin 11”, “color”: “purple”, “note”: “Clock for LedControl” } ]

Analog Joystick

The joystick delivers two analog signals, one for each axis. We won’t use the built-in button in this version.

[ { “from”: “Joystick VCC”, “to”: “Arduino 5V”, “color”: “red” }, { “from”: “Joystick GND”, “to”: “Arduino GND”, “color”: “black” }, { “from”: “Joystick VRx”, “to”: “Pin A0”, “color”: “orange” }, { “from”: “Joystick VRy”, “to”: “Pin A1”, “color”: “orange” } ]

The Code

We will need the LedControl.h library to talk to the MAX7219 chip. You can install it from the library manager.

The code is a bit longer than usual because it includes the game logic. We have divided it into functions for readability.

Global variables and configuration

We define the arrays with a maximum size (e.g. 64 segments, which would be a full screen).

#include <LedControl.h>

// Matrix pins: DIN=12, CLK=11, CS=10
LedControl lc = LedControl(12, 11, 10, 1);

// Joystick pins
const int pinJoyX = A0;
const int pinJoyY = A1;

// --- GAME VARIABLES ---
const int MAX_LENGTH = 64;
int bodyX[MAX_LENGTH];
int bodyY[MAX_LENGTH];

int snakeLength = 3;  // We start with size 3
int direction = 1;       // 0:Up, 1:Right, 2:Down, 3:Left

// Food
int foodX, foodY;
int previousTailX, previousTailY;

// Timing (Game Speed)
unsigned long prevTime = 0;
int speed = 300; // Milliseconds per move (lower = faster)

void setup() {
  // Initialize Matrix
  lc.shutdown(0, false);       // Wake up
  lc.setIntensity(0, 4);       // Brightness (0-15)
  lc.clearDisplay(0);

  // Initial position (center)
  bodyX[0]=4; bodyY[0]=4; // Head
  bodyX[1]=3; bodyY[1]=4;
  bodyX[2]=2; bodyY[2]=4;
  
  randomSeed(analogRead(A5)); // Random seed
  generateFood();
}
Copied!

Main loop

In video games, the loop usually has three phases: Read Inputs -> Update Logic -> Draw (Render).

void loop() {
  // 1. READ INPUTS (Joystick)
  readJoystick();

  // 2. UPDATE LOGIC (Only every 'speed' ms)
  if (millis() - prevTime >= speed) {
    prevTime = millis();
    
    moveSnake();
    checkCollisions();
    
    // 3. DRAW
    drawGame();
  }
}
Copied!

Reading the joystick

Here’s an important trick: Avoid suicide. If the snake is going RIGHT, we cannot allow the user to press LEFT, because it would instantly collide with its own neck.

void readJoystick() {
  int x = analogRead(pinJoyX);
  int y = analogRead(pinJoyY);
  
  // Thresholds to detect direction
  // 0:Up, 1:Right, 2:Down, 3:Left
  
  // Note: Orientation depends on how you place the joystick.
  // Adjust these 'ifs' according to your setup.
  if (y < 200 && direction != 2) direction = 0; // Up (unless going down)
  if (y > 800 && direction != 0) direction = 2; // Down
  if (x > 800 && direction != 3) direction = 1; // Right
  if (x < 200 && direction != 1) direction = 3; // Left
}
Copied!

Movement logic

Here we apply the array shifting algorithm.

void moveSnake() {
  // Save the tail in case the snake eats during this move
  previousTailX = bodyX[snakeLength - 1];
  previousTailY = bodyY[snakeLength - 1];

  // Each segment takes the position of the one in front of it
  for (int i = snakeLength - 1; i > 0; i--) {
    bodyX[i] = bodyX[i - 1];
    bodyY[i] = bodyY[i - 1];
  }

  // Move the head
  if (direction == 0) bodyY[0]--;
  if (direction == 1) bodyX[0]++;
  if (direction == 2) bodyY[0]++;
  if (direction == 3) bodyX[0]--;
}
Copied!

Collisions and food

We check if we have died or if we have eaten.

void checkCollisions() {
  // 1. WALL collision
  if (bodyX[0] < 0 || bodyX[0] > 7 || bodyY[0] < 0 || bodyY[0] > 7) {
    gameOver();
    return;
  }

  // 2. SELF collision
  for (int i = 1; i < snakeLength; i++) {
    if (bodyX[0] == bodyX[i] && bodyY[0] == bodyY[i]) {
      gameOver();
      return;
    }
  }

  // 3. EAT
  if (bodyX[0] == foodX && bodyY[0] == foodY) {
    if (snakeLength < MAX_LENGTH) {
      bodyX[snakeLength] = previousTailX;
      bodyY[snakeLength] = previousTailY;
      snakeLength++;
    }

    // Increase speed (difficulty)
    if (speed > 100) speed -= 10; 
    if (snakeLength < MAX_LENGTH) generateFood();
  }
}

void generateFood() {
  bool occupied;
  do {
    foodX = random(0, 8);
    foodY = random(0, 8);
    occupied = false;

    for (int i = 0; i < snakeLength; i++) {
      if (bodyX[i] == foodX && bodyY[i] == foodY) {
        occupied = true;
        break;
      }
    }
  } while (occupied);
}

void gameOver() {
  // Screen flash
  for(int i=0; i<3; i++){
    lc.clearDisplay(0);
    delay(200);
    drawGame();
    delay(200);
  }
  // Reset game
  snakeLength = 3;
  bodyX[0]=4; bodyY[0]=4;
  bodyX[1]=3; bodyY[1]=4;
  bodyX[2]=2; bodyY[2]=4;
  direction = 1;
  speed = 300;
  generateFood();
}
Copied!

Rendering

Finally, we dump the memory state to the LEDs.

void drawGame() {
  lc.clearDisplay(0);
  
  // Draw Food
  lc.setLed(0, foodY, foodX, true); // (addr, row, col, state)
  
  // Draw Snake
  for (int i = 0; i < snakeLength; i++) {
    lc.setLed(0, bodyY[i], bodyX[i], true);
  }
}
Copied!

Note that lc.setLed usually takes (row, column). Depending on how you soldered your matrix, your X and Y might be swapped. If the snake moves weirdly (up is left), simply swap the parameters in this function.

Possible improvements

You can add sound when picking up food, show a victory animation when occupying all 64 squares, or connect two matrices to create a 16x8 board.