/*
* ====================================================
* GRA SIMON SAYS DLA DZIECI (4-6 lat)
* ====================================================
* Wersja z WiFi AP i interfejsem WWW
* + Tryb gościa "Ktoś:)"
*
* AP: "Powtorz-kolory"
* Hasło: "kubaimilosz"
* Adres: http://192.168.4.1
* ====================================================
*/
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <WiFi.h>
#include <WebServer.h>
#include <Preferences.h>
#include "esp_random.h"
// ============ KONFIGURACJA WiFi ============
const char* AP_SSID = "Powtorz-kolory";
const char* AP_PASS = "kubaimilosz";
WebServer server(80);
Preferences preferences;
// ============ KONFIGURACJA PINÓW ============
#define LED_CZERWONA 2
#define LED_ZIELONA 4
#define LED_NIEBIESKA 5
#define LED_ZOLTA 18
#define BTN_CZERWONY 13 // Poprzednie imię
#define BTN_ZIELONY 12 // Następne imię
#define BTN_NIEBIESKI 14 // Start gry
#define BTN_ZOLTY 27 // Start gry
#define BUZZER 15
// ============ USTAWIENIA GRY ============
#define MAX_POZIOM 20
#define MAX_IMION 10
#define MAX_DLUG_IMIENIA 20
#define MAX_TEKST 250
// Specjalna wartość dla trybu gościa
#define TRYB_GOSC -1
const String IMIE_GOSC = "Ktos:)";
// Domyślne czasy (można zmieniać przez WWW)
int czasPokazu = 600;
int czasPrzerwy = 300;
int czasOczekiwania = 5000;
int predkoscPrzewijania = 300;
// ============ DŹWIĘKI ============
#define TON_CZERWONY 262
#define TON_ZIELONY 330
#define TON_NIEBIESKI 392
#define TON_ZOLTY 523
#define TON_SUKCES 880
#define TON_PORAZKA 150
// ============ LCD ============
LiquidCrystal_I2C lcd(0x27, 16, 2);
// ============ ZMIENNE GRY ============
int sekwencja[MAX_POZIOM];
int poziom = 0;
int krokGracza = 0;
bool graAktywna = false;
unsigned long seedDodatkowy = 0;
// Dane graczy
String imiona[MAX_IMION];
int wyniki[MAX_IMION];
int liczbaImion = 0;
int aktualnyGracz = TRYB_GOSC; // Domyślnie tryb gościa
int ostatniGracz = TRYB_GOSC;
int wynikGosc = 0; // Rekord gościa
// Wyświetlanie tekstu
String tekstDoWyswietlenia = "";
bool wyswietlanieTekstu = false;
unsigned long czasStartuTekstu = 0;
int pozycjaPrzewijania = 0;
unsigned long ostatniePrzesuniecie = 0;
// Tablice
int ledy[] = {LED_CZERWONA, LED_ZIELONA, LED_NIEBIESKA, LED_ZOLTA};
int przyciski[] = {BTN_CZERWONY, BTN_ZIELONY, BTN_NIEBIESKI, BTN_ZOLTY};
int tony[] = {TON_CZERWONY, TON_ZIELONY, TON_NIEBIESKI, TON_ZOLTY};
// ============ WŁASNE ZNAKI LCD ============
byte buzkaUsmiech[] = {B00000, B01010, B01010, B00000, B10001, B01110, B00000, B00000};
byte buzkaSmutek[] = {B00000, B01010, B01010, B00000, B01110, B10001, B00000, B00000};
byte gwiazdka[] = {B00100, B01110, B11111, B01110, B01010, B10001, B00000, B00000};
byte serce[] = {B00000, B01010, B11111, B11111, B01110, B00100, B00000, B00000};
byte strzalkaL[] = {B00010, B00110, B01110, B11110, B01110, B00110, B00010, B00000};
byte strzalkaP[] = {B01000, B01100, B01110, B01111, B01110, B01100, B01000, B00000};
// ============ FUNKCJE POMOCNICZE DLA GRACZA ============
// Pobierz imię aktualnego gracza (lub "Ktoś:)" dla gościa)
String pobierzImieGracza() {
if (aktualnyGracz == TRYB_GOSC) {
return IMIE_GOSC;
}
return imiona[aktualnyGracz];
}
// Pobierz rekord aktualnego gracza
int pobierzRekordGracza() {
if (aktualnyGracz == TRYB_GOSC) {
return wynikGosc;
}
return wyniki[aktualnyGracz];
}
// Zapisz rekord aktualnego gracza
void zapiszRekordGracza(int nowyWynik) {
if (aktualnyGracz == TRYB_GOSC) {
if (nowyWynik > wynikGosc) {
wynikGosc = nowyWynik;
}
} else {
if (nowyWynik > wyniki[aktualnyGracz]) {
wyniki[aktualnyGracz] = nowyWynik;
}
}
}
// Przejdź do następnego gracza (włącznie z gościem)
void nastepnyGracz() {
if (liczbaImion == 0) {
// Tylko tryb gościa dostępny
aktualnyGracz = TRYB_GOSC;
} else if (aktualnyGracz == TRYB_GOSC) {
// Z gościa do pierwszego gracza
aktualnyGracz = 0;
} else if (aktualnyGracz >= liczbaImion - 1) {
// Z ostatniego gracza do gościa
aktualnyGracz = TRYB_GOSC;
} else {
// Następny gracz
aktualnyGracz++;
}
}
// Przejdź do poprzedniego gracza (włącznie z gościem)
void poprzedniGracz() {
if (liczbaImion == 0) {
// Tylko tryb gościa dostępny
aktualnyGracz = TRYB_GOSC;
} else if (aktualnyGracz == TRYB_GOSC) {
// Z gościa do ostatniego gracza
aktualnyGracz = liczbaImion - 1;
} else if (aktualnyGracz <= 0) {
// Z pierwszego gracza do gościa
aktualnyGracz = TRYB_GOSC;
} else {
// Poprzedni gracz
aktualnyGracz--;
}
}
// ============ STRONA HTML ============
const char MAIN_page[] PROGMEM = R"=====(
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simon Says - Panel</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Segoe UI', Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container { max-width: 800px; margin: 0 auto; }
h1 {
text-align: center;
color: white;
margin-bottom: 20px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
font-size: 2em;
}
.tabs { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 20px; }
.tab-btn {
flex: 1;
min-width: 100px;
padding: 12px 15px;
border: none;
background: rgba(255,255,255,0.3);
color: white;
cursor: pointer;
border-radius: 10px 10px 0 0;
font-size: 14px;
font-weight: bold;
transition: all 0.3s;
}
.tab-btn:hover { background: rgba(255,255,255,0.5); }
.tab-btn.active { background: white; color: #667eea; }
.tab-content {
display: none;
background: white;
padding: 25px;
border-radius: 0 0 15px 15px;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
}
.tab-content.active { display: block; }
h2 {
color: #667eea;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid #eee;
}
.form-group { margin-bottom: 20px; }
label { display: block; margin-bottom: 8px; font-weight: bold; color: #333; }
input[type="text"], input[type="number"], textarea, select {
width: 100%;
padding: 12px;
border: 2px solid #ddd;
border-radius: 8px;
font-size: 16px;
transition: border-color 0.3s;
}
input:focus, textarea:focus, select:focus { outline: none; border-color: #667eea; }
textarea { resize: vertical; min-height: 100px; }
.btn {
padding: 12px 25px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 16px;
font-weight: bold;
transition: all 0.3s;
margin: 5px;
}
.btn-primary { background: #667eea; color: white; }
.btn-primary:hover { background: #5a6fd6; transform: translateY(-2px); }
.btn-success { background: #28a745; color: white; }
.btn-success:hover { background: #218838; }
.btn-danger { background: #dc3545; color: white; }
.btn-danger:hover { background: #c82333; }
.btn-warning { background: #ffc107; color: #333; }
.btn-warning:hover { background: #e0a800; }
.btn-group { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 15px; }
.player-list { list-style: none; }
.player-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 15px;
background: #f8f9fa;
margin-bottom: 8px;
border-radius: 8px;
border-left: 4px solid #667eea;
}
.player-item.guest {
background: #fff3cd;
border-left-color: #ffc107;
}
.player-item:nth-child(1) { border-left-color: gold; }
.player-item:nth-child(2) { border-left-color: silver; }
.player-item:nth-child(3) { border-left-color: #cd7f32; }
.player-name { font-weight: bold; color: #333; }
.player-score {
background: #667eea;
color: white;
padding: 5px 15px;
border-radius: 20px;
font-weight: bold;
}
.delete-btn {
background: #dc3545;
color: white;
border: none;
padding: 5px 10px;
border-radius: 5px;
cursor: pointer;
margin-left: 10px;
}
.delete-btn:hover { background: #c82333; }
.slider-container { margin: 15px 0; }
.slider-container input[type="range"] {
width: 100%;
height: 10px;
-webkit-appearance: none;
background: #ddd;
border-radius: 5px;
outline: none;
}
.slider-container input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 25px;
height: 25px;
background: #667eea;
border-radius: 50%;
cursor: pointer;
}
.slider-value {
text-align: center;
font-size: 18px;
font-weight: bold;
color: #667eea;
margin-top: 5px;
}
.status {
padding: 15px;
border-radius: 8px;
margin: 15px 0;
text-align: center;
font-weight: bold;
}
.status.success { background: #d4edda; color: #155724; }
.status.error { background: #f8d7da; color: #721c24; }
.status.info { background: #cce5ff; color: #004085; }
.settings-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
}
.setting-card {
background: #f8f9fa;
padding: 20px;
border-radius: 10px;
text-align: center;
}
.current-player {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
border-radius: 10px;
text-align: center;
margin-bottom: 20px;
}
.current-player h3 { font-size: 1.5em; margin-bottom: 10px; }
.guest-info {
background: #fff3cd;
border: 2px solid #ffc107;
border-radius: 10px;
padding: 15px;
margin-bottom: 20px;
text-align: center;
}
.guest-info h4 { color: #856404; margin-bottom: 5px; }
.guest-info p { color: #856404; font-size: 0.9em; }
@media (max-width: 600px) {
.tab-btn { font-size: 12px; padding: 10px; }
.btn { width: 100%; margin: 5px 0; }
}
</style>
</head>
<body>
<div class="container">
<h1>🎮 Simon Says - Panel 🎮</h1>
<div class="tabs">
<button class="tab-btn active" onclick="openTab('gracze')">👤 Gracze</button>
<button class="tab-btn" onclick="openTab('wyniki')">🏆 Wyniki</button>
<button class="tab-btn" onclick="openTab('ustawienia')">⚙️ Ustawienia</button>
<button class="tab-btn" onclick="openTab('wiadomosc')">💬 Wiadomość</button>
</div>
<!-- GRACZE -->
<div id="gracze" class="tab-content active">
<h2>👤 Zarządzanie Graczami</h2>
<div class="current-player">
<h3>Aktualny gracz:</h3>
<div id="currentPlayerName" style="font-size: 1.8em;">-</div>
</div>
<div class="guest-info">
<h4>🎭 Tryb gościa: "Ktoś:)"</h4>
<p>Zawsze możesz grać bez wybierania imienia!</p>
<p>Rekord gościa: <strong id="guestScore">0</strong> pkt</p>
</div>
<div class="form-group">
<label>Dodaj nowego gracza (max 20 znaków):</label>
<input type="text" id="newPlayerName" maxlength="20" placeholder="Wpisz imię...">
</div>
<div class="btn-group">
<button class="btn btn-success" onclick="addPlayer()">➕ Dodaj gracza</button>
</div>
<div id="playerStatus" class="status" style="display:none;"></div>
<h3 style="margin: 25px 0 15px 0;">Lista graczy (<span id="playerCount">0</span>/10):</h3>
<ul class="player-list" id="playerList"></ul>
</div>
<!-- WYNIKI -->
<div id="wyniki" class="tab-content">
<h2>🏆 Tabela Wyników</h2>
<ul class="player-list" id="scoreList"></ul>
<div class="btn-group">
<button class="btn btn-warning" onclick="resetScores()">🔄 Resetuj wyniki</button>
</div>
</div>
<!-- USTAWIENIA -->
<div id="ustawienia" class="tab-content">
<h2>⚙️ Ustawienia Gry</h2>
<div class="settings-grid">
<div class="setting-card">
<label>Czas świecenia LED (ms):</label>
<input type="number" id="czasPokazu" min="200" max="2000" step="100">
</div>
<div class="setting-card">
<label>Przerwa między kolorami (ms):</label>
<input type="number" id="czasPrzerwy" min="100" max="1000" step="50">
</div>
<div class="setting-card">
<label>Czas na odpowiedź (ms):</label>
<input type="number" id="czasOczekiwania" min="2000" max="15000" step="500">
</div>
<div class="setting-card">
<label>Prędkość przewijania tekstu (ms):</label>
<input type="number" id="predkoscPrzewijania" min="100" max="1000" step="50">
</div>
</div>
<div class="btn-group" style="margin-top: 25px;">
<button class="btn btn-primary" onclick="saveSettings()">💾 Zapisz ustawienia</button>
<button class="btn btn-warning" onclick="loadDefaultSettings()">🔄 Przywróć domyślne</button>
</div>
<div id="settingsStatus" class="status" style="display:none;"></div>
</div>
<!-- WIADOMOŚĆ -->
<div id="wiadomosc" class="tab-content">
<h2>💬 Wyślij Wiadomość na Ekran</h2>
<div class="form-group">
<label>Tekst do wyświetlenia (max 250 znaków):</label>
<textarea id="messageText" maxlength="250" placeholder="Wpisz wiadomość..."></textarea>
<div style="text-align: right; color: #666; margin-top: 5px;">
<span id="charCount">0</span>/250
</div>
</div>
<div class="slider-container">
<label>Prędkość przewijania:</label>
<input type="range" id="scrollSpeed" min="100" max="800" value="300"
oninput="updateScrollSpeed(this.value)">
<div class="slider-value"><span id="scrollSpeedValue">300</span> ms</div>
</div>
<div class="btn-group">
<button class="btn btn-success" onclick="sendMessage()">📤 WYŚLIJ</button>
<button class="btn btn-danger" onclick="stopMessage()">⏹️ STOP</button>
<button class="btn btn-warning" onclick="clearMessage()">🗑️ WYCZYŚĆ</button>
</div>
<div id="messageStatus" class="status" style="display:none;"></div>
</div>
</div>
<script>
function openTab(tabName) {
document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
document.getElementById(tabName).classList.add('active');
event.target.classList.add('active');
if (tabName === 'gracze') loadPlayers();
if (tabName === 'wyniki') loadScores();
if (tabName === 'ustawienia') loadCurrentSettings();
}
document.getElementById('messageText').addEventListener('input', function() {
document.getElementById('charCount').textContent = this.value.length;
});
function loadPlayers() {
fetch('/getPlayers')
.then(r => r.json())
.then(data => {
const list = document.getElementById('playerList');
list.innerHTML = '';
document.getElementById('playerCount').textContent = data.players.length;
document.getElementById('currentPlayerName').textContent = data.currentPlayer || 'Ktos:)';
document.getElementById('guestScore').textContent = data.guestScore || 0;
data.players.forEach((p, i) => {
list.innerHTML += `
<li class="player-item">
<span class="player-name">${i+1}. ${p.name}</span>
<div>
<span class="player-score">Rekord: ${p.score}</span>
<button class="delete-btn" onclick="deletePlayer(${i})">✕</button>
</div>
</li>`;
});
});
}
function addPlayer() {
const name = document.getElementById('newPlayerName').value.trim();
if (!name) {
showStatus('playerStatus', 'Wpisz imię!', 'error');
return;
}
fetch('/addPlayer?name=' + encodeURIComponent(name))
.then(r => r.json())
.then(data => {
if (data.success) {
showStatus('playerStatus', 'Dodano: ' + name, 'success');
document.getElementById('newPlayerName').value = '';
loadPlayers();
} else {
showStatus('playerStatus', data.message, 'error');
}
});
}
function deletePlayer(index) {
if (confirm('Usunąć tego gracza?')) {
fetch('/deletePlayer?index=' + index)
.then(r => r.json())
.then(data => {
if (data.success) {
showStatus('playerStatus', 'Usunięto gracza', 'success');
loadPlayers();
}
});
}
}
function loadScores() {
fetch('/getPlayers')
.then(r => r.json())
.then(data => {
// Dodaj gościa do listy
let allPlayers = [...data.players];
allPlayers.push({name: '🎭 Ktos:)', score: data.guestScore, isGuest: true});
const sorted = allPlayers.sort((a, b) => b.score - a.score);
const list = document.getElementById('scoreList');
list.innerHTML = '';
sorted.forEach((p, i) => {
let medal = '';
if (i === 0) medal = '🥇 ';
else if (i === 1) medal = '🥈 ';
else if (i === 2) medal = '🥉 ';
const guestClass = p.isGuest ? 'guest' : '';
list.innerHTML += `
<li class="player-item ${guestClass}">
<span class="player-name">${medal}${p.name}</span>
<span class="player-score">${p.score} pkt</span>
</li>`;
});
});
}
function resetScores() {
if (confirm('Zresetować wszystkie wyniki (włącznie z gościem)?')) {
fetch('/resetScores')
.then(() => {
loadScores();
loadPlayers();
});
}
}
function loadCurrentSettings() {
fetch('/getSettings')
.then(r => r.json())
.then(data => {
document.getElementById('czasPokazu').value = data.czasPokazu;
document.getElementById('czasPrzerwy').value = data.czasPrzerwy;
document.getElementById('czasOczekiwania').value = data.czasOczekiwania;
document.getElementById('predkoscPrzewijania').value = data.predkoscPrzewijania;
});
}
function loadDefaultSettings() {
document.getElementById('czasPokazu').value = 600;
document.getElementById('czasPrzerwy').value = 300;
document.getElementById('czasOczekiwania').value = 5000;
document.getElementById('predkoscPrzewijania').value = 300;
showStatus('settingsStatus', 'Przywrócono domyślne wartości', 'info');
}
function saveSettings() {
const params = new URLSearchParams({
czasPokazu: document.getElementById('czasPokazu').value,
czasPrzerwy: document.getElementById('czasPrzerwy').value,
czasOczekiwania: document.getElementById('czasOczekiwania').value,
predkoscPrzewijania: document.getElementById('predkoscPrzewijania').value
});
fetch('/saveSettings?' + params)
.then(r => r.json())
.then(data => {
showStatus('settingsStatus', 'Ustawienia zapisane!', 'success');
});
}
function updateScrollSpeed(val) {
document.getElementById('scrollSpeedValue').textContent = val;
}
function sendMessage() {
const text = document.getElementById('messageText').value;
const speed = document.getElementById('scrollSpeed').value;
if (!text.trim()) {
showStatus('messageStatus', 'Wpisz tekst!', 'error');
return;
}
fetch('/sendMessage?text=' + encodeURIComponent(text) + '&speed=' + speed)
.then(r => r.json())
.then(data => {
showStatus('messageStatus', 'Wiadomość wysłana!', 'success');
});
}
function stopMessage() {
fetch('/stopMessage')
.then(() => showStatus('messageStatus', 'Zatrzymano wyświetlanie', 'info'));
}
function clearMessage() {
document.getElementById('messageText').value = '';
document.getElementById('charCount').textContent = '0';
}
function showStatus(id, msg, type) {
const el = document.getElementById(id);
el.textContent = msg;
el.className = 'status ' + type;
el.style.display = 'block';
setTimeout(() => el.style.display = 'none', 3000);
}
loadPlayers();
</script>
</body>
</html>
)=====";
// ============ FUNKCJE ZAPISYWANIA DANYCH ============
void zapiszDane() {
preferences.begin("simon", false);
preferences.putInt("liczbaImion", liczbaImion);
preferences.putInt("ostatniGracz", ostatniGracz);
preferences.putInt("wynikGosc", wynikGosc);
for (int i = 0; i < MAX_IMION; i++) {
String keyName = "imie" + String(i);
String keyScore = "wynik" + String(i);
preferences.putString(keyName.c_str(), imiona[i]);
preferences.putInt(keyScore.c_str(), wyniki[i]);
}
preferences.putInt("czasPokazu", czasPokazu);
preferences.putInt("czasPrzerwy", czasPrzerwy);
preferences.putInt("czasOczekiwania", czasOczekiwania);
preferences.putInt("predkoscPrzew", predkoscPrzewijania);
preferences.end();
Serial.println("Dane zapisane!");
}
void wczytajDane() {
preferences.begin("simon", true);
liczbaImion = preferences.getInt("liczbaImion", 0);
ostatniGracz = preferences.getInt("ostatniGracz", TRYB_GOSC);
wynikGosc = preferences.getInt("wynikGosc", 0);
for (int i = 0; i < MAX_IMION; i++) {
String keyName = "imie" + String(i);
String keyScore = "wynik" + String(i);
imiona[i] = preferences.getString(keyName.c_str(), "");
wyniki[i] = preferences.getInt(keyScore.c_str(), 0);
}
czasPokazu = preferences.getInt("czasPokazu", 600);
czasPrzerwy = preferences.getInt("czasPrzerwy", 300);
czasOczekiwania = preferences.getInt("czasOczekiwania", 5000);
predkoscPrzewijania = preferences.getInt("predkoscPrzew", 300);
preferences.end();
// Ustaw aktualnego gracza
aktualnyGracz = ostatniGracz;
// Sprawdź czy ostatni gracz nadal istnieje
if (aktualnyGracz != TRYB_GOSC && aktualnyGracz >= liczbaImion) {
aktualnyGracz = TRYB_GOSC;
}
Serial.println("Dane wczytane!");
Serial.print("Liczba imion: ");
Serial.println(liczbaImion);
Serial.print("Aktualny gracz: ");
Serial.println(aktualnyGracz == TRYB_GOSC ? "Gosc" : imiona[aktualnyGracz]);
Serial.print("Rekord goscia: ");
Serial.println(wynikGosc);
}
// ============ HANDLERY WWW ============
void handleRoot() {
server.send(200, "text/html", MAIN_page);
}
void handleGetPlayers() {
String json = "{\"players\":[";
for (int i = 0; i < liczbaImion; i++) {
if (i > 0) json += ",";
json += "{\"name\":\"" + imiona[i] + "\",\"score\":" + String(wyniki[i]) + "}";
}
json += "],\"currentPlayer\":\"";
json += pobierzImieGracza();
json += "\",\"guestScore\":";
json += String(wynikGosc);
json += "}";
server.send(200, "application/json", json);
}
void handleAddPlayer() {
if (!server.hasArg("name")) {
server.send(400, "application/json", "{\"success\":false,\"message\":\"Brak imienia\"}");
return;
}
if (liczbaImion >= MAX_IMION) {
server.send(200, "application/json", "{\"success\":false,\"message\":\"Max 10 graczy!\"}");
return;
}
String name = server.arg("name");
name.trim();
if (name.length() == 0 || name.length() > MAX_DLUG_IMIENIA) {
server.send(200, "application/json", "{\"success\":false,\"message\":\"Nieprawidlowa dlugosc imienia\"}");
return;
}
// Sprawdź duplikaty (włącznie z "Ktoś:)")
if (name.equalsIgnoreCase("Ktos:)") || name.equalsIgnoreCase("Ktoś:)")) {
server.send(200, "application/json", "{\"success\":false,\"message\":\"To imie jest zarezerwowane!\"}");
return;
}
for (int i = 0; i < liczbaImion; i++) {
if (imiona[i].equalsIgnoreCase(name)) {
server.send(200, "application/json", "{\"success\":false,\"message\":\"Imie juz istnieje!\"}");
return;
}
}
imiona[liczbaImion] = name;
wyniki[liczbaImion] = 0;
liczbaImion++;
zapiszDane();
server.send(200, "application/json", "{\"success\":true}");
}
void handleDeletePlayer() {
if (!server.hasArg("index")) {
server.send(400, "application/json", "{\"success\":false}");
return;
}
int index = server.arg("index").toInt();
if (index < 0 || index >= liczbaImion) {
server.send(400, "application/json", "{\"success\":false}");
return;
}
// Przesuń pozostałych
for (int i = index; i < liczbaImion - 1; i++) {
imiona[i] = imiona[i + 1];
wyniki[i] = wyniki[i + 1];
}
liczbaImion--;
imiona[liczbaImion] = "";
wyniki[liczbaImion] = 0;
// Popraw indeks aktualnego gracza jeśli trzeba
if (aktualnyGracz != TRYB_GOSC) {
if (aktualnyGracz == index) {
aktualnyGracz = TRYB_GOSC;
} else if (aktualnyGracz > index) {
aktualnyGracz--;
}
}
zapiszDane();
server.send(200, "application/json", "{\"success\":true}");
}
void handleResetScores() {
for (int i = 0; i < liczbaImion; i++) {
wyniki[i] = 0;
}
wynikGosc = 0;
zapiszDane();
server.send(200, "application/json", "{\"success\":true}");
}
void handleGetSettings() {
String json = "{";
json += "\"czasPokazu\":" + String(czasPokazu) + ",";
json += "\"czasPrzerwy\":" + String(czasPrzerwy) + ",";
json += "\"czasOczekiwania\":" + String(czasOczekiwania) + ",";
json += "\"predkoscPrzewijania\":" + String(predkoscPrzewijania);
json += "}";
server.send(200, "application/json", json);
}
void handleSaveSettings() {
if (server.hasArg("czasPokazu")) {
czasPokazu = constrain(server.arg("czasPokazu").toInt(), 200, 2000);
}
if (server.hasArg("czasPrzerwy")) {
czasPrzerwy = constrain(server.arg("czasPrzerwy").toInt(), 100, 1000);
}
if (server.hasArg("czasOczekiwania")) {
czasOczekiwania = constrain(server.arg("czasOczekiwania").toInt(), 2000, 15000);
}
if (server.hasArg("predkoscPrzewijania")) {
predkoscPrzewijania = constrain(server.arg("predkoscPrzewijania").toInt(), 100, 1000);
}
zapiszDane();
server.send(200, "application/json", "{\"success\":true}");
}
void handleSendMessage() {
if (!server.hasArg("text")) {
server.send(400, "application/json", "{\"success\":false}");
return;
}
tekstDoWyswietlenia = server.arg("text");
if (server.hasArg("speed")) {
predkoscPrzewijania = constrain(server.arg("speed").toInt(), 100, 1000);
}
wyswietlanieTekstu = true;
czasStartuTekstu = millis();
pozycjaPrzewijania = 0;
Serial.println("Wiadomosc: " + tekstDoWyswietlenia);
server.send(200, "application/json", "{\"success\":true}");
}
void handleStopMessage() {
wyswietlanieTekstu = false;
tekstDoWyswietlenia = "";
server.send(200, "application/json", "{\"success\":true}");
}
// ============ FUNKCJE LOSOWOŚCI ============
void dodajEntropie() {
seedDodatkowy ^= esp_random();
seedDodatkowy ^= micros();
seedDodatkowy += millis();
for (int i = 32; i <= 35; i++) {
seedDodatkowy ^= analogRead(i);
}
}
int losowyKolor() {
uint32_t losowa = esp_random();
losowa ^= micros();
losowa ^= seedDodatkowy;
return losowa % 4;
}
void generujNowaSekwencje() {
Serial.println("=== NOWA SEKWENCJA ===");
for (int i = 0; i < 10; i++) {
dodajEntropie();
delay(5);
}
for (int i = 0; i < MAX_POZIOM; i++) {
sekwencja[i] = losowyKolor();
}
}
// ============ FUNKCJE POMOCNICZE ============
void inicjalizujPiny() {
for (int i = 0; i < 4; i++) {
pinMode(ledy[i], OUTPUT);
digitalWrite(ledy[i], LOW);
}
for (int i = 0; i < 4; i++) {
pinMode(przyciski[i], INPUT_PULLUP);
}
pinMode(BUZZER, OUTPUT);
}
void zapalWszystkie() {
for (int i = 0; i < 4; i++) digitalWrite(ledy[i], HIGH);
}
void zgasWszystkie() {
for (int i = 0; i < 4; i++) digitalWrite(ledy[i], LOW);
}
void zapalLED(int numer, int czasMs) {
digitalWrite(ledy[numer], HIGH);
tone(BUZZER, tony[numer], czasMs);
delay(czasMs);
digitalWrite(ledy[numer], LOW);
noTone(BUZZER);
}
// ============ WYŚWIETLANIE TEKSTU ============
void wyswietlTekstPrzewijanieLoop() {
if (!wyswietlanieTekstu) return;
// Sprawdź timeout (1 minuta)
if (millis() - czasStartuTekstu > 60000) {
wyswietlanieTekstu = false;
return;
}
// Czy czas na przewinięcie?
if (millis() - ostatniePrzesuniecie < (unsigned long)predkoscPrzewijania) {
return;
}
ostatniePrzesuniecie = millis();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("== WIADOMOSC ==");
int dlugoscTekstu = tekstDoWyswietlenia.length();
if (dlugoscTekstu <= 16) {
lcd.setCursor(0, 1);
lcd.print(tekstDoWyswietlenia);
} else {
String tekstZSpacjami = tekstDoWyswietlenia + " ";
int dlugoscCalkowita = tekstZSpacjami.length();
lcd.setCursor(0, 1);
for (int i = 0; i < 16; i++) {
int idx = (pozycjaPrzewijania + i) % dlugoscCalkowita;
lcd.print(tekstZSpacjami[idx]);
}
pozycjaPrzewijania++;
if (pozycjaPrzewijania >= dlugoscCalkowita) {
pozycjaPrzewijania = 0;
}
}
}
// ============ EKRAN WYBORU GRACZA ============
void wyswietlWyborGracza() {
lcd.clear();
lcd.setCursor(0, 0);
String imie = pobierzImieGracza();
int rekord = pobierzRekordGracza();
// Pierwszy wiersz: strzałki i imię
lcd.write(4); // Strzałka lewa
lcd.print(" ");
// Skróć imię jeśli za długie
if (imie.length() > 12) {
imie = imie.substring(0, 12);
}
// Wycentruj imię
int padding = (12 - imie.length()) / 2;
for (int i = 0; i < padding; i++) lcd.print(" ");
lcd.print(imie);
int pozostalo = 12 - padding - imie.length();
for (int i = 0; i < pozostalo; i++) lcd.print(" ");
lcd.print(" ");
lcd.write(5); // Strzałka prawa
// Drugi wiersz: rekord i informacja o starcie
lcd.setCursor(0, 1);
lcd.print("Rek:");
lcd.print(rekord);
lcd.print(" ");
// Ikona informująca o trybie gościa
if (aktualnyGracz == TRYB_GOSC) {
lcd.write(0); // Buźka
}
lcd.print(" START!");
}
bool ekranWyboruGracza() {
wyswietlWyborGracza();
int animLed = 0;
unsigned long ostatniaAnimacja = millis();
while (true) {
server.handleClient();
if (wyswietlanieTekstu) {
wyswietlTekstPrzewijanieLoop();
continue;
}
// Animacja LED
if (millis() - ostatniaAnimacja > 300) {
zgasWszystkie();
digitalWrite(ledy[animLed], HIGH);
animLed = (animLed + 1) % 4;
ostatniaAnimacja = millis();
dodajEntropie();
}
// CZERWONY - poprzedni gracz
if (digitalRead(BTN_CZERWONY) == LOW) {
delay(200);
poprzedniGracz();
wyswietlWyborGracza();
tone(BUZZER, 300, 100);
while (digitalRead(BTN_CZERWONY) == LOW) delay(10);
}
// ZIELONY - następny gracz
if (digitalRead(BTN_ZIELONY) == LOW) {
delay(200);
nastepnyGracz();
wyswietlWyborGracza();
tone(BUZZER, 400, 100);
while (digitalRead(BTN_ZIELONY) == LOW) delay(10);
}
// NIEBIESKI lub ŻÓŁTY - start gry
if (digitalRead(BTN_NIEBIESKI) == LOW || digitalRead(BTN_ZOLTY) == LOW) {
delay(200);
zgasWszystkie();
// Zapisz ostatniego gracza
ostatniGracz = aktualnyGracz;
zapiszDane();
tone(BUZZER, 500, 200);
return true;
}
delay(50);
}
}
// ============ FUNKCJE GRY ============
void efektSukces() {
String imie = pobierzImieGracza();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" BRAWO ");
lcd.print(imie.substring(0, 6));
lcd.print("!");
lcd.setCursor(0, 1);
lcd.print(" SUPER ROBOTA! ");
lcd.write(2);
for (int i = 0; i < 3; i++) {
zapalWszystkie();
tone(BUZZER, TON_SUKCES, 150);
delay(200);
zgasWszystkie();
noTone(BUZZER);
delay(100);
}
delay(1000);
}
void efektPorazka() {
String imie = pobierzImieGracza();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" Ups ");
lcd.print(imie.substring(0, 8));
lcd.print("!");
lcd.setCursor(0, 1);
lcd.print("Sprobuj jeszcze!");
tone(BUZZER, TON_PORAZKA, 500);
for (int i = 0; i < 3; i++) {
zapalWszystkie();
delay(150);
zgasWszystkie();
delay(150);
}
noTone(BUZZER);
delay(1500);
}
void pokazSekwencje() {
String imie = pobierzImieGracza();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" PATRZ ");
lcd.print(imie.substring(0, 6));
lcd.print("!");
lcd.setCursor(0, 1);
lcd.print(" Zapamietaj!");
delay(800);
for (int i = 0; i < poziom; i++) {
server.handleClient();
lcd.setCursor(0, 1);
lcd.print("Kolor ");
lcd.print(i + 1);
lcd.print(" z ");
lcd.print(poziom);
lcd.print(" ");
zapalLED(sekwencja[i], czasPokazu);
delay(czasPrzerwy);
}
}
int sprawdzPrzycisk() {
for (int i = 0; i < 4; i++) {
if (digitalRead(przyciski[i]) == LOW) {
delay(50);
if (digitalRead(przyciski[i]) == LOW) {
seedDodatkowy ^= micros();
zapalLED(i, 200);
while (digitalRead(przyciski[i]) == LOW) {
delay(10);
}
delay(100);
return i;
}
}
}
return -1;
}
bool czekajNaOdpowiedz() {
String imie = pobierzImieGracza();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Twoja kolej ");
lcd.print(imie.substring(0, 4));
int krok = 0;
while (krok < poziom) {
lcd.setCursor(0, 1);
lcd.print("Kolor ");
lcd.print(krok + 1);
lcd.print(" z ");
lcd.print(poziom);
lcd.print(" ");
unsigned long czasStart = millis();
int nacisniety = -1;
while (nacisniety == -1) {
server.handleClient();
nacisniety = sprawdzPrzycisk();
if (millis() - czasStart > (unsigned long)czasOczekiwania) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Za wolno! ");
lcd.write(1);
delay(1500);
return false;
}
if ((millis() / 500) % 2 == 0) {
lcd.setCursor(15, 0);
lcd.print("*");
} else {
lcd.setCursor(15, 0);
lcd.print(" ");
}
}
if (nacisniety != sekwencja[krok]) {
return false;
}
krok++;
}
return true;
}
void pokazWynik(int wynik) {
String imie = pobierzImieGracza();
int staryRekord = pobierzRekordGracza();
// Sprawdź czy nowy rekord
if (wynik > staryRekord) {
zapiszRekordGracza(wynik);
zapiszDane();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("NOWY REKORD!!!");
lcd.write(2);
lcd.setCursor(0, 1);
lcd.print(imie.substring(0, 10));
lcd.print(": ");
lcd.print(wynik);
// Efekt świetlny dla nowego rekordu
for (int i = 0; i < 5; i++) {
zapalWszystkie();
delay(150);
zgasWszystkie();
delay(150);
}
delay(2000);
}
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Wynik: ");
lcd.print(wynik);
lcd.setCursor(0, 1);
lcd.print("Rekord: ");
lcd.print(pobierzRekordGracza());
delay(3000);
}
void rozgrywka() {
String imie = pobierzImieGracza();
poziom = 0;
graAktywna = true;
generujNowaSekwencje();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Gotowy ");
lcd.print(imie.substring(0, 7));
lcd.print("?");
lcd.setCursor(0, 1);
lcd.print(" 3... 2... 1...");
delay(2000);
while (graAktywna && poziom < MAX_POZIOM) {
poziom++;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Poziom: ");
lcd.print(poziom);
lcd.print(" ");
lcd.write(2);
delay(1000);
pokazSekwencje();
delay(500);
if (czekajNaOdpowiedz()) {
efektSukces();
delay(500);
} else {
efektPorazka();
graAktywna = false;
}
}
int wynik = (poziom > 0) ? poziom - 1 : 0;
pokazWynik(wynik);
}
// ============ SETUP ============
void setup() {
Serial.begin(115200);
Serial.println("\n\n=== SIMON SAYS Z WiFi ===");
Serial.println("Tryb goscia: Ktos:)");
seedDodatkowy = esp_random();
for (int i = 0; i < 20; i++) {
dodajEntropie();
delay(5);
}
inicjalizujPiny();
Wire.begin(21, 22);
lcd.init();
lcd.backlight();
lcd.createChar(0, buzkaUsmiech);
lcd.createChar(1, buzkaSmutek);
lcd.createChar(2, gwiazdka);
lcd.createChar(3, serce);
lcd.createChar(4, strzalkaL);
lcd.createChar(5, strzalkaP);
wczytajDane();
// Jeśli nie ma zapisanego gracza, ustaw gościa
if (aktualnyGracz != TRYB_GOSC && (aktualnyGracz < 0 || aktualnyGracz >= liczbaImion)) {
aktualnyGracz = TRYB_GOSC;
}
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Uruchamiam WiFi");
lcd.setCursor(0, 1);
lcd.print("Czekaj...");
WiFi.mode(WIFI_AP);
WiFi.softAP(AP_SSID, AP_PASS);
IPAddress IP = WiFi.softAPIP();
Serial.print("AP IP: ");
Serial.println(IP);
server.on("/", handleRoot);
server.on("/getPlayers", handleGetPlayers);
server.on("/addPlayer", handleAddPlayer);
server.on("/deletePlayer", handleDeletePlayer);
server.on("/resetScores", handleResetScores);
server.on("/getSettings", handleGetSettings);
server.on("/saveSettings", handleSaveSettings);
server.on("/sendMessage", handleSendMessage);
server.on("/stopMessage", handleStopMessage);
server.begin();
Serial.println("Serwer WWW uruchomiony!");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("WiFi: ");
lcd.print(AP_SSID);
lcd.setCursor(0, 1);
lcd.print("IP: ");
lcd.print(IP);
delay(3000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Haslo WiFi:");
lcd.setCursor(0, 1);
lcd.print(AP_PASS);
delay(3000);
}
// ============ LOOP ============
void loop() {
server.handleClient();
if (wyswietlanieTekstu) {
wyswietlTekstPrzewijanieLoop();
for (int i = 0; i < 4; i++) {
if (digitalRead(przyciski[i]) == LOW) {
wyswietlanieTekstu = false;
delay(300);
break;
}
}
return;
}
if (ekranWyboruGracza()) {
rozgrywka();
}
}