/*
   Project:       Tennis Scoreboard
   Description:   Use to keep score in a tennis match (see Rules.txt)
                  In a real circuit add resistors on each segment
                  and transistors on each digit! (Wokwi doesn't care...)
                  The gray wires are digits, colored are the segments.
   Creation date: 5/21/23
   Author:        AnonEngineering

   License:       https://en.wikipedia.org/wiki/Beerware
*/

#include <LiquidCrystal_I2C.h>

const char PLAYER_1_NAME[] = {"Otto Bounds"};
const char PLAYER_2_NAME[] = {"Dennis Ball"};

const int PLAYER_BTN[2] = {A3, A2};
const int CLOCK_PIN = A1;
const int LATCH_PIN = A0;
const int DATA_PIN  = 13;
// point scores
const uint8_t POINT_SCORES[4] = {0, 15, 30, 40};
// digit driver pins, 0 - 9
const uint8_t DIGIT_PINS[10] = {7, 8, 9, 10, 11, 2, 3, 4, 5, 6};
// lookup table, which segments are on for each number,
// hex values for each digit 0 - 9
const int SEG_BYTES[] = {
  0xFC, /* 0 */
  0x60, /* 1 */
  0xDA, /* 2 */
  0xF2, /* 3 */
  0x66, /* 4 */
  0xB6, /* 5 */
  0xBE, /* 6 */
  0xE0, /* 7 */
  0xFE, /* 8 */
  0xF6  /* 9 */
};

int sets[2] = {0, 0};
int games[2] = {0, 0};
int points[2] = {0, 0};

LiquidCrystal_I2C lcd(0x27, 16, 2);

void checkMatchWin()  {
  if (sets[0] >= 2 || sets[1] >= 2) {
    if (sets[0] > sets[1])  {
      Serial.println("Player 1 match");
      sets[0] = 0;
      sets[1] = 0;
    }
    if (sets[1] > sets[0])  {
      Serial.println("Player 2 match");
      sets[0] = 0;
      sets[1] = 0;
    }
  }
}

void checkSetWin()  {
  if (games[0] >= 6 || games[1] >= 6) {
    if (games[0] >= games[1] + 2)  {
      Serial.println("Player 1 set");
      sets[0]++;
      games[0] = 0;
      games[1] = 0;
      checkMatchWin();
      return;
    }
    if (games[1] >= games[0] + 2)  {
      Serial.println("Player 2 set");
      sets[1]++;
      games[0] = 0;
      games[1] = 0;
      checkMatchWin();
      return;
    }
  }
  Serial.print("P1 games: ");
  Serial.print(games[0]);
  Serial.print("\tP2 games:");
  Serial.println(games[1]);
}

void checkGameWin() {
  if (points[0] >= 4 || points[1] >= 4) {
    if (points[0] >= points[1] + 2)  {
      Serial.println("Player 1 game");
      clearAdvantage();
      games[0]++;
      points[0] = 0;
      points[1] = 0;
      checkSetWin();
      return;
    }
    if (points[1] >= points[0] + 2)  {
      Serial.println("Player 2 game");
      clearAdvantage();
      games[1]++;
      points[0] = 0;
      points[1] = 0;
      checkSetWin();
      return;
    }
  }
  showPoint();
}

void showPoint()  {
  if (points[0] >= 3 || points[1] >= 3) {
    clearAdvantage();
    if (points[0] == points[1]) {
      Serial.println("Deuce");
      return;
    }
  }
  if (points[0] >= 3 && points[1] >= 3) {
    if (points[0] >= points[1] + 1)  {
      Serial.println("Player 1 Adv");
      lcd.setCursor(13, 0);
      lcd.print("ADV");
    }
    if (points[1] >= points[0] + 1)  {
      Serial.println("Player 2 Adv");
      lcd.setCursor(13, 1);
      lcd.print("ADV");
    }
    return;
  }
  Serial.print("P1 points: ");
  Serial.print(points[0]);
  Serial.print("\tP2 points:");
  Serial.println(points[1]);
}

void clearAdvantage() {
  lcd.setCursor(13, 0);
  lcd.print("   ");
  lcd.setCursor(13, 1);
  lcd.print("   ");
}

