#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <SPI.h>
// Pin display ILI9341
#define TFT_CS 5
#define TFT_DC 2
#define TFT_RST 4
// --- AGGIUNTA PIN BUZZER ---
#define BUZZER_PIN 12 // Puoi cambiare il pin se preferisci
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
// Pulsante (start/pause/menu)
#define BUTTON_PIN 27
// Joystick
#define JOY_X 34
#define JOY_Y 35
// Stati del gioco
enum GameState {
STATE_MENU,
STATE_GAME,
STATE_PAUSE,
STATE_WIN,
STATE_LOST
};
GameState gameState = STATE_MENU;
// Direzione serpente
int dirX = 1; // destra
int dirY = 0;
// Timing movimento
unsigned long lastMove = 0;
int moveDelay = 150; // ms
// Coda del serpente
const int MAX_TAIL = 250;
int tailX[MAX_TAIL];
int tailY[MAX_TAIL];
int tailLength = 1;
// Punteggio
int score = 0;
// --- FUNZIONI SUONI ---
void suonoMela() {
tone(BUZZER_PIN, 1000, 100); // Beep corto e acuto
}
void suonoMorte() {
tone(BUZZER_PIN, 300, 200); // Nota bassa
delay(250);
tone(BUZZER_PIN, 150, 500); // Nota ancora più bassa (triste)
}
void suonoVittoria() {
// Scala ascendente
tone(BUZZER_PIN, 800, 150); delay(150);
tone(BUZZER_PIN, 1000, 150); delay(150);
tone(BUZZER_PIN, 1200, 150); delay(150);
tone(BUZZER_PIN, 1500, 400);
}
// ----------------------
// GRIGLIA, SERPENTE, MELA
// ----------------------
const int CELL_SIZE = 8;
const int GRID_W = 30; // 240 / 8
const int GRID_H = 40; // 320 / 8
int snakeX = GRID_W / 2;
int snakeY = GRID_H / 2;
int appleX = 5;
int appleY = 5;
// ----------------------
// UTILITY
// ----------------------
bool buttonPressed() {
static bool lastState = HIGH;
bool current = digitalRead(BUTTON_PIN);
bool pressed = (lastState == HIGH && current == LOW);
lastState = current;
return pressed;
}
void printCentered(const char* text, int y, int size, uint16_t color) {
tft.setTextSize(size);
tft.setTextColor(color, ILI9341_BLACK);
int16_t x1, y1;
uint16_t w, h;
tft.getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
int x = (240 - w) / 2;
tft.setCursor(x, y);
tft.print(text);
}
void drawCell(int gx, int gy, uint16_t color) {
int px = gx * CELL_SIZE;
int py = gy * CELL_SIZE;
tft.fillRect(px, py, CELL_SIZE, CELL_SIZE, color);
}
void drawSnakeHead(int gx, int gy) {
drawCell(gx, gy, ILI9341_CYAN);
int px = gx * CELL_SIZE;
int py = gy * CELL_SIZE;
tft.fillRect(px + 1, py + 1, 2, 2, ILI9341_MAGENTA);
tft.fillRect(px + 5, py + 1, 2, 2, ILI9341_MAGENTA);
}
void drawSnakeBody(int gx, int gy) {
drawCell(gx, gy, ILI9341_BLUE);
}
void drawApple(int gx, int gy) {
int px = gx * CELL_SIZE + CELL_SIZE / 2;
int py = gy * CELL_SIZE + CELL_SIZE / 2;
tft.fillCircle(px, py, 3, ILI9341_GREEN);
}
void drawBorder() {
tft.drawRect(0, 0, GRID_W * CELL_SIZE, GRID_H * CELL_SIZE, ILI9341_RED);
}
// ----------------------
// LOGICA DI GIOCO
// ----------------------
void readJoystick() {
int x = analogRead(JOY_X);
int y = analogRead(JOY_Y);
int center = 2048;
int threshold = 600;
if (x < center - threshold && dirX != -1) { dirX = 1; dirY = 0; }
else if (x > center + threshold && dirX != 1) { dirX = -1; dirY = 0; }
else if (y < center - threshold && dirY != -1) { dirX = 0; dirY = 1; }
else if (y > center + threshold && dirY != 1) { dirX = 0; dirY = -1; }
}
void updateTail() {
for (int i = tailLength - 1; i > 0; i--) {
tailX[i] = tailX[i - 1];
tailY[i] = tailY[i - 1];
}
tailX[0] = snakeX;
tailY[0] = snakeY;
}
void drawTail() {
for (int i = 0; i < tailLength; i++) {
drawSnakeBody(tailX[i], tailY[i]);
}
}
void spawnApple() {
appleX = random(0, GRID_W);
appleY = random(0, GRID_H);
}
void renderGame() {
readJoystick();
if (millis() - lastMove > (unsigned long)moveDelay) {
lastMove = millis();
int nextX = snakeX + dirX;
int nextY = snakeY + dirY;
if (nextX < 0 || nextX >= GRID_W || nextY < 0 || nextY >= GRID_H) {
suonoMorte(); // SUONO MORTE
gameState = STATE_LOST;
return;
}
for (int i = 0; i < tailLength; i++) {
if (tailX[i] == nextX && tailY[i] == nextY) {
suonoMorte(); // SUONO MORTE
gameState = STATE_LOST;
return;
}
}
drawCell(snakeX, snakeY, ILI9341_BLACK);
for (int i = 0; i < tailLength; i++) {
drawCell(tailX[i], tailY[i], ILI9341_BLACK);
}
updateTail();
snakeX = nextX;
snakeY = nextY;
if (snakeX == appleX && snakeY == appleY) {
score += 10;
suonoMela();
if (tailLength < MAX_TAIL) {
// Inizializziamo il nuovo pezzo di coda nella posizione attuale dell'ultimo
tailX[tailLength] = tailX[tailLength - 1];
tailY[tailLength] = tailY[tailLength - 1];
tailLength++;
}
spawnApple();
if (moveDelay > 70) moveDelay -= 5;
if (score >= 250) {
suonoVittoria();
gameState = STATE_WIN;
return;
}
}
}
drawApple(appleX, appleY);
drawTail();
drawSnakeHead(snakeX, snakeY);
drawBorder();
// Posiziona il cursore in alto a sinistra (5, 5)
tft.setCursor(5, 5);
tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
tft.setTextSize(2);
// Stampa la scritta fissa e poi il valore
tft.print("Score: ");
tft.print(score);
}
// SCHERMATE (Senza modifiche a colori o testi)
void renderMenu() {
tft.fillScreen(ILI9341_BLACK);
printCentered("SNAKE", 60, 4, ILI9341_GREEN);
printCentered("Press button", 130, 2, ILI9341_BLUE);
printCentered("to start", 160, 2, ILI9341_BLUE);
}
void renderPause() {
tft.fillScreen(ILI9341_BLACK);
printCentered("PAUSE", 100, 4, ILI9341_YELLOW);
printCentered("Press button", 170, 2, ILI9341_YELLOW);
printCentered("to continue", 195, 2, ILI9341_YELLOW);
}
void renderWin() {
tft.fillScreen(ILI9341_BLACK);
printCentered("YOU WIN", 90, 4, ILI9341_GREEN);
char buf[32];
sprintf(buf, "Score: %d", score);
printCentered(buf, 150, 2, ILI9341_WHITE);
printCentered("Press button", 200, 2, ILI9341_GREEN);
printCentered("to menu", 225, 2, ILI9341_GREEN);
}
void renderLost() {
tft.fillScreen(ILI9341_BLACK);
printCentered("YOU LOST", 90, 4, ILI9341_RED);
char buf[32];
sprintf(buf, "Score: %d", score);
printCentered(buf, 150, 2, ILI9341_WHITE);
printCentered("Press button", 200, 2, ILI9341_YELLOW);
printCentered("to menu", 225, 2, ILI9341_YELLOW);
}
void resetGame() {
score = 0;
tailLength = 1;
moveDelay = 150;
snakeX = GRID_W / 2;
snakeY = GRID_H / 2;
tailX[0] = snakeX - 1;
tailY[0] = snakeY;
dirX = 1;
dirY = 0;
// Pulisce tutta la memoria della coda per evitare quadratini "fantasma"
for (int i = 0; i < MAX_TAIL; i++) {
tailX[i] = 0;
tailY[i] = 0;
}
spawnApple();
}
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(BUZZER_PIN, OUTPUT); // Imposta pin buzzer
tft.begin();
tft.setRotation(0);
tft.fillScreen(ILI9341_BLACK);
resetGame();
}
void loop() {
switch (gameState) {
case STATE_MENU:
renderMenu();
if (buttonPressed()) { tft.fillScreen(ILI9341_BLACK); resetGame(); gameState = STATE_GAME; delay(150); }
break;
case STATE_GAME:
renderGame();
if (buttonPressed()) { gameState = STATE_PAUSE; delay(150); }
break;
case STATE_PAUSE:
renderPause();
if (buttonPressed()) { tft.fillScreen(ILI9341_BLACK); gameState = STATE_GAME; delay(150); }
break;
case STATE_WIN:
renderWin();
if (buttonPressed()) { gameState = STATE_MENU; delay(150); }
break;
case STATE_LOST:
renderLost();
if (buttonPressed()) { gameState = STATE_MENU; delay(150); }
break;
}
}