// Reaction Time Game: MAX7219 + I2C 1602 LCD + Button + Buzzer
// Libraries: LedControl, LiquidCrystal_I2C
#include <LedControl.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

LedControl lc = LedControl(11, 13, 10, 1); // DIN=11, CLK=13, CS=10, 1 device
LiquidCrystal_I2C lcd(0x27, 16, 2);        // change address if your module differs

const int buttonPin = 2;   // player button (INPUT_PULLUP)
const int buzzerPin = 9;

const unsigned long MIN_DELAY = 1500;
const unsigned long MAX_DELAY = 4000;

void setup() {
  pinMode(buttonPin, INPUT_PULLUP);
  pinMode(buzzerPin, OUTPUT);
  digitalWrite(buzzerPin, LOW);

  // Initialize MAX7219
  lc.shutdown(0, false);
  lc.setIntensity(0, 8);   // 0..15
  lc.clearDisplay(0);

  // Initialize LCD
  lcd.init();
  lcd.backlight();

  // Welcome screen
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("Reaction Game");
  lcd.setCursor(0,1);
  lcd.print("Press button...");
  delay(1500);
  lcd.clear();
  randomSeed(analogRead(A0));
}

// small test pattern: flash center cross
void showReadyCue() {
  byte cue[8] = {
    B00011000,
    B00011000,
    B00111100,
    B01111110,
    B01111110,
    B00111100,
    B00011000,
    B00011000
  };
  for (int r=0; r<8; r++) lc.setRow(0, r, cue[r]);
}

void clearMatrix() {
  lc.clearDisplay(0);
}

// map reaction (ms) to 0..8 bars (faster -> more bars)
int reactionToBars(unsigned long reactionMs) {
  const unsigned long FAST_MS = 100;   // bright best time
  const unsigned long SLOW_MS = 2000;  // slow worst time
  if (reactionMs <= FAST_MS) return 8;
  if (reactionMs >= SLOW_MS) return 0;
  // linear mapping
  float frac = 1.0 - (float)(reactionMs - FAST_MS) / (SLOW_MS - FAST_MS);
  int bars = (int)round(frac * 8.0);
  if (bars < 0) bars = 0;
  if (bars > 8) bars = 8;
  return bars;
}

// draw bars on leftmost columns of matrix
void drawBars(int count) {
  // Light columns 0..(count-1) full height
  clearMatrix();
  if (count > 8) count = 8;      // safety

  for (int c = 0; c < count; c++) {
    for (int r = 0; r < 8; r++) {
      // setLed(device, row, column, state)
      lc.setLed(0, r, c, true);
    }
  }
}

void showFalseStart() {
  clearMatrix();
  // small cross to indicate false start
  byte cross[8] = {
    B10000001,
    B01000010,
    B00100100,
    B00011000,
    B00011000,
    B00100100,
    B01000010,
    B10000001
  };
  for (int r=0; r<8; r++) lc.setRow(0, r, cross[r]);
}

void loop() {
  // Wait a random time, but watch for false starts
  unsigned long waitTime = random(MIN_DELAY, MAX_DELAY);
  unsigned long startWait = millis();
  lcd.setCursor(0,0);
  lcd.print("Get Ready...      ");
  lcd.setCursor(0,1);
  lcd.print("Don't press early ");
  clearMatrix();

  // During waiting period detect false start
  bool falseStart = false;
  while (millis() - startWait < waitTime) {
    if (digitalRead(buttonPin) == LOW) {
      // debounce simple
      delay(30);
      if (digitalRead(buttonPin) == LOW) {
        falseStart = true;
        break;
      }
    }
  }

  if (falseStart) {
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("False Start!");
    lcd.setCursor(0,1);
    lcd.print("Wait for cue...");
    tone(buzzerPin, 400, 250);
    showFalseStart();
    delay(1000);
    clearMatrix();
    lcd.clear();
    return; // restart loop
  }

  // Show the cue
  showReadyCue();
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("GO! Press NOW");
  unsigned long cueTime = millis();

  // Wait for button press and record reaction
  while (true) {
    if (digitalRead(buttonPin) == LOW) {
      // debounce
      delay(20);
      if (digitalRead(buttonPin) == LOW) {
        unsigned long reaction = millis() - cueTime;
        // beep
        tone(buzzerPin, 1000, 120);
        // Display on LCD
        lcd.clear();
        lcd.setCursor(0,0);
        lcd.print("Reaction:");
        lcd.setCursor(10,0);
        lcd.print(reaction);
        lcd.print("ms");
        // show small bar graph on matrix
        int bars = reactionToBars(reaction);
        drawBars(bars);
        // also show feedback message
        lcd.setCursor(0,1);
        if (bars >= 6) lcd.print("Excellent!       ");
        else if (bars >= 3) lcd.print("Good             ");
        else lcd.print("Try again!       ");

        // wait until button released to avoid multiple readings
        while (digitalRead(buttonPin) == LOW) delay(10);
        delay(1200); // let player see result
        clearMatrix();
        lcd.clear();
        break;
      }
    }
  }

  // small pause between rounds
  delay(400);
}
