#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
// --- BUZZER ---
#define BUZZER_PIN 12
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
// Pulsante
#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;
int dirY = 0;
// Timing
unsigned long lastMove = 0;
int moveDelay = 150;
// Coda
const int MAX_TAIL = 250;
int tailX[MAX_TAIL];
int tailY[MAX_TAIL];
int tailLength = 1;
// Punteggio
int score = 0;
// ----------------------
// GRIGLIA ORIZZONTALE
// ----------------------
const int CELL_SIZE = 8;
const int GRID_W = 40; // 320 / 8
const int GRID_H = 30; // 240 / 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);
// rileva il fronte HIGH → LOW
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 = (320 - w) / 2; // <-- ORA 320px
tft.setCursor(x, y);
tft.print(text);
}
void drawCell(int gx, int gy, uint16_t color) {
tft.fillRect(gx * CELL_SIZE, gy * CELL_SIZE, 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, 320, 240, ILI9341_RED); // <-- ORA 320×240
}
// ----------------------
// SUONI
// ----------------------
void suonoMela() {
tone(BUZZER_PIN, 1000, 100);
}
void suonoMorte() {
tone(BUZZER_PIN, 300, 200);
delay(250);
tone(BUZZER_PIN, 150, 500);
}
void suonoVittoria() {
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);
}
// ----------------------
// JOYSTICK RUOTATO (DISPLAY ORIZZONTALE PIN A DESTRA)
// ----------------------
void readJoystick() {
int rawX = analogRead(JOY_X); // HORZ
int rawY = analogRead(JOY_Y); // VERT
int center = 2048;
int threshold = 500;
// CORREZIONE ASSI WOKWI:
// - VERT è invertito
// - HORZ è invertito
// - gli assi sono scambiati
int x = center - (rawY - center); // SU/GIÙ corretto
int y = center - (rawX - center); // invertito per correggere sinistra/destra
// SINISTRA / DESTRA
if (y < center - threshold && dirX != 1) {
dirX = -1;
dirY = 0;
}
else if (y > center + threshold && dirX != -1) {
dirX = 1;
dirY = 0;
}
// SU / GIÙ
else if (x < center - threshold && dirY != 1) {
dirX = 0;
dirY = -1;
}
else if (x > center + threshold && dirY != -1) {
dirX = 0;
dirY = 1;
}
}
// ----------------------
// LOGICA DI GIOCO
// ----------------------
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();
gameState = STATE_LOST;
return;
}
for (int i = 0; i < tailLength; i++) {
if (tailX[i] == nextX && tailY[i] == nextY) {
suonoMorte();
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) {
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();
tft.setCursor(5, 5);
tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
tft.setTextSize(2);
tft.print("Score: ");
tft.print(score);
}
// ----------------------
// SCHERMATE
// ----------------------
void renderMenu() {
tft.fillScreen(ILI9341_BLACK);
printCentered("SNAKE", 40, 4, ILI9341_GREEN);
printCentered("Press button", 120, 2, ILI9341_BLUE);
printCentered("to start", 150, 2, ILI9341_BLUE);
}
void renderPause() {
tft.fillScreen(ILI9341_BLACK);
printCentered("PAUSE", 80, 4, ILI9341_YELLOW);
printCentered("Press button", 150, 2, ILI9341_YELLOW);
printCentered("to continue", 175, 2, ILI9341_YELLOW);
}
void renderWin() {
tft.fillScreen(ILI9341_BLACK);
printCentered("YOU WIN", 70, 4, ILI9341_GREEN);
char buf[32];
sprintf(buf, "Score: %d", score);
printCentered(buf, 130, 2, ILI9341_WHITE);
printCentered("Press button", 180, 2, ILI9341_GREEN);
printCentered("to menu", 205, 2, ILI9341_GREEN);
}
void renderLost() {
tft.fillScreen(ILI9341_BLACK);
printCentered("YOU LOST", 70, 4, ILI9341_RED);
char buf[32];
sprintf(buf, "Score: %d", score);
printCentered(buf, 130, 2, ILI9341_WHITE);
printCentered("Press button", 180, 2, ILI9341_YELLOW);
printCentered("to menu", 205, 2, ILI9341_YELLOW);
}
// ----------------------
// RESET
// ----------------------
void resetGame() {
score = 0;
tailLength = 1;
moveDelay = 150;
snakeX = GRID_W / 2;
snakeY = GRID_H / 2;
for (int i = 0; i < MAX_TAIL; i++) {
tailX[i] = 0;
tailY[i] = 0;
}
tailX[0] = snakeX - 1;
tailY[0] = snakeY;
dirX = 1;
dirY = 0;
spawnApple();
}
// ----------------------
// SETUP E LOOP
// ----------------------
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(BUZZER_PIN, OUTPUT);
tft.begin();
tft.setRotation(3); // <-- ORIZZONTALE PIN A DESTRA
tft.fillScreen(ILI9341_BLACK);
resetGame();
}
void loop() {
switch (gameState) {
case STATE_MENU:
renderMenu();
if (buttonPressed()) {
tft.fillScreen(ILI9341_BLACK);
resetGame();
gameState = STATE_GAME;
delay(20);
}
break;
case STATE_GAME:
renderGame();
if (buttonPressed()) {
gameState = STATE_PAUSE;
delay(20);
}
break;
case STATE_PAUSE:
renderPause();
if (buttonPressed()) {
tft.fillScreen(ILI9341_BLACK);
gameState = STATE_GAME;
delay(20);
}
break;
case STATE_WIN:
renderWin();
if (buttonPressed()) {
gameState = STATE_MENU;
delay(20);
}
break;
case STATE_LOST:
renderLost();
if (buttonPressed()) {
gameState = STATE_MENU;
delay(20);
}
break;
}
}