// Score Keeping Display with Timer

// The score keeping and timer code is based on one obtained from Playful Technology
// The programming and a very good tutorial is found at:
// https://www.youtube.com/watch?v=r_yobwaBWr0&t=0s
// Copywrite (c) 2022 Playful Technology


// INCLUDES

#include <Bounce2.h>
  // debounces button inputs
#include <FastLED.h>
  // interfacing to programmable LED strips


// DEFINES

  // Timer LEDs
#define NUM_LEDS_PER_DIGIT_2 28
  // Number of LEDs used in each digit is 28
#define NUM_LEDS_2 112
  // Total number of LEDs in the strip includes LEDs for the two dots = 4 * 28
  // There are only three digits displayed, the fourth digit is the two dots
#define DATA_PIN_2 2
  // The timer Arduino pin number is 2

  // Score LEDs
#define NUM_LEDS_PER_DIGIT 28
  // Number of LEDs used in each digit is 28
#define NUM_LEDS 84
  // Total number of LEDs in the strip = 3 * 28
#define DATA_PIN 3
  // The score Arduino pin number is 3


// CONSTANTS

const uint32_t digits_2[10] = {
  0b00111111111111111111111111110000, // 0
  0b00111111000000000000000011110000, // 1
  0b00110000111111110000111111111111, // 2
  0b00111111111100000000111111111111, // 3
  0b00111111000000001111000011111111, // 4
  0b00111111111100001111111100001111, // 5
  0b00111111111111111111111100001111, // 6
  0b00111111000000000000111111110000, // 7
  0b00111111111111111111111111111111, // 8
  0b00111111111100001111111111111111, // 9
};
  // Timer LEDs includes LEDs for the two dots
  // The third and fourth digits are a constant 1 so they are always lit
  // See Playful Technology tutorial for explanation of the segment sequencing

const uint32_t digits[10] = {
  0b00001111111111111111111111110000, // 0
  0b00001111000000000000000011110000, // 1
  0b00000000111111110000111111111111, // 2
  0b00001111111100000000111111111111, // 3
  0b00001111000000001111000011111111, // 4
  0b00001111111100001111111100001111, // 5
  0b00001111111111111111111100001111, // 6
  0b00001111000000000000111111110000, // 7
  0b00001111111111111111111111111111, // 8
  0b00001111111100001111111111111111, // 9
};
  // Score LEDs
  // The first four digits are zeros for place keeping
  // See Playful Technology tutorial for explanation of the segment sequencing


// INPUT PINS

int irPin2 = 6;
  // 1 point input is Arduino pin 6
int irPin3 = 5;
  // 2 point input is Arduino pin 5
int irPin4 = 4;
  // 3 point input is Arduino pin 4

const byte startPin = 7;
  // Timer start input is Arduino pin 7

const int soundPin2 = 8;
  // call to post output is Arduino pin 8
const int soundPin3 = 9;
  // pacman death output is Arduino pin 9
const int soundPin4 = 10;
  // "one" output is Arduino pin 10
const int soundPin5 = 11;
  // "two" output is Arduino pin 11
const int soundPin6 = 12;
  // "three" output is Arduino pin 12


int count = 0;
  // Score is an integer
boolean state2 = true;
  // Keeps track of program state


// GLOBALS

CRGB leds_2[NUM_LEDS_2];
  // The array of RGB values assigned to each LED in the timer strip
CRGB leds[NUM_LEDS];
  // The array of RGB values assigned to each LED in the score strip

Bounce2::Button btnStart = Bounce2::Button();
  // Bounce object to read debounced start button

unsigned long startTime;
  // The time at which the timer was started

unsigned long timerDuration = 60000;
  // Duration in milliseconds, 60000 milliseconds = 1 minute
  // Round duration is changed here if desired

enum State {Inactive, Active};
State state = State::Inactive;
  // Keeps track of current state

enum Mode {CountUp, CountDown};
Mode mode = Mode::CountDown;
  // Timer is a countdown type timer


// FUNCTIONS

  // Set the values in the timer LED strip corresponding to a particular display/value
void setDigit_2(int display, int val, CHSV colour2){
  for(int i=0;i<NUM_LEDS_PER_DIGIT_2; i++){
    colour2.v = bitRead(digits_2[val], i) * 255;
    leds_2[display*NUM_LEDS_PER_DIGIT_2 + i] = colour2;
  }
}

  // Set the values in the score LED strip corresponding to a particular display/value
void setDigit(int display, int val, CHSV colour){
  for(int i=0;i<NUM_LEDS_PER_DIGIT; i++){
    colour.v = bitRead(digits[val], i) * 127; //was 255
    leds[display*NUM_LEDS_PER_DIGIT + i] = colour;
  }
}

int timeHue;
  // timeHue is an integer
int scoreHue;
  // scoreHue is an integer


void setup() {

  Serial.begin(9600);
  Serial.println(__DATE__);
  Serial.println(state);
    // Added to monitor start in the serial monitor

  FastLED.addLeds<WS2812B, DATA_PIN_2, GRB>(leds_2, NUM_LEDS_2);
    // Initialize the timer LED strip
  FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);
    // Initialize the score LED strip

  btnStart.attach(startPin, INPUT_PULLUP); // Start button, pin 7

  pinMode(irPin2, INPUT); // 1 point, pin 6
  pinMode(irPin3, INPUT); // 2 point, pin 5
  pinMode(irPin4, INPUT); // 3 point, pin 4

  pinMode(soundPin2, OUTPUT); // Call to post, pin 8
  pinMode(soundPin3, OUTPUT); // Pacman death, pin 9
  pinMode(soundPin4, OUTPUT); // "1", pin 10
  pinMode(soundPin5, OUTPUT); // "2", pin 11
  pinMode(soundPin6, OUTPUT); // "3", pin 12

  state = State::Inactive;

}


