Files
HeisserDraht/game_logic.cpp
Jan Remmer Siebels 01e79a4eb2 Initial implementation of HeisserDraht buzz wire game
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>
2026-02-22 00:24:52 +01:00

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;
}
}