// Simple Tetris Game
//
// Uses a fairly standard 32x32 matrix display (similar to Adafruit version).
// Written by Boomer48: January 2021

// Rules
//    There are seven different shapes (tetriminos) made from four squares
//    Periodically, one of the shapes will drop from the top of the screen
//    The shape may be moved horizontally and/or rotated in 90 degree increments
//    The shape will continue to fall until it encounters a filled space
//    Shapes are to be fitted together to fill complete lines
//    Completed lines are removed and all lines above are moved down
//    The game is over if the "pile" of incomplete lines reaches the top of the screen

unsigned long int Full_Line = -1; // All cells turned on
byte Rotate; // Keep track of shape rotation position

#define O_Shape 0
#define I_Shape 1
#define T_Shape 2
#define J_Shape 3
#define L_Shape 4
#define S_Shape 5
#define Z_Shape 6
byte Shape_Type;
byte Shape[4][2];
byte Temp_Shape[4][2];
unsigned int Full_Lines = 0;

// Make routines globally visible
void New_Shape(boolean First_Shape);
void Move_Shape();
boolean Shape_Adjusted();
void Tetris_Game_Over();

//------------------------------------------------------------------------------------------
void Tetris_setup() {
  color = RED;
  cycle_time = 10;

  New_Shape(true);

  // 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, Rotate_Sw1 - 14);
  bitSet(PCMSK1, Rotate_Sw2 - 14);
  PCICR = B00000011; // enable PCIE0 and PCIE1 groups
}

void Tetris_loop() {
  Update_Matrix();

  if (Cycle_Timer == cycle_time)
  {
    noInterrupts();
    Move_Shape();
    interrupts();
  }
  else
    Cycle_Timer++;
}

void Move_Shape() {
  byte temp;

  for (temp = 0; temp < 4; temp++)
  {
    Temp_Shape[temp][1] = Shape[temp][1] + 1;
    Temp_Shape[temp][0] = Shape[temp][0];
  }

  if (!Shape_Adjusted())
    New_Shape(false);

  Cycle_Timer = 0;
}

// If new Shape location is valid then update shape location and matrix cells
boolean Shape_Adjusted() {
  byte temp;

  for (temp = 0; temp < 4; temp++)
    bitClear(cells[Shape[temp][1]], Shape[temp][0]);

  for (temp = 0; temp < 4; temp++)
  {
    if ((bitRead(cells[Temp_Shape[temp][1]], Temp_Shape[temp][0])) == true)
      return false; // Cell is occupied

    if ((Temp_Shape[temp][0] >= WIDTH) || (Temp_Shape[temp][1] >= HEIGHT))
      return false; // Beyond border
  }

  for (temp = 0; temp < 4; temp++)
  {
    Shape[temp][0] = Temp_Shape[temp][0];
    Shape[temp][1] = Temp_Shape[temp][1];
    bitSet(cells[Shape[temp][1]], Shape[temp][0]);
  }

  return true;
}

