/*
Simon Says na ESP32 (wersja poprawiona)
- LCD 1602 I2C
- 4 przyciski (pull-up wewnętrzny ESP32)
- 4 diody LED (różne kolory)
- Buzzer (sterowany natywnymi funkcjami ESP32)
*/
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Preferences.h>
// ====== Konfiguracja pinów ======
#define LCD_ADDR 0x27 // adres I2C wyświetlacza
#define LCD_COLS 16
#define LCD_ROWS 2
#define BTN1_PIN 4 // przycisk 1 (zielony)
#define BTN2_PIN 18 // przycisk 2 (czerwony)
#define BTN3_PIN 19 // przycisk 3 (niebieski)
#define BTN4_PIN 23 // przycisk 4 (żółty)
#define LED1_PIN 2 // dioda zielona
#define LED2_PIN 5 // dioda czerwona
#define LED3_PIN 15 // dioda niebieska
#define LED4_PIN 13 // dioda żółta
#define BUZZER_PIN 25 // Buzzer
// ====== Inicjalizacja sprzętu ======
LiquidCrystal_I2C lcd(LCD_ADDR, LCD_COLS, LCD_ROWS);
Preferences prefs;
// Tablica kolorów
const char* colorNames[] = {"Zielony", "Czerwony", "Niebieski", "Żółty"};
// Mapowanie: przycisk -> dioda -> ton
const int btnPins[4] = {BTN1_PIN, BTN2_PIN, BTN3_PIN, BTN4_PIN};
const int ledPins[4] = {LED1_PIN, LED2_PIN, LED3_PIN, LED4_PIN};
const int tones[4] = {523, 659, 784, 1047}; // C5, E5, G5, C6
// ====== Struktura ButtonState ======
struct ButtonState {
bool justPressed;
bool held;
};
// ====== Zmienne gry ======
const int maxSeqLen = 100;
int sequence[maxSeqLen];
int seqLength = 0;
int level = 0;
int bestScore = 0;
bool playingSequence = false;
// Konfiguracja czasów
int noteOnMs = 500;
int noteGapMs = 150;
int holdToResetMs = 1000;
int sequenceInputTimeoutMs = 3500;
// ====== Setup ======
void setup() {
Serial.begin(115200);
delay(200);
Wire.begin(); // SDA=21, SCL=22
lcd.init();
lcd.backlight();
lcd.clear();
lcd.print("Simon Says");
lcd.setCursor(0, 1);
lcd.print("Wcisnij przycisk");
for (int i = 0; i < 4; i++) {
pinMode(btnPins[i], INPUT_PULLUP);
}
for (int i = 0; i < 4; i++) {
pinMode(ledPins[i], OUTPUT);
digitalWrite(ledPins[i], LOW);
}
// Inicjalizacja buzzera
pinMode(BUZZER_PIN, OUTPUT);
digitalWrite(BUZZER_PIN, LOW);
prefs.begin("simon", true);
bestScore = prefs.getInt("best", 0);
prefs.end();
printMenu();
}
// ====== Pętla główna ======
void loop() {
for (int i = 0; i < 4; i++) {
ButtonState bs = readButton(btnPins[i], holdToResetMs);
if (!bs.justPressed && !bs.held) continue;
if (bs.justPressed) {
if (!playingSequence && seqLength == 0) {
startNewGame();
delay(150);
break;
}
}
if (bs.held) {
if (!playingSequence && seqLength == 0) {
resetBestScore();
delay(200);
break;
}
}
if (!playingSequence && seqLength > 0) {
if (bs.justPressed) {
handlePlayerInput(i);
delay(10);
break;
}
}
}
delay(5);
}
// ====== Funkcje gry ======
void startNewGame() {
lcd.clear();
lcd.print("Nowa gra...");
randomSeed(esp_random());
seqLength = 0;
level = 0;
computeTempo();
delay(350);
addStepToSequence();
playSequence();
promptPlayer();
}
void resetBestScore() {
prefs.begin("simon", false);
prefs.putInt("best", 0);
prefs.end();
bestScore = 0;
lcd.clear();
lcd.print("Rekord wyzerowany");
lcd.setCursor(0, 1);
lcd.print("Best = 0");
delay(700);
printMenu();
}
void computeTempo() {
noteOnMs = 500 - (seqLength * 12);
noteOnMs = max(noteOnMs, 220);
noteGapMs = 150 - (seqLength * 3);
noteGapMs = max(noteGapMs, 70);
}
void addStepToSequence() {
if (seqLength >= maxSeqLen) return;
sequence[seqLength] = random(0, 4);
seqLength++;
}
void playSequence() {
playingSequence = true;
lcd.clear();
lcd.print("Sekwencja...");
for (int i = 0; i < seqLength; i++) {
int idx = sequence[i];
flashTone(idx, noteOnMs);
delay(noteGapMs);
}
playingSequence = false;
}
void promptPlayer() {
lcd.clear();
lcd.print("Twoja kolej!");
lcd.setCursor(0, 1);
lcd.print("Poziom: ");
lcd.print(seqLength);
}
void handlePlayerInput(int idx) {
flashTone(idx, 160);
static int progress = 0;
if (idx == sequence[progress]) {
progress++;
if (progress >= seqLength) {
if (seqLength > bestScore) {
bestScore = seqLength;
prefs.begin("simon", false);
prefs.putInt("best", bestScore);
prefs.end();
}
successJingle();
progress = 0;
addStepToSequence();
computeTempo();
playSequence();
promptPlayer();
}
} else {
errorSignal();
lcd.clear();
lcd.print("Blad! Wynik: ");
lcd.print(seqLength - 1);
lcd.setCursor(0, 1);
lcd.print("Best: ");
lcd.print(bestScore);
delay(2000);
seqLength = 0;
progress = 0;
printMenu();
}
}
// ====== Sygnały dźwiękowe ======
void flashTone(int idx, int durationMs) {
digitalWrite(ledPins[idx], HIGH);
tone(BUZZER_PIN, tones[idx], durationMs);
delay(durationMs);
digitalWrite(ledPins[idx], LOW);
noTone(BUZZER_PIN);
}
void errorSignal() {
for (int i = 0; i < 4; i++) digitalWrite(ledPins[i], HIGH);
tone(BUZZER_PIN, 262, 700);
delay(700);
for (int i = 0; i < 4; i++) digitalWrite(ledPins[i], LOW);
noTone(BUZZER_PIN);
}
void successJingle() {
tone(BUZZER_PIN, 784, 150);
delay(170);
tone(BUZZER_PIN, 1047, 250);
delay(270);
noTone(BUZZER_PIN);
}
// ====== Menu ======
void printMenu() {
lcd.clear();
lcd.print("Simon Says");
lcd.setCursor(0, 1);
lcd.print("Start/Best=");
lcd.print(bestScore);
}
// ====== Obsługa przycisków (debounce + hold) ======
ButtonState readButton(int pin, long holdMs) {
static const int N = 4;
static uint32_t lastDebounceTime[N] = {0};
static bool lastStableState[N] = {true};
static bool lastReading[N] = {true};
static uint32_t pressStartTime[N] = {0};
static bool holdSent[N] = {false};
int idx = -1;
for (int i = 0; i < N; i++) if (btnPins[i] == pin) idx = i;
if (idx < 0) return {false, false};
bool reading = digitalRead(pin);
uint32_t now = millis();
bool changed = false;
if (reading != lastReading[idx]) {
lastDebounceTime[idx] = now;
lastReading[idx] = reading;
}
if ((now - lastDebounceTime[idx]) > 25) {
if (reading != lastStableState[idx]) {
lastStableState[idx] = reading;
changed = true;
}
}
bool justPressed = false;
bool hold = false;
if (changed && lastStableState[idx] == LOW) {
pressStartTime[idx] = now;
holdSent[idx] = false;
justPressed = true;
}
if (lastStableState[idx] == LOW && !holdSent[idx]) {
if ((now - pressStartTime[idx]) >= holdMs) {
hold = true;
holdSent[idx] = true;
}
}
return {justPressed, hold};
}