#include <SevSegShift.h>
#include <EEPROM.h> // Include EEPROM library
// Define shift register pins
#define SHIFT_PIN_SHCP 11
#define SHIFT_PIN_STCP 10
#define SHIFT_PIN_DS 9
// Define switch pins
#define SWITCH_PIN 2
#define SWITCH_PIN_2 3
#define SWITCH_PIN_3 4
// Define reset button pin
#define RESET_BUTTON_PIN 12
// Define LED pins
#define mismatchLED_PIN 5
#define matchLED_PIN 6
#define recordLED_PIN 7
// Define buzzer pin
#define buzzer_PIN 8
// Debounce time in milliseconds
#define DEBOUNCE_DELAY 50
// Create a SevSegShift object
SevSegShift sevseg(SHIFT_PIN_DS, SHIFT_PIN_SHCP, SHIFT_PIN_STCP, 1, true);
String newSequence = "";
int newSequenceLength = 0; // Variable to track the length of newSequence
String randomSequence = ""; // Variable to store the random sequence
int recordSequenceLength = 0; // Variable to store the length of the record sequence
bool lastSwitchState = LOW;
bool currentSwitchState;
bool lastSwitchState2 = LOW;
bool currentSwitchState2;
bool lastSwitchState3 = LOW;
bool currentSwitchState3;
unsigned long lastDebounceTime = 0;
bool initialStateProcessed = false; // Flag to ignore initial state
bool newRecordAchieved = false; // Flag to indicate a new record has been achieved
unsigned long flashStartTime = 0; // Timestamp for flash start
int flashCount = 0; // Counter for the number of flashes
unsigned long previousMillis = 0;
const long interval = 350; // 1 second interval
int currentWordIndex = -1;
// Array of words to display
const char* words[] = {" rE", " rEc", "rEco",
"Ecor", "cord", "ord "};
const int numWords = sizeof(words) / sizeof(words[0]);
// Timing variables for alerts
unsigned long matchAlertStartTime = 0;
bool matchAlertActive = false;
unsigned long mismatchAlertStartTime = 0;
bool mismatchAlertActive = false;
unsigned long recordAlertStartTime = 0;
bool recordAlertActive = false;
unsigned long showRecordStartTime = 0;
bool showRecordActive = true;
bool pauseDisplayUpdates = false;
// Define display position variables
int displayStartPosition = 0; // Start position for the sequence length
unsigned long lastPositionUpdateTime = 0; // Last time the display position was updated
const unsigned long POSITION_UPDATE_INTERVAL = 100; // Interval between position updates (in milliseconds)
bool isUpdatingDisplay = false; // Flag to indicate if the display is updating
void setup() {
// Initialize the serial communication
Serial.begin(9600);
Serial.println("Setup Started");
// Initialize the SevSegShift library
byte numDigits = 4;
byte digitPins[] = {A0, A1, A2, A3}; // Common anode pins for each digit
byte segmentPins[] = {0, 1, 2, 3, 4, 5, 6, 7}; // Shift register pins to segments
bool resistorsOnSegments = false; // Resistors are on common pins
byte hardwareConfig = COMMON_ANODE; // Common anode configuration
bool updateWithDelays = false; // No update with delays
bool leadingZeros = false; // No leading zeros
bool disableDecPoint = false; // Disable decimal point
sevseg.begin(hardwareConfig, numDigits, digitPins, segmentPins, resistorsOnSegments, updateWithDelays, leadingZeros, disableDecPoint);
sevseg.setBrightness(10);
// Initialize the switch pins
pinMode(SWITCH_PIN, INPUT_PULLUP);
pinMode(SWITCH_PIN_2, INPUT_PULLUP);
pinMode(SWITCH_PIN_3, INPUT_PULLUP);
pinMode(RESET_BUTTON_PIN, INPUT_PULLUP); // Set reset button pin as input with pull-up
pinMode(mismatchLED_PIN, OUTPUT); // Set mismatch LED pin as output
pinMode(matchLED_PIN, OUTPUT); // Set match LED pin as output
pinMode(recordLED_PIN, OUTPUT); // Set record LED pin as output
pinMode(buzzer_PIN, OUTPUT); // Set buzzer pin as output
// Read the initial switch states to set up the initial states
currentSwitchState = digitalRead(SWITCH_PIN);
currentSwitchState2 = digitalRead(SWITCH_PIN_2);
currentSwitchState3 = digitalRead(SWITCH_PIN_3);
initialStateProcessed = true; // Indicate that initial state has been processed
// Read the stored record sequence length from EEPROM
recordSequenceLength = EEPROM.read(0);
// Check if EEPROM value is invalid (255 is often used as default value)
if (recordSequenceLength == 255) {
recordSequenceLength = 0; // Set to zero if EEPROM value is invalid
EEPROM.write(0, recordSequenceLength); // Initialize EEPROM with zero
}
Serial.print("Stored record sequence length: ");
Serial.println(recordSequenceLength);
generateRandomSequence(); // Generate the random sequence at setup
}
void loop() {
unsigned long currentMillis = millis();
// Check if the reset button is pressed
if (digitalRead(RESET_BUTTON_PIN) == LOW) { // Button pressed (LOW state with pull-up)
delay(50); // Debounce delay
if (digitalRead(RESET_BUTTON_PIN) == LOW) { // Confirm button press after debounce
resetRecordSequenceLength(); // Reset the record sequence length
}
// Optional: wait until the button is released before continuing
while (digitalRead(RESET_BUTTON_PIN) == LOW) {
// Do nothing, wait for button release
}
}
// Check if display updates should be paused
if (showRecordActive) {
// Show record text and manage display updates for startup
showRecordOnStartup(currentMillis);
pauseDisplayUpdates = true; // Pause regular display updates
} else {
pauseDisplayUpdates = false; // Resume regular display updates
}
// Continuously refresh the display
sevseg.refreshDisplay();
// Check the switches
handleSwitch(SWITCH_PIN, lastSwitchState, currentSwitchState, 1);
handleSwitch(SWITCH_PIN_2, lastSwitchState2, currentSwitchState2, 2);
handleSwitch(SWITCH_PIN_3, lastSwitchState3, currentSwitchState3, 3);
// Handle alerts
if (matchAlertActive) {
handleMatchAlert(currentMillis);
}
if (mismatchAlertActive) {
handlemisMatchAlert(currentMillis);
}
if (recordAlertActive) {
handleRecordAlert(currentMillis);
}
// Update the display with the current sequence lengths if not paused
if (!pauseDisplayUpdates) {
if (isUpdatingDisplay) {
// Update the display position if the interval has elapsed
if (currentMillis - lastPositionUpdateTime >= POSITION_UPDATE_INTERVAL) {
updateDisplayPosition();
lastPositionUpdateTime = currentMillis; // Reset the position update timer
}
} else {
// Directly set the display to the final position
char displayChars[4] = " "; // 4 characters (no null terminator needed for display)
// Determine the starting index based on the number of digits in newSequenceLength
int numberOfDigits = newSequenceLength >= 1000 ? 4 : (newSequenceLength >= 100 ? 3 : (newSequenceLength >= 10 ? 2 : 1));
int startIndex = 4 - numberOfDigits; // Calculate the start index for formatDigits
// Format and set the display characters based on the number of digits
formatDigits(newSequenceLength, displayChars, startIndex);
sevseg.setChars(displayChars);
}
}
}
void handleSwitch(int pin, bool &lastState, bool ¤tState, int switchValue) {
digitalWrite(recordLED_PIN, LOW);
bool reading = digitalRead(pin);
if (reading != lastState) {
lastDebounceTime = millis(); // reset the debouncing timer
}
if ((millis() - lastDebounceTime) > DEBOUNCE_DELAY) {
if (reading != currentState) {
currentState = reading;
if (initialStateProcessed && (currentState == LOW || currentState == HIGH)) {
// Deactivate all previous active alerts
deactivateAllAlerts();
// Process new switch input
newSequence += String(switchValue) + " ";
newSequenceLength++; // Increment the sequence length
Serial.print("New sequence: ");
Serial.println(newSequence);
compareSequences(); // Compare the sequences
startDisplayUpdate(); // Start the display update animation
// Set showRecordActive to false when any switch is pressed
showRecordActive = false;
}
initialStateProcessed = true; // Ensure initial state is only processed once
}
}
lastState = reading;
}
// Function to deactivate all active alerts
void deactivateAllAlerts() {
matchAlertActive = false;
mismatchAlertActive = false;
recordAlertActive = false;
// Turn off all alert LEDs and buzzer
digitalWrite(matchLED_PIN, LOW);
digitalWrite(mismatchLED_PIN, LOW);
digitalWrite(recordLED_PIN, LOW);
digitalWrite(buzzer_PIN, LOW);
// Reset flash count
flashCount = 0;
}
void handleMatchAlert(unsigned long currentMillis) {
if (currentMillis - matchAlertStartTime >= 2000) {
digitalWrite(matchLED_PIN, !digitalRead(matchLED_PIN)); // Toggle LED state
matchAlertStartTime = currentMillis;
flashCount++;
if (flashCount >= 2) { // Flash 2 times for match alert
deactivateAllAlerts();
flashCount = 0; // Reset flash count
digitalWrite(matchLED_PIN, LOW); // Ensure LED is off
}
}
}
void handleRecordAlert(unsigned long currentMillis) {
if (currentMillis - recordAlertStartTime >= 400) {
digitalWrite(recordLED_PIN, !digitalRead(recordLED_PIN)); // Toggle LED state
recordAlertStartTime = currentMillis;
flashCount++;
if (flashCount >= 5) { // Flash 5 times for record alert (2 flashes per cycle)
deactivateAllAlerts();
flashCount = 0; // Reset flash count
digitalWrite(recordLED_PIN, LOW); // Ensure LED is off
}
}
}
void handlemisMatchAlert(unsigned long currentMillis) {
if (currentMillis - mismatchAlertStartTime >= 2000) {
digitalWrite(mismatchLED_PIN, !digitalRead(mismatchLED_PIN)); // Toggle LED state
digitalWrite(buzzer_PIN, !digitalRead(buzzer_PIN)); // Toggle buzzer state
mismatchAlertStartTime = currentMillis;
flashCount++;
if (flashCount >= 2) { // Flash 2 times for match alert
mismatchAlertActive = false; // End alert
flashCount = 0; // Reset flash count
digitalWrite(mismatchLED_PIN, LOW); // Ensure LED is off
digitalWrite(buzzer_PIN, LOW); // Ensure buzzer is off
}
}
}
// Function to generate and store the random sequence
void generateRandomSequence() {
int sequenceLength = 99;
randomSequence = "";
// Seed the random number generator with a random value from an unconnected analog pin
randomSeed(analogRead(A5));
// Generate and store the random sequence
for (int i = 0; i < sequenceLength; i++) {
int randomNumber = random(1, 4); // random(1, 4) generates numbers from 1 to 3
randomSequence += String(randomNumber) + " ";
}
// Print the random sequence
Serial.print("Random sequence: ");
Serial.println(randomSequence);
}
// Function to format numbers to avoid leading zeros
void formatDigits(int value, char* displayChars, int startIndex) {
// Clear the displayChars array
for (int i = 0; i < 4; i++) {
displayChars[i] = ' ';
}
if (value >= 10) {
// Two-digit number
displayChars[startIndex] = '0' + (value / 10); // Tens place
displayChars[startIndex + 1] = '0' + (value % 10); // Units place
} else {
// Single-digit number
displayChars[startIndex] = '0' + value; // Single digit
}
}
// Function to start the display update animation
void startDisplayUpdate() {
displayStartPosition = 0; // Start from the first position
isUpdatingDisplay = true; // Start updating the display
lastPositionUpdateTime = millis(); // Initialize the position update timer
}
// Function to update the display position
void updateDisplayPosition() {
char displayChars[4] = " "; // 4 characters (no null terminator needed for display)
if (mismatchAlertActive) {
displayText("FAIL");
} else if (recordAlertActive) {
displayText("rEc");
} else {
// Check if the number of digits is 1 or 2 and handle accordingly
if (newSequenceLength >= 10) {
if (displayStartPosition < 3) {
// Move from left to right for two-digit numbers
formatDigits(newSequenceLength, displayChars, displayStartPosition);
sevseg.setChars(displayChars);
displayStartPosition++;
} else {
isUpdatingDisplay = false; // Stop updating the display
displayStartPosition = 0; // Reset the start position for future updates
}
} else {
if (displayStartPosition < 4) {
// Move from left to right for single-digit numbers
formatDigits(newSequenceLength, displayChars, displayStartPosition);
sevseg.setChars(displayChars);
displayStartPosition++;
} else {
isUpdatingDisplay = false; // Stop updating the display
displayStartPosition = 0; // Reset the start position for future updates
}
}
}
}
// Function to compare newSequence to the corresponding part of randomSequence
void compareSequences() {
// Remove trailing spaces from the sequences
String trimmedNewSequence = newSequence;
trimmedNewSequence.trim();
// Extract the part of randomSequence that corresponds to the length of newSequence
String trimmedRandomSequence = "";
int spaceCount = 0;
for (int i = 0; i < randomSequence.length(); i++) {
if (randomSequence[i] == ' ') {
spaceCount++;
if (spaceCount == newSequenceLength) {
trimmedRandomSequence += randomSequence.substring(0, i + 1);
break;
}
}
}
trimmedRandomSequence.trim();
// Compare the sequences
if (trimmedNewSequence == trimmedRandomSequence) {
Serial.println("Sequences match!");
triggermatchAlert();
// Update recordSequenceLength if newSequenceLength is greater
if (newSequenceLength > recordSequenceLength) {
recordSequenceLength = newSequenceLength;
EEPROM.write(0, recordSequenceLength); // Store the new record length in EEPROM
Serial.print("New record sequence length: ");
Serial.println(recordSequenceLength);
newRecordAchieved = true; // Set the flag to flash the display
flashStartTime = millis(); // Initialize flash timing
flashCount = 0; // Reset flash count
recordAlertActive = true; // Start record alert
}
} else {
Serial.println("Sequences do not match. Sequence comparison reset.");
triggerMismatchAlert();
resetSequence(); // Reset the sequence comparison
}
}
// Function to reset the sequence comparison
void resetSequence() {
newSequence = "";
newSequenceLength = 0;
}
// Function to trigger buzzer and LED when sequences do not match
void triggerMismatchAlert() {
mismatchAlertActive = true; // Start match alert
mismatchAlertStartTime = millis(); // Initialize match alert timing
}
// Function to trigger green LED when sequences match
void triggermatchAlert() {
matchAlertActive = true; // Start match alert
matchAlertStartTime = millis(); // Initialize match alert timing
}
// Function to trigger yellow LED if new record is achieved
void triggerrecordAlert() {
recordAlertActive = true; // Start record alert
recordAlertStartTime = millis(); // Initialize record alert timing
}
void displayText(const char* text) {
byte charMap[256] = {0}; // Initialize all elements to 0
charMap[' '] = 0b00000000;
charMap['n'] = 0b01110100;
charMap['E'] = 0b01111001;
charMap['U'] = 0b00111110;
charMap['r'] = 0b01010000;
charMap['c'] = 0b01011000;
charMap['o'] = 0b01011100;
charMap['d'] = 0b01011110;
charMap['I'] = 0b00000110;
charMap['S'] = 0b01101101;
charMap['-'] = 0b01000000;
charMap['A'] = 0b01110111; // A
charMap['B'] = 0b01111111; // B (same as '8' due to limitations)
charMap['C'] = 0b00111001; // C
charMap['E'] = 0b01111001; // E
charMap['F'] = 0b01110001; // F
charMap['H'] = 0b01110110; // H
charMap['I'] = 0b00000110; // I
charMap['J'] = 0b00011110; // J
charMap['L'] = 0b00111000; // L
charMap['O'] = 0b00111111; // O
charMap['P'] = 0b01110011; // P
charMap['U'] = 0b00111110; // U
// Add digits
charMap['0'] = 0b00111111;
charMap['1'] = 0b00000110;
charMap['2'] = 0b01011011;
charMap['3'] = 0b01001111;
charMap['4'] = 0b01100110;
charMap['5'] = 0b01101101;
charMap['6'] = 0b01111101;
charMap['7'] = 0b00000111;
charMap['8'] = 0b01111111;
charMap['9'] = 0b01101111;
byte digits[4];
for (int i = 0; i < 4; i++) {
digits[i] = charMap[text[i]];
}
sevseg.setSegments(digits);
}
void showRecordOnStartup(unsigned long currentMillis) {
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
digitalWrite(matchLED_PIN, HIGH);
digitalWrite(mismatchLED_PIN, HIGH);
digitalWrite(recordLED_PIN, HIGH);
currentWordIndex = (currentWordIndex + 1) % (numWords + 4); // Adjust for the sequence without the duplicate "IS "
if (currentWordIndex < numWords) {
// Display the original words
displayText(words[currentWordIndex]);
} else {
// Display the sequences with the number
char displayChars[4] = " ";
int offset = currentWordIndex - numWords;
if (recordSequenceLength < 10) {
// Single-digit case
switch (offset) {
case 0:
formatDigits(recordSequenceLength, displayChars, 3); // "IS 0"
displayChars[0] = 'r';
displayChars[1] = 'd';
displayChars[2] = ' ';
break;
case 1:
formatDigits(recordSequenceLength, displayChars, 2); // "S 0 "
displayChars[0] = 'd';
displayChars[1] = ' ';
displayChars[3] = ' ';
break;
case 2:
formatDigits(recordSequenceLength, displayChars, 1); // " 0 "
displayChars[0] = ' ';
displayChars[2] = ' ';
displayChars[3] = ' ';
break;
case 3:
formatDigits(recordSequenceLength, displayChars, 0); // "0 "
displayChars[1] = ' ';
displayChars[2] = ' ';
displayChars[3] = 'r';
break;
}
} else {
// Double-digit case
switch (offset) {
case 0:
formatDigits(recordSequenceLength, displayChars, 3); // "IS 1"
displayChars[0] = 'r';
displayChars[1] = 'd';
displayChars[2] = ' ';
break;
case 1:
formatDigits(recordSequenceLength, displayChars, 2); // "S 10"
displayChars[0] = 'd';
displayChars[1] = ' ';
break;
case 2:
formatDigits(recordSequenceLength, displayChars, 1); // " 10 "
displayChars[0] = ' ';
displayChars[3] = ' ';
break;
case 3:
formatDigits(recordSequenceLength, displayChars, 0); // " 10 "
displayChars[2] = ' ';
displayChars[3] = 'r';
break;
}
}
sevseg.setChars(displayChars);
}
digitalWrite(matchLED_PIN, LOW);
digitalWrite(mismatchLED_PIN, LOW);
digitalWrite(recordLED_PIN, LOW);
}
}
void displayRecordLength() {
char displayChars[4] = " "; // Initialize display characters to spaces
// Determine the number of digits in recordSequenceLength
int numberOfDigits = recordSequenceLength >= 1000 ? 4 :
(recordSequenceLength >= 100 ? 3 :
(recordSequenceLength >= 10 ? 2 : 1));
// Calculate the correct starting index based on the number of digits
int startIndex = 4 - numberOfDigits;
// Format the record length and set the display characters
formatDigits(recordSequenceLength, displayChars, startIndex);
sevseg.setChars(displayChars);
}
void resetRecordSequenceLength() {
recordSequenceLength = 0; // Reset the record length to 0
EEPROM.write(0, recordSequenceLength); // Update EEPROM with the reset value
Serial.println("Record sequence length reset to 0");
resetSequence();
for (int i = 0; i < 3; i++) {
digitalWrite(matchLED_PIN, HIGH);
digitalWrite(mismatchLED_PIN, HIGH);
digitalWrite(recordLED_PIN, HIGH);
delay(100);
digitalWrite(matchLED_PIN, LOW);
digitalWrite(mismatchLED_PIN, LOW);
digitalWrite(recordLED_PIN, LOW);
delay(100);
}
}