// Simple Snake Game
//
// Uses a fairly standard 32x32 matrix display (similar to Adafruit version).
// Written by Boomer48: January 2021

// Rules
//    Snake direction can only change right or left from current direction
//    Snake grows each time it eats a new apple
//    Game is over when snake crosses a border or runs into itself

#define update_delay 200 // Time in usec that LEDs are on each cycle

#define UP 0
#define RIGHT 1
#define DOWN 2
#define LEFT 3
byte Snake_Direction;
byte Snake[400][2];
byte Apple[2];
unsigned int Tail;

// Make routines globally visible
void Init_Snake();
void Move_Snake();
void New_Apple();
boolean Hit_Snake();
void Snake_Game_Over();
boolean Ate_Apple();

//--------------------------------------------------------------------------
void Snake_setup() {
  color = RED;
  cycle_time = 5;

  Init_Snake();

  // Switch inputs between 8 and 13, inclusive, use PCINT0
  bitSet(PCMSK0, Left_Sw - 8);
  bitSet(PCMSK0, Right_Sw - 8);

  // Switch inputs for analog pins use PCINT1
  bitSet(PCMSK1, Up_Sw - 14);
  bitSet(PCMSK1, Down_Sw - 14);
  PCICR = B00000011; // enable PCIE0 and PCIE1 groups
}

void Snake_loop() {
  Update_Matrix();

  if (Cycle_Timer == cycle_time)
    Move_Snake();
  else
    Cycle_Timer++;
}

void Init_Snake() {
  // Start snake in top, middle, moving down
  Snake_Direction = DOWN;
  Tail = 0;
  Snake[Tail][0] = 15;
  Snake[Tail][1] = 4; // Head
  Tail++;
  Snake[Tail][0] = 15;
  Snake[Tail][1] = 3;
  Tail++;
  Snake[Tail][0] = 15;
  Snake[Tail][1] = 2;
  Tail++;
  Snake[Tail][0] = 15;
  Snake[Tail][1] = 1;
  Tail++;
  Snake[Tail][0] = 15;
  Snake[Tail][1] = 0;

  bitSet(cells[Snake[0][1]], Snake[0][0]);
  bitSet(cells[Snake[1][1]], Snake[1][0]);
  bitSet(cells[Snake[2][1]], Snake[2][0]);
  bitSet(cells[Snake[3][1]], Snake[3][0]);
  bitSet(cells[Snake[4][1]], Snake[4][0]);

  New_Apple();
}

void Move_Snake() {
  byte temp;
  byte error = true;

  // Clear matrix
  for (row = 0; row < HEIGHT; row++)
    cells[row] = 0;

  // Move each segment forward
  for (temp = Tail; temp > 0; temp--)
  {
    Snake[temp][1] = Snake[temp - 1][1];
    Snake[temp][0] = Snake[temp - 1][0];
  }

  // Check for off board condition
  switch (Snake_Direction) {
    case UP:
      if (Snake[0][1] > 0)
      {
        Snake[0][1]--;
        error = false;
      }
      break;
    case RIGHT:
      if (Snake[0][0] < WIDTH - 1)
      {
        Snake[0][0]++;
        error = false;
      }
      break;
    case DOWN:
      if (Snake[0][1] < HEIGHT - 1)
      {
        Snake[0][1]++;
        error = false;
      }
      break;
    case LEFT:
      if (Snake[0][0] > 0)
      {
        Snake[0][0]--;
        error = false;
      }
      break;
  }

  if (error || Hit_Snake())
    Snake_Game_Over();
  else
  {
    if (Ate_Apple())
    {
      // Add to end of snake
      Tail++;
      Snake[Tail][0] = Snake[Tail - 1][0];
      Snake[Tail][1] = Snake[Tail - 1][1];
      New_Apple();
    }

    for (temp = 0; temp <= Tail; temp++)
      bitSet(cells[Snake[temp][1]], Snake[temp][0]);

    bitSet(cells[Apple[1]], Apple[0]);

    Cycle_Timer = 0;
  }
}

boolean Hit_Snake() {
  byte temp;
  for (temp = 1; temp <= Tail; temp++)
    if ((Snake[0][0] == Snake[temp][0]) && (Snake[0][1] == Snake[temp][1]))
      return true;

  return false;
}

boolean Ate_Apple() {
  if ((Snake[0][0] == Apple[0]) && (Snake[0][1] == Apple[1]))
    return true;
  else
    return false;
}

void New_Apple() {
  Apple[0] = random(0, WIDTH);
  Apple[1] = random(0, HEIGHT);
}

void Snake_Game_Over() {
  byte temp;
  char temp_str[5] = "     ";

  PCICR = B00000000; // disable PCIE0 and PCIE1 groups

  for (row = 0; row < HEIGHT; row++)
    cells[row] = 0;

  Display_String("SCORE", 6);

  Tail = Tail - 4; // Reduce by number of starting segments
  temp = Tail / 100;
  temp_str[1] = ASCII[temp];
  temp = Tail % 100;
  if (temp != 0)
    temp = temp / 10;
  temp_str[2] = ASCII[temp];
  temp = Tail % 10;
  temp_str[3] = ASCII[temp];

  Display_String(temp_str, 14);

  for (temp = 0; temp < 50; temp++)
    Update_Matrix();

  while (true)
  {
    Update_Matrix();
    if ((digitalRead(Left_Sw) == LOW) ||
        (digitalRead(Right_Sw) == LOW) ||
        (digitalRead(Up_Sw) == LOW) ||
        (digitalRead(Down_Sw) == LOW))
    {
      // Clear matrix
      for (row = 0; row < HEIGHT; row++)
        cells[row] = 0;

      Snake_setup();
      Cycle_Timer = 0;
      for (temp = 0; temp < 50; temp++)
        Update_Matrix();
      break;
    }
  }
  Snake_Direction = DOWN;
}

// Interrupt-on-change handler
void Snake_ISR0() {
  delayMicroseconds(16000); // debounce switch before reading
  delayMicroseconds(16000); // debounce switch before reading
  delayMicroseconds(16000); // debounce switch before reading

  if ((Snake_Direction == UP) || (Snake_Direction == DOWN))
    if (digitalRead(Left_Sw) == LOW)
      Snake_Direction = LEFT;
    else
      Snake_Direction = RIGHT;
}

// Interrupt-on-change handler
void Snake_ISR1() {
  delayMicroseconds(16000); // debounce switch before reading
  delayMicroseconds(16000); // debounce switch before reading
  delayMicroseconds(16000); // debounce switch before reading

  if ((Snake_Direction == RIGHT) || (Snake_Direction == LEFT))
    if (digitalRead(Up_Sw) == LOW)
      Snake_Direction = UP;
    else
      Snake_Direction = DOWN;
}
