#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <STM32RTC.h>
#include <TM1637Display.h>
// ================= PINS =================
#define CLK_LEFT PB10
#define DIO_LEFT PB11
#define CLK_RIGHT PB0
#define DIO_RIGHT PB1
#define ENC_A PB13
#define ENC_B PB12
#define ENC_SW PB14
#define BUZZER PA15
// ================= BINARY BLITZ VARS =================
int blitzTarget = 0;
int blitzScore = 0;
unsigned long blitzStartTime = 0;
const int blitzTimeLimit = 30;
const int switchPins[8] = {PA0, PA1, PA2, PA3, PA4, PA5, PA6, PA7};
// ================= RADIX ROULETTE VARS =================
int rouletteTarget = 0;
int rouletteScore = 0;
uint8_t currentBase = 10; // 10 = Dec, 16 = Hex, 2 = Binary
unsigned long rouletteStartTime = 0;
const int rouletteTimeLimit = 20; // Faster than Blitz!
// ================= GLOBAL GAME VARIABLES =================
int activeGame = -1; // Tracks which game is running
int scoreVal = 0; // Shared score variable
int targetVal = 0; // Shared target variable
unsigned long gameStartTime = 0;
// --- BIT RUNNER SPECIFIC ---
//uint8_t runnerPos = 0; // Current position of the bit
//int moveDelay = 800; // Speed of movement
//unsigned
//long lastMove = 0;
uint8_t runnerPos = 0; // Current position (0 to 7)
unsigned long lastMove = 0; // Timing tracker
int moveDelay = 800; // Start speed (ms)
// --- FULL BINARY ARITHMETIC ---
int numA = 0;
int numB = 0;
char currentOp = '+';
int arithmeticTarget = 0;
uint8_t opType = 0; // 0=Add, 1=Sub, 2=Mul, 3=Div
// --- PRIME HUNTER V2 VARS ---
int hunterNumbers[8];
byte primeMask = 0; // Each bit represents if the corresponding number is prime
bool rowCleared = false;
// Bitwise Game Variables
byte valA = 0;
byte valB = 0;
byte bitwiseTarget = 0;
char currentBitOpName[4] = " ";
void lcdPrintBinary(byte val); // Just the name, no brackets/logic
// ================= OBJECTS =================
TM1637Display tmScore(CLK_LEFT, DIO_LEFT);
TM1637Display tmTimer(CLK_RIGHT, DIO_RIGHT);
LiquidCrystal_I2C lcd(0x27, 20, 4);
STM32RTC& rtc = STM32RTC::getInstance();
// ================= MENU DEFINITIONS =================
// Use const char* const to ensure these stay in Flash
const char* const mainMenu[] = {
"Binary Blitz", "Radix Roulette", "Bit Runner", "Binary Arithmetic",
"Bitwise Logic", "Prime Hunter", "Morse Trainer", "Electron Shells",
"Periodic Table", "Codon Explorer", "Gene Expression", "Blood Match",
"Self Test", "RTC Setup", "Return"
};
#define MENU_COUNT 15
enum UIState { STATE_DASHBOARD, STATE_MENU, STATE_RTC_SETUP, STATE_SELF_TEST, STATE_GAME_RUNNING };
UIState uiState = STATE_DASHBOARD;
// ================= GLOBAL VARIABLES =================
int menuIndex = 0;
int menuTopIndex = 0;
bool lastEncA;
unsigned long buttonPressStart = 0;
bool buttonHeld = false;
int selfCounter = 0;
bool selfLastA;
int editDay, editMonth, editYear, editHour, editMinute, editField;
// Morse Trainer Variables
char morseTarget;
String userMorseBuffer = "";
unsigned long pressStart = 0;
bool isKeyActive = false;
// Morse Reference Table
const char* morseTable[] = {
".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---",
"-.-", ".-..", "--", "-.", "---", ".--.", "--.-", ".-.", "...", "-",
"..-", "...-", ".--", "-..-", "-.--", "--.."
};
void nextMorseLetter() {
morseTarget = 'A' + random(0, 26);
userMorseBuffer = "";
}
// --- ELECTRON SHELLS & PERIODIC TABLE VARS ---
int targetAtomicNum = 0;
String elementName = "";
int requiredValence = 0;
int currentZ = 1;
// Element Names for Case 7 (L-Shell focus)
const char* const shellElements[] = {
"Lithium", "Beryllium", "Boron", "Carbon",
"Nitrogen", "Oxygen", "Fluorine", "Neon"
};
// Valence electrons for all 118 elements (for Case 8)
const uint8_t valenceTable[118] = {
1, 2, // H, He
1, 2, 3, 4, 5, 6, 7, 8, // Li to Ne
1, 2, 3, 4, 5, 6, 7, 8, // Na to Ar
1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8, // K to Kr
1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8, // Rb to Xe
1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8,
1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 4, 5, 6, 7, 8
};
// --- Global Declarations ---
// --- CODON EXPLORER GLOBALS ---
String targetAmino = "";
String targetCodon = "";
struct CodonData {
char amino[12];
char codon[4];
};
// Gamification: Focus on the "Vital" codons
const CodonData geneticLibrary[] = {
{"START/MET", "AUG"}, {"STOP", "UAA"}, {"GLYCINE", "GGG"},
{"VALINE", "GUU"}, {"ALANINE", "GCU"}, {"LYSINE", "AAA"},
{"STOP/AMB", "UAG"}, {"CYSTEINE", "UGU"}
};
// --- GENE EXPRESSION GLOBALS ---
String targetPhenotype = "";
byte regulatoryMask = 0;
struct GeneData {
char phenotype[16];
byte mask; // The required switch pattern
};
const GeneData geneLibrary[] = {
{"Blue Eyes", 0b10101010},
{"Build Muscle", 0b11110000},
{"Fast Metabolism", 0b00001111},
{"Glow in Dark", 0b10011001},
{"High Immunity", 0b01010101},
{"Brain Power", 0b11001100}
};
// --- BLOOD MATCH GLOBALS ---
String patientType = "";
byte donorTargetMask = 0;
struct BloodData {
char typeName[5];
byte mask; // S0=A, S1=B, S2=Rh
};
const BloodData bloodLibrary[] = {
{"A+", 0b101}, {"A-", 0b001},
{"B+", 0b110}, {"B-", 0b010},
{"AB+", 0b111}, {"AB-", 0b011},
{"O+", 0b100}, {"O-", 0b000}
};
void nextElectronProblem() {
int idx = random(0, 8);
elementName = shellElements[idx];
requiredValence = idx + 1;
targetAtomicNum = idx + 3; // Li starts at Z=3
}
void nextElementProblem() {
currentZ = random(1, 119);
}
void showElectronFinalScore() {
lcd.clear();
digitalWrite(BUZZER, HIGH); delay(500); digitalWrite(BUZZER, LOW);
lcd.setCursor(2, 1); lcd.print(F("SHELLS FINISHED!"));
lcd.setCursor(4, 2); lcd.print(F("SCORE: ")); lcd.print(scoreVal);
tmScore.showNumberDec(scoreVal);
delay(5000);
uiState = STATE_MENU; showMenu();
}
void showPeriodicFinalScore() {
lcd.clear();
digitalWrite(BUZZER, HIGH); delay(500); digitalWrite(BUZZER, LOW);
lcd.setCursor(1, 1); lcd.print(F("PERIODIC OVER!"));
lcd.setCursor(4, 2); lcd.print(F("SCORE: ")); lcd.print(scoreVal);
tmScore.showNumberDec(scoreVal);
delay(5000);
uiState = STATE_MENU; showMenu();
}
// ================= HELPERS =================
void print2digit(int val) {
if (val < 10) lcd.print('0');
lcd.print(val);
}
// ================= DISPATCHER =================
void executeMenuAction(int index) {
lcd.clear();
switch(index) {
case 0: startBinaryBlitz(); break;
case 1: startRadixRoulette(); break; // <-- NEW
case 2: // Bit Runner
uiState = STATE_GAME_RUNNING;
activeGame = 2;
scoreVal = 0;
runnerPos = 0;
moveDelay = 800; // Reset to slow speed
gameStartTime = millis();
lcd.clear();
lcd.print(F("--- BIT RUNNER ---"));
break;
case 3: // Binary Arithmetic
uiState = STATE_GAME_RUNNING;
activeGame = 3;
scoreVal = 0;
gameStartTime = millis();
nextBinaryArithmeticProblem(); // <--- CRITICAL: Generates first A and B
lcd.clear();
break;
case 4: // Bitwise Logic Entry
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(F("---BITWISE LOGIC---"));
lcd.setCursor(0, 2);
lcd.print(F("RESET ALL SWITCHES"));
// Wait until all switches are toggled to 0
while(readSwitches() != 0) {
delay(50);
}
scoreVal = 0;
nextBitwiseProblem();
gameStartTime = millis(); // 60s Timer starts only after reset
uiState = STATE_GAME_RUNNING;
activeGame = 4;
lcd.clear();
break;
case 5: // Prime Hunter Entry
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(F("---PRIME HUNTER---"));
lcd.setCursor(0, 2);
lcd.print(F("RESET ALL SWITCHES"));
lcd.setCursor(0, 3);
lcd.print(F("TO START CLOCK..."));
// The "Ready" Gate
while(readSwitches() != 0) {
delay(50);
}
// Initialize after reset
scoreVal = 0;
nextPrimeRow();
gameStartTime = millis(); // Timer starts NOW
uiState = STATE_GAME_RUNNING;
activeGame = 5;
lcd.clear();
break;
case 6: // Morse Trainer Entry
lcd.clear();
lcd.print(F("MORSE TRAINER"));
lcd.setCursor(0, 2);
lcd.print(F("RESET ALL SWITCHES"));
while(readSwitches() != 0) { delay(10); } // Reset Gate
scoreVal = 0;
nextMorseLetter();
gameStartTime = millis();
activeGame = 6;
uiState = STATE_GAME_RUNNING;
lcd.clear();
break;
case 7: // Electron Shells Menu Selection
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(F("---ELECTRON SHELL---"));
lcd.setCursor(0, 2);
lcd.print(F("RESET ALL SWITCHES"));
// Wait for safety reset
while(readSwitches() != 0) { delay(10); }
scoreVal = 0;
nextElectronProblem(); // Pick first element
gameStartTime = millis(); // Start timer
activeGame = 7; // Set ID to 7
uiState = STATE_GAME_RUNNING;
lcd.clear();
break;
case 8: // Periodic Table Game Entry
lcd.clear();
lcd.print(F("PERIODIC TABLE"));
lcd.setCursor(0, 2);
lcd.print(F("RESET ALL SWITCHES"));
while(readSwitches() != 0) { delay(10); } // Wait for 00000000
scoreVal = 0;
currentZ = random(1, 119); // Pick random Z from 1 to 118
gameStartTime = millis();
activeGame = 8; // Assign unique ID
uiState = STATE_GAME_RUNNING;
lcd.clear();
break;
case 9: // Codon Explorer
lcd.clear();
lcd.print(F("CODON EXPLORER"));
while(readSwitches() != 0) { delay(10); } // Gamification: Clear the deck
scoreVal = 0;
nextCodonProblem();
gameStartTime = millis();
activeGame = 9;
uiState = STATE_GAME_RUNNING;
lcd.clear();
break;
case 10: // Gene Expression Entry
lcd.clear();
lcd.print(F("EXPRESS:")); // Static Label Row 0
lcd.setCursor(0, 1);
lcd.print(F(" GENOME:")); // Static Label Row 1
lcd.setCursor(0, 3);
lcd.print(F("SEEKING HOMEOSTASIS")); // Static Label Row 3
scoreVal = 0;
nextGeneProblem();
gameStartTime = millis();
activeGame = 10;
uiState = STATE_GAME_RUNNING;
break;
case 11: // Blood Match Entry
lcd.clear();
lcd.setCursor(0, 0); lcd.print(F("PATIENT : "));
lcd.setCursor(0, 1); lcd.print(F("DONOR : "));
lcd.setCursor(0, 3); lcd.print(F("S0=A S1=B S2=Rh")); // Tutorial Row
scoreVal = 0;
nextBloodProblem();
gameStartTime = millis();
activeGame = 11;
uiState = STATE_GAME_RUNNING;
break;
case 12: startSelfTest(); break;
case 13: uiState = STATE_RTC_SETUP; enterRTCSetup(); break;
case 14: uiState = STATE_DASHBOARD; showDashboard(); break;
default:
lcd.setCursor(0,1); lcd.print(F("Starting..."));
lcd.setCursor(0,2); lcd.print(mainMenu[index]);
delay(1000);
break;
}
}
// ================= GAME LOGIC =================
void runBinaryBlitz() {
int remaining = blitzTimeLimit - ((millis() - blitzStartTime) / 1000);
if (remaining <= 0) {
digitalWrite(BUZZER, HIGH); delay(500); digitalWrite(BUZZER, LOW);
lcd.clear();
lcd.setCursor(5, 1); lcd.print(F("GAME OVER!"));
lcd.setCursor(4, 2); lcd.print(F("Score: ")); lcd.print(blitzScore);
delay(3000);
uiState = STATE_MENU; showMenu();
return;
}
tmTimer.showNumberDec(remaining);
tmScore.showNumberDec(blitzScore);
byte playerInput = 0;
for (int i = 0; i < 8; i++) if (digitalRead(switchPins[i]) == LOW) playerInput |= (1 << i);
lcd.setCursor(0, 1); lcd.print(F("TARGET: ")); lcd.print(blitzTarget); lcd.print(F(" "));
lcd.setCursor(0, 2); lcd.print(F("YOURS : ")); lcd.print(playerInput); lcd.print(F(" "));
if (playerInput == blitzTarget) {
digitalWrite(BUZZER, HIGH); delay(100); digitalWrite(BUZZER, LOW);
blitzScore += 10;
blitzTarget = random(1, 255);
blitzStartTime = millis();
lcd.setCursor(0, 3); lcd.print(F("CORRECT! NEXT... "));
delay(500);
lcd.setCursor(0, 3); lcd.print(F(" "));
}
}
void startBinaryBlitz() {
uiState = STATE_GAME_RUNNING;
blitzScore = 0;
blitzTarget = random(1, 255);
blitzStartTime = millis();
lcd.clear();
lcd.print(F("--- BINARY BLITZ ---"));
tmScore.clear();
}
// ================= RADIX ROULETTE =================
void startRadixRoulette() {
uiState = STATE_GAME_RUNNING;
rouletteScore = 0;
rouletteTarget = random(1, 255);
currentBase = 10; // Start with Decimal
rouletteStartTime = millis();
lcd.clear();
lcd.print(F("--- RADIX ROULETTE ---"));
tmScore.clear();
}
void runRadixRoulette() {
int remaining = rouletteTimeLimit - ((millis() - rouletteStartTime) / 1000);
if (remaining <= 0) {
digitalWrite(BUZZER, HIGH); delay(500); digitalWrite(BUZZER, LOW);
lcd.clear();
lcd.setCursor(4, 1); lcd.print(F("ROULETTE OVER"));
lcd.setCursor(4, 2); lcd.print(F("Score: ")); lcd.print(rouletteScore);
delay(3000);
uiState = STATE_MENU; showMenu();
return;
}
tmTimer.showNumberDec(remaining);
tmScore.showNumberDec(rouletteScore);
// Read switches
byte playerInput = 0;
for (int i = 0; i < 8; i++) if (digitalRead(switchPins[i]) == LOW) playerInput |= (1 << i);
// Display Target based on current base
lcd.setCursor(0, 1);
if (currentBase == 10) {
lcd.print(F("DEC TARGET: ")); lcd.print(rouletteTarget);
} else if (currentBase == 16) {
lcd.print(F("HEX TARGET: 0x")); lcd.print(rouletteTarget, HEX);
} else {
lcd.print(F("BIN TARGET: ")); lcd.print(rouletteTarget, BIN);
}
lcd.print(F(" ")); // Clear trailing characters
lcd.setCursor(0, 2);
lcd.print(F("YOUR INPUT: ")); lcd.print(playerInput); lcd.print(F(" "));
// Check Success
if (playerInput == rouletteTarget) {
digitalWrite(BUZZER, HIGH); delay(80); digitalWrite(BUZZER, LOW);
rouletteScore += 15; // Higher reward for Radix
rouletteTarget = random(1, 255);
// Pick a new random base for the next round
int r = random(0, 3);
if (r == 0) currentBase = 10;
else if (r == 1) currentBase = 16;
else currentBase = 2;
rouletteStartTime = millis(); // Reset timer
lcd.setCursor(0, 3); lcd.print(F("GREAT! CHANGING... "));
delay(600);
lcd.setCursor(0, 3); lcd.print(F(" "));
}
}
// --- BIT RUNNER VARIABLES ---
byte readSwitches() {
byte val = 0;
for (int i = 0; i < 8; i++) {
if (digitalRead(switchPins[i]) == LOW) val |= (1 << i);
}
return val;
}
void runBitRunner() {
// 1. Timer Logic
int remaining = 30 - ((millis() - gameStartTime) / 1000);
if (remaining <= 0) {
digitalWrite(BUZZER, HIGH); delay(500); digitalWrite(BUZZER, LOW);
lcd.clear(); lcd.print(F("Runner Finished!"));
lcd.setCursor(0, 2); lcd.print(F("Final Score: ")); lcd.print(scoreVal);
delay(3000); uiState = STATE_MENU; showMenu(); return;
}
// 2. Display Updates
tmTimer.showNumberDec(remaining);
tmScore.showNumberDec(scoreVal);
// 3. The Movement Logic
if (millis() - lastMove > moveDelay) {
lastMove = millis();
runnerPos = (runnerPos + 1) % 8; // Move to next bit
targetVal = (1 << runnerPos); // Update the target bit value
// Increase difficulty: speed up slightly every move
if (moveDelay > 200) moveDelay -= 5;
}
// 4. LCD Feedback
lcd.setCursor(0, 1);
lcd.print(F("CATCH BIT: "));
// Show the bit position visually
for(int i=7; i>=0; i--) {
lcd.print(i == runnerPos ? '1' : '0');
}
lcd.setCursor(0, 2);
byte input = readSwitches();
lcd.print(F("YOUR POS: "));
for(int i=7; i>=0; i--) {
lcd.print(bitRead(input, i) ? '1' : '0');
}
// 5. Scoring
if (input == targetVal) {
// We don't use a long delay here so the bit keeps moving
scoreVal += 1; // Constant scoring for staying on the bit
digitalWrite(BUZZER, (millis() % 200 < 50)); // Tiny blip when matched
} else {
digitalWrite(BUZZER, LOW);
}
}
///////////////////////////////////
//////////////////////////////////////
////////////////////////////////////////
void lcdPrintBinary(byte val) {
for (int i = 7; i >= 0; i--) {
lcd.print(bitRead(val, i));
}
}
void nextBinaryArithmeticProblem() {
opType = random(0, 4);
switch (opType) {
case 0: // ADDITION (A + B <= 255)
numA = random(1, 150);
numB = random(1, (255 - numA));
currentOp = '+';
arithmeticTarget = numA + numB;
break;
case 1: // SUBTRACTION (A >= B)
numA = random(50, 255);
numB = random(1, numA);
currentOp = '-';
arithmeticTarget = numA - numB;
break;
case 2: // MULTIPLICATION (Small operands)
numA = random(2, 16);
numB = random(2, (255 / numA));
currentOp = '*';
arithmeticTarget = numA * numB;
break;
case 3: // DIVISION (Clean division)
numB = random(2, 12);
arithmeticTarget = random(1, (255 / numB));
numA = arithmeticTarget * numB;
currentOp = '/';
break;
}
}
void runBinaryArithmetic() {
// 1. Timer Logic
int remaining = 60 - ((millis() - gameStartTime) / 1000);
if (remaining <= 0) {
uiState = STATE_MENU; showMenu(); return;
}
tmTimer.showNumberDec(remaining);
tmScore.showNumberDec(scoreVal);
// 2. DRAW THE SCREEN (Do not use lcd.clear() here!)
lcd.setCursor(0, 0);
lcd.print(F("A: ")); lcdPrintBinary(numA);
lcd.setCursor(0, 1);
lcd.print(currentOp); lcd.print(F(": ")); lcdPrintBinary(numB);
// 3. READ INPUT
byte input = readSwitches();
lcd.setCursor(0, 2);
lcd.print(F("IN:")); lcdPrintBinary(input);
lcd.print(F(" val:")); lcd.print(input);
lcd.print(F(" ")); // Clear ghost numbers
// 4. CHECK WIN
if (input == arithmeticTarget) {
digitalWrite(BUZZER, HIGH); delay(100); digitalWrite(BUZZER, LOW);
scoreVal += 50;
nextBinaryArithmeticProblem();
lcd.clear(); // Clear once only when problem changes
}
}
//===========================================================
//=============================================================
void nextPrimeRow() {
primeMask = 0;
for (int i = 0; i < 8; i++) {
hunterNumbers[i] = random(2, 100); // Random numbers 2-99
if (checkPrime(hunterNumbers[i])) {
bitSet(primeMask, i); // Set the bit if it's prime
}
}
// If a row has NO primes by luck, regenerate it
if (primeMask == 0) nextPrimeRow();
}
void runPrimeHunter() {
lcd.noCursor(); // Kills the blink on S0
lcd.noBlink(); // Kills the blink on S0
int remaining = 60 - ((millis() - gameStartTime) / 1000);
if (remaining <= 0) {
showPrimeFinalScore();
return;
}
tmTimer.showNumberDec(remaining);
tmScore.showNumberDec(scoreVal);
// --- ROW 0: THE NUMBERS ---
lcd.setCursor(0, 0);
for (int i = 7; i >= 0; i--) {
if (hunterNumbers[i] < 10) lcd.print('0');
lcd.print(hunterNumbers[i]);
lcd.print(F(" "));
}
// --- ROW 1: P/C LABELS ---
byte input = readSwitches();
lcd.setCursor(0, 1);
for (int i = 7; i >= 0; i--) {
lcd.print(bitRead(input, i) ? F("P ") : F("C "));
}
// --- ROW 3: STATUS MESSAGE (Row index 3, Column index 4) ---
lcd.setCursor(4, 3); // 5th Column, 4th Row
if (input == primeMask) {
digitalWrite(BUZZER, HIGH); delay(200); digitalWrite(BUZZER, LOW);
scoreVal += 80;
// Clear the specific row 3 area and show match
lcd.setCursor(0, 3);
lcd.print(F(" MATCH! RESET ALL "));
while(readSwitches() != 0) { delay(10); } // Wait for reset
nextPrimeRow();
lcd.clear(); // Clear all to prevent overlap on next round
} else {
// Print starting at the 5th column
lcd.print(F("MAP THE PRIMES ")); // Spaces at end clear any old "MATCH" text
}
}
void showPrimeFinalScore() {
lcd.clear();
digitalWrite(BUZZER, HIGH); delay(1000); digitalWrite(BUZZER, LOW);
// Clean Final Display
lcd.setCursor(0, 1);
lcd.print(F(" TIME EXPIRED "));
lcd.setCursor(0, 2);
lcd.print(F(" FINAL SCORE:"));
lcd.print(scoreVal);
delay(5000); // 5 seconds to read
uiState = STATE_MENU;
showMenu();
}
bool checkPrime(int n) {
if (n <= 1) return false;
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) return false;
}
return true;
}
//===========================================================
//===========================================================
// Global variables for Bitwise Logic
void nextBitwiseProblem() {
valA = random(0, 255);
valB = random(0, 255);
int op = random(0, 7); // Increased range for 7 operators
switch (op) {
case 0:
strcpy(currentBitOpName, "AND");
bitwiseTarget = valA & valB;
break;
case 1:
strcpy(currentBitOpName, "OR ");
bitwiseTarget = valA | valB;
break;
case 2:
strcpy(currentBitOpName, "XOR");
bitwiseTarget = valA ^ valB;
break;
case 3:
strcpy(currentBitOpName, "NAN"); // NAND
bitwiseTarget = ~(valA & valB);
break;
case 4:
strcpy(currentBitOpName, "NOR"); // NOR
bitwiseTarget = ~(valA | valB);
break;
case 5:
strcpy(currentBitOpName, "XNO"); // XNOR
bitwiseTarget = ~(valA ^ valB);
break;
case 6:
strcpy(currentBitOpName, "NOT"); // NOT (A only)
bitwiseTarget = ~valA;
break;
}
}
void nextBitwiseProblem1() {
valA = random(0, 255);
valB = random(0, 255);
int op = random(0, 3); // 0=AND, 1=OR, 2=XOR
if (op == 0) {
strcpy(currentBitOpName, "AND");
bitwiseTarget = valA & valB;
} else if (op == 1) {
strcpy(currentBitOpName, "OR ");
bitwiseTarget = valA | valB;
} else {
strcpy(currentBitOpName, "XOR");
bitwiseTarget = valA ^ valB;
}
}
void showBitwiseFinalScore() {
lcd.clear();
digitalWrite(BUZZER, HIGH); delay(500); digitalWrite(BUZZER, LOW);
// Show final score on both LCD and TM1637
lcd.setCursor(2, 1);
lcd.print(F("LOGIC TIME OVER!"));
lcd.setCursor(4, 2);
lcd.print(F("SCORE: "));
lcd.print(scoreVal);
tmScore.showNumberDec(scoreVal);
delay(5000); // 5 seconds to view result
uiState = STATE_MENU;
showMenu();
}
void runBitwiseGame() {
lcd.noCursor();
lcd.noBlink();
// 1. Timer & TM1637 Update
int remaining = 60 - ((millis() - gameStartTime) / 1000);
if (remaining <= 0) {
showBitwiseFinalScore();
return;
}
tmTimer.showNumberDec(remaining);
tmScore.showNumberDec(scoreVal);
// 2. Aligned LCD Layout (Starts at Column 5 for vertical stacking)
// Row 0: Variable A
lcd.setCursor(0, 0);
lcd.print(F("A: "));
lcd.setCursor(4, 0); // Jump to 5th column
lcdPrintBinary(valA);
// Row 1: Operator and Variable B
lcd.setCursor(0, 1);
lcd.print(currentBitOpName);
lcd.print(F(": "));
lcd.setCursor(4, 1); // Jump to 5th column
lcdPrintBinary(valB);
// Row 2: Live Switch Input
byte input = readSwitches();
lcd.setCursor(0, 2);
lcd.print(F("I: "));
lcd.setCursor(4, 2); // Jump to 5th column
lcdPrintBinary(input);
// 3. Match Logic & Reset Gate
lcd.setCursor(0, 3);
if (input == bitwiseTarget) {
digitalWrite(BUZZER, HIGH); delay(200); digitalWrite(BUZZER, LOW);
scoreVal += 100;
// Using padding to clear "SOLVE LOGIC"
lcd.print(F(" MATCH! RESET SW... "));
// Wait for physical reset (All switches to 0)
while(readSwitches() != 0) {
delay(10);
}
nextBitwiseProblem();
lcd.clear(); // Clear display for next problem
} else {
// Aligned status message
lcd.setCursor(4, 3);
lcd.print(F("SOLVE THE LOGIC "));
}
}
//===========================================================
void showMorseFinalScore() {
lcd.clear();
digitalWrite(BUZZER, HIGH); delay(500); digitalWrite(BUZZER, LOW);
lcd.setCursor(2, 1);
lcd.print(F("MORSE TIME OVER!"));
lcd.setCursor(4, 2);
lcd.print(F("SCORE: "));
lcd.print(scoreVal);
tmScore.showNumberDec(scoreVal);
delay(5000);
uiState = STATE_MENU;
showMenu();
}
void runMorseTrainer() {
lcd.noCursor();
// 1. Timer Logic
int remaining = 60 - ((millis() - gameStartTime) / 1000);
if (remaining <= 0) {
showMorseFinalScore();
return;
}
tmTimer.showNumberDec(remaining);
tmScore.showNumberDec(scoreVal);
// 2. Display Layout
lcd.setCursor(0, 0);
lcd.print(F("TARGET: ")); lcd.print(morseTarget);
lcd.setCursor(0, 1);
lcd.print(F("GOAL : ")); lcd.print(morseTable[morseTarget - 'A']);
lcd.setCursor(0, 2);
lcd.print(F("INPUT : ")); lcd.print(userMorseBuffer);
lcd.print(F(" ")); // Clear trailing characters
// 3. Telegraph Key Logic (S0)
bool s0State = bitRead(readSwitches(), 0);
if (s0State) {
if (!isKeyActive) {
pressStart = millis();
isKeyActive = true;
digitalWrite(BUZZER, HIGH);
}
// LIVE PROGRESS BAR on Row 3
unsigned long currentHold = millis() - pressStart;
lcd.setCursor(0, 3);
lcd.print(F("["));
int bars = map(constrain(currentHold, 0, 300), 0, 300, 0, 10);
...
This file has been truncated, please download it to see its full contents.
Comments