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

Snake con Arduino, joystick e matrice LED 8x8
Schema di montaggio del progetto (tinkercad.com)

Segui questa guida passo-passo:

  1. 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)
  2. 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.
  3. 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

  1. Inclusione libreria
    Viene importata la libreria LedControl.h, necessaria per gestire la matrice LED 8×8 con driver MAX7219.
  2. Definizione dei pin
    Si definiscono i pin collegati alla matrice LED e quelli del joystick (assi X, Y e pulsante).
  3. 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 e gameOver controllano logica e stato del gioco.
  4. 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.
  5. Inizializzazione e cibo (initSnake() e spawnFood())
    • Lo snake parte con tre segmenti al centro della matrice.
    • Il cibo viene posizionato casualmente evitando che compaia sopra il corpo del serpente.
  6. 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).
  7. 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à.
  8. 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.
  9. Reset del gioco (resetGame())
    • Riporta lo snake alla lunghezza iniziale e resetta velocità e direzione.
    • Viene chiamata alla pressione del pulsante del joystick.
  10. 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.

Video tutorial con spiegazione approfondita