#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define ANCHO_PANTALLA 128
#define ALTO_PANTALLA 64
#define moveRight 17 // conexión con la esp32 del botón derecho
#define moveLeft 2 // conexión con la esp32 del botón izquierdo
#define moveUp 5 // conexión con la esp32 del botón arriba
#define moveDown 18 // conexión con la esp32 del botón abajo
// Estructura para devolver las posiciones y velocidades de los fantasmas
struct Fantasma {
  int x;
  int y;
};
Fantasma fantasma1;
Fantasma fantasma2;
bool juegoPerdido = false;
int velFantasmaX = 1;
int velFantasmaY = 1;
int fantasmaX, fantasmaY;
int pacmanX = 0, pacmanY = 0, puntos = 0;;
int ultimaDireccion = 1; // 1: derecha, -1: izquierda, 2: arriba, -2:abajo
int pacmanAncho = 12;
int pacmanAlto = 16;
// Dimensiones de los puntos de comida (ajústalas si es necesario)
int comidaAncho = 1;
int comidaAlto = 1;
bool estadoActualD, estadoAnteriorD, estadoActualL, estadoAnteriorL;
bool estadoActualU, estadoAnteriorU, estadoActualR, estadoAnteriorR;
const int puntosComida[][2] = {
  {15, 5}, {25, 5}, {35, 5}, {45, 5}, {55, 5}, {65, 5}, {75, 5}, {85, 5}, {95, 5}, {105, 5}, {115, 5}, {122, 5}, // Fila superior
  {6, 12}, {25, 12}, {100, 12}, {122, 12},
  {6, 18}, {15, 18}, {25, 18}, {45, 18}, {55, 18}, {65, 18}, {75, 18}, {85, 18}, {100, 18}, {112, 18}, {122, 18}, // Fila antes de la zona de fantasmas
  {6, 30}, {15, 30}, {25, 30}, {35, 30}, {45, 30},  {85, 30}, {95, 30}, {105, 30}, {115, 30}, {122, 30}, // Fila inferior
  {6, 42}, {15, 42}, {25, 42}, {45, 42}, {55, 42}, {65, 42}, {75, 42}, {85, 42}, {100, 42}, {112, 42}, {122, 42}, // Fila después de la zona de fantasmas
  {6, 48}, {45, 48}, {85, 48}, {122, 48},
  {6, 55}, {15, 55}, {25, 55}, {35, 55}, {45, 55}, {55, 55}, {75, 55}, {85, 55}, {95, 55}, {105, 55}, {115, 55}, {122, 55} // Fila inferior
};
const int numPuntosComida = sizeof(puntosComida) / sizeof(puntosComida[0]);
bool puntosRecogidos[numPuntosComida] = {false};  // Array que indica si un punto ha sido recogido
Adafruit_SSD1306 oled(ANCHO_PANTALLA, ALTO_PANTALLA, &Wire, -1);
static const unsigned char PROGMEM pacman_chiqui[] = {
  0x00, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x07, 0xe0, 0x0f, 0xe0, 0x0f, 0x80, 0x0f, 0x80, 0x0f, 0xe0,
  0x07, 0xf0, 0x03, 0xe0, 0x00, 0x00, 0x00, 0x00
};
static const unsigned char PROGMEM pacman_izq[] {
  0x00, 0x00, 0x00, 0x00, 0x07, 0xc0, 0x0f, 0xe0, 0x07, 0xf0, 0x01, 0xf0, 0x01, 0xf0, 0x07, 0xf0,
  0x0f, 0xe0, 0x07, 0xc0, 0x00, 0x00, 0x00, 0x00
};
static const unsigned char PROGMEM pacman_arriba[] {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x80, 0x39, 0xc0, 0x39, 0xc0, 0x3f, 0xc0,
  0x3f, 0xc0, 0x3f, 0xc0, 0x1f, 0x80, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static const unsigned char PROGMEM pacman_abajo[] {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x1f, 0x80, 0x3f, 0xc0, 0x3f, 0xc0,
  0x39, 0xc0, 0x39, 0xc0, 0x19, 0x80, 0x10, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static const unsigned char PROGMEM fantasma[] = {
  0x00, 0x00, 0x01, 0xc0, 0x07, 0xf0, 0x0f, 0xf0, 0x00, 0x00, 0x0c, 0x40, 0x1f, 0xf8, 0x1f, 0xf8,
  0x1f, 0xf8, 0x1f, 0x78, 0x12, 0x48, 0x00, 0x00
};
const int muros[][4] = {
  // Borde superior
  {0, 0, 128, 0},   // Parte superior
  //Borde inferior
  {0, 64, 128, 64}, // Parte inferior
  // Borde izquierdo con abertura
  {0, 0, 0, 24},    // Parte superior izquierda
  {0, 40, 0, 64},   // Parte inferior izquierda
  {0, 24, 18, 24},  // Abertura arriba
  {0, 36, 18, 36},  // Abertura abajo
  // Borde derecho con abertura
  {130, 0, 130, 24}, // Parte superior derecha
  {130, 40, 130, 64}, // Parte inferior derecha
  {116, 24, 130, 24}, // Abertura arriba
  {116, 36, 130, 36}, // Abertura abajo
  // linea de esquina superior izquierda
  {17, 12, 18, 12},
  // linea de esquina superior derecha
  {112, 12, 114, 12},
  // Cuadro central (donde salen fantasmas)
  {63, 35, 74, 35},
  {63, 35, 63, 25},
  {74, 35, 74, 25},
  {63, 25, 74, 25}, // aqui se hizo modificación para que el pacman no pueda entrar a lo de fantasmas
  // L izquierda superior
  {45, 12, 53, 12}, // Horizontal
  {35, 12, 35, 20}, // Vertical
  // L derecha superiorS
  {77, 12, 94, 12}, // Horizontal
  {94, 12, 94, 20}, // Vertical
  // L izquierda inferior
  {35, 40, 35, 48}, // Horizontal
  {17, 48, 35, 48}, // Vertical
  // L derecha inferior
  {94, 40, 94, 48}, // vertical
  {94, 48, 114, 48}, // horizontal
  // T inferior vertical
  {60, 48, 74, 48}, // Horizontal
  {64, 48, 64, 64}, // Vertical
};
void setup() {
  Serial.begin(115200);
  Serial.println("Hello, ESP32!");
  pinMode(moveRight, INPUT);
  pinMode(moveLeft, INPUT);
  pinMode(moveUp, INPUT);
  pinMode(moveDown, INPUT);
  if (!oled.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(("Fallo la conexión con la pantalla..."));
    while (1);
  }
  Serial.println(("Pantalla conectada al sistema"));
  setup_oled();
  inicializar();
}
void inicializar() {
  for (int i = 0; i < numPuntosComida; i++) {
    puntosRecogidos[i] = false; // Reinicia todos los puntos a no recogidos
  }
  // Reinicia otras variables de estado del juego si es necesario
  juegoPerdido = false;
  puntos = 0;
  pacmanX = 0;
  pacmanY = 0;
  fantasma2.x = 115;
  fantasma2.y = 40;
  fantasma1.x = 115;
  fantasma1.y = 0;
  oled.clearDisplay();
  oled.drawBitmap(pacmanX, pacmanY, pacman_chiqui, 16, 12, WHITE);
  oled.display();
}
void setup_oled() {
  delay(2000);
  oled.clearDisplay();
  oled.setTextSize(1); // Medida texto
  oled.setTextColor(WHITE);
  oled.setCursor(30, 30); // Posición(x,y) -> las posiciones de y estan invertidas
  oled.println("PACMAN ");
  oled.display();
  delay(2000);
}
// ---------------------------------------------------------------------
// -------------------- FUNCIONES PARA PACMAN --------------------------
// Función para mover el Pacman
void mover_pacman() {
  estadoActualR = digitalRead(moveRight);
  if (estadoActualR && !estadoAnteriorR) {
    int nuevoX = pacmanX + 6;
    if (!colision(nuevoX, pacmanY)) {
      if (nuevoX > ANCHO_PANTALLA) {
        pacmanX = 0;
      } else {
        pacmanX = nuevoX;
      }
    }
    ultimaDireccion = 1; // Se movió hacia la derecha
  }
  estadoAnteriorR = estadoActualR;
  estadoActualL = digitalRead(moveLeft);
  if (estadoActualL && !estadoAnteriorL) {
    int nuevoX = pacmanX - 6;
    if (!colision(nuevoX, pacmanY)) {
      if (nuevoX < 0) {
        pacmanX = 126;
      } else {
        pacmanX = nuevoX;
      }
    }
    ultimaDireccion = -1; // Se movió hacia la izquierda
  }
  estadoAnteriorL = estadoActualL;
  // Mover hacia arriba
  estadoActualU = digitalRead(moveUp);
  if (estadoActualU && !estadoAnteriorU) {
    int nuevoY = pacmanY - 6; // Velocidad de 6 hacia arriba
    if (!colision(pacmanX, nuevoY)) {
      pacmanY = nuevoY;
    }
    ultimaDireccion = 2;
  }
  estadoAnteriorU = estadoActualU;
  // Mover hacia abajo
  estadoActualD = digitalRead(moveDown);
  if (estadoActualD && !estadoAnteriorD) {
    int nuevoY = pacmanY + 6; // Velocidad de 6 hacia abajo
    if (!colision(pacmanX, nuevoY)) {
      pacmanY = nuevoY;
    }
    ultimaDireccion = -2;
  }
  estadoAnteriorD = estadoActualD;
  verificarColisionComida();  // Verifica si Pacman ha recogido un punto de comida
  verificarColisionFantasma();
}
// ------------------- FUNCIONES PARA FANTASMAS ------------------------
Fantasma mover_fantasma(int fantasmaX, int fantasmaY, int velFantasmaX, int velFantasmaY) {
  // Calcula la diferencia entre las posiciones de Pacman y el fantasma
  int dx = pacmanX - fantasmaX;
  int dy = pacmanY - fantasmaY;
  bool movido = false;
  // Intentar moverse hacia Pacman en el eje X primero
  if (abs(dx) > abs(dy)) {
    // Pacman está a la derecha
    if (dx > 0 && !colisionF(fantasmaX + velFantasmaX, fantasmaY)) {
      // Mover hacia la derecha
      fantasmaX += velFantasmaX;
      movido = true;
    }
    // Pacman está a la izquierda
    else if (dx < 0 && !colisionF(fantasmaX - velFantasmaX, fantasmaY)) {
      // Mover hacia la izquierda
      fantasmaX -= velFantasmaX;
      movido = true;
    }
  }
  // Si no se pudo mover en el eje X, intentar en el eje Y
  if (!movido) {
    if (dy > 0 && !colisionF(fantasmaX, fantasmaY + velFantasmaY)) {
      // Mover hacia abajo
      fantasmaY += velFantasmaY;
      movido = true;
    } else if (dy < 0 && !colisionF(fantasmaX, fantasmaY - velFantasmaY)) {
      // Mover hacia arriba
      fantasmaY -= velFantasmaY;
      movido = true;
    }
  }
  // Si no se pudo mover ni en el eje X ni en el eje Y, intentar moverse en una dirección perpendicular
  if (!movido) {
    if (abs(dx) > abs(dy)) {
      // Si no pudo moverse en el eje X, intentar moverse en el eje Y (perpendicular)
      if (dy > 0 && !colisionF(fantasmaX, fantasmaY + velFantasmaY)) {
        fantasmaY += velFantasmaY;
      } else if (dy < 0 && !colisionF(fantasmaX, fantasmaY - velFantasmaY)) {
        fantasmaY -= velFantasmaY;
      }
    } else {
      // Si no pudo moverse en el eje Y, intentar moverse en el eje X (perpendicular)
      if (dx > 0 && !colisionF(fantasmaX + velFantasmaX, fantasmaY)) {
        fantasmaX += velFantasmaX;
      } else if (dx < 0 && !colisionF(fantasmaX - velFantasmaX, fantasmaY)) {
        fantasmaX -= velFantasmaX;
      }
    }
  }
  // Si no se pudo mover en ninguna dirección, elegir un movimiento aleatorio para evitar estancarse
  if (!movido) {
    int direccion = rand() % 4;
    switch (direccion) {
      case 0: // Intentar mover a la derecha
        if (!colisionF(fantasmaX + velFantasmaX, fantasmaY)) {
          fantasmaX += velFantasmaX;
        }
        break;
      case 1: // Intentar mover a la izquierda
        if (!colisionF(fantasmaX - velFantasmaX, fantasmaY)) {
          fantasmaX -= velFantasmaX;
        }
        break;
      case 2: // Intentar mover hacia abajo
        if (!colisionF(fantasmaX, fantasmaY + velFantasmaY)) {
          fantasmaY += velFantasmaY;
        }
        break;
      case 3: // Intentar mover hacia arriba
        if (!colisionF(fantasmaX, fantasmaY - velFantasmaY)) {
          fantasmaY -= velFantasmaY;
        }
        break;
    }
  }
  // Retornar la nueva posición del fantasma
  Fantasma resultado;
  resultado.x = fantasmaX;
  resultado.y = fantasmaY;
  return resultado;
}
// ------------------- FUNCIONES PARA LABERINTO -----------------------
void generar_laberinto() {
  // Borde superior
  oled.drawLine(0, 0, 128, 0, SSD1306_WHITE);    // Parte superior
  // Borde izquierdo con abertura
  oled.drawLine(0, 0, 0, 24, SSD1306_WHITE);      // Parte superior izquierda
  oled.drawLine(0, 36, 0, 64, SSD1306_WHITE);     // Parte inferior izquierda
  oled.drawLine(0, 24, 18, 24, SSD1306_WHITE);     // abertura arriba
  oled.drawLine(0, 36, 18, 36, SSD1306_WHITE);     // abertura abajo
  // Borde inferior
  oled.drawLine(0, 63, 128, 63, SSD1306_WHITE);   // Parte inferior
  // Borde derecho con abertura
  oled.drawLine(127, 0, 127, 24, SSD1306_WHITE);   // Parte superior derecha
  oled.drawLine(127, 36, 127, 64, SSD1306_WHITE);   // Parte inferior derecha
  oled.drawLine(127, 24, 108, 24, SSD1306_WHITE);     // abertura arriba
  oled.drawLine(127, 36, 108, 36, SSD1306_WHITE);     // abertura abajo
  // linea de esquina superior izquierda
  oled.drawLine(13, 12, 19, 12, SSD1306_WHITE);
  // linea de esquina superior derecha
  oled.drawLine(114, 12, 108, 12, SSD1306_WHITE);
  // Cuadro central (donde salen fantasmas)
  oled.drawLine(54, 35, 74, 35, SSD1306_WHITE); // Línea horizontal inferior
  oled.drawLine(54, 35, 54, 25, SSD1306_WHITE); // Línea vertical izquierda
  oled.drawLine(74, 35, 74, 25, SSD1306_WHITE); // Línea vertical derecha
  oled.drawLine(54, 25, 59, 25, SSD1306_WHITE); // Línea horizontal superior izquierda
  oled.drawLine(69, 25, 74, 25, SSD1306_WHITE); // Línea horizontal superior derecha
  // L izquierda superior
  oled.drawLine(35, 12, 57, 12, SSD1306_WHITE); // Horizontal
  oled.drawLine(35, 12, 35, 24, SSD1306_WHITE); // Vertical
  // L derecha superior
  oled.drawLine(71, 12, 93, 12, SSD1306_WHITE); // Horizontal
  oled.drawLine(93, 12, 93, 24, SSD1306_WHITE); // Vertical
  // L izquierda inferior
  oled.drawLine(35, 36, 35, 48, SSD1306_WHITE); // Vertical
  oled.drawLine(13, 48, 35, 48, SSD1306_WHITE); //Horizontal
  // L derecha inferior
  oled.drawLine(93, 36, 93, 48, SSD1306_WHITE); // Vertical
  oled.drawLine(114, 48, 93, 48, SSD1306_WHITE); // Horizontal
  // T inferior vertical
  oled.drawLine(54, 46, 74, 46, SSD1306_WHITE); //linea horizontal
  oled.drawLine(64, 46, 64, 64, SSD1306_WHITE); //linea vertical
}
bool colision(int nuevoX, int nuevoY) {
  // Verificar colisión con los muros
  for (int i = 0; i < sizeof(muros) / sizeof(muros[0]); i++) {
    int x1 = muros[i][0];
    int y1 = muros[i][1];
    int x2 = muros[i][2];
    int y2 = muros[i][3];
    // Comprobación de colisión simple
    if (nuevoX < x2 && nuevoX + 16 > x1 && nuevoY < y2 && nuevoY + 12 > y1) {
      Serial.println("X1: " + String(x1) + " Y1: " + String(y1) + "X2: " + String(x2) + " Y2: " + String(y2));
      return true; // Colisión detectada
    }
  }
  return false; // Sin colisión
}
bool colisionF(int nuevoX, int nuevoY) {
  // Verificar colisión con los muros
  for (int i = 0; i < sizeof(muros) / sizeof(muros[0]); i++) {
    int x1 = muros[i][0];
    int y1 = muros[i][1];
    int x2 = muros[i][2];
    int y2 = muros[i][3];
    // Comprobación de colisión simple
    if (nuevoX < x2 && nuevoX + 16 > x1 && nuevoY < y2 && nuevoY + 12 > y1) {
      //Serial.println("X1: " + String(x1) + " Y1: " + String(y1) + "X2: " + String(x2) + " Y2: " + String(y2));
      return true; // Colisión detectada
    }
  }
  return false; // Sin colisión
}
// ------------------- FUNCIONES PARA COMIDA --------------------------
void dibujarPuntosComida() {
  for (int i = 0; i < numPuntosComida; i++) {
    if (!puntosRecogidos[i]) {  // Solo dibuja los puntos no recogidos
      oled.drawPixel(puntosComida[i][0], puntosComida[i][1], WHITE);
    }
  }
}
void verificarColisionComida() {
  for (int i = 0; i < numPuntosComida; i++) {
    if (!puntosRecogidos[i]) {  // Si el punto no ha sido recogido aún
      // Posición del punto de comida
      int comidaX = puntosComida[i][0];
      int comidaY = puntosComida[i][1];
      // Comprobamos si el rectángulo de Pacman intersecta con el rectángulo del punto de comida
      bool colision = (pacmanX < comidaX + comidaAncho &&
                       pacmanX + pacmanAncho > comidaX &&
                       pacmanY < comidaY + comidaAlto &&
                       pacmanY + pacmanAlto > comidaY);
      if (colision) {
        puntosRecogidos[i] = true;  // Marca el punto como recogido
        puntos += 1;  // Aumenta el puntaje
        Serial.println("Puntos: " + String(puntos));  // Muestra el puntaje en el monitor serial
      }
    }
  }
}
// Función para verificar si Pacman se choca con fantasma
// Función para verificar colisión entre Pacman y fantasmas
void verificarColisionFantasma() {
  const int rangoColision = 5;
  // Comprobar colisión con el primer fantasma
  if (pacmanX >= (fantasma1.x - rangoColision) && pacmanX <= (fantasma1.x + rangoColision) &&
      pacmanY >= (fantasma1.y - rangoColision) && pacmanY <= (fantasma1.y + rangoColision)) {
    Serial.println("juego perdido ");
    juegoPerdido = true;
  }
  // Comprobar colisión con el segundo fantasma
  if (pacmanX >= (fantasma2.x - rangoColision) && pacmanX <= (fantasma2.x + rangoColision) &&
      pacmanY >= (fantasma2.y - rangoColision) && pacmanY <= (fantasma2.y + rangoColision)) {
    Serial.println("juego perdido ");
    juegoPerdido = true;
  }
}
// ------------------- FUNCIONES TÉCNICAS -----------------------------
// Dibujar ambos objetos
void dibujar_escena() {
  oled.clearDisplay();
  generar_laberinto();
  dibujarPuntosComida();
  // Dibujar el fantasma
  oled.drawBitmap(fantasma1.x, fantasma1.y, fantasma, 16, 12, WHITE);
  oled.drawBitmap(fantasma2.x, fantasma2.y, fantasma, 16, 12, WHITE);
  // Dibujar Pacman dependiendo de la última dirección
  if (ultimaDireccion == -1) {
    oled.drawBitmap(pacmanX, pacmanY, pacman_izq, 16, 12, WHITE);
  } else if (ultimaDireccion == 1) {
    oled.drawBitmap(pacmanX, pacmanY, pacman_chiqui, 16, 12, WHITE);
  } else if (ultimaDireccion == 2) {
    oled.drawBitmap(pacmanX, pacmanY, pacman_arriba, 16, 12, WHITE);
  } else {
    oled.drawBitmap(pacmanX, pacmanY, pacman_abajo, 16, 12, WHITE);
  }
  oled.display();
}
void inicializar_juego() {
  mover_pacman();
  fantasma1 = mover_fantasma(fantasma1.x, fantasma1.y, velFantasmaX, velFantasmaY);
  fantasma2 = mover_fantasma(fantasma2.x, fantasma2.y, velFantasmaX, velFantasmaY);
  dibujar_escena();
  delay(50);
}
void loop() {
  if (puntos == 64) {
    oled.clearDisplay(); // Limpia la pantalla
    oled.setCursor(30, 15);
    oled.setTextSize(1);
    oled.println("¡Ganaste!");
    oled.setTextSize(1);
    oled.print("Total de puntos: ");
    oled.println(puntos);
    oled.display();
    delay(3000);
    inicializar();
    inicializar_juego();
  } else if (juegoPerdido) {
    oled.clearDisplay();
    oled.setCursor(30, 15);
    oled.setTextSize(1);
    oled.println("¡Perdiste!");
    oled.setTextSize(1);
    oled.print("Total de puntos: ");
    oled.println(puntos);
    oled.display();
    delay(3000);
    inicializar();
    inicializar_juego();
  } else {
    inicializar_juego();
  }
}