/**
A Simon Says style game using the Arduino Uno board.
The Arduino UNO generates a sequence of lights and sounds.
The player has to remember this sequence and then repeat it
using the buttons that correspond to the lights.
A 7-segment display shows the current level, and a buzzer
plays sounds for feedback.
*/
#include <Arduino.h>
namespace GameConfig {
// Tones used in the game
constexpr uint16_t NOTES[] = {
31, 33, 35, 37, 39, 41, 44, 46, 49, 52, 55, 58, 62, 65, 69, 73,
78, 82, 87, 93, 98, 104, 110, 117, 123, 131, 139, 147, 156, 164,
174, 185, 196, 208, 220, 233, 247, 262, 277, 294, 311, 330, 349,
370, 392, 415, 440, 466, 494, 523, 554, 587, 622, 659, 698, 740
};
// Defines game constants
constexpr uint8_t MAX_GAME_LENGTH = 9;
constexpr uint8_t NUM_BUTTONS = 4;
constexpr uint16_t GAME_TONES[] = {NOTES[29], NOTES[36], NOTES[40], NOTES[47]};
}
class SimonSaysGame {
private:
// Static const arrays defined outside the class with external linkage
static const uint8_t LED_PINS[4];
static const uint8_t BUTTON_PINS[4];
static const uint8_t SPEAKER_PIN = 8;
static const uint8_t LATCH_PIN = A1;
static const uint8_t DATA_PIN = A0;
static const uint8_t CLOCK_PIN = A2;
// Display lookup table
static const uint8_t DIGIT_TABLE[10];
// Game state variables
uint8_t gameSequence[GameConfig::MAX_GAME_LENGTH] = {0};
uint8_t gameIndex = 1; // Defines start level
bool isNewGame = true;
void sendScore(uint8_t value) {
digitalWrite(LATCH_PIN, LOW);
shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, value);
digitalWrite(LATCH_PIN, HIGH);
}
void lightLedAndPlayTone(uint8_t ledIndex) {
digitalWrite(LED_PINS[ledIndex], HIGH);
tone(SPEAKER_PIN, GameConfig::GAME_TONES[ledIndex]);
delay(300);
digitalWrite(LED_PINS[ledIndex], LOW);
noTone(SPEAKER_PIN);
}
void playSequence() {
for (uint8_t i = 0; i < gameIndex; i++) {
lightLedAndPlayTone(gameSequence[i]);
delay(120);
}
}
uint8_t readButtons() {
while (true) {
for (uint8_t i = 0; i < GameConfig::NUM_BUTTONS; i++) {
if (digitalRead(BUTTON_PINS[i]) == LOW) {
return i;
}
}
delay(1);
}
}
bool checkUserSequence() {
for (uint8_t i = 0; i < gameIndex; i++) {
uint8_t expectedButton = gameSequence[i];
uint8_t actualButton = readButtons();
lightLedAndPlayTone(actualButton);
if (expectedButton != actualButton) {
return false;
}
}
return true;
}
void playGameOverTones() {
const uint16_t gameOverMelody[] = {
GameConfig::NOTES[42], GameConfig::NOTES[41], GameConfig::NOTES[40]
};
for (uint8_t i = 0; i < 3; i++) {
tone(SPEAKER_PIN, gameOverMelody[i]);
delay(300);
}
// Sliding tone effect
for (uint8_t i = 0; i < 10; i++) {
for (int16_t pitch = -10; pitch <= 10; pitch++) {
tone(SPEAKER_PIN, GameConfig::NOTES[36] + pitch);
delay(5);
}
}
noTone(SPEAKER_PIN);
}
void flashGameOverEffects(uint8_t score) {
uint8_t scoreDigit = DIGIT_TABLE[score % 10];
for (uint8_t i = 0; i < 6; i++) {
for (uint8_t j = 0; j < 4; j++) {
digitalWrite(LED_PINS[random(0, 4)], HIGH);
}
sendScore(scoreDigit);
delay(300);
for (uint8_t j = 0; j < 4; j++) {
digitalWrite(LED_PINS[j], LOW);
}
sendScore(0xFF);
delay(300);
}
}
void playLevelUpSound() {
const uint16_t levelUpTones[] = {
GameConfig::NOTES[40], GameConfig::NOTES[43],
GameConfig::NOTES[52], GameConfig::NOTES[48],
GameConfig::NOTES[49], GameConfig::NOTES[47]
};
const uint16_t levelUpDurations[] = {150, 150, 150, 150, 150, 150};
for (uint8_t i = 0; i < 6; i++) {
tone(SPEAKER_PIN, levelUpTones[i]);
delay(levelUpDurations[i]);
}
noTone(SPEAKER_PIN);
}
void playMarioFlagpole() {
const uint16_t melody[] = {
GameConfig::NOTES[52], GameConfig::NOTES[47],
GameConfig::NOTES[64], GameConfig::NOTES[48],
GameConfig::NOTES[50], GameConfig::NOTES[59]
};
const uint16_t noteDurations[] = {100, 100, 100, 100, 100, 300};
for (uint8_t i = 0; i < 6; i++) {
tone(SPEAKER_PIN, melody[i]);
delay(noteDurations[i]);
noTone(SPEAKER_PIN);
delay(50);
}
}
void countdown() {
const uint16_t countdownTones[] = {
GameConfig::NOTES[36], GameConfig::NOTES[36],
GameConfig::NOTES[36], GameConfig::NOTES[47]
};
const uint16_t delays[] = {500, 500, 500, 300};
for (uint8_t i = 0; i < 3; i++) {
tone(SPEAKER_PIN, countdownTones[i]);
digitalWrite(LED_PINS[i], HIGH);
delay(delays[i]);
digitalWrite(LED_PINS[i], LOW);
noTone(SPEAKER_PIN);
delay(200);
}
tone(SPEAKER_PIN, countdownTones[3]);
for (uint8_t i = 0; i < 4; i++) {
digitalWrite(LED_PINS[i], HIGH);
}
delay(700);
for (uint8_t i = 0; i < 4; i++) {
digitalWrite(LED_PINS[i], LOW);
}
noTone(SPEAKER_PIN);
delay(500);
}
public:
void init() {
Serial.begin(9600);
// LED and button modes
for (uint8_t i = 0; i < GameConfig::NUM_BUTTONS; i++) {
pinMode(LED_PINS[i], OUTPUT);
pinMode(BUTTON_PINS[i], INPUT_PULLUP);
}
// Other pins
pinMode(SPEAKER_PIN, OUTPUT);
pinMode(LATCH_PIN, OUTPUT);
pinMode(CLOCK_PIN, OUTPUT);
pinMode(DATA_PIN, OUTPUT);
// Seed randomizer
randomSeed(analogRead(A3));
}
void waitForStart() {
Serial.println("Press any button to start the game...");
while (true) {
for (uint8_t i = 0; i < GameConfig::NUM_BUTTONS; i++) {
if (digitalRead(BUTTON_PINS[i]) == LOW) {
delay(500);
isNewGame = false;
return;
}
}
delay(1);
}
}
void gameOver() {
uint8_t finalScore = gameIndex - 1;
Serial.print("Game over! Your score: ");
Serial.println(finalScore);
// Defines which level to reset to once gameOver called
resetGame();
playGameOverTones();
flashGameOverEffects(finalScore);
isNewGame = true;
}
void resetGame() {
// Change this to pick reset level
gameIndex = 1;
// Resets game sequence
memset(gameSequence, 0, sizeof(gameSequence));
}
void run() {
if (isNewGame) {
waitForStart();
countdown();
}
// Current score display
sendScore(DIGIT_TABLE[gameIndex % 10]);
// Add a new random step to the sequence
gameSequence[gameIndex - 1] = random(0, 4);
// Final level instruction set
if (gameIndex == GameConfig::MAX_GAME_LENGTH) {
playSequence();
if (checkUserSequence()) {
playMarioFlagpole();
delay(1000);
resetGame(); // Defines reset to level 1
isNewGame = true;
} else {
gameOver();
}
return;
}
playSequence();
if (!checkUserSequence()) {
gameOver();
return;
}
// Increment game index after successful sequence check
gameIndex++;
delay(500);
playLevelUpSound();
delay(300);
}
};
// Defining the static const arrays outside the class
const uint8_t SimonSaysGame::LED_PINS[4] = {9, 10, 11, 12};
const uint8_t SimonSaysGame::BUTTON_PINS[4] = {2, 3, 4, 5};
const uint8_t SimonSaysGame::DIGIT_TABLE[10] = {
0b11000000, 0b11111001, 0b10100100, 0b10110000,
0b10011001, 0b10010010, 0b10000010, 0b11111000,
0b10000000, 0b10010000
};
SimonSaysGame game;
void setup() {
game.init();
}
void loop() {
game.run();
}