void New_Shape(boolean First_Shape) {
  byte temp;
  byte line;

  Rotate = 0;

  if (!First_Shape)
  {
    // Add old shape to structure
    for (temp = 0; temp < 4; temp++)
      bitSet(cells[Shape[temp][1]], Shape[temp][0]);

    // Remove full lines
    for (temp = 1; temp < HEIGHT; temp++)
      if (cells[temp] == Full_Line)
      {
        for (line = temp; line > 0; line--)
          cells[line] = cells[line - 1];

        Full_Lines++;
      }

    // Check for game over
    if (cells[0] != 0)
      Tetris_Game_Over();
  }

  Shape_Type = micros() % 7;

  switch (Shape_Type) {
    case O_Shape:
      Shape[0][0] = 15;
      Shape[0][1] = 0;
      Shape[1][0] = 14;
      Shape[1][1] = 0;
      Shape[2][0] = 15;
      Shape[2][1] = 1;
      Shape[3][0] = 14;
      Shape[3][1] = 1;
      break;
    case I_Shape:
      Shape[0][0] = 15;
      Shape[0][1] = 0;
      Shape[1][0] = 15;
      Shape[1][1] = 1;
      Shape[2][0] = 15;
      Shape[2][1] = 2;
      Shape[3][0] = 15;
      Shape[3][1] = 3;
      break;
    case T_Shape:
      Shape[0][0] = 15;
      Shape[0][1] = 1;
      Shape[1][0] = 14;
      Shape[1][1] = 0;
      Shape[2][0] = 15;
      Shape[2][1] = 0;
      Shape[3][0] = 16;
      Shape[3][1] = 0;
      break;
    case J_Shape:
      Shape[0][0] = 14;
      Shape[0][1] = 2;
      Shape[1][0] = 15;
      Shape[1][1] = 2;
      Shape[2][0] = 15;
      Shape[2][1] = 1;
      Shape[3][0] = 15;
      Shape[3][1] = 0;
      break;
    case L_Shape:
      Shape[0][0] = 16;
      Shape[0][1] = 2;
      Shape[1][0] = 15;
      Shape[1][1] = 2;
      Shape[2][0] = 15;
      Shape[2][1] = 1;
      Shape[3][0] = 15;
      Shape[3][1] = 0;
      break;
    case S_Shape:
      Shape[0][0] = 16;
      Shape[0][1] = 0;
      Shape[1][0] = 15;
      Shape[1][1] = 0;
      Shape[2][0] = 15;
      Shape[2][1] = 1;
      Shape[3][0] = 14;
      Shape[3][1] = 1;
      break;
    case Z_Shape:
      Shape[0][0] = 14;
      Shape[0][1] = 0;
      Shape[1][0] = 15;
      Shape[1][1] = 0;
      Shape[2][0] = 15;
      Shape[2][1] = 1;
      Shape[3][0] = 16;
      Shape[3][1] = 1;
      break;
  }

  // Display new shape
  for (temp = 0; temp < 4; temp++)
    bitSet(cells[Shape[temp][1]], Shape[temp][0]);
}

void Tetris_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);

  temp = Full_Lines / 10000;
  temp_str[0] = ASCII[temp];
  temp = Full_Lines / 1000;
  temp_str[1] = ASCII[temp];
  temp = Full_Lines / 100;
  temp_str[2] = ASCII[temp];
  temp = Full_Lines % 100;
  if (temp != 0)
    temp = temp / 10;
  temp_str[3] = ASCII[temp];
  temp = Full_Lines % 10;
  temp_str[4] = 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;

      Tetris_setup();
      Cycle_Timer = 0;
      break;
    }
  }
}

void Tetris_ISR0() {
  byte temp;

  delayMicroseconds(16000); // debounce switch before reading
  delayMicroseconds(16000); // debounce switch before reading
  delayMicroseconds(16000); // debounce switch before reading

  if (digitalRead(Left_Sw) == LOW)
  {
    for (temp = 0; temp < 4; temp++)
      Temp_Shape[temp][0] = Shape[temp][0] - 1;

    temp = Shape_Adjusted(); // Don't need return value
  }
  else if (digitalRead(Right_Sw) == LOW)
  {
    for (temp = 0; temp < 4; temp++)
      Temp_Shape[temp][0] = Shape[temp][0] + 1;

    temp = Shape_Adjusted(); // Don't need return value
  }
}

