#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#define DEBUG 1 // Set Debug level 0-1 to prints result on serial monitor (Set it to 0 when you don't need serial prints)
#if DEBUG == 1
#define debug(x) Serial.print(x)
#define debugln(x) Serial.println(x)
#else
#define debug(x)
#define debugln(x)
#endif
const int maxLDRInput = 500; // Maximum analog read of LDR Array
const int rows[4] = { 6, 7, 8, 9 }; // Digital pins for rows
const int columns[4] = { A0, A1, A2, A3 }; // Analog pins for columns
int previousPotValue = 0; // Variable to hold previous pot value
int ldrThreshold = 0; // Variable to hold LDR threshold value
#define HARDWARE_TYPE MD_MAX72XX::PAROLA_HW
// #define HARDWARE_TYPE MD_MAX72XX::FC16_HW
// Defining size, and output pins
#define MAX_DEVICES 4
#define CS_PIN 3
// Create a new instance of the MD_Parola class with hardware SPI connection
MD_Parola myDisplay = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
#define CLK_PIN 13
#define DATA_PIN 11
#define pushButtonPin 2
#define ledPin 4
#define potentiometerPin A4
#define inactivityTimeout 120000 // 2 minutes standby
unsigned long lastIdleScrollTime = millis(); // Variable to hold last idle scroll time
unsigned long lastScoreShownTime = millis(); // Variable to hold last score shown time
const int ldrPins[] = { A0, A1, A2, A3 };
const int ldrThresholdDefault = 75; // Default LDR sensitivity threshold
unsigned long startTime, endTime, reactionTime;
unsigned long recentScores[5];
unsigned long lastActivity = -inactivityTimeout;
int scoreCount = 0;
bool gameActive = false;
bool showHighScores = false;
bool exitGame = false; // Flag to exit the game
String sc1, sc2, sc3, sc4, sc5; // Variables for recent scores in string form
String lastMessage = ""; // Variable to hold last displayed message on dot matrix
bool checkLdrArray() {
int ldrValues[4][4]; // Array to store LDR readings
// Iterate through each row
for (int i = 0; i < 4; i++) {
// Activate the current row
digitalWrite(rows[i], HIGH);
debug("Row ");
debug(i + 1);
// Read all columns for the current row
for (int j = 0; j < 4; j++) {
int inputRaw = analogRead(columns[j]);
ldrValues[i][j] = inputRaw > ldrThreshold ? 1 : 0;
debug(", LDR Value ");
debug(ldrValues[i][j]);
if (ldrValues[i][j] == 1) {
for (int i = 0; i < 4; i++) { // reset pins low
digitalWrite(rows[i], LOW);
digitalWrite(columns[j], LOW);
}
return true;
}
debug(", Col ");
debug(j + 1);
debug(": ");
debug(ldrValues[i][j]);
Serial.print(", ");
Serial.print(inputRaw);
Serial.print(", ");
}
debugln();
// Deactivate the current row
digitalWrite(rows[i], LOW);
}
return false;
}
void checkButtonPress() { // Function to check if the button is pressed for more than 3 seconds
if (digitalRead(pushButtonPin) == LOW) { // Check for button press if it's more then 3 seconds then stop the game and display the high score
debugln("Button Pressed");
unsigned long pressStart = millis(); // Start the timer
while (digitalRead(pushButtonPin) == LOW)
; // Wait for the button to be released
unsigned long pressDuration = millis() - pressStart; // Calculate the duration of the button press
if (pressDuration >= 3000) { // If the button was pressed for more than 3 seconds
exitGame = true; // Set the exitGame flag to true
showHighScores = true; // Set the showHighScores flag to true
gameActive = false; // Set the gameActive flag to false
myDisplay.displayClear(); // Clear the display
return; // Exit the function
}
}
}
void standbyMode() {
myDisplay.displayClear();
// myDisplay.print("L.E.D. shooter");
lastMessage = "L.E.D. shooter";
// Convert String to char array
char scrollText[lastMessage.length() + 1];
lastMessage.toCharArray(scrollText, lastMessage.length() + 1);
// Set the scrolling parameters
myDisplay.displayScroll(scrollText, PA_CENTER, PA_SCROLL_LEFT, 100);
// Animate the scroll until complete
while (!myDisplay.displayAnimate()) {
// Continuously update the animation
if (digitalRead(pushButtonPin) == LOW) {
return;
}
}
debugln("L.E.D. Shooter");
}
void customDelay(unsigned long waitTime) {
unsigned long startTime = millis();
while (millis() - startTime <= waitTime) {
checkForPotentiometerChange(); // keep checking if potentiometer value changes
checkButtonPress(); // check if button is pressed for more than 3 seconds
if (exitGame) { // If the exitGame flag is set to true then exit the function
return;
}
}
}
void startGame() {
for (int round = 1; round <= 5; round++) {
myDisplay.print("READY");
lastMessage = "READY";
debugln("Get Ready");
customDelay(random(5000, 10000));
digitalWrite(ledPin, HIGH);
myDisplay.print("FIRE");
lastMessage = "FIRE";
debugln("Fire");
startTime = millis();
bool hit = false;
while (millis() - startTime <= 5000) {
checkForPotentiometerChange(); // keep checking if potentiometer value changes
checkButtonPress(); // check if button is pressed for more than 3 seconds
if (exitGame) { // If the exitGame flag is set to true then exit the function
return;
}
if (checkLdrArray()) {
hit = true;
break;
}
}
digitalWrite(ledPin, LOW);
if (hit) {
endTime = millis();
if (exitGame) { // If the exitGame flag is set to true then exit the function
return;
}
reactionTime = endTime - startTime;
debug("Reaction Time: ");
debug(reactionTime);
debugln(" ms");
addScore(reactionTime);
myDisplay.setTextAlignment(PA_RIGHT);
myDisplay.print(String(reactionTime));
lastMessage = String(reactionTime);
for (int i = 0; i < 3; i++) {
digitalWrite(ledPin, HIGH);
customDelay(250);
digitalWrite(ledPin, LOW);
customDelay(250);
}
customDelay(3000);
updateScoreStrings();
if (exitGame) { // If the exitGame flag is set to true then exit the function
return;
}
} else {
myDisplay.setTextAlignment(PA_CENTER);
myDisplay.print("MISS");
Serial.println("Miss");
customDelay(3000);
if (exitGame) { // If the exitGame flag is set to true then exit the function
return;
}
}
customDelay(2000);
}
}
void setup() {
pinMode(pushButtonPin, INPUT_PULLUP);
pinMode(ledPin, OUTPUT);
Serial.begin(9600);
// Intialize the object
myDisplay.begin();
// Set the intensity (brightness) of the display (0-15)
myDisplay.setIntensity(0);
// Clear the display
myDisplay.displayClear();
// Initialize LDR Array pins
// Initialize row pins as outputs
for (int i = 0; i < 4; i++) {
pinMode(rows[i], OUTPUT);
digitalWrite(rows[i], LOW);
}
// Initialize column pins as inputs
for (int i = 0; i < 4; i++) {
pinMode(columns[i], INPUT);
}
myDisplay.begin();
myDisplay.setTextAlignment(PA_CENTER);
standbyMode();
lastIdleScrollTime = millis(); // Set the last idle scroll time
previousPotValue = map(analogRead(potentiometerPin), 0, 1023, 0, maxLDRInput);
ldrThreshold = previousPotValue; // Set LDR threshold to potentiometer value
resetScores();
debugln("Start...");
}
void checkForPotentiometerChange() {
int potValue = map(analogRead(potentiometerPin), 0, 1023, 0, maxLDRInput);
if (abs(potValue - previousPotValue) > 2) {
previousPotValue = potValue;
ldrThreshold = previousPotValue;
String sensitivityMsg = "Sensitivity: " + String(ldrThreshold);
myDisplay.displayClear();
myDisplay.print(ldrThreshold);
debug("Threshold:");
debugln(ldrThreshold);
delay(1000);
myDisplay.displayClear();
myDisplay.print(lastMessage);
}
}
void loop() {
unsigned long currentMillis = millis();
checkForPotentiometerChange();
// Check button press
if (digitalRead(pushButtonPin) == LOW) {
debugln("Button Pressed");
delay(50); // Debounce
unsigned long pressStart = millis();
while (digitalRead(pushButtonPin) == LOW)
;
unsigned long pressDuration = millis() - pressStart;
if (pressDuration >= 3000) { // Long press
debugln("Button pressed for more then 3 show score");
showHighScores = true;
gameActive = false;
myDisplay.displayClear();
printScores();
DisplayDot();
} else { // Short press
gameActive = true;
showHighScores = false;
myDisplay.displayClear();
startGame();
// Step 5: Print recent scores sorted in descending order
printScores();
DisplayDot();
lastScoreShownTime = millis(); // Set the last score shown time
}
lastActivity = currentMillis;
}
// Standby mode after inactivity
if (currentMillis - lastActivity > inactivityTimeout && !gameActive && !showHighScores) {
if (millis() - lastIdleScrollTime > 10000) { // Scroll the message after 10 seconds of inactivity
standbyMode();
lastIdleScrollTime = millis();
}
} else if ((millis() - lastScoreShownTime) > 15000) { // If the high score is displayed then scroll the message after 10 seconds
DisplayDot();
lastScoreShownTime = millis();
}
}
void resetScores() {
for (int i = 0; i < 5; i++) {
recentScores[i] = 0;
}
updateScoreStrings();
}
void addScore(unsigned long newScore) {
if (scoreCount < 5) {
recentScores[scoreCount++] = newScore;
} else {
for (int i = 1; i < 5; i++) {
recentScores[i - 1] = recentScores[i];
}
recentScores[4] = newScore;
}
sortScores();
updateScoreStrings();
}
void sortScores() {
for (int i = 0; i < scoreCount - 1; i++) {
for (int j = i + 1; j < scoreCount; j++) {
if (recentScores[i] > recentScores[j]) { // Sort descending
unsigned long temp = recentScores[i];
recentScores[i] = recentScores[j];
recentScores[j] = temp;
}
}
}
}
void printScores() {
debugln("Recent Scores:");
for (int i = 0; i < scoreCount; i++) {
debug(i + 1);
debug(". ");
debug(recentScores[i]);
debugln(" ms");
}
}
void updateScoreStrings() {
sc1 = (scoreCount > 0) ? String(recentScores[0]) : ""; //+ " ms" : "N/A";
sc2 = (scoreCount > 1) ? String(recentScores[1]) : ""; //+ " ms" : "N/A";
sc3 = (scoreCount > 2) ? String(recentScores[2]) : ""; //+ " ms" : "N/A";
sc4 = (scoreCount > 3) ? String(recentScores[3]) : ""; //+ " ms" : "N/A";
sc5 = (scoreCount > 4) ? String(recentScores[4]) : ""; //+ " ms" : "N/A";
}
void DisplayDot() {
// Reset game and score flags and also exit game flag
gameActive = false;
showHighScores = false;
exitGame = false;
myDisplay.displayClear();
String ST = " 1st " + sc1 + " 2nd " + sc2 + " 3rd " + sc3 + " 4th " + sc4 + " 5th " + sc5;
// Convert String to char array
char scrollText[ST.length() + 1];
ST.toCharArray(scrollText, ST.length() + 1);
// Set the scrolling parameters
myDisplay.displayScroll(scrollText, PA_CENTER, PA_SCROLL_LEFT, 100);
// Animate the scroll until complete
while (!myDisplay.displayAnimate()) {
// Continuously update the animation
// Check if button is pressed then exit the function
if (digitalRead(pushButtonPin) == LOW) {
return;
}
}
}