#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// OLED setup
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
// Pin assignments
const int ledPins[4] = {25, 26, 27, 14};
const int btnPins[4] = {5, 18, 12, 19};
const int buzzerPin = 4;
// Button mapping for menu navigation
#define BTN_GREEN 3
#define BTN_BLUE 1
#define BTN_RED 0
// Game names for the menu
const char* gameNames[] = {"Memory Game", "Tower Stack", "Reaction Game", "Dino Runner"};
int menuIndex = 0;
const int numGames = 4;
// Game states
enum GameState {MENU, MEMORY, STACKER, REACTION, DINO, GAME_OVER, WINNER};
GameState currentGame = MENU;
int memoryHighScore = 0;
int stackerHighScore = 0;
int reactionHighScore = 0;
int dinoHighScore = 0;
int lastScore = 0;
bool gotNewHigh = false;
// --- Animated Menu Variables ---
int menuSelY = 0;
const int menuItemHeight = 13;
// --- Buzzer beep helper ---
void beep(int duration = 100, int freq = 1000) {
tone(buzzerPin, freq, duration);
delay(duration);
noTone(buzzerPin);
}
bool buttonPressed(int index) {
if (digitalRead(btnPins[index]) == LOW) {
delay(20);
while (digitalRead(btnPins[index]) == LOW);
return true;
}
return false;
}
// --- Loading Animation ---
void showLoadingAnimation() {
int x = 54, y = 28, r = 6;
display.clearDisplay();
display.setTextSize(2);
display.setCursor(24, 10);
display.print("Loading");
display.display();
for (int i = 0; i < 18; i++) {
int phase = i % 4;
for (int d = 0; d < 4; d++) {
float angle = d * 1.5708; // 90 deg
int dx = x + cos(angle) * r;
int dy = y + sin(angle) * r;
if (d == phase)
display.fillCircle(dx, dy, 3, SSD1306_WHITE);
else
display.fillCircle(dx, dy, 2, SSD1306_WHITE);
}
display.display();
delay(60);
for (int d = 0; d < 4; d++) {
float angle = d * 1.5708;
int dx = x + cos(angle) * r;
int dy = y + sin(angle) * r;
display.fillCircle(dx, dy, 3, SSD1306_BLACK);
}
}
display.clearDisplay();
display.display();
}
// --- Animated Menu Drawing ---
void drawMenu(int highlightY) {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.fillRect(0, highlightY, SCREEN_WIDTH, menuItemHeight, SSD1306_INVERSE);
for(int i=0; i<numGames; i++) {
int y = i * menuItemHeight;
display.setCursor(8, y+2);
if (y == highlightY) {
display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
} else {
display.setTextColor(SSD1306_WHITE, SSD1306_BLACK);
}
display.print(gameNames[i]);
display.print(" HS:");
if(i == 0) display.print(memoryHighScore);
else if(i == 1) display.print(stackerHighScore);
else if(i == 2) display.print(reactionHighScore);
else if(i == 3) display.print(dinoHighScore);
display.setTextColor(SSD1306_WHITE, SSD1306_BLACK);
}
display.setCursor(0, highlightY + 2);
display.print(">");
display.display();
}
// --- Animated showMenu() ---
void showMenuAnimated(int oldIndex, int newIndex) {
int oldY = oldIndex * menuItemHeight;
int newY = newIndex * menuItemHeight;
int steps = abs(newY - oldY) / 2;
if (steps < 1) steps = 1;
for(int s = 0; s <= steps; s++) {
int y = oldY + (newY - oldY) * s / steps;
drawMenu(y);
delay(15);
}
menuSelY = newY;
drawMenu(menuSelY);
}
void showMenuFirst() {
menuSelY = menuIndex * menuItemHeight;
drawMenu(menuSelY);
}
void gameOver(bool winner) {
display.clearDisplay();
display.setTextSize(2);
display.setCursor(12,12);
if (gotNewHigh) {
display.print("NEW HIGH");
display.setCursor(24,35);
display.print("SCORE!");
} else if (winner) {
display.print("Winner!!!");
} else {
display.print("Game Over");
}
display.display();
beep(300);
display.setTextSize(1);
display.setCursor(0,48);
display.print("Score: "); display.println(lastScore);
display.setCursor(0,56);
display.print("Red=Again Blue=Menu");
display.display();
while(true) {
if(buttonPressed(BTN_RED)) {
delay(500);
gotNewHigh = false;
if (menuIndex == 0) currentGame = MEMORY;
else if (menuIndex == 1) currentGame = STACKER;
else if (menuIndex == 2) currentGame = REACTION;
else if (menuIndex == 3) currentGame = DINO;
return;
}
if(buttonPressed(BTN_BLUE)) {
delay(500);
gotNewHigh = false;
currentGame = MENU;
showMenuFirst();
return;
}
}
}
// --- Memory Game with Easy/Hard Mode ---
void playMemoryGame() {
int mode = 0;
while (1) {
display.clearDisplay();
display.setTextSize(2);
display.setCursor(10, 10);
display.print("Memory");
display.setTextSize(1);
display.setCursor(15, 36);
display.print("Green: Easy Blue: Hard");
display.display();
if (buttonPressed(BTN_GREEN)) { mode = 0; break; }
if (buttonPressed(BTN_BLUE)) { mode = 1; break; }
delay(100);
}
int maxSeqLen = mode ? 16 : 10;
int patternLen = mode ? 4 : 2;
int playDelay = mode ? 180 : 350;
int pauseDelay = mode ? 120 : 200;
int numColors = mode ? 4 : 2;
int pattern[maxSeqLen];
randomSeed(millis());
lastScore = 0;
gotNewHigh = false;
for(int i=0; i<maxSeqLen; i++) {
pattern[i] = random(0, numColors);
}
while(1) {
display.clearDisplay();
display.setCursor(0,0);
display.setTextSize(1);
display.println("Watch...");
display.display();
delay(700);
for(int i=0; i<patternLen; i++) {
digitalWrite(ledPins[pattern[i]], HIGH);
beep(100,600+pattern[i]*200);
delay(playDelay);
digitalWrite(ledPins[pattern[i]], LOW);
delay(pauseDelay);
}
display.clearDisplay();
display.setCursor(0,0);
display.setTextSize(1);
display.println("Repeat!");
display.display();
for(int i=0; i<patternLen; i++) {
bool pressed = false;
int pressedColor = -1;
unsigned long t0 = millis();
while(!pressed && (millis()-t0 < 3500)) {
for(int j=0;j<numColors;j++) {
if(buttonPressed(j)) {
digitalWrite(ledPins[j], HIGH);
beep(100,600+j*200);
delay(120);
digitalWrite(ledPins[j], LOW);
pressedColor = j;
pressed = true;
break;
}
}
}
if(pressedColor != pattern[i]) {
lastScore = patternLen-1;
if(lastScore > memoryHighScore) {
memoryHighScore = lastScore;
gotNewHigh = true;
}
display.clearDisplay();
display.setTextSize(3);
display.setCursor(40,20);
display.print("X");
display.display();
beep(500,200);
delay(1000);
currentGame = GAME_OVER;
return;
}
}
patternLen++;
if(patternLen > maxSeqLen) {
lastScore = patternLen-1;
if(lastScore > memoryHighScore) {
memoryHighScore = lastScore;
gotNewHigh = true;
}
currentGame = WINNER;
return;
}
display.clearDisplay();
display.setCursor(20,20);
display.setTextSize(2);
display.print("Good!");
display.display();
beep(200,1200);
delay(800);
}
}
// --- Tower Stack (now taller!) ---
void playStackerGame() {
const int rows = 16;
const int cols = 8;
const int blockWidth = SCREEN_WIDTH / cols;
const int blockHeight = SCREEN_HEIGHT / rows;
const int startStackCols = 3;
int stackCols = startStackCols;
int stackRow = 0;
int stackPos = 0;
int dir = 1;
int speed = 150;
int blocks[rows][cols] = {0};
lastScore = 0;
gotNewHigh = false;
while (true) {
display.clearDisplay();
for (int r = 0; r < stackRow; r++) {
for (int c = 0; c < cols; c++) {
if (blocks[r][c]) {
display.fillRect(c * blockWidth, SCREEN_HEIGHT - (r + 1) * blockHeight, blockWidth - 1, blockHeight - 1, SSD1306_WHITE);
}
}
}
if (stackRow < rows) {
for (int i = 0; i < stackCols; i++) {
int col = stackPos + i;
if (col < cols) {
display.fillRect(col * blockWidth, SCREEN_HEIGHT - (stackRow + 1) * blockHeight, blockWidth - 1, blockHeight - 1, SSD1306_WHITE);
}
}
}
display.display();
delay(speed);
if(dir == 1) {
stackPos++;
if(stackPos > cols - stackCols) {
stackPos = cols - stackCols;
dir = -1;
}
} else {
stackPos--;
if(stackPos < 0) {
stackPos = 0;
dir = 1;
}
}
if (buttonPressed(BTN_RED)) {
int newBlocks[cols] = {0};
if (stackRow == 0) {
for (int i = 0; i < stackCols; i++) {
int col = stackPos + i;
if (col < cols) newBlocks[col] = 1;
}
} else {
for (int i = 0; i < stackCols; i++) {
int col = stackPos + i;
if (col < cols && blocks[stackRow - 1][col]) {
newBlocks[col] = 1;
}
}
}
int newCols = 0;
for (int c = 0; c < cols; c++) {
blocks[stackRow][c] = newBlocks[c];
if (newBlocks[c]) newCols++;
}
if (newCols == 0) {
lastScore = stackRow;
if(lastScore > stackerHighScore) {
stackerHighScore = lastScore;
gotNewHigh = true;
}
currentGame = GAME_OVER;
return;
}
stackCols = newCols;
stackRow++;
if (stackRow >= rows) {
lastScore = stackRow;
if(lastScore > stackerHighScore) {
stackerHighScore = lastScore;
gotNewHigh = true;
}
currentGame = WINNER;
return;
}
speed = max(35, speed - 15);
stackPos = 0;
dir = 1;
beep(100, 800 + stackRow * 40);
}
}
}
// --- Reaction Game (2 seconds) ---
void playReactionGame() {
int score = 0;
randomSeed(millis());
lastScore = 0;
gotNewHigh = false;
while(true) {
int color = random(0,4);
digitalWrite(ledPins[color], HIGH);
display.clearDisplay();
display.setCursor(0,0);
display.setTextSize(1);
display.println("Press the");
switch(color){
case 0: display.println("RED!"); break;
case 1: display.println("BLUE!"); break;
case 2: display.println("WHITE!"); break;
case 3: display.println("GREEN!"); break;
}
display.display();
unsigned long t0 = millis();
bool pressed = false;
while(millis()-t0 < 2000 && !pressed) {
for(int i=0;i<4;i++) {
if(buttonPressed(i)) {
if(i == color) {
score++;
beep(100,1200);
pressed = true;
break;
} else {
lastScore = score;
if(lastScore > reactionHighScore) {
reactionHighScore = lastScore;
gotNewHigh = true;
}
display.clearDisplay();
display.setTextSize(3);
display.setCursor(40,20);
display.print("X");
display.display();
beep(500,200);
delay(1000);
currentGame = GAME_OVER;
digitalWrite(ledPins[color], LOW);
return;
}
}
}
}
digitalWrite(ledPins[color], LOW);
if(!pressed) {
lastScore = score;
if(lastScore > reactionHighScore) {
reactionHighScore = lastScore;
gotNewHigh = true;
}
display.clearDisplay();
display.setTextSize(3);
display.setCursor(40,20);
display.print("X");
display.display();
beep(500,200);
delay(1000);
currentGame = GAME_OVER;
return;
}
delay(500);
}
}
// --- Dino Runner Game (slower difficulty increase) ---
void playDinoGame() {
const int groundY = SCREEN_HEIGHT - 12;
const int dinoX = 16;
const int dinoW = 12, dinoH = 12;
int dinoY = groundY - dinoH;
int dinoVY = 0;
bool isJumping = false;
int jumpPower = 9;
int gravity = 1;
int obstacleW = 8, obstacleH = 16;
int obstacleX = SCREEN_WIDTH;
int obstacleGapMin = 30, obstacleGapMax = 80;
int obstacleY = groundY - obstacleH + 4;
int speed = 4;
int speedupInterval = 3;
int maxSpeed = 10;
unsigned long lastFrame = 0;
unsigned long minFrameDelay = 30;
int score = 0;
gotNewHigh = false;
randomSeed(millis());
while (true) {
unsigned long now = millis();
if (now - lastFrame < minFrameDelay) continue;
lastFrame = now;
if (isJumping) {
dinoY -= dinoVY;
dinoVY -= gravity;
if (dinoY >= groundY - dinoH) { dinoY = groundY - dinoH; isJumping = false; }
}
if (!isJumping && buttonPressed(BTN_RED)) {
dinoVY = jumpPower;
isJumping = true;
beep(40, 880);
}
obstacleX -= speed;
if (obstacleX + obstacleW < 0) {
obstacleX = SCREEN_WIDTH + random(obstacleGapMin, obstacleGapMax);
score++;
beep(10, 1400);
if (score % speedupInterval == 0 && speed < maxSpeed) {
speed++;
}
}
if (dinoX + dinoW > obstacleX && dinoX < obstacleX + obstacleW) {
if (dinoY + dinoH > obstacleY) {
lastScore = score;
if(lastScore > dinoHighScore) {
dinoHighScore = lastScore;
gotNewHigh = true;
}
beep(300, 200);
currentGame = GAME_OVER;
return;
}
}
display.clearDisplay();
display.drawLine(0, groundY + dinoH, SCREEN_WIDTH, groundY + dinoH, SSD1306_WHITE);
display.fillRect(dinoX, dinoY, dinoW - 2, dinoH, SSD1306_WHITE);
display.fillCircle(dinoX + dinoW - 2, dinoY + 4, 3, SSD1306_WHITE);
display.fillRect(obstacleX, obstacleY, obstacleW, obstacleH, SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(0,0);
display.print("Score: "); display.print(score);
display.print(" HS:"); display.print(dinoHighScore);
display.display();
}
}
// --- Arduino Setup/Loop ---
void setup() {
Serial.begin(115200);
for (int i = 0; i < 4; i++) {
pinMode(ledPins[i], OUTPUT);
pinMode(btnPins[i], INPUT_PULLUP);
digitalWrite(ledPins[i], LOW);
}
pinMode(buzzerPin, OUTPUT);
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("SSD1306 allocation failed");
for(;;);
}
display.clearDisplay();
display.display();
showMenuFirst();
}
void loop() {
switch (currentGame) {
case MENU: {
static int prevMenuIndex = 0;
if (buttonPressed(BTN_GREEN)) {
int oldIndex = menuIndex;
menuIndex--;
if (menuIndex < 0) menuIndex = numGames - 1;
showMenuAnimated(oldIndex, menuIndex);
prevMenuIndex = menuIndex;
delay(120);
}
if (buttonPressed(BTN_BLUE)) {
int oldIndex = menuIndex;
menuIndex++;
if (menuIndex >= numGames) menuIndex = 0;
showMenuAnimated(oldIndex, menuIndex);
prevMenuIndex = menuIndex;
delay(120);
}
if (buttonPressed(BTN_RED)) {
showLoadingAnimation();
display.clearDisplay();
display.display();
delay(80);
if (menuIndex == 0) currentGame = MEMORY;
if (menuIndex == 1) currentGame = STACKER;
if (menuIndex == 2) currentGame = REACTION;
if (menuIndex == 3) currentGame = DINO;
}
break;
}
case MEMORY:
playMemoryGame();
break;
case STACKER:
playStackerGame();
break;
case REACTION:
playReactionGame();
break;
case DINO:
playDinoGame();
break;
case GAME_OVER:
gameOver(false);
break;
case WINNER:
gameOver(true);
break;
}
}