#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
const int PIN_BTN_ACTION = 7;
const int PIN_BTN_MENU = 8;
const int PIN_BUZZER = 9;
// Bitmaps για Dino Runner
byte dinoRun1[8] = { B00000, B00111, B00111, B10110, B11111, B01110, B01010, B00011 };
byte dinoRun2[8] = { B00000, B00111, B00111, B10110, B11111, B01110, B01010, B11000 };
byte dinoJump[8] = { B00111, B00111, B10110, B11111, B01110, B01010, B10001, B00000 };
byte cactus[8] = { B00100, B00101, B10101, B10111, B11100, B00100, B00100, B00100 };
// Bitmaps για Flappy Bird
byte birdDown[8] = { B00000, B01110, B11101, B11111, B01110, B00000, B00000, B00000 };
byte birdUp[8] = { B00000, B00000, B01110, B11101, B11111, B01110, B00000, B00000 };
byte pipeTop[8] = { B11111, B11111, B01110, B01110, B01110, B01110, B01110, B01110 };
byte pipeBtm[8] = { B01110, B01110, B01110, B01110, B01110, B01110, B11111, B11111 };
enum SystemState { MAIN_MENU, PLAY_DINO, PLAY_FLAPPY, PLAY_REFLEX, GAME_OVER_SCREEN };
SystemState currentSystemState = MAIN_MENU;
int selectedGame = 0;
const int TOTAL_GAMES = 3;
const char* gameTitles[] = { "1. Dino Runner", "2. Flappy Bird", "3. Reflex Test" };
// Debounce & State tracking
bool lastActionState = HIGH;
bool lastMenuState = HIGH;
unsigned long lastTickTime = 0;
unsigned long score = 0;
int gameSpeed = 200;
// Dino
int dinoRow = 1;
int obstacleCol = 15;
bool isJumping = false;
unsigned long jumpStartTime = 0;
bool legToggle = false;
// Flappy
int birdRow = 0;
int pipeCol = 15;
int pipeEmptyRow = 0;
// Reflex
unsigned long reflexTargetTime = 0;
bool waitingForSignal = false;
bool signalActive = false;
void playTone(int freq, int dur) {
tone(PIN_BUZZER, freq, dur);
}
void playGameOverSound() {
playTone(300, 120);
delay(130);
playTone(150, 250);
}
void loadDinoSprites() {
lcd.createChar(0, dinoRun1);
lcd.createChar(1, dinoRun2);
lcd.createChar(2, dinoJump);
lcd.createChar(3, cactus);
}
void loadFlappySprites() {
lcd.createChar(0, birdDown);
lcd.createChar(1, birdUp);
lcd.createChar(2, pipeTop);
lcd.createChar(3, pipeBtm);
}
void drawMenu() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(">CARDBOARD OS<");
lcd.setCursor(0, 1);
lcd.print(gameTitles[selectedGame]);
}
void triggerGameOver() {
currentSystemState = GAME_OVER_SCREEN;
playGameOverSound();
lcd.clear();
lcd.setCursor(3, 0);
lcd.print("GAME OVER!");
lcd.setCursor(1, 1);
lcd.print("Score:");
lcd.print(score);
lcd.print(" Menu:Rst");
}
void initDino() {
loadDinoSprites();
score = 0;
gameSpeed = 190;
obstacleCol = 15;
dinoRow = 1;
isJumping = false;
currentSystemState = PLAY_DINO;
lcd.clear();
}
void updateDino(bool actionJustPressed, unsigned long currentMillis) {
if (actionJustPressed && !isJumping) {
isJumping = true;
jumpStartTime = currentMillis;
dinoRow = 0;
playTone(600, 40);
}
if (isJumping && (currentMillis - jumpStartTime >= 550)) {
isJumping = false;
dinoRow = 1;
}
if (currentMillis - lastTickTime >= (unsigned long)gameSpeed) {
lastTickTime = currentMillis;
score++;
if (score % 20 == 0 && gameSpeed > 70) gameSpeed -= 10;
obstacleCol--;
if (obstacleCol < 0) obstacleCol = 15;
if (obstacleCol == 1 && dinoRow == 1) {
triggerGameOver();
return;
}
lcd.clear();
lcd.setCursor(1, dinoRow);
if (isJumping) lcd.write(byte(2));
else {
lcd.write(legToggle ? byte(0) : byte(1));
legToggle = !legToggle;
}
lcd.setCursor(obstacleCol, 1);
lcd.write(byte(3));
lcd.setCursor(10, 0);
lcd.print("S:");
lcd.print(score);
}
}
void initFlappy() {
loadFlappySprites();
score = 0;
gameSpeed = 240;
pipeCol = 15;
birdRow = 0;
pipeEmptyRow = 0;
currentSystemState = PLAY_FLAPPY;
lcd.clear();
}
void updateFlappy(bool actionJustPressed, unsigned long currentMillis) {
if (actionJustPressed) {
birdRow = (birdRow == 0) ? 1 : 0;
playTone(700, 30);
}
if (currentMillis - lastTickTime >= (unsigned long)gameSpeed) {
lastTickTime = currentMillis;
score++;
if (score % 15 == 0 && gameSpeed > 100) gameSpeed -= 10;
pipeCol--;
if (pipeCol < 0) {
pipeCol = 15;
pipeEmptyRow = random(0, 2);
}
if (pipeCol == 2 && birdRow != pipeEmptyRow) {
triggerGameOver();
return;
}
lcd.clear();
lcd.setCursor(2, birdRow);
lcd.write(byte(birdRow == 0 ? 0 : 1));
lcd.setCursor(pipeCol, pipeEmptyRow == 0 ? 1 : 0);
lcd.write(byte(pipeEmptyRow == 0 ? 3 : 2));
lcd.setCursor(10, 0);
lcd.print("S:");
lcd.print(score);
}
}
void initReflex() {
score = 0;
waitingForSignal = true;
signalActive = false;
reflexTargetTime = millis() + random(2000, 4500);
currentSystemState = PLAY_REFLEX;
lcd.clear();
lcd.setCursor(2, 0);
lcd.print("Get Ready...");
}
void updateReflex(bool actionJustPressed, unsigned long currentMillis) {
if (waitingForSignal) {
if (actionJustPressed) {
playTone(150, 200);
lcd.clear();
lcd.setCursor(2, 0);
lcd.print("Too Early!");
delay(800);
initReflex();
return;
}
if (currentMillis >= reflexTargetTime) {
waitingForSignal = false;
signalActive = true;
lastTickTime = currentMillis;
playTone(1000, 80);
lcd.clear();
lcd.setCursor(4, 0);
lcd.print(">> HIT! <<");
}
} else if (signalActive) {
if (actionJustPressed) {
unsigned long reactionTime = currentMillis - lastTickTime;
signalActive = false;
playTone(880, 100);
lcd.clear();
lcd.setCursor(1, 0);
lcd.print("Time: ");
lcd.print(reactionTime);
lcd.print("ms");
lcd.setCursor(0, 1);
lcd.print("Action: Play");
currentSystemState = GAME_OVER_SCREEN;
}
}
}
void setup() {
pinMode(PIN_BTN_ACTION, INPUT_PULLUP);
pinMode(PIN_BTN_MENU, INPUT_PULLUP);
pinMode(PIN_BUZZER, OUTPUT);
randomSeed(analogRead(A0));
lcd.begin(16, 2);
drawMenu();
}
void loop() {
unsigned long currentMillis = millis();
int actionRead = digitalRead(PIN_BTN_ACTION);
int menuRead = digitalRead(PIN_BTN_MENU);
bool actionJustPressed = (actionRead == LOW && lastActionState == HIGH);
bool menuJustPressed = (menuRead == LOW && lastMenuState == HIGH);
lastActionState = actionRead;
lastMenuState = menuRead;
switch (currentSystemState) {
case MAIN_MENU:
if (menuJustPressed) {
selectedGame = (selectedGame + 1) % TOTAL_GAMES;
playTone(500, 30);
drawMenu();
}
if (actionJustPressed) {
playTone(800, 50);
if (selectedGame == 0) initDino();
else if (selectedGame == 1) initFlappy();
else if (selectedGame == 2) initReflex();
}
break;
case PLAY_DINO:
if (menuJustPressed) { currentSystemState = MAIN_MENU; drawMenu(); }
else updateDino(actionJustPressed, currentMillis);
break;
case PLAY_FLAPPY:
if (menuJustPressed) { currentSystemState = MAIN_MENU; drawMenu(); }
else updateFlappy(actionJustPressed, currentMillis);
break;
case PLAY_REFLEX:
if (menuJustPressed) { currentSystemState = MAIN_MENU; drawMenu(); }
else updateReflex(actionJustPressed, currentMillis);
break;
case GAME_OVER_SCREEN:
if (menuJustPressed || actionJustPressed) {
currentSystemState = MAIN_MENU;
drawMenu();
}
break;
}
}