//Arduino Retro game
//Code based in: https://eloctavobit.com/arduino/juego-de-la-serpiente-con-arduino/
//© Año 2024 por está bajo licencia CC BY-NC 4.0  https://creativecommons.org/licenses/by-nc/4.0/
#include <Adafruit_NeoPixel.h>
#include <LiquidCrystal_I2C.h>    // Librería de la pantalla LCD con I2C 

#define PIN_PIXEL 6               // Pin al que están conectados los Neopixeles
#define NUM_PIXELS 64             // Número total de Neopixeles (8x8)
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUM_PIXELS, PIN_PIXEL, NEO_GRB + NEO_KHZ800);
LiquidCrystal_I2C lcd(0x27, 16, 2); // Dirección LCD para una pantalla de 16x2

#define BUTTON_UP 2    // Pin del botón de dirección arriba
#define BUTTON_DOWN 3  // Pin del botón de dirección abajo
#define BUTTON_LEFT 4  // Pin del botón de dirección izquierda
#define BUTTON_RIGHT 5 // Pin del botón de dirección derecha

#define PIN_BUZZER 7    // Pin del zumbador

int snakeSize = 1;        // Tamaño inicial de la serpiente
int snakePos[64] = {0};    // Posiciones de la serpiente (máximo 64 Neopixeles)
int foodPos = -1;          // Posición de la comida
int direction = 1;         // Dirección inicial (1: derecha, -1: izquierda)
int buttonState[4] = {0};  // Estados de los botones (arriba, abajo, izquierda, derecha)
int score = 0;             // Puntuación inicial del juego snake
int lastButtonPress = -1;
int lastButtonPos = -1;

void setup() {
  lcd.init();
  lcd.backlight();    // Activar luz de fondo 
  lcd.clear();        // Borrar LCD
  delay(1000);
  lcd.clear();        // Borrar LCD
  lcd.setCursor(0, 0);   // Coordenadas LCD (x,y)
  lcd.print("Retro Game");  // Mensaje de inicio
  delay(2000); // Mostrar el título durante 2 segundos
  showMenu();
  pixels.begin();
  pixels.setBrightness(50);
  pinMode(BUTTON_UP, INPUT_PULLUP);
  pinMode(BUTTON_DOWN, INPUT_PULLUP);
  pinMode(BUTTON_LEFT, INPUT_PULLUP);
  pinMode(BUTTON_RIGHT, INPUT_PULLUP);
  pinMode(PIN_BUZZER, OUTPUT);    // CONFIGURAMOS EL ZUMBADOR COMO SALIDA
  spawnFood();
}

void loop() {
  int choice = getMenuChoice();
  if (choice == 1) {
    // Jugar
    playSnakeGame();
  } else if (choice == 2) {
    // Salir
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Adios!");
    delay(2000);
    exit(0);
  }
}

void showMenu() {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("1. Jugar");
  lcd.setCursor(0, 1);
  lcd.print("2. Salir");
}

int getMenuChoice() {
  int choice = 0;
  while (true) {
    readButtons();
    if (buttonState[0] == LOW) {
      choice = 1; // Jugar
      break;
    } else if (buttonState[1] == LOW) {
      choice = 2; // Salir
      break;
    }
  }
  delay(500); // Debounce
  return choice;
}

void playSnakeGame() {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Score: ");
  lcd.print(score);

  while (true) {
    readButtons();
    moveSnake();
    playBuzzer(4, 200);
    checkCollision();
    drawSnake();
    delay(200);
  }
}

void readButtons() {
  buttonState[0] = digitalRead(BUTTON_UP);
  buttonState[1] = digitalRead(BUTTON_DOWN);
  buttonState[2] = digitalRead(BUTTON_LEFT);
  buttonState[3] = digitalRead(BUTTON_RIGHT);

  for (int i = 0; i < 4; i++) {
    if (buttonState[i] == LOW) {
      // Si se detecta un nuevo botón presionado, almacenar la información
      lastButtonPress = i;
      lastButtonPos = snakePos[0];
    }
  }
}

void moveSnake() {
  for (int i = snakeSize - 1; i > 0; i--) {
    snakePos[i] = snakePos[i - 1];
  }

  // Si se presionó un botón en la posición actual, actualiza la dirección
  if (lastButtonPos == snakePos[0]) {
    switch (lastButtonPress) {
      case 0: // UP
        direction = -8;
        break;
      case 1: // DOWN
        direction = 8;
        break;
      case 2: // LEFT
        direction = -1;
        break;
      case 3: // RIGHT
        direction = 1;
        break;
    }
    lastButtonPos = -1;
  }

  snakePos[0] += direction;

  if (snakePos[0] >= NUM_PIXELS) {
    snakePos[0] = 0;
  } else if (snakePos[0] < 0) {
    snakePos[0] = NUM_PIXELS - 1;
  }

  if (snakePos[0] == foodPos) {
    score += 100;
    lcd.setCursor(0, 0);
    lcd.print("Score: ");
    lcd.print(score);
    spawnFood();
    snakeSize++;
  }
}

void checkCollision() {
  for (int i = 1; i < snakeSize; i++) {
    if (snakePos[0] == snakePos[i]) {
      // Colisión con el cuerpo de la serpiente, reinicia el juego
      snakeSize = 1;
      score = 0;
      spawnFood();
    }
  }
}

void drawSnake() {
  pixels.clear();
  lcd.clear();
  lcd.setCursor(0, 0);       // Coordenadas LCD (x,y)
  lcd.print("Score: ");
  lcd.print(score);   // Mensaje de inicio 
  // Dibuja la serpiente
  for (int i = 0; i < snakeSize; i++) {
    pixels.setPixelColor(snakePos[i], pixels.Color(0, 255, 0)); // Color verde
  }

  // Dibuja la comida
  pixels.setPixelColor(foodPos, pixels.Color(255, 0, 0)); // Color rojo

  pixels.show();
}

void spawnFood() {
  // Genera una nueva posición para la comida de forma aleatoria
  foodPos = random(NUM_PIXELS);
}

void playBuzzer(int frequency, int duration) {
  tone(PIN_BUZZER, frequency, duration);
  delay(duration);
  noTone(PIN_BUZZER);
}