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