#include <MD_MAX72xx.h>
// Definição do número de dispositivos MAX7219
#define MAX_DEVICES 1
// Definição das dimensões da matriz de LED
const int maxX = MAX_DEVICES * 8 - 1;
const int maxY = 7;
// Definição dos pinos de conexão
#define CLK_PIN 13
#define DATA_PIN 11
#define CS_PIN 10
#define VERT_PIN A0
#define HORZ_PIN A1
#define SEL_PIN 2
// Inicialização da matriz de LED
MD_MAX72XX mx = MD_MAX72XX(MD_MAX72XX::PAROLA_HW, CS_PIN, MAX_DEVICES);
// Variáveis para o jogo
int snakeX[64]; // Coordenadas X da cobra
int snakeY[64]; // Coordenadas Y da cobra
int snakeLength = 1; // Comprimento inicial da cobra
int foodX, foodY; // Coordenadas da comida
int direction = 0; // Direção inicial (0 = direita, 1 = baixo, 2 = esquerda, 3 = cima)
unsigned long previousMillis = 0; // Armazena o último tempo de atualização
const long interval = 200; // Intervalo de tempo entre atualizações (em milissegundos)
bool gameOver = false; // Indica se o jogo terminou
// Função para inicializar o jogo
void setup() {
  mx.begin();
  pinMode(SEL_PIN, INPUT_PULLUP);
  pinMode(5, OUTPUT);
  randomSeed(analogRead(0));
  snakeX[0] = maxX / 2;
  snakeY[0] = maxY / 2;
  generateFood();
}
// Função principal do jogo
void loop() {
  if (gameOver) {
    // Desenha um "X" na matriz de LED
    drawGameOver();
    delay(2000); // Espera 2 segundos antes de reiniciar o jogo
    resetGame();
    return;
  }
  int vert = analogRead(VERT_PIN);
  int horz = analogRead(HORZ_PIN);
  int sel = digitalRead(SEL_PIN);
  // Atualiza a direção com base na leitura do joystick
  if (vert < 400) direction = 3; // Cima
  else if (vert > 600) direction = 1; // Baixo
  else if (horz < 400) direction = 2; // Esquerda
  else if (horz > 600) direction = 0; // Direita
  // Verifica se é hora de atualizar o jogo
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    // Move a cobra
    moveSnake();
    // Verifica se a cobra comeu a comida
    if (snakeX[0] == foodX && snakeY[0] == foodY) {
      snakeLength++;
      generateFood();
    }
    // Verifica se a cobra colidiu com ela mesma
    checkCollision();
    // Atualiza a matriz de LED
    updateMatrix();
  }
}
// Função para mover a cobra
void moveSnake() {
  for (int i = snakeLength - 1; i > 0; i--) {
    snakeX[i] = snakeX[i - 1];
    snakeY[i] = snakeY[i - 1];
  }
  if (direction == 0) snakeX[0]++;
  else if (direction == 1) snakeY[0]++;
  else if (direction == 2) snakeX[0]--;
  else if (direction == 3) snakeY[0]--;
  
  // Verifica se a cobra saiu da matriz e a reposiciona
  if (snakeX[0] > maxX) snakeX[0] = 0;
  if (snakeX[0] < 0) snakeX[0] = maxX;
  if (snakeY[0] > maxY) snakeY[0] = 0;
  if (snakeY[0] < 0) snakeY[0] = maxY;
}
// Função para gerar comida em uma posição aleatória
void generateFood() {
  foodX = random(0, maxX + 1);
  foodY = random(0, maxY + 1);
}
// Função para verificar colisão da cobra com ela mesma
void checkCollision() {
  for (int i = 1; i < snakeLength; i++) {
    if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]) {
      gameOver = true;
      break;
    }
  }
}
// Função para desenhar um "X" na matriz de LED
void drawGameOver() {
  digitalWrite(5, HIGH);
  delay(250);
  digitalWrite(5, LOW);
  mx.clear();
  for (int i = 0; i <= maxX; i++) {
    mx.setPoint(i, i, true); // Diagonal principal
    mx.setPoint(i, maxX - i, true); // Diagonal secundária
  }
}
// Função para reiniciar o jogo
void resetGame() {
  snakeLength = 1;
  snakeX[0] = maxX / 2;
  snakeY[0] = maxY / 2;
  direction = 0;
  gameOver = false;
  generateFood();
  previousMillis = millis();
}
// Função para atualizar a matriz de LED
void updateMatrix() {
  mx.clear();
  for (int i = 0; i < snakeLength; i++) {
    mx.setPoint(snakeY[i], snakeX[i], true);
  }
  mx.setPoint(foodY, foodX, true);
}