// Simple Pong Game
//
// Uses a fairly standard 32x32 matrix display (similar to Adafruit version).
// Written by Boomer48: January 2021

// Rules
//    Ping Pong ball travels from side to side and must be blocked by the player's
//    paddle before reaching the edge.
//    A point is scored if the ball leaves the game area.
//    The ball will travel in one of three directions depending on which part of the paddle it strikes.
//    If the ball strikes the top or bottom boundary it reflects at the same angle as which it hit.

// Ball direction for each player
#define STRAIGHT1 0
#define ANGLE_R1 1
#define ANGLE_L1 2
#define STRAIGHT2 3
#define ANGLE_R2 4
#define ANGLE_L2 5
byte Ball_Direction;

byte Paddle1;
byte Paddle2;
byte Ball[4];

// Make routines globally visible
void Init_Pong_Game();
void Update_Paddles();
void Move_Ball();
boolean Ball_In_Play();

//-------------------------------------------------------------------------------
void Pong_setup() {
  unsigned long Old_Time;

  cycle_time = 1;
  Init_Pong_Game();
  Old_Time = millis();

  // Initial delay to allow positioning of paddles
  while ((millis() - Old_Time) < 3000)
  {
    for (row = 0; row < HEIGHT; row++)
      cells[row] = 0;

    bitSet(cells[Ball[1]], Ball[0]);
    Update_Paddles();
    Update_Matrix();
  }
}

void Pong_loop() {
  Update_Paddles();
  Update_Matrix();

  if (Cycle_Timer == cycle_time)
    Move_Ball();
  else
    Cycle_Timer++;
}

void Init_Pong_Game() {
  Ball_Direction = STRAIGHT1;
  Ball[0] = 16;
  Ball[1] = 16;
  bitSet(cells[Ball[1]], Ball[0]);
}

void Update_Paddles() {
  Paddle1 = analogRead(Paddle1_Port) / 32;
  if (Paddle1 == 0)
    Paddle1 = Paddle1 + 2;
  else if (Paddle1 > HEIGHT - 2)
    Paddle1 = Paddle1 - 2;

  Paddle2 = analogRead(Paddle2_Port) / 32;
  if (Paddle2 == 0)
    Paddle2 = Paddle2 + 2;
  else if (Paddle2 > HEIGHT - 2)
    Paddle2 = Paddle2 - 2;

  bitSet(cells[Paddle1], 0);
  bitSet(cells[Paddle1 - 1], 0);
  bitSet(cells[Paddle1 - 2], 0);
  bitSet(cells[Paddle1 + 1], 0);
  bitSet(cells[Paddle1 + 2], 0);
  bitSet(cells[Paddle2], WIDTH - 1);
  bitSet(cells[Paddle2 - 1], WIDTH - 1);
  bitSet(cells[Paddle2 - 2], WIDTH - 1);
  bitSet(cells[Paddle2 + 1], WIDTH - 1);
  bitSet(cells[Paddle2 + 2], WIDTH - 1);
}