void loop() {

  btnStart.update();
    // check if start button has been pushed

  static long timeValue = 0;
    // Defines timeValue
  static long score = 0;
    // Defines score

  timeHue = 170;
    // Sets time hue to "blue"
  scoreHue = 127;
    // Sets score hue to "green"

  timeValue = timerDuration;
    // timer starts at timerDuration = 60000 milliseconds = 1 minute

  if(state == State::Inactive){
    timeHue = beatsin8(20, 8, 32);
  }
    // Varies color of timer while waiting to start

  if(btnStart.pressed()) {
    Serial.println("Timer Started");
    Serial.print("Score = ");
    Serial.println(score);
      // Added to monitor score in serial monitor

    score = 0;
      // Start score at zero
    FastLED.show();
      // Display score LEDs

    digitalWrite(soundPin2, HIGH);
      // Play "Call to Post"
    delay(7000);
      // "Call to Post" is about 7 seconds long
    digitalWrite(soundPin2, LOW);
      // Stop "Call to Post"

    state = State::Active;
      // Change state to active
    startTime = millis();
    }


  if(state == State::Active) {

      unsigned long currentTime = millis();  // Grab the current timestamp

      timeValue = timerDuration - (currentTime - startTime);

      timeHue = map(timeValue, 0, timerDuration, 0, 100);

      if (!digitalRead(irPin2) && state2) {  
        score = score + 1;
          // Adds 1 to score if "1" holes are triggered
        digitalWrite(soundPin4, HIGH);
          // Plays "one"
        delay(120);
          // Delay required to play "one"
        digitalWrite(soundPin4, LOW);
          // Stops playing "one"
        state2 = false;
          // Switches program state
        Serial.print("Add 1 Point, Total = ");
        Serial.println(score);
        scoreHue = 96;
          // Changes score color to "green" briefly
        delay(100);
          // Total delay is 220 milliseconds
      }
    
      if (!digitalRead(irPin3) && state2) {  
        score = score + 2;
          // Adds 2 to score if "2" holes are triggered
        digitalWrite(soundPin5, HIGH);
          // Plays "two"
        delay(120);
          // Delay required to play "two"
        digitalWrite(soundPin5, LOW);
          // Stops playing "two"
        state2 = false;
          // Switches program state
        Serial.print("Add 2 Points, Total = ");
        Serial.println(score);
        scoreHue = 192;
          // Changes score color to "purple" briefly
        delay(100);
          // Total wait time is 220 milliseconds
      }

      if (!digitalRead(irPin4) && state2) {  
        score = score + 3;
          // Adds 3 to score if "3" hole is triggered
        digitalWrite(soundPin6, HIGH);
          // Plays "3"
        delay(120);
          // Delay required to play "three"
        digitalWrite(soundPin6, LOW);
          // Stops playing "three"
        state2 = false;
          // Switches program state
        Serial.print("Add 3 Points, Total = ");
        Serial.println(score);
        scoreHue = 0;
          //  Changes score color to "red" briefly
        delay(100);
          // Total wait time is 220 milliseconds
      }

      if (digitalRead(irPin2) || digitalRead(irPin3) || digitalRead(irPin4)) {  
        state2 = true;
          // Switches program state
        delay(230);
          // Delay required to keep from multi-counting hole trigger

        }

    }

    if(timeValue <= 1) {
      digitalWrite(soundPin3, HIGH);
        // Plays "Death of Pacman"
      Serial.println("Timer Finished");
      delay(1000);
        // "Death of Pacman" is about 1 second long
      digitalWrite(soundPin3, LOW);
        // Stops playing "Death of Pacman"
      timeValue = 0;
        // Forces timeValue to zero


      for(int j=0; j<3; j++){
        setDigit(2, (score / 1) % 10, CHSV(0, 0, 0));  // was 10
        setDigit(1, (score / 10) % 10, CHSV(0, 0, 0));  // was 100
        setDigit(0, (score / 100) % 10, CHSV(0, 0, 0)); // left digit was 1000
        FastLED.show();
        delay(1000);

        setDigit(2, (score / 1) % 10, CHSV(128, 255, 255));  // was 10
        setDigit(1, (score / 10) % 10, CHSV(128, 255, 255));  // was 100
        setDigit(0, (score / 100) % 10, CHSV(128, 255, 255)); // left digit was 1000
        FastLED.show();
        delay(1000);
          // Blinks score color from "white" to "aqua" at end of round
        }

      delay(5000);
         // Five second zero timer display
    
      state = State::Inactive;
         // Switches program state

      }

  int seconds = (timeValue / 1000) % 60;
    // Use modulo to calculate "remainder" seconds
  int minutes = timeValue / 60000;
    // Time value in minutes

  setDigit_2(2, seconds%10, CHSV(timeHue, 255, 255));
  setDigit_2(1,(seconds/10)%10, CHSV(timeHue, 255, 255));
  setDigit_2(0, minutes%10, CHSV(timeHue, 255, 255));
  setDigit_2(3, 2, CHSV(timeHue, 255, 255));
    // lights up dots

  setDigit(2, (score / 1) % 10, CHSV(scoreHue, 255, 255));
  setDigit(1, (score / 10) % 10, CHSV(scoreHue, 255, 255));
  setDigit(0, (score / 100) % 10, CHSV(scoreHue, 255, 255));

  FastLED.show();
    // Display time and score
 
  delay(20);

}
