Devices & Components
1
40 colored male-male jumper wires
1
5mm Blue LED
1
Monochrome 1.3" 128x64 OLED graphic display
2
Mini breadboard - White
5
Push Button de 4 pines
1
Red LEDs 5mm
1
5mm Yellow LED
1
5mm Green LED
1
Grove - Buzzer - Piezo
Software & Tools
1
Thinkercad Circuits
Arduino IDE
Project description
Code
SequenceMaster.ino
c
Source Code
1#include <EEPROM.h> 2#include <Wire.h> 3#include <Adafruit_GFX.h> 4#include <Adafruit_SSD1306.h> 5#include "pitches.h" 6 7#define SCREEN_WIDTH 128 8#define SCREEN_HEIGHT 64 9#define SCREEN_I2C_ADDR 0x3C 10 11#define NUM_BUTTONS 4 12#define BUZZER 12 13#define MAX_GAME_LENGTH 10 14 15const uint8_t gameLEDs[NUM_BUTTONS] = { 2, 3, 4, 5 }; 16const uint8_t gameButtons[NUM_BUTTONS] = { 8, 9, 10, 11 }; 17const uint8_t gameStart = 7; 18 19const int gameTones[NUM_BUTTONS] = { 20 NOTE_G3, NOTE_C4, NOTE_E4, NOTE_G5 21}; 22 23Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1); 24 25uint8_t gameSequence[MAX_GAME_LENGTH]; 26uint8_t currentLevel = 1; 27uint16_t currentScore = 0; 28uint16_t highScore = 0; 29 30const uint16_t EEPROM_MAGIC = 0xABCD; 31const int ADDR_MAGIC = 0; 32const int ADDR_HIGHSCORE = 2; 33 34char textBuffer[24]; 35 36uint8_t bounceIndex = 0; 37int8_t bounceDir = 1; 38uint32_t previousMillis = 0; 39const uint16_t bounceInterval = 100; 40 41bool startScreenShown = false; 42 43void drawText(const char* text, uint8_t size, int16_t x, int16_t y, bool clear) { 44 if (clear) display.clearDisplay(); 45 display.setTextSize(size); 46 display.setTextColor(SSD1306_WHITE); 47 display.setCursor(x, y); 48 display.print(text); 49} 50 51void drawCentered(const char* text, uint8_t size) { 52 int16_t x1, y1; 53 uint16_t w, h; 54 display.setTextSize(size); 55 display.getTextBounds(text, 0, 0, &x1, &y1, &w, &h); 56 int16_t x = (SCREEN_WIDTH - w) / 2; 57 int16_t y = (SCREEN_HEIGHT - h) / 2; 58 drawText(text, size, x, y, true); 59 display.display(); 60} 61 62void drawStartScreen(uint16_t highScoreValue) { 63 display.clearDisplay(); 64 65 const char* title = "START"; 66 display.setTextSize(2); 67 display.setTextColor(SSD1306_WHITE); 68 69 int16_t x1, y1; 70 uint16_t w, h; 71 display.getTextBounds(title, 0, 0, &x1, &y1, &w, &h); 72 int16_t xTitle = (SCREEN_WIDTH - w) / 2; 73 int16_t yTitle = 15; 74 display.setCursor(xTitle, yTitle); 75 display.print(title); 76 77 char scoreBuffer[20]; 78 snprintf(scoreBuffer, sizeof(scoreBuffer), "High Score: %d", highScoreValue); 79 display.setTextSize(1); 80 display.getTextBounds(scoreBuffer, 0, 0, &x1, &y1, &w, &h); 81 int16_t xScore = (SCREEN_WIDTH - w) / 2; 82 int16_t yScore = yTitle + 25; 83 display.setCursor(xScore, yScore); 84 display.print(scoreBuffer); 85 86 display.display(); 87} 88 89void bounce() { 90 if (millis() - previousMillis >= bounceInterval) { 91 previousMillis = millis(); 92 for (uint8_t i = 0; i < NUM_BUTTONS; i++) digitalWrite(gameLEDs[i], LOW); 93 digitalWrite(gameLEDs[bounceIndex], HIGH); 94 bounceIndex += bounceDir; 95 if (bounceIndex == 0 || bounceIndex == NUM_BUTTONS - 1) bounceDir = -bounceDir; 96 } 97} 98 99void loadHighScore() { 100 uint16_t magic = 0; 101 EEPROM.get(ADDR_MAGIC, magic); 102 103 if (magic != EEPROM_MAGIC) { 104 highScore = 0; // first time running 105 EEPROM.put(ADDR_MAGIC, EEPROM_MAGIC); 106 EEPROM.put(ADDR_HIGHSCORE, highScore); 107 } else { 108 EEPROM.get(ADDR_HIGHSCORE, highScore); 109 } 110} 111 112void saveHighScore() { 113 highScore = currentScore; 114 EEPROM.put(ADDR_HIGHSCORE, highScore); 115} 116 117void resetGame() { 118 currentLevel = 1; 119 currentScore = 0; 120 for (uint8_t i = 0; i < NUM_BUTTONS; i++) digitalWrite(gameLEDs[i], LOW); 121} 122 123void extendSequence() { 124 for (int i = 0; i < currentLevel; i++) { 125 gameSequence[i] = random(0, NUM_BUTTONS); 126 } 127} 128 129void lightLedAndPlay(uint8_t idx) { 130 digitalWrite(gameLEDs[idx], HIGH); 131 tone(BUZZER, gameTones[idx]); 132 delay(300); 133 digitalWrite(gameLEDs[idx], LOW); 134 noTone(BUZZER); 135} 136 137void playSequence(uint16_t pauseTime) { 138 for (uint8_t i = 0; i < currentLevel; i++) { 139 lightLedAndPlay(gameSequence[i]); 140 delay(pauseTime); 141 } 142} 143 144int8_t readButtons(uint32_t timeout) { 145 uint32_t start = millis(); 146 while (millis() - start < timeout) { 147 for (uint8_t i = 0; i < NUM_BUTTONS; i++) { 148 if (digitalRead(gameButtons[i]) == LOW) { 149 delay(40); 150 return i; 151 } 152 } 153 } 154 return -1; 155} 156 157bool checkUserSequence() { 158 const uint32_t maxTimePerButton = 5000; // 5 seconds 159 for (uint8_t i = 0; i < currentLevel; i++) { 160 uint32_t startTime = millis(); 161 int8_t btn = -1; 162 163 while (millis() - startTime < maxTimePerButton) { 164 for (uint8_t j = 0; j < NUM_BUTTONS; j++) { 165 if (digitalRead(gameButtons[j]) == LOW) { 166 delay(40); // debounce 167 btn = j; 168 break; 169 } 170 } 171 if (btn >= 0) break; 172 } 173 174 if (btn < 0) return false; 175 176 lightLedAndPlay(btn); 177 178 if (btn != gameSequence[i]) return false; 179 180 uint32_t timeTaken = millis() - startTime; 181 uint16_t points = map(maxTimePerButton - timeTaken, 0, maxTimePerButton, 1, 10); 182 currentScore += points; 183 184 if (currentScore > highScore) { 185 saveHighScore(); 186 } 187 } 188 189 return true; 190} 191 192void displayReadyCountDown() { 193 char countDownBuffer[10]; 194 for (uint8_t count = 3; count > 0; count--) { 195 tone(BUZZER, NOTE_C4); 196 197 for(uint8_t i = 0; i < NUM_BUTTONS; i++) digitalWrite(gameLEDs[i], HIGH); 198 snprintf(countDownBuffer, sizeof(countDownBuffer), "READY %d", count); 199 drawCentered(countDownBuffer, 2); 200 for(uint8_t i = 0; i < NUM_BUTTONS; i++) digitalWrite(gameLEDs[i], LOW); 201 202 noTone(BUZZER); 203 delay(500); 204 } 205 snprintf(countDownBuffer, sizeof(countDownBuffer), "Let's Go"); 206 drawCentered(countDownBuffer, 2); 207 delay(300); 208} 209 210void displayGameData() { 211 display.clearDisplay(); 212 snprintf(textBuffer, sizeof(textBuffer), " Level: %d", currentLevel); 213 drawText(textBuffer, 1, 10, 10, false); 214 snprintf(textBuffer, sizeof(textBuffer), " Score: %d", currentScore); 215 drawText(textBuffer, 1, 10, 20, false); 216 snprintf(textBuffer, sizeof(textBuffer), " High Score: %d", highScore); 217 drawText(textBuffer, 1, 10, 30, false); 218 display.display(); 219} 220 221void gameOver() { 222 drawCentered("GAME OVER", 2); 223 tone(BUZZER, NOTE_DS5); 224 delay(200); 225 tone(BUZZER, NOTE_D5); 226 delay(200); 227 tone(BUZZER, NOTE_CS5); 228 delay(200); 229 noTone(BUZZER); 230} 231 232void levelUpSound() { 233 drawCentered("CLEAR!", 2); 234 tone(BUZZER, NOTE_E4); 235 delay(120); 236 tone(BUZZER, NOTE_G4); 237 delay(120); 238 tone(BUZZER, NOTE_E5); 239 delay(120); 240 noTone(BUZZER); 241} 242 243void melodyCelebration() { 244 const int melody[] = { NOTE_C5, NOTE_E5, NOTE_G5, NOTE_C6 }; 245 const int duration = 150; 246 247 for (int round = 0; round < 5; round++) { 248 for (uint8_t i = 0; i < NUM_BUTTONS; i++) { 249 digitalWrite(gameLEDs[i], HIGH); 250 tone(BUZZER, melody[i % 4]); 251 delay(duration); 252 digitalWrite(gameLEDs[i], LOW); 253 noTone(BUZZER); 254 } 255 } 256} 257 258void displayCelebration() { 259 int textWidth = 6 * 2 * 8; // approx: 6 pixels per char * 8 chars * textSize 2 260 int x = (SCREEN_WIDTH - textWidth); 261 int y = 20; 262 int dir = -1; 263 264 for (int i = 0; i < 100; i++) { // adjust number of iterations 265 display.clearDisplay(); 266 display.setCursor(x, y); 267 display.setTextSize(2); 268 display.setTextColor(SSD1306_WHITE); 269 display.print("YOU WIN!"); 270 display.display(); 271 272 for (uint8_t j = 0; j < NUM_BUTTONS; j++) 273 digitalWrite(gameLEDs[j], (millis() / 100 + j) % 2); 274 275 x += dir; 276 277 if (x <= -textWidth || x >= (SCREEN_WIDTH - textWidth)) dir = -dir; 278 279 delay(30); 280 } 281 282 for (uint8_t j = 0; j < NUM_BUTTONS; j++) digitalWrite(gameLEDs[j], LOW); 283} 284 285void gameCelebration() { 286 melodyCelebration(); 287 displayCelebration(); 288} 289 290void setup() { 291 for (uint8_t i = 0; i < NUM_BUTTONS; i++) { 292 pinMode(gameLEDs[i], OUTPUT); 293 pinMode(gameButtons[i], INPUT_PULLUP); 294 } 295 pinMode(gameStart, INPUT_PULLUP); 296 pinMode(BUZZER, OUTPUT); 297 298 display.begin(SSD1306_SWITCHCAPVCC, SCREEN_I2C_ADDR); 299 display.clearDisplay(); 300 display.display(); 301 302 loadHighScore(); 303 randomSeed(analogRead(0)); 304} 305 306void loop() { 307 if (!startScreenShown) { 308 drawStartScreen(highScore); 309 startScreenShown = true; 310 } 311 312 bounce(); 313 314 if (digitalRead(gameStart) == LOW) { 315 delay(100); 316 while (digitalRead(gameStart) == LOW) 317 ; 318 resetGame(); 319 startScreenShown = false; 320 321 while (currentLevel <= MAX_GAME_LENGTH) { 322 snprintf(textBuffer, sizeof(textBuffer), "LEVEL %d", currentLevel); 323 drawCentered(textBuffer, 2); 324 delay(800); 325 326 extendSequence(); 327 328 displayReadyCountDown(); 329 330 displayGameData(); 331 playSequence(100); 332 333 if (!checkUserSequence()) { 334 gameOver(); 335 delay(1000); 336 break; 337 } 338 339 currentLevel++; 340 if (currentLevel <= MAX_GAME_LENGTH) { 341 levelUpSound(); 342 delay(500); 343 } 344 345 if (currentLevel > MAX_GAME_LENGTH) { 346 gameCelebration(); 347 } 348 } 349 } 350}
Downloadable files
Circuit
Circuit view using ThinkerCAD
sequence_master_circuit_vw.png

Documentation
Play a Melody using the tone() function
To make the pitches.h file
https://docs.arduino.cc/built-in-examples/digital/toneMelody/
Comments
Only logged in users can leave comments