In questo progetto realizziamo il classico gioco Snake utilizzando un joystick analogico e una matrice LED 8×8 con driver MAX7219.
Il serpente sarà rappresentato dai pixel accesi sulla matrice e potremo guidarlo spostando il joystick nelle quattro direzioni.
Lista della spesa
Per realizzare questo progetto, avrai bisogno di:
- Breadoard
- Arduino UNO (o compatibile)
- Joystick analogico
- Matrice LED 8×8
- Cavi jumper (Dupont)
- Cavo USB
Schema di montaggio

Segui questa guida passo-passo:
- Posiziona la breadboard su un tavolo e collega il Power Rail – a GND di Arduino e quello + a VCC di Arduino (Se non sai come fare i collegamenti su una breadboard guarda questo video: https://www.youtube.com/watch?v=w4CLsFViD2w)
- Joystick analogico: collega il primo piedino (GND) al Power Rail – della breaboard, il secondo (VCC) al Power Rail +, il terzo (VRx) al pin A0 di Arduino, il quarto (VRy) al pin A1 di Arduino e l’ultimo (SW) al pin D2 di Arduino.
- Matrice LED 8×8: collega il primo piedino (VCC) al Power Rail + della breaboard, il secondo (GND) al Power Rail -, il terzo (DIN) al pin D12 di Arduino, il quarto (CS) al pin D11 di Arduino e l’ultimo (CLK) al pin D10 di Arduino.
Se non ti ritrovi con questa spiegazione in fondo al post c’è un video che spiega in maniera dettagliata tutti i passaggi.
Codice per Snake con Arduino, joystick e matrice LED 8×8
#include <LedControl.h>
// Pin per la matrice LED
#define DIN_PIN 12
#define CS_PIN 11
#define CLK_PIN 10
// Pin per il joystick
#define JOYSTICK_X A0
#define JOYSTICK_Y A1
#define JOYSTICK_BUTTON 2 // Opzionale
LedControl lc = LedControl(DIN_PIN, CLK_PIN, CS_PIN, 1);
// Struttura per la posizione
struct Position {
int x;
int y;
};
// Variabili di gioco
Position snake[64];
int snakeLength = 3;
Position food;
int direction = 0; // 0=right, 1=up, 2=left, 3=down
int lastDirection = 0;
unsigned long lastUpdate = 0;
int gameSpeed = 300; // ms tra i movimenti
bool gameOver = false;
// Soglie per il joystick
const int JOYSTICK_THRESHOLD = 200;
const int CENTER_MIN = 450;
const int CENTER_MAX = 550;
void setup() {
Serial.begin(9600);
// Inizializza matrice LED
lc.shutdown(0, false);
lc.setIntensity(0, 8);
lc.clearDisplay(0);
// Configura pin pulsante joystick (opzionale)
pinMode(JOYSTICK_BUTTON, INPUT_PULLUP);
// Inizializza serpente
initSnake();
spawnFood();
// Mostra schermata iniziale
updateDisplay();
randomSeed(analogRead(A2)); // Usa un pin analogico non collegato per random
}
void initSnake() {
// Posizione iniziale del serpente al centro
for (int i = 0; i < snakeLength; i++) {
snake[i].x = 3 - i;
snake[i].y = 3;
}
}
void spawnFood() {
bool validPosition = false;
while (!validPosition) {
food.x = random(0, 8);
food.y = random(0, 8);
validPosition = true;
// Controlla che il cibo non sia sul serpente
for (int i = 0; i < snakeLength; i++) {
if (snake[i].x == food.x && snake[i].y == food.y) {
validPosition = false;
break;
}
}
}
}
void readJoystick() {
int xValue = analogRead(JOYSTICK_X);
int yValue = analogRead(JOYSTICK_Y);
// Determina la direzione in base ai valori analogici
if (xValue < CENTER_MIN - JOYSTICK_THRESHOLD) {
// Sinistra
if (lastDirection != 0) { // Non permettere inversione diretta
direction = 2;
}
}
else if (xValue > CENTER_MAX + JOYSTICK_THRESHOLD) {
// Destra
if (lastDirection != 2) {
direction = 0;
}
}
else if (yValue < CENTER_MIN - JOYSTICK_THRESHOLD) {
// Su
if (lastDirection != 3) {
direction = 1;
}
}
else if (yValue > CENTER_MAX + JOYSTICK_THRESHOLD) {
// Giù
if (lastDirection != 1) {
direction = 3;
}
}
// Debug (opzionale)
// Serial.print("X: "); Serial.print(xValue);
// Serial.print(" Y: "); Serial.print(yValue);
// Serial.print(" Dir: "); Serial.println(direction);
}
void moveSnake() {
// Salva la direzione corrente
lastDirection = direction;
// Memorizza la posizione della testa precedente
Position prevHead = snake[0];
// Muovi la testa nella nuova direzione
switch (direction) {
case 0: // Right
snake[0].x++;
break;
case 1: // Up
snake[0].y--;
break;
case 2: // Left
snake[0].x--;
break;
case 3: // Down
snake[0].y++;
break;
}
// Controlla collisioni con i bordi
if (snake[0].x < 0 || snake[0].x >= 8 || snake[0].y < 0 || snake[0].y >= 8) {
gameOver = true;
return;
}
// Controlla collisioni con il corpo
for (int i = 1; i < snakeLength; i++) {
if (snake[0].x == snake[i].x && snake[0].y == snake[i].y) {
gameOver = true;
return;
}
}
// Controlla se mangia il cibo
if (snake[0].x == food.x && snake[0].y == food.y) {
snakeLength++;
// Aggiungi nuovo segmento alla fine
snake[snakeLength - 1] = snake[snakeLength - 2];
spawnFood();
// Aumenta difficoltà
if (gameSpeed > 100) {
gameSpeed -= 10;
}
}
// Muovi il corpo
for (int i = snakeLength - 1; i > 0; i--) {
snake[i] = snake[i - 1];
}
}
void updateDisplay() {
lc.clearDisplay(0);
if (gameOver) {
// Illumina tutta la matrice
for (int row = 0; row < 8; row++) {
for (int col = 0; col < 8; col++) {
lc.setLed(0, row, col, true);
}
}
return;
}
// Disegna il serpente
for (int i = 0; i < snakeLength; i++) {
lc.setLed(0, snake[i].y, snake[i].x, true);
}
// Disegna il cibo
lc.setLed(0, food.y, food.x, true);
}
void resetGame() {
snakeLength = 3;
direction = 0;
lastDirection = 0;
gameSpeed = 300;
gameOver = false;
initSnake();
spawnFood();
updateDisplay();
}
void loop() {
if (gameOver) {
// Reset con pulsante joystick o dopo 3 secondi di attesa
static unsigned long gameOverTime = 0;
if (gameOverTime == 0) {
gameOverTime = millis();
}
if (digitalRead(JOYSTICK_BUTTON) == LOW) {
resetGame();
gameOverTime = 0;
}
return;
}
readJoystick();
unsigned long currentTime = millis();
if (currentTime - lastUpdate >= gameSpeed) {
lastUpdate = currentTime;
moveSnake();
updateDisplay();
}
// Piccolo delay per stabilizzare la lettura analogica
delay(50);
}
Come funziona il codice
- Inclusione libreria
Viene importata la libreriaLedControl.h
, necessaria per gestire la matrice LED 8×8 con driver MAX7219. - Definizione dei pin
Si definiscono i pin collegati alla matrice LED e quelli del joystick (assi X, Y e pulsante). - Strutture e variabili di gioco
- Lo snake è rappresentato da un array di posizioni (
snake[]
). - Viene memorizzata anche la posizione del cibo.
- Variabili come
direction
,gameSpeed
egameOver
controllano logica e stato del gioco.
- Lo snake è rappresentato da un array di posizioni (
- Setup iniziale (
setup()
)- Inizializza la matrice LED (accensione, intensità, display pulito).
- Configura il pulsante del joystick.
- Posiziona il serpente al centro e genera il primo cibo casuale.
- Disegna la schermata iniziale.
- Inizializzazione e cibo (
initSnake()
espawnFood()
)- Lo snake parte con tre segmenti al centro della matrice.
- Il cibo viene posizionato casualmente evitando che compaia sopra il corpo del serpente.
- Lettura del joystick (
readJoystick()
)- I valori analogici di X e Y vengono tradotti in direzioni (su, giù, sinistra, destra).
- Non è permesso un movimento inverso immediato (es. destra → sinistra diretta).
- Movimento del serpente (
moveSnake()
)- Aggiorna la posizione della testa e sposta il corpo di conseguenza.
- Controlla collisioni con i bordi o con se stesso → in caso,
gameOver = true
. - Se il serpente mangia il cibo, aumenta la lunghezza e la velocità.
- Aggiornamento display (
updateDisplay()
)- Cancella e ridisegna la matrice.
- Accende i LED corrispondenti al serpente e al cibo.
- In caso di game over, illumina tutta la matrice.
- Reset del gioco (
resetGame()
)- Riporta lo snake alla lunghezza iniziale e resetta velocità e direzione.
- Viene chiamata alla pressione del pulsante del joystick.
- Loop principale (
loop()
)- Se il gioco è finito, attende il reset.
- Altrimenti legge il joystick, aggiorna direzione e posizione dello snake a intervalli regolari, poi ridisegna la matrice.