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>
133 lines
3.9 KiB
C++
133 lines
3.9 KiB
C++
#include "led_effects.h"
|
|
#include "config.h"
|
|
#include "game_logic.h"
|
|
#include <Adafruit_NeoPixel.h>
|
|
|
|
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;
|
|
}
|
|
}
|