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>
165 lines
3.8 KiB
C++
165 lines
3.8 KiB
C++
#include "display.h"
|
|
#include "config.h"
|
|
#include "game_logic.h"
|
|
#include <Wire.h>
|
|
#include <Adafruit_GFX.h>
|
|
#include <Adafruit_SSD1306.h>
|
|
|
|
static Adafruit_SSD1306 oled(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
|
|
static unsigned long lastUpdateMs = 0;
|
|
|
|
// ── Screens ─────────────────────────────────────────────────────────
|
|
|
|
static void screenSplash() {
|
|
oled.clearDisplay();
|
|
oled.setTextColor(SSD1306_WHITE);
|
|
|
|
oled.setTextSize(2);
|
|
oled.setCursor(8, 10);
|
|
oled.print("HEISSER");
|
|
oled.setCursor(20, 30);
|
|
oled.print("DRAHT");
|
|
|
|
oled.setTextSize(1);
|
|
oled.setCursor(48, 52);
|
|
oled.print(VERSION_STR);
|
|
|
|
oled.display();
|
|
}
|
|
|
|
static void screenIdle() {
|
|
oled.clearDisplay();
|
|
oled.setTextColor(SSD1306_WHITE);
|
|
|
|
oled.setTextSize(2);
|
|
oled.setCursor(8, 5);
|
|
oled.print("HEISSER");
|
|
oled.setCursor(20, 23);
|
|
oled.print("DRAHT");
|
|
|
|
// Blinking "Druecke START"
|
|
if ((millis() / 500) % 2 == 0) {
|
|
oled.setTextSize(1);
|
|
oled.setCursor(12, 50);
|
|
oled.print("Druecke START");
|
|
}
|
|
|
|
oled.display();
|
|
}
|
|
|
|
static void screenCountdown() {
|
|
GameData& gd = gameGetData();
|
|
oled.clearDisplay();
|
|
oled.setTextColor(SSD1306_WHITE);
|
|
|
|
if (gd.countdownValue > 0) {
|
|
oled.setTextSize(4);
|
|
oled.setCursor(52, 16);
|
|
oled.print(gd.countdownValue);
|
|
} else {
|
|
oled.setTextSize(3);
|
|
oled.setCursor(36, 20);
|
|
oled.print("GO!");
|
|
}
|
|
|
|
oled.display();
|
|
}
|
|
|
|
static void screenPlaying() {
|
|
GameData& gd = gameGetData();
|
|
oled.clearDisplay();
|
|
oled.setTextColor(SSD1306_WHITE);
|
|
|
|
// Timer MM:SS
|
|
unsigned long remainMs = 0;
|
|
unsigned long totalMs = (unsigned long)GAME_DURATION_S * 1000UL;
|
|
if (gd.elapsedMs < totalMs) remainMs = totalMs - gd.elapsedMs;
|
|
uint8_t mins = (remainMs / 1000) / 60;
|
|
uint8_t secs = (remainMs / 1000) % 60;
|
|
|
|
oled.setTextSize(1);
|
|
oled.setCursor(0, 0);
|
|
oled.print("Zeit: ");
|
|
if (mins < 10) oled.print('0');
|
|
oled.print(mins);
|
|
oled.print(':');
|
|
if (secs < 10) oled.print('0');
|
|
oled.print(secs);
|
|
|
|
// Strikes
|
|
oled.setCursor(0, 14);
|
|
oled.print("Fehler: ");
|
|
oled.print(gd.strikes);
|
|
oled.print('/');
|
|
oled.print(MAX_STRIKES);
|
|
|
|
// Score (large)
|
|
oled.setTextSize(3);
|
|
oled.setCursor(10, 34);
|
|
oled.print(gd.score);
|
|
|
|
oled.display();
|
|
}
|
|
|
|
static void screenGameOver() {
|
|
GameData& gd = gameGetData();
|
|
oled.clearDisplay();
|
|
oled.setTextColor(SSD1306_WHITE);
|
|
|
|
oled.setTextSize(1);
|
|
oled.setCursor(20, 0);
|
|
oled.print(gd.won ? "GESCHAFFT!" : "GAME OVER!");
|
|
|
|
// Final score
|
|
oled.setTextSize(2);
|
|
oled.setCursor(10, 14);
|
|
oled.print("Score:");
|
|
oled.print(gd.score);
|
|
|
|
// Stats
|
|
oled.setTextSize(1);
|
|
unsigned long totalSec = gd.elapsedMs / 1000;
|
|
oled.setCursor(0, 36);
|
|
oled.print("Zeit: ");
|
|
oled.print(totalSec);
|
|
oled.print("s Fehler: ");
|
|
oled.print(gd.strikes);
|
|
|
|
// Restart hint (blinking)
|
|
if ((millis() / 600) % 2 == 0) {
|
|
oled.setCursor(8, 54);
|
|
oled.print("START = Nochmal");
|
|
}
|
|
|
|
oled.display();
|
|
}
|
|
|
|
// ── Public API ──────────────────────────────────────────────────────
|
|
|
|
void displayInit() {
|
|
Wire.begin(PIN_SDA, PIN_SCL);
|
|
|
|
if (!oled.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
|
|
Serial.println("[OLED] Init FAILED");
|
|
return;
|
|
}
|
|
|
|
Serial.println("[OLED] Init OK");
|
|
screenSplash();
|
|
delay(1500);
|
|
}
|
|
|
|
void displayUpdate() {
|
|
unsigned long now = millis();
|
|
if (now - lastUpdateMs < DISPLAY_UPDATE_MS) return;
|
|
lastUpdateMs = now;
|
|
|
|
GameData& gd = gameGetData();
|
|
switch (gd.state) {
|
|
case STATE_IDLE: screenIdle(); break;
|
|
case STATE_COUNTDOWN: screenCountdown(); break;
|
|
case STATE_PLAYING: screenPlaying(); break;
|
|
case STATE_GAME_OVER: screenGameOver(); break;
|
|
}
|
|
}
|