ESP32-C3 based hot wire game with WS2812B LED effects, SSD1306 OLED display, debounced touch detection, and full game state machine (IDLE → COUNTDOWN → PLAYING → GAME_OVER). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
143 lines
3.6 KiB
C++
143 lines
3.6 KiB
C++
#include "game_logic.h"
|
|
#include "config.h"
|
|
#include "led_effects.h"
|
|
|
|
static GameData gd;
|
|
|
|
void gameInit() {
|
|
pinMode(PIN_WIRE_TOUCH, INPUT_PULLUP);
|
|
pinMode(PIN_START_BTN, INPUT_PULLUP);
|
|
pinMode(PIN_BUZZER, OUTPUT);
|
|
digitalWrite(PIN_BUZZER, LOW);
|
|
|
|
gd.state = STATE_IDLE;
|
|
gd.strikes = 0;
|
|
gd.score = SCORE_BASE;
|
|
gd.gameStartMs = 0;
|
|
gd.elapsedMs = 0;
|
|
gd.countdownValue = COUNTDOWN_SECS;
|
|
gd.countdownStepMs = 0;
|
|
gd.touchActive = false;
|
|
gd.lastTouchMs = 0;
|
|
gd.lastButtonMs = 0;
|
|
gd.won = false;
|
|
}
|
|
|
|
GameData& gameGetData() {
|
|
return gd;
|
|
}
|
|
|
|
void gameCheckStartButton() {
|
|
if (digitalRead(PIN_START_BTN) == LOW) {
|
|
unsigned long now = millis();
|
|
if (now - gd.lastButtonMs < BUTTON_DEBOUNCE_MS) return;
|
|
gd.lastButtonMs = now;
|
|
|
|
if (gd.state == STATE_IDLE || gd.state == STATE_GAME_OVER) {
|
|
// Reset game data
|
|
gd.strikes = 0;
|
|
gd.score = SCORE_BASE;
|
|
gd.elapsedMs = 0;
|
|
gd.countdownValue = COUNTDOWN_SECS;
|
|
gd.touchActive = false;
|
|
gd.won = false;
|
|
|
|
// Start countdown
|
|
gd.state = STATE_COUNTDOWN;
|
|
gd.countdownStepMs = millis();
|
|
|
|
Serial.println("[GAME] START pressed -> COUNTDOWN");
|
|
}
|
|
}
|
|
}
|
|
|
|
void gameCheckTouch() {
|
|
if (gd.state != STATE_PLAYING) return;
|
|
|
|
bool touching = (digitalRead(PIN_WIRE_TOUCH) == LOW);
|
|
unsigned long now = millis();
|
|
|
|
if (touching && !gd.touchActive) {
|
|
if (now - gd.lastTouchMs < TOUCH_DEBOUNCE_MS) return;
|
|
gd.lastTouchMs = now;
|
|
gd.touchActive = true;
|
|
gd.strikes++;
|
|
|
|
Serial.print("[GAME] TOUCH! Strikes: ");
|
|
Serial.println(gd.strikes);
|
|
|
|
// Buzzer alert
|
|
tone(PIN_BUZZER, TONE_TOUCH_HZ, TONE_DURATION_MS);
|
|
|
|
// Blocking LED flash
|
|
ledsTouchFlash();
|
|
|
|
// Check for game over by max strikes
|
|
if (gd.strikes >= MAX_STRIKES) {
|
|
gd.won = false;
|
|
gd.state = STATE_GAME_OVER;
|
|
// Lose tone
|
|
tone(PIN_BUZZER, TONE_LOSE_HZ, 500);
|
|
Serial.println("[GAME] MAX STRIKES -> GAME_OVER (lose)");
|
|
}
|
|
} else if (!touching) {
|
|
gd.touchActive = false;
|
|
}
|
|
}
|
|
|
|
void gameUpdate() {
|
|
unsigned long now = millis();
|
|
|
|
switch (gd.state) {
|
|
case STATE_IDLE:
|
|
// Nothing to update, waiting for start button
|
|
break;
|
|
|
|
case STATE_COUNTDOWN:
|
|
if (now - gd.countdownStepMs >= 1000) {
|
|
gd.countdownStepMs = now;
|
|
if (gd.countdownValue > 0) {
|
|
// Countdown beep
|
|
tone(PIN_BUZZER, TONE_COUNTDOWN_HZ, 100);
|
|
Serial.print("[GAME] Countdown: ");
|
|
Serial.println(gd.countdownValue);
|
|
gd.countdownValue--;
|
|
} else {
|
|
// GO!
|
|
tone(PIN_BUZZER, TONE_GO_HZ, 200);
|
|
gd.state = STATE_PLAYING;
|
|
gd.gameStartMs = millis();
|
|
Serial.println("[GAME] GO! -> PLAYING");
|
|
}
|
|
}
|
|
break;
|
|
|
|
case STATE_PLAYING:
|
|
gd.elapsedMs = now - gd.gameStartMs;
|
|
|
|
// Calculate live score
|
|
gd.score = SCORE_BASE
|
|
- (gd.strikes * SCORE_STRIKE_PENALTY)
|
|
- (int)(gd.elapsedMs / SCORE_TIME_DIVISOR);
|
|
if (gd.score < 0) gd.score = 0;
|
|
|
|
// Check time up
|
|
if (gd.elapsedMs >= (unsigned long)GAME_DURATION_S * 1000UL) {
|
|
gd.won = (gd.strikes < MAX_STRIKES);
|
|
gd.state = STATE_GAME_OVER;
|
|
if (gd.won) {
|
|
tone(PIN_BUZZER, TONE_WIN_HZ, 300);
|
|
Serial.println("[GAME] TIME UP -> GAME_OVER (win)");
|
|
} else {
|
|
tone(PIN_BUZZER, TONE_LOSE_HZ, 500);
|
|
Serial.println("[GAME] TIME UP -> GAME_OVER (lose)");
|
|
}
|
|
}
|
|
break;
|
|
|
|
case STATE_GAME_OVER:
|
|
// Waiting for start button to restart
|
|
break;
|
|
}
|
|
}
|