// =========================================================================
// == JUEGO SNAKE CON CONTROLES HORIZONTALES INVERTIDOS ==
// =========================================================================
// Autor: Josue Cardenas Aguirre (Adaptado por Gemini)
// Descripción: El movimiento izquierda/derecha del joystick está invertido.
// =========================================================================
// --- LIBRERÍAS ---
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <Adafruit_SSD1306.h>
// =========================================================================
// == CONFIGURACIÓN GENERAL ==
// =========================================================================
// --- PINES DE CONEXIÓN ---
#define TFT_CS 15
#define TFT_DC 2
#define TFT_RST 4
#define JOY_HORIZ 35 // Eje X del joystick
#define JOY_VERT 32 // Eje Y del joystick
#define JOY_SEL 25 // Botón del joystick
// --- CONFIGURACIÓN OLED ---
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
// --- CONFIGURACIÓN DEL JUEGO ---
#define GRID_SIZE 10
#define GRID_W 32
#define GRID_H 24
#define INITIAL_SNAKE_LENGTH 3
#define MAX_SNAKE_LENGTH 100
#define GAME_SPEED 150
// --- PALETA DE COLORES ---
#define COLOR_BACKGROUND ILI9341_BLACK
#define COLOR_SNAKE ILI9341_GREEN
#define COLOR_FOOD ILI9341_RED
#define COLOR_TEXT ILI9341_WHITE
// =========================================================================
// == INSTANCIAS Y VARIABLES ==
// =========================================================================
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
struct Point { int x; int y; };
Point snake[MAX_SNAKE_LENGTH];
int snakeLength;
Point food;
enum Direction { UP, DOWN, LEFT, RIGHT };
Direction currentDirection;
int score;
int highScore = 0;
enum GameState { START_SCREEN, PLAYING, GAME_OVER };
GameState currentState = START_SCREEN;
unsigned long lastUpdateTime = 0;
// =========================================================================
// == FUNCIONES DEL JUEGO ==
// =========================================================================
void initGame() {
score = 0;
snakeLength = INITIAL_SNAKE_LENGTH;
currentDirection = RIGHT;
for (int i = 0; i < snakeLength; i++) {
snake[i] = {GRID_W / 2 - i, GRID_H / 2};
}
generateFood();
tft.fillScreen(COLOR_BACKGROUND);
for (int i = 0; i < snakeLength; i++) {
tft.fillRect(snake[i].x * GRID_SIZE, snake[i].y * GRID_SIZE, GRID_SIZE, GRID_SIZE, COLOR_SNAKE);
}
tft.fillRect(food.x * GRID_SIZE, food.y * GRID_SIZE, GRID_SIZE, GRID_SIZE, COLOR_FOOD);
}
void generateFood() {
bool onSnake;
do {
onSnake = false;
food.x = random(0, GRID_W);
food.y = random(0, GRID_H);
for (int i = 0; i < snakeLength; i++) {
if (food.x == snake[i].x && food.y == snake[i].y) {
onSnake = true;
break;
}
}
} while (onSnake);
}
/**
* @brief Lee el joystick y actualiza la dirección de la serpiente. (HORIZONTAL INVERTIDO)
*/
void readJoystick() {
int horiz = analogRead(JOY_HORIZ);
int vert = analogRead(JOY_VERT);
// Izquierda (valor alto) y Derecha (valor bajo) -- LÓGICA INVERTIDA
if (horiz < 500 && currentDirection != LEFT) {
currentDirection = RIGHT;
} else if (horiz > 3500 && currentDirection != RIGHT) {
currentDirection = LEFT;
}
// Arriba (valor alto) y Abajo (valor bajo)
else if (vert > 3500 && currentDirection != DOWN) {
currentDirection = UP;
} else if (vert < 500 && currentDirection != UP) {
currentDirection = DOWN;
}
}
void drawStartScreen() {
tft.fillScreen(COLOR_BACKGROUND);
tft.setTextSize(4);
tft.setCursor(90, 80);
tft.setTextColor(COLOR_SNAKE);
tft.print("SNAKE");
tft.setTextSize(2);
tft.setCursor(40, 150);
tft.setTextColor(COLOR_TEXT);
tft.print("Presiona para jugar");
}
void drawGameOverScreen() {
if (score > highScore) highScore = score;
tft.fillScreen(0xD8A7);
tft.setTextSize(4);
tft.setCursor(40, 60);
tft.setTextColor(COLOR_TEXT);
tft.print("GAME OVER");
tft.setTextSize(2);
tft.setCursor(80, 120);
tft.print("Puntaje: ");
tft.print(score);
tft.setCursor(40, 160);
tft.print("Presiona para reintentar");
}
void updateOLED() {
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.print("Score:");
display.setCursor(80, 0);
display.print(score);
display.setCursor(0, 20);
display.print("Record:");
display.setCursor(80, 20);
display.print(highScore);
display.setTextSize(1);
display.setCursor(0, 45);
if (currentState == PLAYING) display.print("Estado: Jugando");
if (currentState == GAME_OVER) display.print("Estado: Fin del Juego");
if (currentState == START_SCREEN) display.print("Estado: En el menu");
display.display();
}
// =========================================================================
// == PROGRAMA PRINCIPAL ==
// =========================================================================
void setup() {
tft.begin();
tft.setRotation(1);
pinMode(JOY_SEL, INPUT_PULLUP);
Wire.begin();
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { for(;;); }
drawStartScreen();
updateOLED();
}
void loop() {
switch (currentState) {
case START_SCREEN:
if (digitalRead(JOY_SEL) == LOW) {
initGame();
currentState = PLAYING;
}
break;
case PLAYING:
readJoystick();
updateOLED();
if (millis() - lastUpdateTime > GAME_SPEED) {
lastUpdateTime = millis();
Point tail = snake[snakeLength - 1];
tft.fillRect(tail.x * GRID_SIZE, tail.y * GRID_SIZE, GRID_SIZE, GRID_SIZE, COLOR_BACKGROUND);
for (int i = snakeLength - 1; i > 0; i--) {
snake[i] = snake[i - 1];
}
if (currentDirection == UP) snake[0].y--;
if (currentDirection == DOWN) snake[0].y++;
if (currentDirection == LEFT) snake[0].x--;
if (currentDirection == RIGHT) snake[0].x++;
if (snake[0].x < 0 || snake[0].x >= GRID_W || snake[0].y < 0 || snake[0].y >= GRID_H) {
currentState = GAME_OVER;
drawGameOverScreen();
break;
}
for (int i = 1; i < snakeLength; i++) {
if (snake[0].x == snake[i].x && snake[0].y == snake[i].y) {
currentState = GAME_OVER;
drawGameOverScreen();
break;
}
}
if (currentState == GAME_OVER) break;
if (snake[0].x == food.x && snake[0].y == food.y) {
score++;
snakeLength++;
if(snakeLength > MAX_SNAKE_LENGTH) snakeLength = MAX_SNAKE_LENGTH;
generateFood();
tft.fillRect(food.x * GRID_SIZE, food.y * GRID_SIZE, GRID_SIZE, GRID_SIZE, COLOR_FOOD);
}
tft.fillRect(snake[0].x * GRID_SIZE, snake[0].y * GRID_SIZE, GRID_SIZE, GRID_SIZE, COLOR_SNAKE);
}
break;
case GAME_OVER:
updateOLED();
if (digitalRead(JOY_SEL) == LOW) {
initGame();
currentState = PLAYING;
}
break;
}
}