void getPoints(int playerIndex) {
  static int lastPlayerState[] = {1, 1};

  int playerState[] = {0, 0};
  playerState[playerIndex] = digitalRead(PLAYER_BTN[playerIndex]);
  if (playerState[playerIndex] != lastPlayerState[playerIndex]) {
    if (playerState[playerIndex] == LOW) {
      points[playerIndex]++;
      checkGameWin();
    }
    delay(20);  // debounce
  }
  lastPlayerState[playerIndex] = playerState[playerIndex];
}

void updateSet()  {
  writeShiftDigit(0, sets[0]);
  writeShiftDigit(5, sets[1]);
}

void updateGame() {
  int gameHi[2] = {0, 0};
  int gameLo[2] = {0, 0};
  gameHi[0] = (games[0] % 100) / 10;   // tens
  gameHi[1] = (games[1] % 100) / 10;   // tens
  gameLo[0] = games[0] % 10;           // ones
  gameLo[1] = games[1] % 10;           // ones
  writeShiftDigit(1, gameHi[0]);
  writeShiftDigit(2, gameLo[0]);
  writeShiftDigit(6, gameHi[1]);
  writeShiftDigit(7, gameLo[1]);
}

void updatePoint()  {
  int displayPoints[2] = {0, 0};
  int displayPtHi[2] = {0, 0};
  int displayPtLo[2] = {0, 0};

  displayPoints[0] = points[0];
  displayPoints[1] = points[1];
  if (displayPoints[0] > 3) displayPoints[0] = 3;
  if (displayPoints[1] > 3) displayPoints[1] = 3;

  displayPoints[0] = POINT_SCORES[displayPoints[0]];
  displayPoints[1] = POINT_SCORES[displayPoints[1]];
  displayPtHi[0] = (displayPoints[0] % 100) / 10;   // tens
  displayPtHi[1] = (displayPoints[1] % 100) / 10;   // tens
  displayPtLo[0] = displayPoints[0] % 10;           // ones
  displayPtLo[1] = displayPoints[1] % 10;           // ones

  writeShiftDigit(3, displayPtHi[0]);
  writeShiftDigit(4, displayPtLo[0]);
  writeShiftDigit(8, displayPtHi[1]);
  writeShiftDigit(9, displayPtLo[1]);
}

// for test, not called
void testDisplay()  {
  writeShiftDigit(0, 0);
  writeShiftDigit(1, 1);
  writeShiftDigit(2, 2);
  writeShiftDigit(3, 3);
  writeShiftDigit(4, 4);
  writeShiftDigit(5, 5);
  writeShiftDigit(6, 6);
  writeShiftDigit(7, 7);
  writeShiftDigit(8, 8);
  writeShiftDigit(9, 9);
}

void writeShiftDigit(int digit, int number)  {
  digitalWrite(LATCH_PIN, LOW);
  shiftOut(DATA_PIN, CLOCK_PIN, LSBFIRST, SEG_BYTES[number]);
  digitalWrite(LATCH_PIN, HIGH);
  digitalWrite(DIGIT_PINS[digit], LOW);
  digitalWrite(DIGIT_PINS[digit], HIGH);
}

void setup() {
  Serial.begin(115200); // for test
  lcd.init();
  pinMode(PLAYER_BTN[0], INPUT_PULLUP);
  pinMode(PLAYER_BTN[1], INPUT_PULLUP);
  pinMode(DATA_PIN, OUTPUT);
  pinMode(CLOCK_PIN, OUTPUT);
  pinMode(LATCH_PIN, OUTPUT);
  for (int i = 0; i < 10; i++) {
    pinMode(DIGIT_PINS[i], OUTPUT);
    digitalWrite(DIGIT_PINS[i], HIGH);
  }
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print(PLAYER_1_NAME);
  lcd.setCursor(0, 1);
  lcd.print(PLAYER_2_NAME);
}

void loop() {
  //testDisplay();
  getPoints(0);
  getPoints(1);
  updatePoint();
  updateGame();
  updateSet();
}
74HC595
Players
Sets
Games
Points