From 1deb022d39ba021d6d5b788ef658503809d401d0 Mon Sep 17 00:00:00 2001 From: whywaita Date: Thu, 8 Jan 2026 20:36:46 +0000 Subject: [PATCH] Refactor shot clock state handling --- src/main.cpp | 284 ++++++++++++++++++++++++++------------------------- 1 file changed, 146 insertions(+), 138 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index a8a7f6d..7867ff1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,26 +1,14 @@ #include -#include - -// Global variables -Preferences preferences; -const int DEFAULT_TIME = 60; // Constant default time -int defaultTime = DEFAULT_TIME; -int currentTime = defaultTime; -bool isRunning = false; -bool isTimeUp = false; -bool flashState = false; // For background flashing effect -unsigned long lastFlashTime = 0; // For controlling flash timing - -// Screen layout constants -const int FONT_SIZE = 3; -const int TIME_X = 160; // Center X for landscape mode (320/2) -const int TIME_Y = 100; // Slightly above center for better layout with buttons -const int BUTTON_HEIGHT = 50; -const int BUTTON_WIDTH = 120; // Increased width since we only have 2 buttons now -const int BUTTON_Y = 180; // Moved up slightly for better layout -const int FLASH_INTERVAL = 500; // Flash interval in milliseconds - -// Button position definitions + +constexpr int DEFAULT_TIME = 60; +constexpr int FONT_SIZE = 3; +constexpr int TIME_X = 160; +constexpr int TIME_Y = 100; +constexpr int BUTTON_HEIGHT = 50; +constexpr int BUTTON_WIDTH = 120; +constexpr int BUTTON_Y = 180; +constexpr int FLASH_INTERVAL = 500; // milliseconds + struct Button { int x; int y; @@ -30,108 +18,149 @@ struct Button { uint16_t color; }; -// Adjusted button positions for landscape mode with proper spacing +struct ShotClock { + int remaining = DEFAULT_TIME; + bool running = false; + bool timeUp = false; + bool flashState = false; + bool alarmPlayed = false; + unsigned long lastSecondTick = 0; + unsigned long lastFlashToggle = 0; + + void reset() { + remaining = DEFAULT_TIME; + running = false; + timeUp = false; + flashState = false; + alarmPlayed = false; + const unsigned long now = millis(); + lastSecondTick = now; + lastFlashToggle = now; + } + + void start() { + if (timeUp) { + return; + } + if (remaining <= 0) { + remaining = DEFAULT_TIME; + } + running = true; + lastSecondTick = millis(); + } + + void stop() { + running = false; + } + + bool update() { + const unsigned long now = millis(); + bool changed = false; + + if (running && !timeUp) { + const unsigned long elapsed = now - lastSecondTick; + if (elapsed >= 1000) { + const int secondsPassed = elapsed / 1000; + lastSecondTick += secondsPassed * 1000; + + if (secondsPassed > 0) { + remaining = (remaining > secondsPassed) ? remaining - secondsPassed : 0; + changed = true; + } + + if (remaining == 0) { + setTimeUp(now); + changed = true; + } + } + } + + if (timeUp && (now - lastFlashToggle) >= FLASH_INTERVAL) { + flashState = !flashState; + lastFlashToggle = now; + changed = true; + } + + return changed; + } + + private: + void setTimeUp(unsigned long now) { + running = false; + timeUp = true; + flashState = true; + lastFlashToggle = now; + if (!alarmPlayed) { + M5.Speaker.tone(1000, 1000); + alarmPlayed = true; + } + } +}; + Button startButton = {20, BUTTON_Y, BUTTON_WIDTH, BUTTON_HEIGHT, "START", GREEN}; Button stopButton = {180, BUTTON_Y, BUTTON_WIDTH, BUTTON_HEIGHT, "STOP", RED}; -Button resetButton = {180, BUTTON_Y, BUTTON_WIDTH, BUTTON_HEIGHT, "RESET", BLUE}; // Same position as STOP button +Button resetButton = {180, BUTTON_Y, BUTTON_WIDTH, BUTTON_HEIGHT, "RESET", BLUE}; + +ShotClock shotClock; -// Forward declarations void updateDisplay(); void drawButtons(); +void drawButton(const Button& btn, const char* label); void handleTouch(const m5::touch_detail_t& touch); bool isInButton(const m5::touch_detail_t& touch, const Button& btn); -void resetTimer(); void setup() { - // Initialize M5Stack auto cfg = M5.config(); - cfg.internal_imu = false; // Disable IMU as it's not needed - cfg.internal_rtc = false; // Disable RTC as it's not needed - cfg.internal_mic = false; // Disable microphone as it's not needed - cfg.internal_spk = true; // Enable speaker for buzzer + cfg.internal_imu = false; + cfg.internal_rtc = false; + cfg.internal_mic = false; + cfg.internal_spk = true; M5.begin(cfg); - // Initialize display - M5.Display.setRotation(1); // Set rotation so buttons are at the bottom + M5.Display.setRotation(1); M5.Display.setTextSize(FONT_SIZE); - M5.Display.setTextColor(WHITE, BLACK); M5.Display.setTextDatum(MC_DATUM); - - // Clear and initialize preferences - preferences.begin("shotclock", false); - preferences.clear(); // Clear any stored preferences - defaultTime = DEFAULT_TIME; - currentTime = defaultTime; - - // Initialize display + + shotClock.reset(); updateDisplay(); - drawButtons(); } void loop() { M5.update(); - - auto t = M5.Touch.getDetail(); - if (t.isPressed()) { - handleTouch(t); + + auto touch = M5.Touch.getDetail(); + if (touch.isPressed()) { + handleTouch(touch); } - - if (isRunning && !isTimeUp) { - currentTime--; - if (currentTime <= 0) { - currentTime = 0; - isTimeUp = true; - isRunning = false; - M5.Speaker.tone(1000, 1000); // Time up sound - lastFlashTime = millis(); // Initialize flash timing - flashState = true; // Start with red background - } + + if (shotClock.update()) { updateDisplay(); } - - // Handle background flashing when time is up - if (isTimeUp) { - unsigned long currentTime = millis(); - if (currentTime - lastFlashTime >= FLASH_INTERVAL) { - flashState = !flashState; // Toggle flash state - lastFlashTime = currentTime; - updateDisplay(); - } - } - - delay(1000); // Screen update interval -} -void resetTimer() { - currentTime = defaultTime; - isTimeUp = false; - isRunning = false; - flashState = false; - updateDisplay(); + delay(10); } void handleTouch(const m5::touch_detail_t& touch) { - // Start button - if (isInButton(touch, startButton) && !isRunning) { - isRunning = true; - isTimeUp = false; - if (currentTime == 0) { - currentTime = defaultTime; // Reset time if it was at 0 + if (isInButton(touch, startButton) && !shotClock.running) { + if (shotClock.timeUp) { + shotClock.reset(); } - updateDisplay(); // Immediately update display to clear flashing + shotClock.start(); + updateDisplay(); + return; } - - // Stop/Reset button - if (isTimeUp) { - // Act as Reset button when time is up + + if (shotClock.timeUp) { if (isInButton(touch, resetButton)) { - resetTimer(); - } - } else { - // Act as Stop button during normal operation - if (isInButton(touch, stopButton) && isRunning) { - isRunning = false; + shotClock.reset(); + updateDisplay(); } + return; + } + + if (isInButton(touch, stopButton) && shotClock.running) { + shotClock.stop(); + updateDisplay(); } } @@ -141,56 +170,35 @@ bool isInButton(const m5::touch_detail_t& touch, const Button& btn) { } void updateDisplay() { - // Set background color based on time up state and flash state - if (isTimeUp && flashState) { - M5.Display.fillScreen(RED); - M5.Display.setTextColor(WHITE, RED); - } else { - M5.Display.fillScreen(BLACK); - M5.Display.setTextColor(WHITE, BLACK); - } - + const bool showFlash = shotClock.timeUp && shotClock.flashState; + const uint16_t background = showFlash ? RED : BLACK; + + M5.Display.fillScreen(background); + M5.Display.setTextColor(WHITE, background); M5.Display.setTextSize(FONT_SIZE); - - // Time display + char timeStr[10]; - sprintf(timeStr, "%02d", currentTime); + snprintf(timeStr, sizeof(timeStr), "%02d", shotClock.remaining); M5.Display.drawString(timeStr, TIME_X, TIME_Y); - - // Time up display - if (isTimeUp) { - M5.Display.setTextColor(WHITE, flashState ? RED : BLACK); + + if (shotClock.timeUp) { + M5.Display.setTextColor(WHITE, background); M5.Display.drawString("TIME UP!", TIME_X, TIME_Y - 30); } - + drawButtons(); } void drawButtons() { - // Start button - M5.Display.fillRoundRect(startButton.x, startButton.y, - startButton.w, startButton.h, 8, startButton.color); - M5.Display.setTextColor(WHITE, startButton.color); - M5.Display.drawString(startButton.label, - startButton.x + startButton.w/2, - startButton.y + startButton.h/2); - - // Stop/Reset button (changes based on state) - if (isTimeUp) { - // Draw as Reset button - M5.Display.fillRoundRect(resetButton.x, resetButton.y, - resetButton.w, resetButton.h, 8, resetButton.color); - M5.Display.setTextColor(WHITE, resetButton.color); - M5.Display.drawString(resetButton.label, - resetButton.x + resetButton.w/2, - resetButton.y + resetButton.h/2); - } else { - // Draw as Stop button - M5.Display.fillRoundRect(stopButton.x, stopButton.y, - stopButton.w, stopButton.h, 8, stopButton.color); - M5.Display.setTextColor(WHITE, stopButton.color); - M5.Display.drawString(stopButton.label, - stopButton.x + stopButton.w/2, - stopButton.y + stopButton.h/2); - } -} \ No newline at end of file + drawButton(startButton, startButton.label); + + const Button& activeButton = shotClock.timeUp ? resetButton : stopButton; + const char* label = shotClock.timeUp ? resetButton.label : stopButton.label; + drawButton(activeButton, label); +} + +void drawButton(const Button& btn, const char* label) { + M5.Display.fillRoundRect(btn.x, btn.y, btn.w, btn.h, 8, btn.color); + M5.Display.setTextColor(WHITE, btn.color); + M5.Display.drawString(label, btn.x + btn.w / 2, btn.y + btn.h / 2); +}