void Move_Ball() {
  byte temp;

  for (row = 0; row < HEIGHT; row++)
    cells[row] = 0;

  // Update position based on direction
  switch (Ball_Direction) {
    case STRAIGHT1:
      Ball[0]++;
      break;
    case ANGLE_R1:
      Ball[0]++;
      Ball[1]++;
      break;
    case ANGLE_L1:
      Ball[0]++;
      Ball[1]--;
      break;
    case STRAIGHT2:
      Ball[0]--;
      break;
    case ANGLE_R2:
      Ball[0]--;
      Ball[1]--;
      break;
    case ANGLE_L2:
      Ball[0]--;
      Ball[1]++;
      break;
  }

  // Check X-axis for board edge
  if (Ball_In_Play() == false)
  {
    for (temp = 0; temp < 50; temp++)
      Update_Matrix();

    // Game over
    while (true)
    {
      Update_Matrix();
      if ((digitalRead(Left_Sw) == LOW) ||
          (digitalRead(Right_Sw) == LOW) ||
          (digitalRead(Up_Sw) == LOW) ||
          (digitalRead(Down_Sw) == LOW))
      {
        Pong_setup();
        break;
      }
    }
    goto new_game;
  }

  // Check Y-axis for board edge
  if (Ball[1] >= HEIGHT)
  {
    // Update position based on direction
    switch (Ball_Direction) {
      case STRAIGHT1:
        break;
      case ANGLE_R1:
        Ball[1] -= 2;
        Ball_Direction = ANGLE_L1;
        break;
      case ANGLE_L1:
        Ball[1] += 2;
        Ball_Direction = ANGLE_R1;
        break;
      case STRAIGHT2:
        break;
      case ANGLE_R2:
        Ball[1] += 2;
        Ball_Direction = ANGLE_L2;
        break;
      case ANGLE_L2:
        Ball[1] -= 2;
        Ball_Direction = ANGLE_R2;
        break;
    }
  }

  bitSet(cells[Paddle1], 0);
  bitSet(cells[Paddle1 - 1], 0);
  bitSet(cells[Paddle1 + 1], 0);
  bitSet(cells[Paddle2], WIDTH - 1);
  bitSet(cells[Paddle2 - 1], WIDTH - 1);
  bitSet(cells[Paddle2 + 1], WIDTH - 1);
  bitSet(cells[Ball[1]], Ball[0]);

new_game:
  Cycle_Timer = 0;
}

boolean Ball_In_Play() {
  if (Ball[0] == 0)
  { // Paddle1
    if (Ball[1] == Paddle1)
    {
      Ball_Direction = STRAIGHT1;
      Ball[0] += 2;
      return true;
    }
    else if ((Ball[1] == Paddle1 - 1) || (Ball[1] == Paddle1 + 2))
    {
      Ball_Direction = ANGLE_L1;
      Ball[0] += 2;
      Ball[1]--;
      return true;
    }
    else if ((Ball[1] == Paddle1 + 1) || (Ball[1] == Paddle1 - 2))
    {
      Ball_Direction = ANGLE_R1;
      Ball[0] += 2;
      Ball[1]++;
      return true;
    }
    else
    {
      // Missed Paddle1
      Paddle2 = 16;
      bitSet(cells[Paddle2], WIDTH - 1);
      bitSet(cells[Paddle2 - 1], WIDTH - 1);
      bitSet(cells[Paddle2 - 2], WIDTH - 1);
      bitSet(cells[Paddle2 + 1], WIDTH - 1);
      bitSet(cells[Paddle2 + 2], WIDTH - 1);
      Ball[0] = WIDTH - 2;
      Ball[1] = 16;
      bitSet(cells[Ball[1]], Ball[0]);
      return false;
    }
  }

  else if (Ball[0] == WIDTH - 1)
  { // Paddle2
    if (Ball[1] == Paddle2)
    {
      Ball_Direction = STRAIGHT2;
      Ball[0] -= 2;
      return true;
    }
    else if ((Ball[1] == Paddle2 - 1) || (Ball[1] == Paddle2 + 2))
    {
      Ball_Direction = ANGLE_R2;
      Ball[0] -= 2;
      Ball[1]--;
      return true;
    }
    else if ((Ball[1] == Paddle2 + 1) || (Ball[1] == Paddle2 - 2))
    {
      Ball_Direction = ANGLE_L2;
      Ball[0] -= 2;
      Ball[1]++;
      return true;
    }
    else
    {
      // Missed Paddle2
      Paddle1 = 16;
      bitSet(cells[Paddle1], 0);
      bitSet(cells[Paddle1 - 1], 0);
      bitSet(cells[Paddle1 - 2], 0);
      bitSet(cells[Paddle1 + 1], 0);
      bitSet(cells[Paddle1 + 2], 0);
      Ball[0] = 1;
      Ball[1] = 16;
      bitSet(cells[Ball[1]], Ball[0]);
      return false;
    }
  }
  else
    return true; // Didn't hit paddle but still in play
}