void Tetris_ISR1() {
  byte temp;

  delayMicroseconds(16000); // debounce switch before reading
  delayMicroseconds(16000); // debounce switch before reading
  delayMicroseconds(16000); // debounce switch before reading

  if ((digitalRead(Rotate_Sw1) == LOW) ||
      (digitalRead(Rotate_Sw2) == LOW))
  {
    for (temp = 0; temp < 4; temp++)
      //      bitClear(cells[Shape[temp][1]], Shape[temp][0]);
    {
      Temp_Shape[temp][0] = Shape[temp][0];
      Temp_Shape[temp][1] = Shape[temp][1];
    }

    switch (Shape_Type) {
      case O_Shape:
        // No rotation
        break;
      case I_Shape:
        // Two positions
        if (Rotate == 0)
          // Change from vertical to horizontal
        {
          // Rotate left if on right side or right if on left side
          if (Shape[3][0] > 16)
          {
            Temp_Shape[3][1] = Shape[0][1];
            Temp_Shape[2][1] = Shape[0][1];
            Temp_Shape[1][1] = Shape[0][1];
            Temp_Shape[3][0] = Shape[3][0] - 3;
            Temp_Shape[2][0] = Shape[2][0] - 2;
            Temp_Shape[1][0] = Shape[1][0] - 1;
          }
          else
          {
            Temp_Shape[3][1] = Shape[0][1];
            Temp_Shape[2][1] = Shape[0][1];
            Temp_Shape[1][1] = Shape[0][1];
            Temp_Shape[3][0] = Shape[3][0] + 3;
            Temp_Shape[2][0] = Shape[2][0] + 2;
            Temp_Shape[1][0] = Shape[1][0] + 1;
          }
          if (Shape_Adjusted())
            Rotate++;
        }
        else
          // Change from horizontal to vertical
        {
          Temp_Shape[3][0] = Shape[0][0];
          Temp_Shape[2][0] = Shape[0][0];
          Temp_Shape[1][0] = Shape[0][0];
          Temp_Shape[3][1] = Shape[3][1] + 3;
          Temp_Shape[2][1] = Shape[2][1] + 2;
          Temp_Shape[1][1] = Shape[1][1] + 1;

          if (Shape_Adjusted())
            Rotate = 0;
        }
        break;
      case T_Shape:
        // Four positions
        if (Rotate == 0)
        {
          // Change from upright to pointing right
          Temp_Shape[1][0] = Shape[0][0];
          Temp_Shape[1][1] = Shape[0][1];
          Temp_Shape[0][0] = Shape[3][0];
          Temp_Shape[0][1] = Shape[3][1];
          Temp_Shape[3][0] = Shape[3][0] - 1;
          Temp_Shape[3][1] = Shape[3][1] - 1;

          if (Shape_Adjusted())
            Rotate++;
        }
        // Change from pointing right to pointing left
        else if (Rotate == 1)
        {
          Temp_Shape[0][0] = Shape[0][0] - 2;

          if (Shape_Adjusted())
            Rotate++;
        }
        // Change from pointing left to upside down
        else if (Rotate == 2)
        {
          Temp_Shape[1][0] = Shape[0][0];
          Temp_Shape[1][1] = Shape[0][1];
          Temp_Shape[0][0] = Shape[3][0];
          Temp_Shape[0][1] = Shape[3][1];
          Temp_Shape[3][0] = Shape[3][0] + 1;
          Temp_Shape[3][1] = Shape[3][1] + 1;

          if (Shape_Adjusted())
            Rotate++;
        }
        // Change from upside down to upright
        else
        {
          Temp_Shape[0][1] = Shape[0][1] + 2;

          if (Shape_Adjusted())
            Rotate = 0;
        }
        break;
      case J_Shape:
        // Four positions
        if (Rotate == 0)
        {
          // Change from upright to pointing left
          Temp_Shape[0][0] = Shape[1][0];
          Temp_Shape[1][1] = Shape[2][1];
          Temp_Shape[2][0] = Shape[2][0] - 1;
          Temp_Shape[3][0] = Shape[3][0] - 2;
          Temp_Shape[3][1] = Shape[3][1] + 1;

          if (Shape_Adjusted())
            Rotate++;
        }
        // Change from pointing left to upside down
        else if (Rotate == 1)
        {
          Temp_Shape[0][1] = Shape[0][1] - 1;
          Temp_Shape[1][0] = Shape[1][0] - 1;
          Temp_Shape[2][1] = Shape[2][1] + 1;
          Temp_Shape[3][0] = Shape[3][0] + 1;
          Temp_Shape[3][1] = Shape[3][1] + 2;

          if (Shape_Adjusted())
            Rotate++;
        }
        // Change from upside down to pointing right
        else if (Rotate == 2)
        {
          Temp_Shape[0][0] = Shape[1][0];
          Temp_Shape[1][1] = Shape[1][1] + 1;
          Temp_Shape[2][0] = Shape[2][0] + 1;
          Temp_Shape[3][0] = Shape[3][0] + 2;
          Temp_Shape[3][1] = Shape[3][1] - 1;

          if (Shape_Adjusted())
            Rotate++;
        }
        // Change from pointing right to upright
        else
        {
          Temp_Shape[0][1] = Shape[1][1];
          Temp_Shape[1][0] = Shape[1][0] + 1;
          Temp_Shape[2][1] = Shape[2][1] - 1;
          Temp_Shape[3][0] = Shape[3][0] - 1;
          Temp_Shape[3][1] = Shape[3][1] - 2;

          if (Shape_Adjusted())
            Rotate = 0;
        }
        break;
      case L_Shape:
        // Four positions
        if (Rotate == 0)
        {
          // Change from upright to pointing right
          Temp_Shape[0][0] = Shape[1][0];
          Temp_Shape[1][1] = Shape[2][1];
          Temp_Shape[2][0] = Shape[2][0] + 1;
          Temp_Shape[3][0] = Shape[3][0] + 2;
          Temp_Shape[3][1] = Shape[3][1] + 1;

          if (Shape_Adjusted())
            Rotate++;
        }
        // Change from pointing right to upside down
        else if (Rotate == 1)
        {
          Temp_Shape[0][1] = Shape[0][1] - 1;
          Temp_Shape[1][0] = Shape[1][0] + 1;
          Temp_Shape[2][1] = Shape[2][1] + 1;
          Temp_Shape[3][0] = Shape[3][0] - 1;
          Temp_Shape[3][1] = Shape[3][1] + 2;

          if (Shape_Adjusted())
            Rotate++;
        }
        // Change from upside down to pointing right
        else if (Rotate == 2)
        {
          Temp_Shape[0][0] = Shape[1][0];
          Temp_Shape[1][1] = Shape[1][1] + 1;
          Temp_Shape[2][0] = Shape[2][0] - 1;
          Temp_Shape[3][0] = Shape[3][0] - 2;
          Temp_Shape[3][1] = Shape[3][1] - 1;

          if (Shape_Adjusted())
            Rotate++;
        }
        // Change from pointing left to upright
        else
        {
          Temp_Shape[0][1] = Shape[1][1];
          Temp_Shape[1][0] = Shape[1][0] - 1;
          Temp_Shape[2][1] = Shape[2][1] - 1;
          Temp_Shape[3][0] = Shape[3][0] + 1;
          Temp_Shape[3][1] = Shape[3][1] - 2;

          if (Shape_Adjusted())
            Rotate = 0;
        }
        break;
      case S_Shape:
        // Two positions
        if (Rotate == 0)
        {
          // Change from horizontal to vertical
          Temp_Shape[3][0] = Shape[3][0] + 2;
          Temp_Shape[2][1] = Shape[2][1] - 2;

          if (Shape_Adjusted())
            Rotate++;
        }
        // Change from vertical to horizontal
        else
        {
          Temp_Shape[3][0] = Shape[3][0] - 2;
          Temp_Shape[2][1] = Shape[2][1] + 2;

          if (Shape_Adjusted())
            Rotate = 0;
        }
        break;
      case Z_Shape:
        // Two positions
        if (Rotate == 0)
        {
          // Change from horizontal to vertical
          Temp_Shape[3][0] = Shape[3][0] - 2;
          Temp_Shape[2][1] = Shape[2][1] - 2;

          if (Shape_Adjusted())
            Rotate++;
        }
        // Change from vertical to horizontal
        else
        {
          Temp_Shape[3][0] = Shape[3][0] + 2;
          Temp_Shape[2][1] = Shape[2][1] + 2;

          if (Shape_Adjusted())
            Rotate = 0;
        }
        break;
    }
  }
}
