#include "led_effects.h" #include "config.h" #include "game_logic.h" #include static Adafruit_NeoPixel strip(NUM_LEDS, PIN_NEOPIXEL, NEO_GRB + NEO_KHZ800); // ── Helpers ───────────────────────────────────────────────────────── static uint32_t colorWheel(uint8_t pos) { pos = 255 - pos; if (pos < 85) return strip.Color(255 - pos * 3, 0, pos * 3); if (pos < 170) { pos -= 85; return strip.Color(0, pos * 3, 255 - pos * 3); } pos -= 170; return strip.Color(pos * 3, 255 - pos * 3, 0); } // ── Animations ────────────────────────────────────────────────────── static void animIdle() { // Rainbow chase static uint16_t hue = 0; for (int i = 0; i < NUM_LEDS; i++) { strip.setPixelColor(i, colorWheel((i * 256 / NUM_LEDS + hue) & 0xFF)); } hue += 2; strip.show(); } static void animCountdown() { GameData& gd = gameGetData(); uint32_t color; switch (gd.countdownValue) { case 3: // fall through case 2: color = strip.Color(255, 0, 0); break; // Red case 1: color = strip.Color(255, 180, 0); break; // Yellow default: color = strip.Color(0, 255, 0); break; // Green (GO!) } for (int i = 0; i < NUM_LEDS; i++) { strip.setPixelColor(i, color); } strip.show(); } static void animPlaying() { GameData& gd = gameGetData(); unsigned long totalMs = (unsigned long)GAME_DURATION_S * 1000UL; float progress = 1.0f - (float)gd.elapsedMs / (float)totalMs; if (progress < 0.0f) progress = 0.0f; int litLeds = (int)(progress * NUM_LEDS + 0.5f); for (int i = 0; i < NUM_LEDS; i++) { if (i < litLeds) { // Color transitions: green → yellow → red as time depletes if (progress > 0.5f) strip.setPixelColor(i, strip.Color(0, 255, 0)); else if (progress > 0.2f) strip.setPixelColor(i, strip.Color(255, 180, 0)); else strip.setPixelColor(i, strip.Color(255, 0, 0)); } else { strip.setPixelColor(i, 0); } } strip.show(); } static void animGameOverWin() { // Green/gold sparkle static unsigned long lastSparkle = 0; unsigned long now = millis(); if (now - lastSparkle > 80) { lastSparkle = now; for (int i = 0; i < NUM_LEDS; i++) { if (random(3) == 0) strip.setPixelColor(i, strip.Color(255, 215, 0)); // Gold else strip.setPixelColor(i, strip.Color(0, 180, 0)); // Green } strip.show(); } } static void animGameOverLose() { // Pulsing red breathing static uint8_t breathVal = 0; static int8_t breathDir = 2; breathVal += breathDir; if (breathVal >= 250 || breathVal <= 5) breathDir = -breathDir; for (int i = 0; i < NUM_LEDS; i++) { strip.setPixelColor(i, strip.Color(breathVal, 0, 0)); } strip.show(); } // ── Public API ────────────────────────────────────────────────────── void ledsInit() { strip.begin(); strip.setBrightness(LED_BRIGHTNESS); strip.show(); } void ledsSetBrightness(uint8_t b) { strip.setBrightness(b); } void ledsTouchFlash() { // Blocking 3x red blink (~300ms total) for (int flash = 0; flash < 3; flash++) { for (int i = 0; i < NUM_LEDS; i++) strip.setPixelColor(i, strip.Color(255, 0, 0)); strip.show(); delay(50); for (int i = 0; i < NUM_LEDS; i++) strip.setPixelColor(i, 0); strip.show(); delay(50); } } void ledsUpdate() { GameData& gd = gameGetData(); switch (gd.state) { case STATE_IDLE: animIdle(); break; case STATE_COUNTDOWN: animCountdown(); break; case STATE_PLAYING: animPlaying(); break; case STATE_GAME_OVER: if (gd.won) animGameOverWin(); else animGameOverLose(); break; } }