From 55c04d5f0ab826bb1d7de2ec5d7c390cf8d267b9 Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Fri, 30 Jan 2026 19:08:39 -0600 Subject: [PATCH 01/17] Increment score as flares complete cycle --- include/games/phase-evasion/flare-manager.h | 4 +++- include/games/phase-evasion/flare.h | 14 ++++++-------- src/games/phase-evasion/controller.cpp | 3 ++- src/games/phase-evasion/flare-manager.cpp | 7 +++++++ src/games/phase-evasion/flare.cpp | 7 +++---- 5 files changed, 21 insertions(+), 14 deletions(-) diff --git a/include/games/phase-evasion/flare-manager.h b/include/games/phase-evasion/flare-manager.h index e2db21f..f166121 100644 --- a/include/games/phase-evasion/flare-manager.h +++ b/include/games/phase-evasion/flare-manager.h @@ -1,5 +1,6 @@ #pragma once +#include "core/context-manager.h" #include "games/phase-evasion/flare.h" namespace Games::PhaseEvasion @@ -7,7 +8,7 @@ namespace Games::PhaseEvasion class FlareManager : public Engine::Timer { public: - FlareManager() = default; + FlareManager(SystemCore::ContextManager *ctx) : contextManager{ctx} {} FlareManager(const FlareManager &) = delete; FlareManager &operator=(const FlareManager &) = delete; @@ -25,6 +26,7 @@ namespace Games::PhaseEvasion void dispatch(); private: + SystemCore::ContextManager *contextManager; std::array flarePool; }; } \ No newline at end of file diff --git a/include/games/phase-evasion/flare.h b/include/games/phase-evasion/flare.h index a5ddcfe..342ee50 100644 --- a/include/games/phase-evasion/flare.h +++ b/include/games/phase-evasion/flare.h @@ -13,10 +13,7 @@ namespace Games::PhaseEvasion class Flare : public Engine::Timer { public: - Flare() : active{false}, - color{Lights::Color::WhiteSmoke}, - speed{0.0f}, - positionFloat{SystemCore::Configuration::numLeds + width} {}; + Flare() = default; static constexpr uint16_t width = 10; void updatePosition(); @@ -26,11 +23,12 @@ namespace Games::PhaseEvasion bool isActive() const { return active; } void activate(Lights::Color color, float speed); void deactivate(); + bool completedCycle = false; private: - bool active; - Lights::Color color; - float speed; - float positionFloat; + bool active = false; + Lights::Color color = Lights::Color::WhiteSmoke; + float speed = 0.0f; + float positionFloat = SystemCore::Configuration::numLeds + width; }; } \ No newline at end of file diff --git a/src/games/phase-evasion/controller.cpp b/src/games/phase-evasion/controller.cpp index 66ddbfe..dfb4e57 100644 --- a/src/games/phase-evasion/controller.cpp +++ b/src/games/phase-evasion/controller.cpp @@ -4,7 +4,8 @@ namespace Games::PhaseEvasion { Controller::Controller(SystemCore::ContextManager *ctx) : contextManager{ctx}, - player{ctx} + player{ctx}, + flareManager{ctx} { state = contextManager->stateManager.getPhaseEvasionGameState(); state.reset(); diff --git a/src/games/phase-evasion/flare-manager.cpp b/src/games/phase-evasion/flare-manager.cpp index b30a3a4..1d6b27f 100644 --- a/src/games/phase-evasion/flare-manager.cpp +++ b/src/games/phase-evasion/flare-manager.cpp @@ -28,6 +28,13 @@ namespace Games::PhaseEvasion continue; flare.updatePosition(); + + if (flare.completedCycle) + { + contextManager->stateManager.getPhaseEvasionGameState().spectersDodged++; + contextManager->stateManager.displayShouldUpdate = true; + flare.completedCycle = false; + } } } diff --git a/src/games/phase-evasion/flare.cpp b/src/games/phase-evasion/flare.cpp index 8dae6a2..132a8a6 100644 --- a/src/games/phase-evasion/flare.cpp +++ b/src/games/phase-evasion/flare.cpp @@ -4,20 +4,19 @@ namespace Games::PhaseEvasion { void Flare::updatePosition() { + positionFloat -= speed; if (positionFloat <= 0.0f) { active = false; color = Lights::Color::WhiteSmoke; - } - else - { - positionFloat -= speed; + completedCycle = true; } } void Flare::activate(Lights::Color _color, float _speed) { active = true; + completedCycle = false; color = _color; speed = _speed; positionFloat = static_cast(SystemCore::Configuration::numLeds + width); From 0f8c74e2b234f374200edaf18abe2c773f7b380b Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Sat, 31 Jan 2026 11:01:44 -0600 Subject: [PATCH 02/17] Consistent nomenclature --- include/games/phase-evasion/state.h | 6 +++--- src/display/display.cpp | 4 ++-- src/games/phase-evasion/flare-manager.cpp | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/games/phase-evasion/state.h b/include/games/phase-evasion/state.h index 4c9778c..51de1cc 100644 --- a/include/games/phase-evasion/state.h +++ b/include/games/phase-evasion/state.h @@ -14,10 +14,10 @@ namespace Games::PhaseEvasion class GameState { public: - GameState() : highScore{0}, spectersDodged{0} {} + GameState() : highScore{0}, flaresEvaded{0} {} uint16_t highScore; - uint16_t spectersDodged; + uint16_t flaresEvaded; Actions current = Actions::Startup; - void reset() { highScore = spectersDodged = 0; } + void reset() { highScore = flaresEvaded = 0; } }; } \ No newline at end of file diff --git a/src/display/display.cpp b/src/display/display.cpp index 59a53b3..8b971fd 100644 --- a/src/display/display.cpp +++ b/src/display/display.cpp @@ -189,8 +189,8 @@ namespace Display drawHeader("Phase Evasion"); display.setCursor(0, 16); - display.print("Specters Dodged: "); - display.print(contextManager->stateManager.getPhaseEvasionGameState().spectersDodged); + display.print("Flares Evaded: "); + display.print(contextManager->stateManager.getPhaseEvasionGameState().flaresEvaded); display.setCursor(0, 24); display.print("High Score: -"); diff --git a/src/games/phase-evasion/flare-manager.cpp b/src/games/phase-evasion/flare-manager.cpp index 1d6b27f..bc7719e 100644 --- a/src/games/phase-evasion/flare-manager.cpp +++ b/src/games/phase-evasion/flare-manager.cpp @@ -31,7 +31,7 @@ namespace Games::PhaseEvasion if (flare.completedCycle) { - contextManager->stateManager.getPhaseEvasionGameState().spectersDodged++; + contextManager->stateManager.getPhaseEvasionGameState().flaresEvaded++; contextManager->stateManager.displayShouldUpdate = true; flare.completedCycle = false; } From b43bb6e02fa7bd0fc64fcd2a540050536e02fb92 Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Sat, 31 Jan 2026 11:38:40 -0600 Subject: [PATCH 03/17] Fine tune game start settings --- include/games/phase-evasion/controller.h | 3 +++ include/games/phase-evasion/flare-manager.h | 2 +- src/games/phase-evasion/controller.cpp | 7 ++++--- src/games/phase-evasion/flare-manager.cpp | 4 ++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/include/games/phase-evasion/controller.h b/include/games/phase-evasion/controller.h index 083a9e6..59c226f 100644 --- a/include/games/phase-evasion/controller.h +++ b/include/games/phase-evasion/controller.h @@ -21,6 +21,9 @@ namespace Games::PhaseEvasion GameState &state = contextManager->stateManager.getPhaseEvasionGameState(); Player player; FlareManager flareManager; + uint32_t interval = 2000; + uint32_t gap = 2000; + float speed = 0.4f; void getUpdates(); void renderUserColor(); diff --git a/include/games/phase-evasion/flare-manager.h b/include/games/phase-evasion/flare-manager.h index f166121..917d51e 100644 --- a/include/games/phase-evasion/flare-manager.h +++ b/include/games/phase-evasion/flare-manager.h @@ -23,7 +23,7 @@ namespace Games::PhaseEvasion const size_t size() const; void updatePositions(); - void dispatch(); + void dispatch(float speed); private: SystemCore::ContextManager *contextManager; diff --git a/src/games/phase-evasion/controller.cpp b/src/games/phase-evasion/controller.cpp index dfb4e57..1c057ab 100644 --- a/src/games/phase-evasion/controller.cpp +++ b/src/games/phase-evasion/controller.cpp @@ -21,7 +21,7 @@ namespace Games::PhaseEvasion if (isReady()) { state.current = Actions::ActiveGame; - flareManager.dispatch(); + flareManager.dispatch(speed); log("Starting new game."); wait(1000); } @@ -94,8 +94,9 @@ namespace Games::PhaseEvasion { if (isReady()) { - flareManager.dispatch(); - wait(1000); + flareManager.dispatch(speed); + uint32_t timeDelay = (esp_random() % interval) + gap; + wait(timeDelay); } } } \ No newline at end of file diff --git a/src/games/phase-evasion/flare-manager.cpp b/src/games/phase-evasion/flare-manager.cpp index bc7719e..493170e 100644 --- a/src/games/phase-evasion/flare-manager.cpp +++ b/src/games/phase-evasion/flare-manager.cpp @@ -4,7 +4,7 @@ namespace Games::PhaseEvasion { - void FlareManager::dispatch() + void FlareManager::dispatch(float speed) { auto flare = flarePool.begin(); while (flare != flarePool.end()) @@ -12,7 +12,7 @@ namespace Games::PhaseEvasion if (!flare->isActive()) { uint16_t colorIndex = static_cast(esp_random()) % arraySize(Lights::colorPalette); - flare->activate(Lights::colorPalette[colorIndex], 0.75); + flare->activate(Lights::colorPalette[colorIndex], speed); return; } ++flare; From b99bda0f8c7c101f6e368a8a435679ae17755971 Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Sun, 1 Feb 2026 20:07:02 -0600 Subject: [PATCH 04/17] Remove hiccup at start --- include/games/phase-evasion/controller.h | 2 +- src/games/phase-evasion/controller.cpp | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/include/games/phase-evasion/controller.h b/include/games/phase-evasion/controller.h index 59c226f..d68234c 100644 --- a/include/games/phase-evasion/controller.h +++ b/include/games/phase-evasion/controller.h @@ -22,7 +22,7 @@ namespace Games::PhaseEvasion Player player; FlareManager flareManager; uint32_t interval = 2000; - uint32_t gap = 2000; + uint32_t gap = 1500; float speed = 0.4f; void getUpdates(); diff --git a/src/games/phase-evasion/controller.cpp b/src/games/phase-evasion/controller.cpp index 1c057ab..d2e7bd7 100644 --- a/src/games/phase-evasion/controller.cpp +++ b/src/games/phase-evasion/controller.cpp @@ -21,9 +21,7 @@ namespace Games::PhaseEvasion if (isReady()) { state.current = Actions::ActiveGame; - flareManager.dispatch(speed); log("Starting new game."); - wait(1000); } break; case Actions::ActiveGame: From a5359779d3ac7682bbb46591680a1d4d6c659916 Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Mon, 2 Feb 2026 21:18:27 -0600 Subject: [PATCH 05/17] Taper tail of flare --- include/games/phase-evasion/controller.h | 3 ++- include/games/phase-evasion/player.h | 4 ++-- src/games/phase-evasion/controller.cpp | 18 ++++++++++++------ 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/include/games/phase-evasion/controller.h b/include/games/phase-evasion/controller.h index d68234c..29ebcf9 100644 --- a/include/games/phase-evasion/controller.h +++ b/include/games/phase-evasion/controller.h @@ -14,7 +14,8 @@ namespace Games::PhaseEvasion public: Controller(SystemCore::ContextManager *ctx); void nextEvent() override; - static constexpr uint16_t playerClearance = 20; + static constexpr uint16_t playerClearance = 25; + static constexpr uint16_t playerWidth = 5; private: SystemCore::ContextManager *contextManager; diff --git a/include/games/phase-evasion/player.h b/include/games/phase-evasion/player.h index 830fe91..d871c07 100644 --- a/include/games/phase-evasion/player.h +++ b/include/games/phase-evasion/player.h @@ -8,10 +8,10 @@ namespace Games::PhaseEvasion class Player : public ::Player::Player { public: - Player(SystemCore::ContextManager *ctx) : contextManager{ctx}, ::Player::Player{ctx} {}; + Player(SystemCore::ContextManager *ctx, uint16_t w) : ::Player::Player{ctx}, contextManager{ctx}, width{w} {}; void checkColorChangeRequest(); Lights::Color getColor() { return currentColor; } - static constexpr uint16_t width = 10; + const uint16_t width; private: SystemCore::ContextManager *contextManager; diff --git a/src/games/phase-evasion/controller.cpp b/src/games/phase-evasion/controller.cpp index d2e7bd7..826082d 100644 --- a/src/games/phase-evasion/controller.cpp +++ b/src/games/phase-evasion/controller.cpp @@ -4,7 +4,7 @@ namespace Games::PhaseEvasion { Controller::Controller(SystemCore::ContextManager *ctx) : contextManager{ctx}, - player{ctx}, + player{ctx, playerWidth}, flareManager{ctx} { state = contextManager->stateManager.getPhaseEvasionGameState(); @@ -61,12 +61,15 @@ namespace Games::PhaseEvasion if (!flare.isActive()) continue; - uint16_t start = std::max(flare.getPosition() - flare.width, 0); - uint16_t end = std::min(flare.getPosition(), contextManager->config.numLeds); + uint16_t flareHead = std::max(flare.getPosition() - flare.width, 0); + uint16_t flareTail = std::min(flare.getPosition(), contextManager->config.numLeds); - for (uint16_t i = start; i < end; ++i) + for (uint16_t i = flareHead; i < flareTail; ++i) { - contextManager->leds.buffer[i] = flare.getColor(); + uint16_t distance = i - flareHead; + double attenuation = std::clamp(1.0 - 0.085 * static_cast(distance), 0.0, 1.0); + + contextManager->leds.buffer[i] = flare.getColor() * attenuation; } } } @@ -80,8 +83,11 @@ namespace Games::PhaseEvasion uint16_t start = std::max(flare.getPosition() - flare.width, 0); uint16_t end = std::min(flare.getPosition(), contextManager->config.numLeds); + bool isUnmatchingColor = player.getColor() != flare.getColor(); + bool hasEnteredRegion = start <= playerClearance + player.width; + bool hasNotExitedRegion = end >= playerClearance; - if (start <= playerClearance + player.width && end >= playerClearance && player.getColor() != flare.getColor()) + if (isUnmatchingColor && hasEnteredRegion && hasNotExitedRegion) { state.current = Actions::GameOver; } From 2a20bed03dc938704f4abadb9d5bcaf7a6b4ab9f Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Mon, 2 Feb 2026 21:52:52 -0600 Subject: [PATCH 06/17] Centralize available buttons source --- include/games/phase-evasion/player.h | 5 ----- include/games/recall/controller.h | 7 ++----- include/player/player.h | 7 +++++++ src/games/phase-evasion/player.cpp | 2 +- src/games/recall/controller.cpp | 10 +++++----- 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/include/games/phase-evasion/player.h b/include/games/phase-evasion/player.h index d871c07..af8af2d 100644 --- a/include/games/phase-evasion/player.h +++ b/include/games/phase-evasion/player.h @@ -16,10 +16,5 @@ namespace Games::PhaseEvasion private: SystemCore::ContextManager *contextManager; Lights::Color currentColor; - static constexpr ::Player::ControllerButton availableGameplayButtons[] = { - ::Player::ControllerButton::Cross, - ::Player::ControllerButton::Square, - ::Player::ControllerButton::Triangle, - ::Player::ControllerButton::Circle}; }; } \ No newline at end of file diff --git a/include/games/recall/controller.h b/include/games/recall/controller.h index 81c4777..2a17e33 100644 --- a/include/games/recall/controller.h +++ b/include/games/recall/controller.h @@ -5,6 +5,7 @@ #include "engine/timer.h" #include "lights/color.h" #include "lights/color-code.h" +#include "player/player.h" namespace Games::Recall { @@ -21,11 +22,7 @@ namespace Games::Recall uint16_t gameplaySpeedIlluminated = 500; uint16_t gameplaySpeedPaused = gameplaySpeedIlluminated / 6; static constexpr uint16_t maxRound = 1000; - static constexpr Player::ControllerButton availableGameplayButtons[] = { - Player::ControllerButton::Cross, - Player::ControllerButton::Square, - Player::ControllerButton::Triangle, - Player::ControllerButton::Circle}; + static constexpr auto &availableGameplayButtons = ::Player::Player::availableGameplayButtons; uint16_t sequenceIndex = 0; Player::ControllerButton gameplayColors[maxRound]; float gameOverLedPhaseShift = 0.0f; diff --git a/include/player/player.h b/include/player/player.h index 6ded16e..f05eeff 100644 --- a/include/player/player.h +++ b/include/player/player.h @@ -10,10 +10,17 @@ namespace Player Player(SystemCore::ContextManager *ctx) : contextManager{ctx} {}; void move(const int distance, const float speed); uint16_t getPosition() { return position; } + static constexpr ControllerButton availableGameplayButtons[] = { + ControllerButton::Cross, + ControllerButton::Square, + ControllerButton::Triangle, + ControllerButton::Circle}; protected: SystemCore::ContextManager *contextManager; uint16_t position = 0; float positionPrecise = 0.0f; }; + + inline constexpr auto &availableGameplayButtons = Player::Player::availableGameplayButtons; } \ No newline at end of file diff --git a/src/games/phase-evasion/player.cpp b/src/games/phase-evasion/player.cpp index 6ee4599..735f543 100644 --- a/src/games/phase-evasion/player.cpp +++ b/src/games/phase-evasion/player.cpp @@ -5,7 +5,7 @@ namespace Games::PhaseEvasion { void Player::checkColorChangeRequest() { - for (auto button : availableGameplayButtons) + for (auto button : Player::availableGameplayButtons) { if (contextManager->controller.isDown(button)) { diff --git a/src/games/recall/controller.cpp b/src/games/recall/controller.cpp index 55b16ed..324a760 100644 --- a/src/games/recall/controller.cpp +++ b/src/games/recall/controller.cpp @@ -20,9 +20,9 @@ namespace Games::Recall for (uint16_t i = 0; i < maxRound; ++i) { // for testing - // gameplayColors[i] = static_cast(i % arraySize(availableGameplayButtons)); + // gameplayColors[i] = static_cast(i % arraySize(Controller::availableGameplayButtons)); - uint16_t colorIndex = static_cast(esp_random()) % arraySize(availableGameplayButtons); + uint16_t colorIndex = static_cast(esp_random()) % arraySize(Controller::availableGameplayButtons); gameplayColors[i] = static_cast(colorIndex); } auto button = gameplayColors[0]; @@ -142,7 +142,7 @@ namespace Games::Recall void Controller::evaluateUserButton(Player::ControllerButton expectedButton) { - for (auto button : availableGameplayButtons) + for (auto button : Controller::availableGameplayButtons) { if (contextManager->controller.wasPressedAndReleased(button)) { @@ -166,7 +166,7 @@ namespace Games::Recall static uint32_t lastLightTime = 0; static int pressedButtonIndex = -1; bool buttonPressed = false; - for (uint16_t i = 0; i < arraySize(availableGameplayButtons); ++i) + for (uint16_t i = 0; i < arraySize(Controller::availableGameplayButtons); ++i) { auto btn = static_cast(i); if (contextManager->controller.isDown(btn)) @@ -181,7 +181,7 @@ namespace Games::Recall bool keepLit = ((millis() - lastLightTime) < gameplaySpeedPaused); if (buttonPressed || keepLit) { - if (pressedButtonIndex >= 0 && pressedButtonIndex < static_cast(arraySize(availableGameplayButtons))) + if (pressedButtonIndex >= 0 && pressedButtonIndex < static_cast(arraySize(Controller::availableGameplayButtons))) { auto boundaries = directionBoundaries(static_cast(pressedButtonIndex)); for (uint16_t i = boundaries.first; i <= boundaries.second; ++i) From 1b255a42e7f68e7e54d693d969b6ddf430a23765 Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Mon, 2 Feb 2026 22:58:35 -0600 Subject: [PATCH 07/17] Setup quick replay when game is over --- include/games/phase-evasion/controller.h | 1 + include/games/phase-evasion/flare-manager.h | 1 + include/games/phase-evasion/flare.h | 11 +++++----- src/games/phase-evasion/controller.cpp | 23 +++++++++++++++++---- src/games/phase-evasion/flare-manager.cpp | 8 +++++++ src/games/phase-evasion/flare.cpp | 8 +++++++ 6 files changed, 43 insertions(+), 9 deletions(-) diff --git a/include/games/phase-evasion/controller.h b/include/games/phase-evasion/controller.h index 29ebcf9..eb8af54 100644 --- a/include/games/phase-evasion/controller.h +++ b/include/games/phase-evasion/controller.h @@ -31,5 +31,6 @@ namespace Games::PhaseEvasion void renderFlare(); void checkCollision(); void checkGrowth(); + void gameOver(); }; } \ No newline at end of file diff --git a/include/games/phase-evasion/flare-manager.h b/include/games/phase-evasion/flare-manager.h index 917d51e..db986c7 100644 --- a/include/games/phase-evasion/flare-manager.h +++ b/include/games/phase-evasion/flare-manager.h @@ -24,6 +24,7 @@ namespace Games::PhaseEvasion void updatePositions(); void dispatch(float speed); + void reset(); private: SystemCore::ContextManager *contextManager; diff --git a/include/games/phase-evasion/flare.h b/include/games/phase-evasion/flare.h index 342ee50..9bbd4a4 100644 --- a/include/games/phase-evasion/flare.h +++ b/include/games/phase-evasion/flare.h @@ -13,7 +13,7 @@ namespace Games::PhaseEvasion class Flare : public Engine::Timer { public: - Flare() = default; + Flare() { reset(); }; static constexpr uint16_t width = 10; void updatePosition(); @@ -24,11 +24,12 @@ namespace Games::PhaseEvasion void activate(Lights::Color color, float speed); void deactivate(); bool completedCycle = false; + void reset(); private: - bool active = false; - Lights::Color color = Lights::Color::WhiteSmoke; - float speed = 0.0f; - float positionFloat = SystemCore::Configuration::numLeds + width; + bool active; + Lights::Color color; + float speed; + float positionFloat; }; } \ No newline at end of file diff --git a/src/games/phase-evasion/controller.cpp b/src/games/phase-evasion/controller.cpp index 826082d..37f0545 100644 --- a/src/games/phase-evasion/controller.cpp +++ b/src/games/phase-evasion/controller.cpp @@ -1,4 +1,5 @@ #include "games/phase-evasion/controller.h" +#include "player/controller.h" #include "logger.h" namespace Games::PhaseEvasion @@ -32,10 +33,7 @@ namespace Games::PhaseEvasion renderUserColor(); break; case Actions::GameOver: - for (uint16_t i = 0; i < contextManager->config.numLeds; ++i) - { - contextManager->leds.buffer[i] = Lights::ColorCode::GameRed; - } + gameOver(); break; } } @@ -103,4 +101,21 @@ namespace Games::PhaseEvasion wait(timeDelay); } } + + void Controller::gameOver() + { + if (contextManager->controller.wasPressed(::Player::ControllerButton::Start)) + { + state.current = Actions::Startup; + state.reset(); + contextManager->stateManager.displayShouldUpdate = true; + flareManager.reset(); + return; + } + + for (uint16_t i = 0; i < contextManager->config.numLeds; ++i) + { + contextManager->leds.buffer[i] = Lights::ColorCode::GameRed; + } + } } \ No newline at end of file diff --git a/src/games/phase-evasion/flare-manager.cpp b/src/games/phase-evasion/flare-manager.cpp index 493170e..5b04f2a 100644 --- a/src/games/phase-evasion/flare-manager.cpp +++ b/src/games/phase-evasion/flare-manager.cpp @@ -43,4 +43,12 @@ namespace Games::PhaseEvasion return std::count_if(flarePool.begin(), flarePool.end(), [](const Flare &flare) { return flare.isActive(); }); } + + void FlareManager::reset() + { + for (Flare &flare : flarePool) + { + flare.reset(); + } + } } \ No newline at end of file diff --git a/src/games/phase-evasion/flare.cpp b/src/games/phase-evasion/flare.cpp index 132a8a6..2462077 100644 --- a/src/games/phase-evasion/flare.cpp +++ b/src/games/phase-evasion/flare.cpp @@ -26,4 +26,12 @@ namespace Games::PhaseEvasion { active = false; } + + void Flare::reset() + { + active = false; + color = Lights::Color::WhiteSmoke; + speed = 0.0f; + positionFloat = static_cast(SystemCore::Configuration::numLeds + width); + } } \ No newline at end of file From 2cf849f1d8311b6fd3e3df4ff941c5d89af669a7 Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Tue, 3 Feb 2026 00:28:41 -0600 Subject: [PATCH 08/17] Create wind-down breaks between rounds --- include/games/phase-evasion/controller.h | 6 +++-- include/games/phase-evasion/state.h | 1 + src/games/phase-evasion/controller.cpp | 33 +++++++++++++++++++----- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/include/games/phase-evasion/controller.h b/include/games/phase-evasion/controller.h index eb8af54..259a63f 100644 --- a/include/games/phase-evasion/controller.h +++ b/include/games/phase-evasion/controller.h @@ -16,9 +16,11 @@ namespace Games::PhaseEvasion void nextEvent() override; static constexpr uint16_t playerClearance = 25; static constexpr uint16_t playerWidth = 5; + static constexpr uint32_t intermissionDelay = 10'000; private: SystemCore::ContextManager *contextManager; + Engine::Timer intermissionTimer; GameState &state = contextManager->stateManager.getPhaseEvasionGameState(); Player player; FlareManager flareManager; @@ -27,10 +29,10 @@ namespace Games::PhaseEvasion float speed = 0.4f; void getUpdates(); - void renderUserColor(); + void renderPlayer(); void renderFlare(); void checkCollision(); - void checkGrowth(); + void orchestrateCollection(); void gameOver(); }; } \ No newline at end of file diff --git a/include/games/phase-evasion/state.h b/include/games/phase-evasion/state.h index 51de1cc..4e1467a 100644 --- a/include/games/phase-evasion/state.h +++ b/include/games/phase-evasion/state.h @@ -8,6 +8,7 @@ namespace Games::PhaseEvasion { Startup, ActiveGame, + WindDown, GameOver }; diff --git a/src/games/phase-evasion/controller.cpp b/src/games/phase-evasion/controller.cpp index 37f0545..8768eb2 100644 --- a/src/games/phase-evasion/controller.cpp +++ b/src/games/phase-evasion/controller.cpp @@ -12,6 +12,7 @@ namespace Games::PhaseEvasion state.reset(); state.current = Actions::Startup; wait(500); + intermissionTimer.wait(intermissionDelay); } void Controller::nextEvent() @@ -26,11 +27,12 @@ namespace Games::PhaseEvasion } break; case Actions::ActiveGame: + case Actions::WindDown: getUpdates(); - checkGrowth(); + orchestrateCollection(); checkCollision(); renderFlare(); - renderUserColor(); + renderPlayer(); break; case Actions::GameOver: gameOver(); @@ -44,7 +46,7 @@ namespace Games::PhaseEvasion flareManager.updatePositions(); } - void Controller::renderUserColor() + void Controller::renderPlayer() { for (uint16_t i = playerClearance; i < playerClearance + player.width; ++i) { @@ -92,13 +94,29 @@ namespace Games::PhaseEvasion } } - void Controller::checkGrowth() + void Controller::orchestrateCollection() { if (isReady()) { - flareManager.dispatch(speed); - uint32_t timeDelay = (esp_random() % interval) + gap; - wait(timeDelay); + if (intermissionTimer.isReady()) + { + state.current = Actions::WindDown; + } + + if (state.current == Actions::ActiveGame) + { + + flareManager.dispatch(speed); + uint32_t timeDelay = (esp_random() % interval) + gap; + wait(timeDelay); + } + + bool shouldStartNextRound = state.current == Actions::WindDown && flareManager.size() == 1; + if (shouldStartNextRound) + { + intermissionTimer.wait(intermissionDelay); + state.current = Actions::ActiveGame; + } } } @@ -110,6 +128,7 @@ namespace Games::PhaseEvasion state.reset(); contextManager->stateManager.displayShouldUpdate = true; flareManager.reset(); + intermissionTimer.wait(intermissionDelay); return; } From 21bc4509e2d3585c030d7b61425fb94d19f5755f Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Tue, 3 Feb 2026 01:23:26 -0600 Subject: [PATCH 09/17] Set wind down timing tweaks --- include/games/phase-evasion/controller.h | 13 ++++---- src/games/phase-evasion/controller.cpp | 39 +++++++++++++++--------- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/include/games/phase-evasion/controller.h b/include/games/phase-evasion/controller.h index 259a63f..ecd185a 100644 --- a/include/games/phase-evasion/controller.h +++ b/include/games/phase-evasion/controller.h @@ -14,19 +14,19 @@ namespace Games::PhaseEvasion public: Controller(SystemCore::ContextManager *ctx); void nextEvent() override; - static constexpr uint16_t playerClearance = 25; + static constexpr uint16_t playerOffset = 25; static constexpr uint16_t playerWidth = 5; - static constexpr uint32_t intermissionDelay = 10'000; + static constexpr uint32_t windDownLength = 30'000; private: SystemCore::ContextManager *contextManager; - Engine::Timer intermissionTimer; + Engine::Timer windDownTimer; GameState &state = contextManager->stateManager.getPhaseEvasionGameState(); Player player; FlareManager flareManager; - uint32_t interval = 2000; - uint32_t gap = 1500; - float speed = 0.4f; + float interval; + float gap; + float speed; void getUpdates(); void renderPlayer(); @@ -34,5 +34,6 @@ namespace Games::PhaseEvasion void checkCollision(); void orchestrateCollection(); void gameOver(); + void reset(); }; } \ No newline at end of file diff --git a/src/games/phase-evasion/controller.cpp b/src/games/phase-evasion/controller.cpp index 8768eb2..9c1e125 100644 --- a/src/games/phase-evasion/controller.cpp +++ b/src/games/phase-evasion/controller.cpp @@ -11,8 +11,9 @@ namespace Games::PhaseEvasion state = contextManager->stateManager.getPhaseEvasionGameState(); state.reset(); state.current = Actions::Startup; + reset(); wait(500); - intermissionTimer.wait(intermissionDelay); + windDownTimer.wait(windDownLength); } void Controller::nextEvent() @@ -48,7 +49,7 @@ namespace Games::PhaseEvasion void Controller::renderPlayer() { - for (uint16_t i = playerClearance; i < playerClearance + player.width; ++i) + for (uint16_t i = playerOffset; i < playerOffset + player.width; ++i) { contextManager->leds.buffer[i] = player.getColor(); } @@ -67,7 +68,7 @@ namespace Games::PhaseEvasion for (uint16_t i = flareHead; i < flareTail; ++i) { uint16_t distance = i - flareHead; - double attenuation = std::clamp(1.0 - 0.085 * static_cast(distance), 0.0, 1.0); + double attenuation = std::clamp(1.0 - 0.08 * static_cast(distance), 0.0, 1.0); contextManager->leds.buffer[i] = flare.getColor() * attenuation; } @@ -84,8 +85,8 @@ namespace Games::PhaseEvasion uint16_t start = std::max(flare.getPosition() - flare.width, 0); uint16_t end = std::min(flare.getPosition(), contextManager->config.numLeds); bool isUnmatchingColor = player.getColor() != flare.getColor(); - bool hasEnteredRegion = start <= playerClearance + player.width; - bool hasNotExitedRegion = end >= playerClearance; + bool hasEnteredRegion = start <= playerOffset + player.width; + bool hasNotExitedRegion = end >= playerOffset; if (isUnmatchingColor && hasEnteredRegion && hasNotExitedRegion) { @@ -98,43 +99,51 @@ namespace Games::PhaseEvasion { if (isReady()) { - if (intermissionTimer.isReady()) + if (windDownTimer.isReady()) { state.current = Actions::WindDown; } if (state.current == Actions::ActiveGame) { - flareManager.dispatch(speed); - uint32_t timeDelay = (esp_random() % interval) + gap; + uint32_t timeDelay = static_cast((esp_random() % static_cast(interval)) + gap); wait(timeDelay); } bool shouldStartNextRound = state.current == Actions::WindDown && flareManager.size() == 1; if (shouldStartNextRound) { - intermissionTimer.wait(intermissionDelay); + windDownTimer.wait(windDownLength); state.current = Actions::ActiveGame; + speed *= 1.06; + interval *= 0.9; + gap *= 0.90; } } } void Controller::gameOver() { + for (uint16_t i = 0; i < contextManager->config.numLeds; ++i) + { + contextManager->leds.buffer[i] = Lights::ColorCode::GameRed; + } + if (contextManager->controller.wasPressed(::Player::ControllerButton::Start)) { state.current = Actions::Startup; state.reset(); contextManager->stateManager.displayShouldUpdate = true; flareManager.reset(); - intermissionTimer.wait(intermissionDelay); - return; + windDownTimer.wait(windDownLength); } + } - for (uint16_t i = 0; i < contextManager->config.numLeds; ++i) - { - contextManager->leds.buffer[i] = Lights::ColorCode::GameRed; - } + void Controller::reset() + { + interval = 2000; + gap = 1500; + speed = 0.4f; } } \ No newline at end of file From 11b11633c63a13f2627e66007ddde4bb53d06d42 Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Tue, 3 Feb 2026 18:23:49 -0600 Subject: [PATCH 10/17] Add muzzle flash --- include/games/phase-evasion/controller.h | 3 +- include/games/phase-evasion/state.h | 1 + src/games/phase-evasion/controller.cpp | 44 ++++++++++++++++++++---- src/games/recall/controller.cpp | 2 +- 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/include/games/phase-evasion/controller.h b/include/games/phase-evasion/controller.h index ecd185a..741e472 100644 --- a/include/games/phase-evasion/controller.h +++ b/include/games/phase-evasion/controller.h @@ -16,7 +16,7 @@ namespace Games::PhaseEvasion void nextEvent() override; static constexpr uint16_t playerOffset = 25; static constexpr uint16_t playerWidth = 5; - static constexpr uint32_t windDownLength = 30'000; + static constexpr uint32_t windDownLength = 20'000; private: SystemCore::ContextManager *contextManager; @@ -33,6 +33,7 @@ namespace Games::PhaseEvasion void renderFlare(); void checkCollision(); void orchestrateCollection(); + void muzzleFlash(); void gameOver(); void reset(); }; diff --git a/include/games/phase-evasion/state.h b/include/games/phase-evasion/state.h index 4e1467a..df47d42 100644 --- a/include/games/phase-evasion/state.h +++ b/include/games/phase-evasion/state.h @@ -9,6 +9,7 @@ namespace Games::PhaseEvasion Startup, ActiveGame, WindDown, + MuzzleFlash, GameOver }; diff --git a/src/games/phase-evasion/controller.cpp b/src/games/phase-evasion/controller.cpp index 9c1e125..c4e4d75 100644 --- a/src/games/phase-evasion/controller.cpp +++ b/src/games/phase-evasion/controller.cpp @@ -35,7 +35,12 @@ namespace Games::PhaseEvasion renderFlare(); renderPlayer(); break; + case Actions::MuzzleFlash: + muzzleFlash(); + break; case Actions::GameOver: + renderFlare(); + renderPlayer(); gameOver(); break; } @@ -90,7 +95,8 @@ namespace Games::PhaseEvasion if (isUnmatchingColor && hasEnteredRegion && hasNotExitedRegion) { - state.current = Actions::GameOver; + state.current = Actions::MuzzleFlash; + wait(20); } } } @@ -111,30 +117,54 @@ namespace Games::PhaseEvasion wait(timeDelay); } - bool shouldStartNextRound = state.current == Actions::WindDown && flareManager.size() == 1; + bool shouldStartNextRound = state.current == Actions::WindDown && flareManager.size() == 2; if (shouldStartNextRound) { windDownTimer.wait(windDownLength); state.current = Actions::ActiveGame; - speed *= 1.06; - interval *= 0.9; - gap *= 0.90; + speed *= 1.07; + interval *= 0.85; + gap *= 0.85; } } } - void Controller::gameOver() + void Controller::muzzleFlash() { for (uint16_t i = 0; i < contextManager->config.numLeds; ++i) { - contextManager->leds.buffer[i] = Lights::ColorCode::GameRed; + contextManager->leds.buffer[i] = Lights::Color::White; + } + + if (isReady()) + { + state.current = Actions::GameOver; } + } + + void Controller::gameOver() + { + static float gameOverPhaseShift = static_cast(contextManager->leds.size() / 3.0); + + for (uint16_t i = playerOffset * 2 + player.width; i <= contextManager->leds.size(); ++i) + { + float offset = std::cos((2.0f * M_PI * i / contextManager->leds.size()) + (2.0f * M_PI * gameOverPhaseShift / contextManager->leds.size())); + float phase = offset * 127 + 128; + contextManager->leds.buffer[i] = {static_cast(std::floor(phase)), + static_cast(0), + static_cast(0)}; + } + + gameOverPhaseShift += 0.5f; + if (gameOverPhaseShift > contextManager->leds.size()) + gameOverPhaseShift = 0.0; if (contextManager->controller.wasPressed(::Player::ControllerButton::Start)) { state.current = Actions::Startup; state.reset(); contextManager->stateManager.displayShouldUpdate = true; + gameOverPhaseShift = static_cast(contextManager->leds.size() / 3.0); flareManager.reset(); windDownTimer.wait(windDownLength); } diff --git a/src/games/recall/controller.cpp b/src/games/recall/controller.cpp index 324a760..1637650 100644 --- a/src/games/recall/controller.cpp +++ b/src/games/recall/controller.cpp @@ -255,6 +255,6 @@ namespace Games::Recall gameOverLedPhaseShift += 0.5f; if (gameOverLedPhaseShift > contextManager->leds.size()) - gameOverLedPhaseShift = 0; + gameOverLedPhaseShift = 0.0f; } } \ No newline at end of file From f167d5c72bd9680a38f8c359e1c14610c81e72fb Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Wed, 4 Feb 2026 18:10:46 -0600 Subject: [PATCH 11/17] Disallow wrap around for Phase Evasion --- include/core/context-manager.h | 2 +- include/games/demo/controller.h | 3 ++- include/games/demo/player.h | 6 +----- include/games/phase-evasion/player.h | 4 +--- include/player/player.h | 11 ++++++---- src/games/demo/player.cpp | 8 ++++---- src/games/phase-evasion/controller.cpp | 27 +++++++++++++++---------- src/player/player.cpp | 28 +++++++++++++++++++------- 8 files changed, 53 insertions(+), 36 deletions(-) diff --git a/include/core/context-manager.h b/include/core/context-manager.h index acf2aec..4666899 100644 --- a/include/core/context-manager.h +++ b/include/core/context-manager.h @@ -19,7 +19,7 @@ namespace SystemCore Engine::Layer *application = nullptr; Engine::StateManager stateManager; - SystemCore::Configuration config; + Configuration config; Player::Controller controller; Lights::LedStrip leds; Display::OledDisplay display; diff --git a/include/games/demo/controller.h b/include/games/demo/controller.h index c750e7e..4107ce3 100644 --- a/include/games/demo/controller.h +++ b/include/games/demo/controller.h @@ -9,7 +9,7 @@ namespace Games::Demo class Controller : public Engine::Layer { public: - Controller(SystemCore::ContextManager *ctx) : Engine::Layer{}, contextManager{ctx}, player1{Player{ctx}}, player2{Player{ctx}} + Controller(SystemCore::ContextManager *ctx) : Engine::Layer{}, contextManager{ctx}, player1{Player{ctx, width}}, player2{Player{ctx, width}} { contextManager->stateManager.getDemoGameState().reset(); } @@ -25,5 +25,6 @@ namespace Games::Demo Player player1; Player player2; static constexpr float speed = 4.0f; + static constexpr uint16_t width = 7; }; } \ No newline at end of file diff --git a/include/games/demo/player.h b/include/games/demo/player.h index a4f4252..bf90573 100644 --- a/include/games/demo/player.h +++ b/include/games/demo/player.h @@ -8,12 +8,8 @@ namespace Games::Demo class Player : public ::Player::Player { public: - Player(SystemCore::ContextManager *ctx) : contextManager{ctx}, ::Player::Player{ctx} {}; + Player(SystemCore::ContextManager *ctx, const uint16_t width) : ::Player::Player{ctx, width} {}; void updatePlayer1LedBuffer(); void updatePlayer2LedBuffer(); - - private: - SystemCore::ContextManager *contextManager; - static constexpr uint16_t width = 7; }; } \ No newline at end of file diff --git a/include/games/phase-evasion/player.h b/include/games/phase-evasion/player.h index af8af2d..f8d8226 100644 --- a/include/games/phase-evasion/player.h +++ b/include/games/phase-evasion/player.h @@ -8,13 +8,11 @@ namespace Games::PhaseEvasion class Player : public ::Player::Player { public: - Player(SystemCore::ContextManager *ctx, uint16_t w) : ::Player::Player{ctx}, contextManager{ctx}, width{w} {}; + Player(SystemCore::ContextManager *ctx, uint16_t width) : ::Player::Player{ctx, width} {}; void checkColorChangeRequest(); Lights::Color getColor() { return currentColor; } - const uint16_t width; private: - SystemCore::ContextManager *contextManager; Lights::Color currentColor; }; } \ No newline at end of file diff --git a/include/player/player.h b/include/player/player.h index f05eeff..1049fa1 100644 --- a/include/player/player.h +++ b/include/player/player.h @@ -7,9 +7,13 @@ namespace Player class Player { public: - Player(SystemCore::ContextManager *ctx) : contextManager{ctx} {}; - void move(const int distance, const float speed); - uint16_t getPosition() { return position; } + Player(SystemCore::ContextManager *ctx, const uint16_t w) : contextManager{ctx}, width{w} {}; + + const uint16_t width; + + void move(const int distance, const float speed, const bool shouldWrap = true); + uint16_t getPosition() { return static_cast(positionPrecise); } + static constexpr ControllerButton availableGameplayButtons[] = { ControllerButton::Cross, ControllerButton::Square, @@ -18,7 +22,6 @@ namespace Player protected: SystemCore::ContextManager *contextManager; - uint16_t position = 0; float positionPrecise = 0.0f; }; diff --git a/src/games/demo/player.cpp b/src/games/demo/player.cpp index 645df10..11194b2 100644 --- a/src/games/demo/player.cpp +++ b/src/games/demo/player.cpp @@ -8,9 +8,9 @@ namespace Games::Demo void Player::updatePlayer1LedBuffer() { float center = (width + 1) / 2.0f; - for (uint16_t i = 1; i <= width; ++i) + for (uint16_t i = 0; i <= width; ++i) { - uint16_t index = (position + i) % contextManager->leds.size(); + uint16_t index = (getPosition() + i) % contextManager->leds.size(); float intensity = 1.0f - std::abs(i - center) / center; if (intensity < 0) intensity = 0; @@ -25,9 +25,9 @@ namespace Games::Demo { float center = (width + 1) / 2.0f; - for (uint16_t i = 1; i <= width; ++i) + for (uint16_t i = 0; i <= width; ++i) { - uint16_t index = (position + i) % contextManager->leds.size(); + uint16_t index = (getPosition() + i) % contextManager->leds.size(); float intensity = 1.0f - std::abs(i - center) / center; if (intensity < 0) intensity = 0; diff --git a/src/games/phase-evasion/controller.cpp b/src/games/phase-evasion/controller.cpp index c4e4d75..e7c4481 100644 --- a/src/games/phase-evasion/controller.cpp +++ b/src/games/phase-evasion/controller.cpp @@ -31,7 +31,7 @@ namespace Games::PhaseEvasion case Actions::WindDown: getUpdates(); orchestrateCollection(); - checkCollision(); + // checkCollision(); renderFlare(); renderPlayer(); break; @@ -39,9 +39,9 @@ namespace Games::PhaseEvasion muzzleFlash(); break; case Actions::GameOver: + gameOver(); renderFlare(); renderPlayer(); - gameOver(); break; } } @@ -50,13 +50,16 @@ namespace Games::PhaseEvasion { player.checkColorChangeRequest(); flareManager.updatePositions(); + auto leftInput = contextManager->controller.leftAnalog(); + player.move(leftInput.x, 1.5, false); } void Controller::renderPlayer() { - for (uint16_t i = playerOffset; i < playerOffset + player.width; ++i) + for (uint16_t i = 0; i < player.width; ++i) { - contextManager->leds.buffer[i] = player.getColor(); + uint16_t index = (player.getPosition() + i) % contextManager->leds.size(); + contextManager->leds.buffer[index] = player.getColor(); } } @@ -87,11 +90,12 @@ namespace Games::PhaseEvasion if (!flare.isActive()) continue; - uint16_t start = std::max(flare.getPosition() - flare.width, 0); - uint16_t end = std::min(flare.getPosition(), contextManager->config.numLeds); + uint16_t flareStart = std::max(flare.getPosition() - flare.width, 0); + uint16_t flareEnd = std::min(flare.getPosition(), contextManager->config.numLeds); + bool isUnmatchingColor = player.getColor() != flare.getColor(); - bool hasEnteredRegion = start <= playerOffset + player.width; - bool hasNotExitedRegion = end >= playerOffset; + bool hasEnteredRegion = flareStart <= player.getPosition() + player.width; + bool hasNotExitedRegion = flareEnd >= player.getPosition(); if (isUnmatchingColor && hasEnteredRegion && hasNotExitedRegion) { @@ -144,9 +148,9 @@ namespace Games::PhaseEvasion void Controller::gameOver() { - static float gameOverPhaseShift = static_cast(contextManager->leds.size() / 3.0); + static float gameOverPhaseShift = static_cast(player.getPosition()); - for (uint16_t i = playerOffset * 2 + player.width; i <= contextManager->leds.size(); ++i) + for (uint16_t i = 0; i <= contextManager->leds.size(); ++i) { float offset = std::cos((2.0f * M_PI * i / contextManager->leds.size()) + (2.0f * M_PI * gameOverPhaseShift / contextManager->leds.size())); float phase = offset * 127 + 128; @@ -164,7 +168,7 @@ namespace Games::PhaseEvasion state.current = Actions::Startup; state.reset(); contextManager->stateManager.displayShouldUpdate = true; - gameOverPhaseShift = static_cast(contextManager->leds.size() / 3.0); + gameOverPhaseShift = static_cast(player.getPosition()); flareManager.reset(); windDownTimer.wait(windDownLength); } @@ -175,5 +179,6 @@ namespace Games::PhaseEvasion interval = 2000; gap = 1500; speed = 0.4f; + wait(500); } } \ No newline at end of file diff --git a/src/player/player.cpp b/src/player/player.cpp index eab4260..5138603 100644 --- a/src/player/player.cpp +++ b/src/player/player.cpp @@ -2,7 +2,7 @@ namespace Player { - void Player::move(const int distance, const float speed) + void Player::move(const int distance, const float speed, const bool shouldWrap) { if (distance == 0) return; @@ -10,12 +10,26 @@ namespace Player float step = contextManager->controller.analogToSpeed(distance, speed); positionPrecise += step; - const float ledCountF = static_cast(contextManager->leds.size()); - positionPrecise = std::fmod(positionPrecise, ledCountF); + const float adjustedLedCount = static_cast(contextManager->leds.size() - width); + if (shouldWrap) + { + positionPrecise = std::fmod(positionPrecise, adjustedLedCount); - if (positionPrecise < 0.0f) - positionPrecise += ledCountF; - - position = static_cast(std::floor(positionPrecise)) % contextManager->leds.size(); + if (positionPrecise < 0.0f) + { + positionPrecise += adjustedLedCount; + } + } + else + { + if (positionPrecise >= adjustedLedCount) + { + positionPrecise = adjustedLedCount; + } + else if (positionPrecise < 0.0f) + { + positionPrecise = 0.0f; + } + } } } \ No newline at end of file From 36a6bef007043a90e8c3b159582992c0c8925458 Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Wed, 4 Feb 2026 18:16:10 -0600 Subject: [PATCH 12/17] Rename Controller->Driver to remove confusion over game controller --- include/games/demo/{controller.h => driver.h} | 4 +-- .../phase-evasion/{controller.h => driver.h} | 4 +-- .../games/recall/{controller.h => driver.h} | 4 +-- .../scenes/canvas/{controller.h => driver.h} | 4 +-- src/core/context-manager.cpp | 16 ++++----- src/games/demo/{core.cpp => driver.cpp} | 8 ++--- .../{controller.cpp => driver.cpp} | 26 +++++++------- .../recall/{controller.cpp => driver.cpp} | 36 +++++++++---------- .../canvas/{controller.cpp => driver.cpp} | 18 +++++----- 9 files changed, 60 insertions(+), 60 deletions(-) rename include/games/demo/{controller.h => driver.h} (79%) rename include/games/phase-evasion/{controller.h => driver.h} (89%) rename include/games/recall/{controller.h => driver.h} (91%) rename include/scenes/canvas/{controller.h => driver.h} (84%) rename src/games/demo/{core.cpp => driver.cpp} (85%) rename src/games/phase-evasion/{controller.cpp => driver.cpp} (88%) rename src/games/recall/{controller.cpp => driver.cpp} (88%) rename src/scenes/canvas/{controller.cpp => driver.cpp} (89%) diff --git a/include/games/demo/controller.h b/include/games/demo/driver.h similarity index 79% rename from include/games/demo/controller.h rename to include/games/demo/driver.h index 4107ce3..b71f0a3 100644 --- a/include/games/demo/controller.h +++ b/include/games/demo/driver.h @@ -6,10 +6,10 @@ namespace Games::Demo { - class Controller : public Engine::Layer + class Driver : public Engine::Layer { public: - Controller(SystemCore::ContextManager *ctx) : Engine::Layer{}, contextManager{ctx}, player1{Player{ctx, width}}, player2{Player{ctx, width}} + Driver(SystemCore::ContextManager *ctx) : Engine::Layer{}, contextManager{ctx}, player1{Player{ctx, width}}, player2{Player{ctx, width}} { contextManager->stateManager.getDemoGameState().reset(); } diff --git a/include/games/phase-evasion/controller.h b/include/games/phase-evasion/driver.h similarity index 89% rename from include/games/phase-evasion/controller.h rename to include/games/phase-evasion/driver.h index 741e472..5935b3d 100644 --- a/include/games/phase-evasion/controller.h +++ b/include/games/phase-evasion/driver.h @@ -9,10 +9,10 @@ namespace Games::PhaseEvasion { - class Controller : public Engine::Layer, private Engine::Timer + class Driver : public Engine::Layer, private Engine::Timer { public: - Controller(SystemCore::ContextManager *ctx); + Driver(SystemCore::ContextManager *ctx); void nextEvent() override; static constexpr uint16_t playerOffset = 25; static constexpr uint16_t playerWidth = 5; diff --git a/include/games/recall/controller.h b/include/games/recall/driver.h similarity index 91% rename from include/games/recall/controller.h rename to include/games/recall/driver.h index 2a17e33..3a877fe 100644 --- a/include/games/recall/controller.h +++ b/include/games/recall/driver.h @@ -9,10 +9,10 @@ namespace Games::Recall { - class Controller : public Engine::Layer, private Engine::Timer + class Driver : public Engine::Layer, private Engine::Timer { public: - Controller(SystemCore::ContextManager *ctx); + Driver(SystemCore::ContextManager *ctx); void nextEvent() override; private: diff --git a/include/scenes/canvas/controller.h b/include/scenes/canvas/driver.h similarity index 84% rename from include/scenes/canvas/controller.h rename to include/scenes/canvas/driver.h index d62998f..5e20db0 100644 --- a/include/scenes/canvas/controller.h +++ b/include/scenes/canvas/driver.h @@ -6,10 +6,10 @@ namespace Scenes::Canvas { - class Controller : public Engine::Layer, private Engine::Timer + class Driver : public Engine::Layer, private Engine::Timer { public: - Controller(SystemCore::ContextManager *ctx); + Driver(SystemCore::ContextManager *ctx); void nextEvent() override; private: diff --git a/src/core/context-manager.cpp b/src/core/context-manager.cpp index 422fdc5..bc24dd6 100644 --- a/src/core/context-manager.cpp +++ b/src/core/context-manager.cpp @@ -1,8 +1,8 @@ #include "core/context-manager.h" -#include "games/demo/controller.h" -#include "games/recall/controller.h" -#include "games/phase-evasion/controller.h" -#include "scenes/canvas/controller.h" +#include "games/demo/driver.h" +#include "games/recall/driver.h" +#include "games/phase-evasion/driver.h" +#include "scenes/canvas/driver.h" #include "logger.h" namespace SystemCore @@ -150,19 +150,19 @@ namespace SystemCore switch (stateManager.current()) { case Engine::SystemState::GameRecall: - application = new Games::Recall::Controller{this}; + application = new Games::Recall::Driver{this}; logf("Transitioning to Recall (Game)"); break; case Engine::SystemState::GamePhaseEvasion: - application = new Games::PhaseEvasion::Controller{this}; + application = new Games::PhaseEvasion::Driver{this}; logf("Transitioning to Phase Evasion (Game)"); break; case Engine::SystemState::GameDemo: - application = new Games::Demo::Controller{this}; + application = new Games::Demo::Driver{this}; logf("Transitioning to Demo (Game)"); break; case Engine::SystemState::SceneCanvas: - application = new Scenes::Canvas::Controller{this}; + application = new Scenes::Canvas::Driver{this}; logf("Transitioning to Canvas (Scene)"); break; } diff --git a/src/games/demo/core.cpp b/src/games/demo/driver.cpp similarity index 85% rename from src/games/demo/core.cpp rename to src/games/demo/driver.cpp index b5ffe14..ec8f0d4 100644 --- a/src/games/demo/core.cpp +++ b/src/games/demo/driver.cpp @@ -1,10 +1,10 @@ -#include "games/demo/controller.h" +#include "games/demo/driver.h" #include "player/controller.h" #include "logger.h" namespace Games::Demo { - void Controller::nextEvent() + void Driver::nextEvent() { if (contextManager->controller.wasPressed(::Player::ControllerButton::Cross)) { @@ -20,13 +20,13 @@ namespace Games::Demo player2.updatePlayer2LedBuffer(); } - void Controller::incrementCurrentScore() + void Driver::incrementCurrentScore() { ++contextManager->stateManager.getDemoGameState().currentScore; contextManager->stateManager.displayShouldUpdate = true; } - void Controller::incrementHighScore() + void Driver::incrementHighScore() { ++contextManager->stateManager.getDemoGameState().highScore; contextManager->stateManager.displayShouldUpdate = true; diff --git a/src/games/phase-evasion/controller.cpp b/src/games/phase-evasion/driver.cpp similarity index 88% rename from src/games/phase-evasion/controller.cpp rename to src/games/phase-evasion/driver.cpp index e7c4481..03ad5d1 100644 --- a/src/games/phase-evasion/controller.cpp +++ b/src/games/phase-evasion/driver.cpp @@ -1,12 +1,12 @@ -#include "games/phase-evasion/controller.h" +#include "games/phase-evasion/driver.h" #include "player/controller.h" #include "logger.h" namespace Games::PhaseEvasion { - Controller::Controller(SystemCore::ContextManager *ctx) : contextManager{ctx}, - player{ctx, playerWidth}, - flareManager{ctx} + Driver::Driver(SystemCore::ContextManager *ctx) : contextManager{ctx}, + player{ctx, playerWidth}, + flareManager{ctx} { state = contextManager->stateManager.getPhaseEvasionGameState(); state.reset(); @@ -16,7 +16,7 @@ namespace Games::PhaseEvasion windDownTimer.wait(windDownLength); } - void Controller::nextEvent() + void Driver::nextEvent() { switch (state.current) { @@ -46,7 +46,7 @@ namespace Games::PhaseEvasion } } - void Controller::getUpdates() + void Driver::getUpdates() { player.checkColorChangeRequest(); flareManager.updatePositions(); @@ -54,7 +54,7 @@ namespace Games::PhaseEvasion player.move(leftInput.x, 1.5, false); } - void Controller::renderPlayer() + void Driver::renderPlayer() { for (uint16_t i = 0; i < player.width; ++i) { @@ -63,7 +63,7 @@ namespace Games::PhaseEvasion } } - void Controller::renderFlare() + void Driver::renderFlare() { for (const auto &flare : flareManager) { @@ -83,7 +83,7 @@ namespace Games::PhaseEvasion } } - void Controller::checkCollision() + void Driver::checkCollision() { for (const auto &flare : flareManager) { @@ -105,7 +105,7 @@ namespace Games::PhaseEvasion } } - void Controller::orchestrateCollection() + void Driver::orchestrateCollection() { if (isReady()) { @@ -133,7 +133,7 @@ namespace Games::PhaseEvasion } } - void Controller::muzzleFlash() + void Driver::muzzleFlash() { for (uint16_t i = 0; i < contextManager->config.numLeds; ++i) { @@ -146,7 +146,7 @@ namespace Games::PhaseEvasion } } - void Controller::gameOver() + void Driver::gameOver() { static float gameOverPhaseShift = static_cast(player.getPosition()); @@ -174,7 +174,7 @@ namespace Games::PhaseEvasion } } - void Controller::reset() + void Driver::reset() { interval = 2000; gap = 1500; diff --git a/src/games/recall/controller.cpp b/src/games/recall/driver.cpp similarity index 88% rename from src/games/recall/controller.cpp rename to src/games/recall/driver.cpp index 1637650..134585b 100644 --- a/src/games/recall/controller.cpp +++ b/src/games/recall/driver.cpp @@ -1,12 +1,12 @@ #include "esp_system.h" -#include "games/recall/controller.h" +#include "games/recall/driver.h" #include "common.h" #include "logger.h" namespace Games::Recall { - Controller::Controller(SystemCore::ContextManager *ctx) : contextManager{ctx} + Driver::Driver(SystemCore::ContextManager *ctx) : contextManager{ctx} { setupGameColors(); state = contextManager->stateManager.getRecallGameState(); @@ -15,14 +15,14 @@ namespace Games::Recall wait(gameplaySpeedIlluminated); } - void Controller::setupGameColors() + void Driver::setupGameColors() { for (uint16_t i = 0; i < maxRound; ++i) { // for testing - // gameplayColors[i] = static_cast(i % arraySize(Controller::availableGameplayButtons)); + // gameplayColors[i] = static_cast(i % arraySize(Driver::availableGameplayButtons)); - uint16_t colorIndex = static_cast(esp_random()) % arraySize(Controller::availableGameplayButtons); + uint16_t colorIndex = static_cast(esp_random()) % arraySize(Driver::availableGameplayButtons); gameplayColors[i] = static_cast(colorIndex); } auto button = gameplayColors[0]; @@ -31,7 +31,7 @@ namespace Games::Recall logf(" Color=%u (%u - %u - %u)", button, color.r, color.g, color.b); } - void Controller::nextEvent() + void Driver::nextEvent() { handleUserSpeedChange(); switch (state.current) @@ -61,7 +61,7 @@ namespace Games::Recall } } - void Controller::handleUserSpeedChange() + void Driver::handleUserSpeedChange() { if (contextManager->controller.wasPressed(Player::ControllerButton::Up) || contextManager->controller.leftAnalog().y < -64) { @@ -77,7 +77,7 @@ namespace Games::Recall } } - void Controller::displayComputerPlayback() + void Driver::displayComputerPlayback() { if (isReady()) { @@ -104,7 +104,7 @@ namespace Games::Recall } } - void Controller::pauseComputerPlayback() + void Driver::pauseComputerPlayback() { if (sequenceIndex >= state.round) { @@ -124,7 +124,7 @@ namespace Games::Recall } } - void Controller::evaluateUserRecall() + void Driver::evaluateUserRecall() { if (sequenceIndex > state.round && isReady()) { @@ -140,9 +140,9 @@ namespace Games::Recall evaluateUserButton(gameplayColors[sequenceIndex]); } - void Controller::evaluateUserButton(Player::ControllerButton expectedButton) + void Driver::evaluateUserButton(Player::ControllerButton expectedButton) { - for (auto button : Controller::availableGameplayButtons) + for (auto button : Driver::availableGameplayButtons) { if (contextManager->controller.wasPressedAndReleased(button)) { @@ -161,12 +161,12 @@ namespace Games::Recall } } - void Controller::illuminateOnSelection() + void Driver::illuminateOnSelection() { static uint32_t lastLightTime = 0; static int pressedButtonIndex = -1; bool buttonPressed = false; - for (uint16_t i = 0; i < arraySize(Controller::availableGameplayButtons); ++i) + for (uint16_t i = 0; i < arraySize(Driver::availableGameplayButtons); ++i) { auto btn = static_cast(i); if (contextManager->controller.isDown(btn)) @@ -181,7 +181,7 @@ namespace Games::Recall bool keepLit = ((millis() - lastLightTime) < gameplaySpeedPaused); if (buttonPressed || keepLit) { - if (pressedButtonIndex >= 0 && pressedButtonIndex < static_cast(arraySize(Controller::availableGameplayButtons))) + if (pressedButtonIndex >= 0 && pressedButtonIndex < static_cast(arraySize(Driver::availableGameplayButtons))) { auto boundaries = directionBoundaries(static_cast(pressedButtonIndex)); for (uint16_t i = boundaries.first; i <= boundaries.second; ++i) @@ -190,7 +190,7 @@ namespace Games::Recall } } - std::pair Controller::directionBoundaries(Player::ControllerButton button) + std::pair Driver::directionBoundaries(Player::ControllerButton button) { const auto &boundary = contextManager->config.recallBoundaries; @@ -209,7 +209,7 @@ namespace Games::Recall } } - void Controller::prepareComputerPlayback() + void Driver::prepareComputerPlayback() { if (isReady()) { @@ -235,7 +235,7 @@ namespace Games::Recall successFadeawayAnimation = std::clamp(successFadeawayAnimation - 0.08, 0.0, 1.0); } - void Controller::gameOver() + void Driver::gameOver() { if (contextManager->controller.wasPressed(Player::ControllerButton::Start)) { diff --git a/src/scenes/canvas/controller.cpp b/src/scenes/canvas/driver.cpp similarity index 89% rename from src/scenes/canvas/controller.cpp rename to src/scenes/canvas/driver.cpp index cdce2a7..89f9900 100644 --- a/src/scenes/canvas/controller.cpp +++ b/src/scenes/canvas/driver.cpp @@ -1,4 +1,4 @@ -#include "scenes/canvas/controller.h" +#include "scenes/canvas/driver.h" #include "esp_system.h" #include "logger.h" #include @@ -6,12 +6,12 @@ namespace Scenes::Canvas { - Controller::Controller(SystemCore::ContextManager *ctx) : contextManager{ctx} + Driver::Driver(SystemCore::ContextManager *ctx) : contextManager{ctx} { reset(); } - void Controller::nextEvent() + void Driver::nextEvent() { checkAnalogColorChange(); checkBalanceColorRequest(); @@ -25,7 +25,7 @@ namespace Scenes::Canvas } } - void Controller::checkAnalogColorChange() + void Driver::checkAnalogColorChange() { if (contextManager->controller.leftAnalog().x > 64) { @@ -59,7 +59,7 @@ namespace Scenes::Canvas } } - void Controller::checkNewColorRequest() + void Driver::checkNewColorRequest() { if (contextManager->controller.wasPressed(Player::ControllerButton::Triangle)) { @@ -68,7 +68,7 @@ namespace Scenes::Canvas } } - void Controller::checkBalanceColorRequest() + void Driver::checkBalanceColorRequest() { if (contextManager->controller.wasPressed(Player::ControllerButton::L1)) { @@ -91,7 +91,7 @@ namespace Scenes::Canvas } } - void Controller::checkChangeOccured() + void Driver::checkChangeOccured() { if (hasChange) { @@ -102,7 +102,7 @@ namespace Scenes::Canvas } } - void Controller::checkStableControllerForDisplay() + void Driver::checkStableControllerForDisplay() { bool isMoving = abs(contextManager->controller.leftAnalog().x) > 64 || @@ -122,7 +122,7 @@ namespace Scenes::Canvas } } - void Controller::reset() + void Driver::reset() { colorHsl.hue = static_cast(esp_random() % std::numeric_limits::max()); colorHsl.saturation = static_cast((esp_random() % 64u) + 191u); From f9c5e1342f873b72a4b6c71e03e52e21e110753d Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Wed, 4 Feb 2026 19:30:56 -0600 Subject: [PATCH 13/17] System wide dependency cleanup --- include/core/context-manager.h | 2 -- include/engine/engine.h | 6 ++--- include/engine/layer.h | 1 - include/engine/state-manager.h | 12 ++++++---- include/engine/timer.h | 5 +--- include/games/demo/player.h | 4 ++-- include/games/phase-evasion/driver.h | 4 ++++ include/games/phase-evasion/gem.h | 36 ++++++++++++++++++++++++++++ include/games/phase-evasion/player.h | 4 ++-- include/games/recall/driver.h | 2 +- include/lights/led-buffer.h | 34 +++++--------------------- include/lights/led-luminance.h | 3 +-- include/lights/led-strip.h | 3 +-- include/player/controller.h | 6 +---- include/player/player.h | 9 ++++--- src/core/context-manager.cpp | 2 +- src/engine/engine.cpp | 18 +++++++------- src/games/demo/player.cpp | 4 ++-- src/games/phase-evasion/driver.cpp | 19 +++++++++------ src/games/recall/driver.cpp | 15 ++++++------ src/lights/color-hsl.cpp | 1 - src/lights/led-luminance.cpp | 2 +- src/lights/led-strip.cpp | 4 +--- src/player/controller.cpp | 15 ------------ src/player/player.cpp | 21 +++++++++++++--- src/scenes/canvas/driver.cpp | 2 +- 26 files changed, 123 insertions(+), 111 deletions(-) create mode 100644 include/games/phase-evasion/gem.h diff --git a/include/core/context-manager.h b/include/core/context-manager.h index 4666899..4d51781 100644 --- a/include/core/context-manager.h +++ b/include/core/context-manager.h @@ -2,7 +2,6 @@ #include "engine/layer.h" #include "engine/state-manager.h" -#include "core/configuration.h" #include "player/controller.h" #include "lights/led-strip.h" #include "display/display.h" @@ -19,7 +18,6 @@ namespace SystemCore Engine::Layer *application = nullptr; Engine::StateManager stateManager; - Configuration config; Player::Controller controller; Lights::LedStrip leds; Display::OledDisplay display; diff --git a/include/engine/engine.h b/include/engine/engine.h index 634bf66..4c9b742 100644 --- a/include/engine/engine.h +++ b/include/engine/engine.h @@ -8,9 +8,6 @@ namespace Engine { public: GameEngine(); - virtual ~GameEngine() = default; - GameEngine(const GameEngine &) = delete; - GameEngine &operator=(const GameEngine &) = delete; void runApplication(); void standbyControllerConnection(); @@ -18,12 +15,13 @@ namespace Engine private: SystemCore::ContextManager contextManager; uint32_t lastRender = 0; + float disconnectedLedPhaseShift = 0; + void initializeEngine(); void renderLedStrip(); void displayMainMenuSelection(); void displayGameSelection(); void displaySceneSelection(); static void displayTask(void *param); - float disconnectedLedPhaseShift = 0; }; } \ No newline at end of file diff --git a/include/engine/layer.h b/include/engine/layer.h index 3e286f5..4907316 100644 --- a/include/engine/layer.h +++ b/include/engine/layer.h @@ -5,7 +5,6 @@ namespace Engine class Layer { public: - Layer() {}; virtual void nextEvent() = 0; }; } \ No newline at end of file diff --git a/include/engine/state-manager.h b/include/engine/state-manager.h index fa116d6..5f3e67c 100644 --- a/include/engine/state-manager.h +++ b/include/engine/state-manager.h @@ -54,10 +54,12 @@ namespace Engine StateManager() : systemState{SystemState::Initialize}, userMainMenuChoice{MainMenuSelection::Games}, userGameChoice{GameSelection::Recall} {} - bool isRunning() { return systemState != SystemState::Error; } + bool displayShouldUpdate = true; bool displayIsVisible = true; + bool isRunning() const { return systemState != SystemState::Error; } + const SystemState current() const { return systemState; } void setNext(SystemState state); @@ -83,11 +85,11 @@ namespace Engine Scenes::Canvas::SceneState &getCanvasSceneState() { return canvasSceneState; } private: - SystemState systemState = SystemState::MenuHome; + SystemState systemState; - MainMenuSelection userMainMenuChoice = MainMenuSelection::Games; - GameSelection userGameChoice = GameSelection::Demo; - SceneSelection userSceneChoice = SceneSelection::Canvas; + MainMenuSelection userMainMenuChoice; + GameSelection userGameChoice; + SceneSelection userSceneChoice; Games::Recall::GameState recallGameState; Games::PhaseEvasion::GameState phaseEvasionGameState; diff --git a/include/engine/timer.h b/include/engine/timer.h index 20a9f17..ae4125d 100644 --- a/include/engine/timer.h +++ b/include/engine/timer.h @@ -7,12 +7,9 @@ namespace Engine class Timer { public: - Timer() = default; - virtual ~Timer() = default; - void wait(uint32_t futureTime); const bool isReady() const; - uint32_t nextOccurrence() { return next; } + uint32_t nextOccurrence() const { return next; } private: uint32_t next{millis()}; diff --git a/include/games/demo/player.h b/include/games/demo/player.h index bf90573..cd1b79f 100644 --- a/include/games/demo/player.h +++ b/include/games/demo/player.h @@ -5,10 +5,10 @@ namespace Games::Demo { - class Player : public ::Player::Player + class Player : public ::Player::BasePlayer { public: - Player(SystemCore::ContextManager *ctx, const uint16_t width) : ::Player::Player{ctx, width} {}; + Player(SystemCore::ContextManager *ctx, const uint16_t width) : ::Player::BasePlayer{ctx, width} {}; void updatePlayer1LedBuffer(); void updatePlayer2LedBuffer(); }; diff --git a/include/games/phase-evasion/driver.h b/include/games/phase-evasion/driver.h index 5935b3d..8321075 100644 --- a/include/games/phase-evasion/driver.h +++ b/include/games/phase-evasion/driver.h @@ -6,6 +6,7 @@ #include "games/phase-evasion/player.h" #include "games/phase-evasion/flare.h" #include "games/phase-evasion/flare-manager.h" +#include "games/phase-evasion/gem.h" namespace Games::PhaseEvasion { @@ -24,6 +25,8 @@ namespace Games::PhaseEvasion GameState &state = contextManager->stateManager.getPhaseEvasionGameState(); Player player; FlareManager flareManager; + Gem bonusGem; + float interval; float gap; float speed; @@ -31,6 +34,7 @@ namespace Games::PhaseEvasion void getUpdates(); void renderPlayer(); void renderFlare(); + void renderGem(); void checkCollision(); void orchestrateCollection(); void muzzleFlash(); diff --git a/include/games/phase-evasion/gem.h b/include/games/phase-evasion/gem.h new file mode 100644 index 0000000..a7bb03e --- /dev/null +++ b/include/games/phase-evasion/gem.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include "core/configuration.h" + +namespace Games::PhaseEvasion +{ + + class Gem + { + public: + Gem() { reset(0); } + + static constexpr uint16_t width = 3; + uint16_t getPosition() { return position; } + + void capture() { isCaptured = true; } + void reset(const uint16_t pos) + { + bool isOutsideLeftRegion = pos < (width - 1); + bool isOutsideRightRegion = pos > SystemCore::Configuration::numLeds; + if (isOutsideLeftRegion || isOutsideRightRegion) + { + return; + } + + position = pos; + isCaptured = false; + } + + private: + uint16_t position; + bool isCaptured; + }; +} \ No newline at end of file diff --git a/include/games/phase-evasion/player.h b/include/games/phase-evasion/player.h index f8d8226..5551044 100644 --- a/include/games/phase-evasion/player.h +++ b/include/games/phase-evasion/player.h @@ -5,10 +5,10 @@ namespace Games::PhaseEvasion { - class Player : public ::Player::Player + class Player : public ::Player::BasePlayer { public: - Player(SystemCore::ContextManager *ctx, uint16_t width) : ::Player::Player{ctx, width} {}; + Player(SystemCore::ContextManager *ctx, uint16_t width) : ::Player::BasePlayer{ctx, width} {}; void checkColorChangeRequest(); Lights::Color getColor() { return currentColor; } diff --git a/include/games/recall/driver.h b/include/games/recall/driver.h index 3a877fe..9f2a75e 100644 --- a/include/games/recall/driver.h +++ b/include/games/recall/driver.h @@ -22,7 +22,7 @@ namespace Games::Recall uint16_t gameplaySpeedIlluminated = 500; uint16_t gameplaySpeedPaused = gameplaySpeedIlluminated / 6; static constexpr uint16_t maxRound = 1000; - static constexpr auto &availableGameplayButtons = ::Player::Player::availableGameplayButtons; + static constexpr auto &availableGameplayButtons = ::Player::BasePlayer::availableGameplayButtons; uint16_t sequenceIndex = 0; Player::ControllerButton gameplayColors[maxRound]; float gameOverLedPhaseShift = 0.0f; diff --git a/include/lights/led-buffer.h b/include/lights/led-buffer.h index 8c3abda..67396ef 100644 --- a/include/lights/led-buffer.h +++ b/include/lights/led-buffer.h @@ -2,40 +2,19 @@ #include +#include "core/configuration.h" + namespace Lights { class LedBuffer { public: - LedBuffer(uint16_t numLeds) : _size{numLeds}, leds{new Color[numLeds]} {} + LedBuffer() : leds{new Color[SystemCore::Configuration::numLeds]} {} ~LedBuffer() { delete leds; } LedBuffer(LedBuffer &&other) = delete; LedBuffer &operator=(LedBuffer &&other) = delete; - - LedBuffer(const LedBuffer &other) : _size(other._size), leds(new Color[other._size]) - { - for (uint16_t i = 0; i < _size; ++i) - { - leds[i] = other.leds[i]; - } - } - - LedBuffer &operator=(const LedBuffer &other) - { - if (this != &other) - { - Color *newLeds = new Color[other._size]; - for (uint16_t i = 0; i < other._size; ++i) - { - newLeds[i] = other.leds[i]; - } - - delete[] leds; - leds = newLeds; - _size = other._size; - } - return *this; - } + LedBuffer(const LedBuffer &other) = delete; + LedBuffer &operator=(const LedBuffer &other) = delete; Color &operator[](uint16_t index) { return leds[index]; } const Color &operator[](uint16_t index) const { return leds[index]; } @@ -43,10 +22,9 @@ namespace Lights explicit operator Color *() { return leds; } explicit operator const Color *() const { return leds; } - uint16_t size() { return _size; } + uint16_t size() { return SystemCore::Configuration::numLeds; } private: - uint16_t _size; Color *leds; }; } \ No newline at end of file diff --git a/include/lights/led-luminance.h b/include/lights/led-luminance.h index d10a4b1..d5637b6 100644 --- a/include/lights/led-luminance.h +++ b/include/lights/led-luminance.h @@ -7,7 +7,7 @@ namespace Lights class LedLuminance { public: - LedLuminance(SystemCore::Configuration &configuration) : config{configuration} {} + LedLuminance() {} static constexpr float MAX_LED_BRIGHTNESS = 255.0f; static constexpr int MAX_ADC_READING = 4095; @@ -17,7 +17,6 @@ namespace Lights static uint8_t applyGamma(uint8_t value); private: - const SystemCore::Configuration &config; int currentLuminance = MAX_ADC_READING; }; } \ No newline at end of file diff --git a/include/lights/led-strip.h b/include/lights/led-strip.h index c7dadcd..db2ffe6 100644 --- a/include/lights/led-strip.h +++ b/include/lights/led-strip.h @@ -10,8 +10,8 @@ namespace Lights class LedStrip { public: + LedStrip(); LedBuffer buffer; - LedStrip(SystemCore::Configuration &configuration); Color *getRawColors(); static constexpr uint16_t size() { return SystemCore::Configuration::numLeds; } @@ -19,7 +19,6 @@ namespace Lights void adjustLuminance(); private: - const SystemCore::Configuration &config; LedLuminance luminance; }; } \ No newline at end of file diff --git a/include/player/controller.h b/include/player/controller.h index 3ca1285..42d7943 100644 --- a/include/player/controller.h +++ b/include/player/controller.h @@ -66,9 +66,6 @@ namespace Player void poll(); bool isDown(const ControllerButton button) const; void reset(); - float analogToSpeed(int value, float maxOutput) const; - void setResponseExponent(float exp) { responseExponent = exp; } - void setResponseBlend(float blend) { responseBlend = blend; } private: Ps3Controller controller; @@ -77,8 +74,7 @@ namespace Player bool buttonLastState[17] = {0}; bool buttonPressedEvent[17] = {0}; bool buttonReleasedEvent[17] = {0}; - float responseExponent = 2.0f; - float responseBlend = 0.35f; + static Controller *instance; bool connection = false; diff --git a/include/player/player.h b/include/player/player.h index 1049fa1..34730ef 100644 --- a/include/player/player.h +++ b/include/player/player.h @@ -4,15 +4,16 @@ namespace Player { - class Player + class BasePlayer { public: - Player(SystemCore::ContextManager *ctx, const uint16_t w) : contextManager{ctx}, width{w} {}; + BasePlayer(SystemCore::ContextManager *ctx, const uint16_t w) : contextManager{ctx}, width{w} {}; const uint16_t width; void move(const int distance, const float speed, const bool shouldWrap = true); uint16_t getPosition() { return static_cast(positionPrecise); } + float analogToSpeed(int value, float maxOutput) const; static constexpr ControllerButton availableGameplayButtons[] = { ControllerButton::Cross, @@ -23,7 +24,9 @@ namespace Player protected: SystemCore::ContextManager *contextManager; float positionPrecise = 0.0f; + float responseExponent = 2.0f; + float responseBlend = 0.35f; }; - inline constexpr auto &availableGameplayButtons = Player::Player::availableGameplayButtons; + inline constexpr auto &availableGameplayButtons = BasePlayer::BasePlayer::availableGameplayButtons; } \ No newline at end of file diff --git a/src/core/context-manager.cpp b/src/core/context-manager.cpp index bc24dd6..927bc6b 100644 --- a/src/core/context-manager.cpp +++ b/src/core/context-manager.cpp @@ -7,7 +7,7 @@ namespace SystemCore { - ContextManager::ContextManager() : leds{config}, display{this} {} + ContextManager::ContextManager() : display{this} {} ContextManager::~ContextManager() { diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 7044a89..d0700e5 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -69,11 +69,11 @@ namespace Engine void GameEngine::initializeEngine() { - contextManager.controller.begin(contextManager.config.macAddress); + contextManager.controller.begin(SystemCore::Configuration::macAddress); // If debugging, ensure serial connection is stable before setting up components #if defined(VIRTUALIZATION) || defined(DEBUG) - Serial.begin(contextManager.config.serialBaud); + Serial.begin(SystemCore::Configuration::serialBaud); contextManager.leds.reset(); renderLedStrip(); log("Connecting to computer using a serial connection for debugging."); @@ -127,14 +127,14 @@ namespace Engine return; } - for (uint16_t i = 0; i <= contextManager.leds.size(); ++i) + for (uint16_t i = 0; i <= SystemCore::Configuration::numLeds; ++i) { - float phase = std::cos((2 * M_PI * i / contextManager.leds.size()) + (2 * M_PI * disconnectedLedPhaseShift / contextManager.leds.size())) * 127 + 128; + float phase = std::cos((2 * M_PI * i / SystemCore::Configuration::numLeds) + (2 * M_PI * disconnectedLedPhaseShift / SystemCore::Configuration::numLeds)) * 127 + 128; contextManager.leds.buffer[i] = {static_cast(std::floor(phase)), 0, 0}; } disconnectedLedPhaseShift += 0.5; - if (disconnectedLedPhaseShift > contextManager.leds.size()) + if (disconnectedLedPhaseShift > SystemCore::Configuration::numLeds) disconnectedLedPhaseShift = 0; } @@ -142,7 +142,7 @@ namespace Engine { constexpr uint8_t numOfSupportedModes = static_cast(MainMenuSelection::COUNT); uint8_t option = static_cast(contextManager.stateManager.getUserMenuChoice()); - uint16_t boundaryWidth = contextManager.leds.size() / numOfSupportedModes; + uint16_t boundaryWidth = SystemCore::Configuration::numLeds / numOfSupportedModes; uint16_t boundaryStart = boundaryWidth * option; uint16_t boundaryEnd = boundaryWidth * (option + 1); double mu = (boundaryStart + boundaryEnd) / 2.0; @@ -160,7 +160,7 @@ namespace Engine { constexpr uint8_t numOfSupportedModes = static_cast(GameSelection::COUNT); uint8_t option = static_cast(contextManager.stateManager.getUserGameChoice()); - uint16_t boundaryWidth = contextManager.leds.size() / numOfSupportedModes; + uint16_t boundaryWidth = SystemCore::Configuration::numLeds / numOfSupportedModes; uint16_t boundaryStart = boundaryWidth * option; uint16_t boundaryEnd = boundaryWidth * (option + 1); double mu = (boundaryStart + boundaryEnd) / 2.0; @@ -178,7 +178,7 @@ namespace Engine { constexpr uint8_t numOfSupportedModes = static_cast(SceneSelection::COUNT); uint8_t option = static_cast(contextManager.stateManager.getUserSceneChoice()); - uint16_t boundaryWidth = contextManager.leds.size() / numOfSupportedModes; + uint16_t boundaryWidth = SystemCore::Configuration::numLeds / numOfSupportedModes; uint16_t boundaryStart = boundaryWidth * option; uint16_t boundaryEnd = boundaryWidth * (option + 1); double mu = (boundaryStart + boundaryEnd) / 2.0; @@ -201,7 +201,7 @@ namespace Engine #ifdef VIRTUALIZATION Serial.write(0xAA); // sync bytes Serial.write(0x55); - Serial.write(reinterpret_cast(contextManager.leds.getRawColors()), contextManager.leds.size() * sizeof(Lights::Color)); + Serial.write(reinterpret_cast(contextManager.leds.getRawColors()), SystemCore::Configuration::numLeds * sizeof(Lights::Color)); #endif } diff --git a/src/games/demo/player.cpp b/src/games/demo/player.cpp index 11194b2..bf9c82b 100644 --- a/src/games/demo/player.cpp +++ b/src/games/demo/player.cpp @@ -10,7 +10,7 @@ namespace Games::Demo float center = (width + 1) / 2.0f; for (uint16_t i = 0; i <= width; ++i) { - uint16_t index = (getPosition() + i) % contextManager->leds.size(); + uint16_t index = (getPosition() + i) % SystemCore::Configuration::numLeds; float intensity = 1.0f - std::abs(i - center) / center; if (intensity < 0) intensity = 0; @@ -27,7 +27,7 @@ namespace Games::Demo float center = (width + 1) / 2.0f; for (uint16_t i = 0; i <= width; ++i) { - uint16_t index = (getPosition() + i) % contextManager->leds.size(); + uint16_t index = (getPosition() + i) % SystemCore::Configuration::numLeds; float intensity = 1.0f - std::abs(i - center) / center; if (intensity < 0) intensity = 0; diff --git a/src/games/phase-evasion/driver.cpp b/src/games/phase-evasion/driver.cpp index 03ad5d1..5adaab5 100644 --- a/src/games/phase-evasion/driver.cpp +++ b/src/games/phase-evasion/driver.cpp @@ -34,6 +34,7 @@ namespace Games::PhaseEvasion // checkCollision(); renderFlare(); renderPlayer(); + renderGem(); break; case Actions::MuzzleFlash: muzzleFlash(); @@ -58,7 +59,7 @@ namespace Games::PhaseEvasion { for (uint16_t i = 0; i < player.width; ++i) { - uint16_t index = (player.getPosition() + i) % contextManager->leds.size(); + uint16_t index = (player.getPosition() + i) % SystemCore::Configuration::numLeds; contextManager->leds.buffer[index] = player.getColor(); } } @@ -71,7 +72,7 @@ namespace Games::PhaseEvasion continue; uint16_t flareHead = std::max(flare.getPosition() - flare.width, 0); - uint16_t flareTail = std::min(flare.getPosition(), contextManager->config.numLeds); + uint16_t flareTail = std::min(flare.getPosition(), SystemCore::Configuration::numLeds); for (uint16_t i = flareHead; i < flareTail; ++i) { @@ -83,6 +84,10 @@ namespace Games::PhaseEvasion } } + void Driver::renderGem() + { + } + void Driver::checkCollision() { for (const auto &flare : flareManager) @@ -91,7 +96,7 @@ namespace Games::PhaseEvasion continue; uint16_t flareStart = std::max(flare.getPosition() - flare.width, 0); - uint16_t flareEnd = std::min(flare.getPosition(), contextManager->config.numLeds); + uint16_t flareEnd = std::min(flare.getPosition(), SystemCore::Configuration::numLeds); bool isUnmatchingColor = player.getColor() != flare.getColor(); bool hasEnteredRegion = flareStart <= player.getPosition() + player.width; @@ -135,7 +140,7 @@ namespace Games::PhaseEvasion void Driver::muzzleFlash() { - for (uint16_t i = 0; i < contextManager->config.numLeds; ++i) + for (uint16_t i = 0; i < SystemCore::Configuration::numLeds; ++i) { contextManager->leds.buffer[i] = Lights::Color::White; } @@ -150,9 +155,9 @@ namespace Games::PhaseEvasion { static float gameOverPhaseShift = static_cast(player.getPosition()); - for (uint16_t i = 0; i <= contextManager->leds.size(); ++i) + for (uint16_t i = 0; i <= SystemCore::Configuration::numLeds; ++i) { - float offset = std::cos((2.0f * M_PI * i / contextManager->leds.size()) + (2.0f * M_PI * gameOverPhaseShift / contextManager->leds.size())); + float offset = std::cos((2.0f * M_PI * i / SystemCore::Configuration::numLeds) + (2.0f * M_PI * gameOverPhaseShift / SystemCore::Configuration::numLeds)); float phase = offset * 127 + 128; contextManager->leds.buffer[i] = {static_cast(std::floor(phase)), static_cast(0), @@ -160,7 +165,7 @@ namespace Games::PhaseEvasion } gameOverPhaseShift += 0.5f; - if (gameOverPhaseShift > contextManager->leds.size()) + if (gameOverPhaseShift > SystemCore::Configuration::numLeds) gameOverPhaseShift = 0.0; if (contextManager->controller.wasPressed(::Player::ControllerButton::Start)) diff --git a/src/games/recall/driver.cpp b/src/games/recall/driver.cpp index 134585b..b6ac0ab 100644 --- a/src/games/recall/driver.cpp +++ b/src/games/recall/driver.cpp @@ -88,11 +88,12 @@ namespace Games::Recall auto boundaries = directionBoundaries(gameplayColors[sequenceIndex]); double mu = (boundaries.first + boundaries.second) / 2.0; + const auto &recallBoundary = SystemCore::Configuration::recallBoundaries; double delta = ((gameplayColors[sequenceIndex] == Player::ControllerButton::Circle || gameplayColors[sequenceIndex] == Player::ControllerButton::Square) - ? contextManager->config.recallBoundaries[2] - contextManager->config.recallBoundaries[1] - : contextManager->config.recallBoundaries[1] - contextManager->config.recallBoundaries[0]) / + ? recallBoundary[2] - recallBoundary[1] + : recallBoundary[1] - recallBoundary[0]) / 5; for (uint16_t i = boundaries.first; i <= boundaries.second; ++i) @@ -192,7 +193,7 @@ namespace Games::Recall std::pair Driver::directionBoundaries(Player::ControllerButton button) { - const auto &boundary = contextManager->config.recallBoundaries; + const auto &boundary = SystemCore::Configuration::recallBoundaries; switch (button) { @@ -203,7 +204,7 @@ namespace Games::Recall case Player::ControllerButton::Cross: return {boundary[2], static_cast(boundary[3] - 1)}; case Player::ControllerButton::Square: - return {boundary[3], static_cast(contextManager->leds.size() - 1)}; + return {boundary[3], static_cast(SystemCore::Configuration::numLeds - 1)}; default: return {0, 0}; } @@ -247,14 +248,14 @@ namespace Games::Recall contextManager->stateManager.displayShouldUpdate = true; } - for (uint16_t i = 0; i <= contextManager->leds.size(); ++i) + for (uint16_t i = 0; i <= SystemCore::Configuration::numLeds; ++i) { - float phase = std::cos((2.0f * M_PI * i / contextManager->leds.size()) + (2.0f * M_PI * gameOverLedPhaseShift / contextManager->leds.size())) * 127 + 128; + float phase = std::cos((2.0f * M_PI * i / SystemCore::Configuration::numLeds) + (2.0f * M_PI * gameOverLedPhaseShift / SystemCore::Configuration::numLeds)) * 127 + 128; contextManager->leds.buffer[i] = {static_cast(std::floor(phase)), 0, 0}; } gameOverLedPhaseShift += 0.5f; - if (gameOverLedPhaseShift > contextManager->leds.size()) + if (gameOverLedPhaseShift > SystemCore::Configuration::numLeds) gameOverLedPhaseShift = 0.0f; } } \ No newline at end of file diff --git a/src/lights/color-hsl.cpp b/src/lights/color-hsl.cpp index 28bb52a..c0d44a2 100644 --- a/src/lights/color-hsl.cpp +++ b/src/lights/color-hsl.cpp @@ -1,5 +1,4 @@ #include -#include #include "lights/color-hsl.h" namespace Lights diff --git a/src/lights/led-luminance.cpp b/src/lights/led-luminance.cpp index b2cc65f..f1854b0 100644 --- a/src/lights/led-luminance.cpp +++ b/src/lights/led-luminance.cpp @@ -6,7 +6,7 @@ namespace Lights { void LedLuminance::adjustLuminance() { - int dialReading = analogRead(config.ledDimmerGpio); + int dialReading = analogRead(SystemCore::Configuration::ledDimmerGpio); dialReading = map(dialReading, 0, LedLuminance::MAX_ADC_READING, 0, LedLuminance::MAX_LED_BRIGHTNESS); dialReading = constrain(dialReading, 0, LedLuminance::MAX_LED_BRIGHTNESS); diff --git a/src/lights/led-strip.cpp b/src/lights/led-strip.cpp index f07fd62..3a45cea 100644 --- a/src/lights/led-strip.cpp +++ b/src/lights/led-strip.cpp @@ -2,9 +2,7 @@ namespace Lights { - LedStrip::LedStrip(SystemCore::Configuration &configuration) : config{configuration}, - buffer{configuration.numLeds}, - luminance{configuration} + LedStrip::LedStrip() { #ifdef RELEASE FastLED.addLeds(static_cast(buffer), size()); diff --git a/src/player/controller.cpp b/src/player/controller.cpp index b83f709..70e767e 100644 --- a/src/player/controller.cpp +++ b/src/player/controller.cpp @@ -102,21 +102,6 @@ namespace Player return instance->buttonLastState[idx]; } - float Controller::analogToSpeed(int value, float maxOutput) const - { - if (value == 0) - return 0.0f; - - float normalizedAnalogValue = static_cast(value) / 127.0f; - float sign = (normalizedAnalogValue > 0.0f) ? 1.0f : -1.0f; - float absoluteValueNormalized = std::abs(normalizedAnalogValue); - - float nonlinear = std::pow(absoluteValueNormalized, instance->responseExponent); - float mixed = (1.0f - instance->responseBlend) * nonlinear + instance->responseBlend * absoluteValueNormalized; - float scaled = mixed * maxOutput; - return sign * scaled; - } - void Controller::reset() { for (uint32_t i = 0; i < arraySize(buttonPressedEvent); ++i) diff --git a/src/player/player.cpp b/src/player/player.cpp index 5138603..00ccc01 100644 --- a/src/player/player.cpp +++ b/src/player/player.cpp @@ -2,15 +2,15 @@ namespace Player { - void Player::move(const int distance, const float speed, const bool shouldWrap) + void BasePlayer::move(const int distance, const float speed, const bool shouldWrap) { if (distance == 0) return; - float step = contextManager->controller.analogToSpeed(distance, speed); + float step = analogToSpeed(distance, speed); positionPrecise += step; - const float adjustedLedCount = static_cast(contextManager->leds.size() - width); + const float adjustedLedCount = static_cast(SystemCore::Configuration::numLeds - width); if (shouldWrap) { positionPrecise = std::fmod(positionPrecise, adjustedLedCount); @@ -32,4 +32,19 @@ namespace Player } } } + + float BasePlayer::analogToSpeed(int value, float maxOutput) const + { + if (value == 0) + return 0.0f; + + float normalizedAnalogValue = static_cast(value) / 127.0f; + float sign = (normalizedAnalogValue > 0.0f) ? 1.0f : -1.0f; + float absoluteValueNormalized = std::abs(normalizedAnalogValue); + + float nonlinear = std::pow(absoluteValueNormalized, responseExponent); + float mixed = (1.0f - responseBlend) * nonlinear + responseBlend * absoluteValueNormalized; + float scaled = mixed * maxOutput; + return sign * scaled; + } } \ No newline at end of file diff --git a/src/scenes/canvas/driver.cpp b/src/scenes/canvas/driver.cpp index 89f9900..b96d55d 100644 --- a/src/scenes/canvas/driver.cpp +++ b/src/scenes/canvas/driver.cpp @@ -19,7 +19,7 @@ namespace Scenes::Canvas checkChangeOccured(); checkStableControllerForDisplay(); - for (uint16_t i; i < contextManager->leds.size(); ++i) + for (uint16_t i; i < SystemCore::Configuration::numLeds; ++i) { contextManager->leds.buffer[i] = contextManager->stateManager.getCanvasSceneState().currentColor; } From b87d214d679ce6c70112af24ece0f2e60625b6b9 Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Wed, 4 Feb 2026 22:28:22 -0600 Subject: [PATCH 14/17] Add gem capture --- include/games/phase-evasion/driver.h | 5 +- include/games/phase-evasion/flare.h | 1 + include/games/phase-evasion/gem.h | 17 +++--- include/games/phase-evasion/state.h | 7 ++- include/player/player.h | 4 ++ src/display/display.cpp | 14 ++++- src/games/phase-evasion/driver.cpp | 88 ++++++++++++++++++++++------ src/games/phase-evasion/flare.cpp | 1 + 8 files changed, 103 insertions(+), 34 deletions(-) diff --git a/include/games/phase-evasion/driver.h b/include/games/phase-evasion/driver.h index 8321075..325ee62 100644 --- a/include/games/phase-evasion/driver.h +++ b/include/games/phase-evasion/driver.h @@ -25,7 +25,7 @@ namespace Games::PhaseEvasion GameState &state = contextManager->stateManager.getPhaseEvasionGameState(); Player player; FlareManager flareManager; - Gem bonusGem; + Gem gem; float interval; float gap; @@ -36,7 +36,8 @@ namespace Games::PhaseEvasion void renderFlare(); void renderGem(); void checkCollision(); - void orchestrateCollection(); + void checkGemCapture(); + void checkChallenge(); void muzzleFlash(); void gameOver(); void reset(); diff --git a/include/games/phase-evasion/flare.h b/include/games/phase-evasion/flare.h index 9bbd4a4..453c7e1 100644 --- a/include/games/phase-evasion/flare.h +++ b/include/games/phase-evasion/flare.h @@ -24,6 +24,7 @@ namespace Games::PhaseEvasion void activate(Lights::Color color, float speed); void deactivate(); bool completedCycle = false; + bool impacted = false; void reset(); private: diff --git a/include/games/phase-evasion/gem.h b/include/games/phase-evasion/gem.h index a7bb03e..2d5c2f3 100644 --- a/include/games/phase-evasion/gem.h +++ b/include/games/phase-evasion/gem.h @@ -7,30 +7,33 @@ namespace Games::PhaseEvasion { - class Gem + class Gem : public Engine::Timer { public: - Gem() { reset(0); } + Gem() : active{false} { spawn(0); } static constexpr uint16_t width = 3; uint16_t getPosition() { return position; } + bool isActive() { return active; } - void capture() { isCaptured = true; } - void reset(const uint16_t pos) + Lights::Color getColor() { return Lights::Color::NavajoWhite; } + + void capture() { active = false; } + void spawn(const uint16_t pos) { bool isOutsideLeftRegion = pos < (width - 1); - bool isOutsideRightRegion = pos > SystemCore::Configuration::numLeds; + bool isOutsideRightRegion = pos > SystemCore::Configuration::numLeds + (width - 1); if (isOutsideLeftRegion || isOutsideRightRegion) { return; } position = pos; - isCaptured = false; + active = true; } private: uint16_t position; - bool isCaptured; + bool active; }; } \ No newline at end of file diff --git a/include/games/phase-evasion/state.h b/include/games/phase-evasion/state.h index df47d42..7e31976 100644 --- a/include/games/phase-evasion/state.h +++ b/include/games/phase-evasion/state.h @@ -16,10 +16,11 @@ namespace Games::PhaseEvasion class GameState { public: - GameState() : highScore{0}, flaresEvaded{0} {} - uint16_t highScore; + GameState() : highScore{0}, flaresEvaded{0}, gemsCaptured{0} {} uint16_t flaresEvaded; + uint16_t gemsCaptured; + uint16_t highScore; Actions current = Actions::Startup; - void reset() { highScore = flaresEvaded = 0; } + void reset() { highScore = flaresEvaded = gemsCaptured = 0; } }; } \ No newline at end of file diff --git a/include/player/player.h b/include/player/player.h index 34730ef..35bf3b6 100644 --- a/include/player/player.h +++ b/include/player/player.h @@ -12,7 +12,11 @@ namespace Player const uint16_t width; void move(const int distance, const float speed, const bool shouldWrap = true); + uint16_t getPosition() { return static_cast(positionPrecise); } + void setPosition(float position) { positionPrecise = position; } + void setPosition(uint16_t position) { positionPrecise = static_cast(position); } + float analogToSpeed(int value, float maxOutput) const; static constexpr ControllerButton availableGameplayButtons[] = { diff --git a/src/display/display.cpp b/src/display/display.cpp index 8b971fd..17ea21d 100644 --- a/src/display/display.cpp +++ b/src/display/display.cpp @@ -185,14 +185,22 @@ namespace Display void OledDisplay::drawPhaseEvasionGameHud() { + const auto flaresEvaded = contextManager->stateManager.getPhaseEvasionGameState().flaresEvaded; + const auto gemsCaptured = contextManager->stateManager.getPhaseEvasionGameState().gemsCaptured; + display.clearDisplay(); drawHeader("Phase Evasion"); display.setCursor(0, 16); - display.print("Flares Evaded: "); - display.print(contextManager->stateManager.getPhaseEvasionGameState().flaresEvaded); + display.printf("Flares: %3u", flaresEvaded); + + display.setCursor(DISPLAY_WIDTH / 2 + 8, 16); + display.printf("Gems: %3u", gemsCaptured); + display.setCursor(0, 24); - display.print("High Score: -"); + display.printf(" High: %3u", flaresEvaded); + display.setCursor(DISPLAY_WIDTH / 2 + 8, 24); + display.printf(" %2u", gemsCaptured); display.display(); } diff --git a/src/games/phase-evasion/driver.cpp b/src/games/phase-evasion/driver.cpp index 5adaab5..b6cbd5c 100644 --- a/src/games/phase-evasion/driver.cpp +++ b/src/games/phase-evasion/driver.cpp @@ -13,6 +13,7 @@ namespace Games::PhaseEvasion state.current = Actions::Startup; reset(); wait(500); + gem.wait(5000); windDownTimer.wait(windDownLength); } @@ -30,11 +31,12 @@ namespace Games::PhaseEvasion case Actions::ActiveGame: case Actions::WindDown: getUpdates(); - orchestrateCollection(); - // checkCollision(); + checkChallenge(); + checkCollision(); + checkGemCapture(); renderFlare(); - renderPlayer(); renderGem(); + renderPlayer(); break; case Actions::MuzzleFlash: muzzleFlash(); @@ -42,6 +44,7 @@ namespace Games::PhaseEvasion case Actions::GameOver: gameOver(); renderFlare(); + renderGem(); renderPlayer(); break; } @@ -51,8 +54,8 @@ namespace Games::PhaseEvasion { player.checkColorChangeRequest(); flareManager.updatePositions(); - auto leftInput = contextManager->controller.leftAnalog(); - player.move(leftInput.x, 1.5, false); + auto leftAnalog = contextManager->controller.leftAnalog(); + player.move(leftAnalog.x, 1.25, false); } void Driver::renderPlayer() @@ -71,6 +74,12 @@ namespace Games::PhaseEvasion if (!flare.isActive()) continue; + if (state.current == Actions::GameOver) + { + if (!flare.impacted) + continue; + } + uint16_t flareHead = std::max(flare.getPosition() - flare.width, 0); uint16_t flareTail = std::min(flare.getPosition(), SystemCore::Configuration::numLeds); @@ -86,11 +95,21 @@ namespace Games::PhaseEvasion void Driver::renderGem() { + if (!gem.isActive()) + return; + + uint16_t gemLeft = std::max(gem.getPosition() - gem.width, 0); + uint16_t gemRight = std::min(gem.getPosition(), SystemCore::Configuration::numLeds); + + for (uint16_t i = gemLeft; i < gemRight; ++i) + { + contextManager->leds.buffer[i] = gem.getColor(); + } } void Driver::checkCollision() { - for (const auto &flare : flareManager) + for (auto &flare : flareManager) { if (!flare.isActive()) continue; @@ -104,13 +123,40 @@ namespace Games::PhaseEvasion if (isUnmatchingColor && hasEnteredRegion && hasNotExitedRegion) { + flare.impacted = true; state.current = Actions::MuzzleFlash; wait(20); } } } - void Driver::orchestrateCollection() + void Driver::checkGemCapture() + { + if (!gem.isActive() && gem.isReady()) + { + const auto randGemPosition = static_cast((esp_random() % static_cast(SystemCore::Configuration::numLeds)) + gem.width); + gem.spawn(randGemPosition); + } + + if (gem.isActive()) + { + uint16_t gemStart = std::max(gem.getPosition() - gem.width, 0); + uint16_t gemEnd = std::min(gem.getPosition(), SystemCore::Configuration::numLeds); + + bool hasEnteredRegion = gemStart <= player.getPosition() + player.width; + bool hasNotExitedRegion = gemEnd >= player.getPosition(); + + if (hasEnteredRegion && hasNotExitedRegion) + { + gem.capture(); + contextManager->stateManager.getPhaseEvasionGameState().gemsCaptured++; + contextManager->stateManager.displayShouldUpdate = true; + gem.wait(5000); + } + } + } + + void Driver::checkChallenge() { if (isReady()) { @@ -132,8 +178,8 @@ namespace Games::PhaseEvasion windDownTimer.wait(windDownLength); state.current = Actions::ActiveGame; speed *= 1.07; - interval *= 0.85; - gap *= 0.85; + interval *= 0.8; + gap *= 0.82; } } } @@ -153,18 +199,18 @@ namespace Games::PhaseEvasion void Driver::gameOver() { - static float gameOverPhaseShift = static_cast(player.getPosition()); + static float gameOverPhaseShift = static_cast(((SystemCore::Configuration::numLeds / 2) + player.getPosition()) % SystemCore::Configuration::numLeds); for (uint16_t i = 0; i <= SystemCore::Configuration::numLeds; ++i) { - float offset = std::cos((2.0f * M_PI * i / SystemCore::Configuration::numLeds) + (2.0f * M_PI * gameOverPhaseShift / SystemCore::Configuration::numLeds)); - float phase = offset * 127 + 128; + float offset = std::cos((2.0f * M_PI * i / SystemCore::Configuration::numLeds) - (2.0f * M_PI * static_cast(gameOverPhaseShift) / SystemCore::Configuration::numLeds)); + float phase = offset * 127.5 + 127.5; contextManager->leds.buffer[i] = {static_cast(std::floor(phase)), - static_cast(0), - static_cast(0)}; + static_cast(std::floor(phase) * 0.15), + static_cast(std::floor(phase) * 0.25)}; } - gameOverPhaseShift += 0.5f; + gameOverPhaseShift += 0.0f; if (gameOverPhaseShift > SystemCore::Configuration::numLeds) gameOverPhaseShift = 0.0; @@ -173,17 +219,21 @@ namespace Games::PhaseEvasion state.current = Actions::Startup; state.reset(); contextManager->stateManager.displayShouldUpdate = true; - gameOverPhaseShift = static_cast(player.getPosition()); - flareManager.reset(); + gameOverPhaseShift = static_cast(((SystemCore::Configuration::numLeds / 2) + player.getPosition()) % SystemCore::Configuration::numLeds); + reset(); windDownTimer.wait(windDownLength); } } void Driver::reset() { - interval = 2000; - gap = 1500; + interval = 2500; + gap = 1600; speed = 0.4f; + gem.capture(); + gem.wait(5000); + flareManager.reset(); + player.setPosition(static_cast(0)); wait(500); } } \ No newline at end of file diff --git a/src/games/phase-evasion/flare.cpp b/src/games/phase-evasion/flare.cpp index 2462077..0927093 100644 --- a/src/games/phase-evasion/flare.cpp +++ b/src/games/phase-evasion/flare.cpp @@ -32,6 +32,7 @@ namespace Games::PhaseEvasion active = false; color = Lights::Color::WhiteSmoke; speed = 0.0f; + impacted = false; positionFloat = static_cast(SystemCore::Configuration::numLeds + width); } } \ No newline at end of file From df5ac51fad2a20d71acf7dd674239e8e48e460b3 Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Wed, 4 Feb 2026 23:14:10 -0600 Subject: [PATCH 15/17] Focus GameOver lights on collision site --- include/games/phase-evasion/driver.h | 2 ++ src/games/phase-evasion/driver.cpp | 9 +++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/include/games/phase-evasion/driver.h b/include/games/phase-evasion/driver.h index 325ee62..da378ed 100644 --- a/include/games/phase-evasion/driver.h +++ b/include/games/phase-evasion/driver.h @@ -30,6 +30,8 @@ namespace Games::PhaseEvasion float interval; float gap; float speed; + float gameOverPhaseShift = 0.0f; + float gameOverPhaseOffset = 0.0f; void getUpdates(); void renderPlayer(); diff --git a/src/games/phase-evasion/driver.cpp b/src/games/phase-evasion/driver.cpp index b6cbd5c..1c7c526 100644 --- a/src/games/phase-evasion/driver.cpp +++ b/src/games/phase-evasion/driver.cpp @@ -125,6 +125,7 @@ namespace Games::PhaseEvasion { flare.impacted = true; state.current = Actions::MuzzleFlash; + gameOverPhaseShift = static_cast(((SystemCore::Configuration::numLeds / 2) + player.getPosition()) % SystemCore::Configuration::numLeds); wait(20); } } @@ -199,18 +200,19 @@ namespace Games::PhaseEvasion void Driver::gameOver() { - static float gameOverPhaseShift = static_cast(((SystemCore::Configuration::numLeds / 2) + player.getPosition()) % SystemCore::Configuration::numLeds); for (uint16_t i = 0; i <= SystemCore::Configuration::numLeds; ++i) { float offset = std::cos((2.0f * M_PI * i / SystemCore::Configuration::numLeds) - (2.0f * M_PI * static_cast(gameOverPhaseShift) / SystemCore::Configuration::numLeds)); float phase = offset * 127.5 + 127.5; - contextManager->leds.buffer[i] = {static_cast(std::floor(phase)), + contextManager->leds.buffer[i] = {static_cast(std::floor(phase) * 0.95), static_cast(std::floor(phase) * 0.15), static_cast(std::floor(phase) * 0.25)}; } - gameOverPhaseShift += 0.0f; + gameOverPhaseShift += std::cos(gameOverPhaseOffset) / 16.0f; + gameOverPhaseOffset += 1.0 / 32.0f; + if (gameOverPhaseShift > SystemCore::Configuration::numLeds) gameOverPhaseShift = 0.0; @@ -219,7 +221,6 @@ namespace Games::PhaseEvasion state.current = Actions::Startup; state.reset(); contextManager->stateManager.displayShouldUpdate = true; - gameOverPhaseShift = static_cast(((SystemCore::Configuration::numLeds / 2) + player.getPosition()) % SystemCore::Configuration::numLeds); reset(); windDownTimer.wait(windDownLength); } From e13f3da9a249bef55b5f933d608c3e7d7d881bcd Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Thu, 5 Feb 2026 19:05:53 -0600 Subject: [PATCH 16/17] Set timeout for gem before respawn --- README.md | 2 +- include/games/phase-evasion/driver.h | 11 ++++++-- include/games/phase-evasion/gem.h | 2 -- src/games/phase-evasion/driver.cpp | 41 +++++++++++++++++----------- 4 files changed, 35 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index c67df8b..4d39d2e 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Because LumenLab is restricted to a single dimension composed of an array of LED | **Title** | **Description** | | --- | --- | | **Recall** |*Recall* is an adaptation of Hasbro's fifty-year-old game [*Simon*](https://en.wikipedia.org/wiki/Simon_(game)), chosen for its familiarity and simplicity. Players easily recognize the format, making it a natural fit for LumenLab’s constraints. Similar to the original, the LED strip flashes a color, and the player must press the matching button on the PS3 controller. Each round adds another color to the sequence, which must be recalled in full and in order.| -| **Phase Evasion** | *Phase Evasion* is a game inspired by Google Chrome's [*Dinosaur Game*](https://en.wikipedia.org/wiki/Dinosaur_Game), replacing jumping and ducking obstacles with color-based evasion. You are a solid-colored specter pursued by incoming flares. To survive, you must phase shift to match each flare’s color and blend in to evade capture. As the game progresses, the flares accelerate, testing reflexes and timing.

*In development.* | +| **Phase Evasion** | *Phase Evasion* is a fast, colorful test of reflexes and hand-eye coordination. You are a color-shifting specter that is being pursued by colorful flares. You must match their color at the right moment to phase through them to evade. Gems are scattered throughout periodically. Quickly move over and grab them for bonus points if you dare. As the speed climbs and the screen fills with color, quick reactions and bold moves are the only way to stay in the game. | | **Light Strike** | *Light Strike* combines the spirit of Atari's [*Space Invaders*](https://en.wikipedia.org/wiki/Space_Invaders) with elements of [*Asteroids*](https://en.wikipedia.org/wiki/Asteroids_(video_game)). As a lone agent in deep space, you enounter waves of predators approaching you from directions sides at varying speeds. Moving freely across the x-axis, and your survival requires defending yourself with your laser pistol. Conserve your ammunition however, your weapon can only fire so many rounds a minute!

*In development.* | | **Reflex** | *Reflex* is a fast-moving inspired by the modern arcade game [*Pop the Lock*](https://www.baytekent.com/pop-the-lock/), testing precision and timing under pressure. A moving marker sweeps across the field toward a shifting target. Fire at the perfect moment to score a hit. Each success quickens the pace, reverses the marker's direction, and relocates the target to a new random location. Survive fifty flawless shots to win.

*In development.* | | **Spectrum** | *Spectrum* tests your perception of color and precision of control. A random RGB color appears on one end of the display, and you must use the analog stick to recreate it as closely as possible. The closer your match, the higher your score.

*In development* | diff --git a/include/games/phase-evasion/driver.h b/include/games/phase-evasion/driver.h index da378ed..0ecb6ba 100644 --- a/include/games/phase-evasion/driver.h +++ b/include/games/phase-evasion/driver.h @@ -21,15 +21,22 @@ namespace Games::PhaseEvasion private: SystemCore::ContextManager *contextManager; - Engine::Timer windDownTimer; GameState &state = contextManager->stateManager.getPhaseEvasionGameState(); + Player player; FlareManager flareManager; Gem gem; + Engine::Timer windDownTimer; + Engine::Timer gemTimeoutTimer; + float interval; float gap; float speed; + + uint32_t gemRespawnDelay = 5000; + uint32_t gemCaptureDelay = 15000; + float gameOverPhaseShift = 0.0f; float gameOverPhaseOffset = 0.0f; @@ -39,7 +46,7 @@ namespace Games::PhaseEvasion void renderGem(); void checkCollision(); void checkGemCapture(); - void checkChallenge(); + void assessDifficulty(); void muzzleFlash(); void gameOver(); void reset(); diff --git a/include/games/phase-evasion/gem.h b/include/games/phase-evasion/gem.h index 2d5c2f3..0d60774 100644 --- a/include/games/phase-evasion/gem.h +++ b/include/games/phase-evasion/gem.h @@ -24,9 +24,7 @@ namespace Games::PhaseEvasion bool isOutsideLeftRegion = pos < (width - 1); bool isOutsideRightRegion = pos > SystemCore::Configuration::numLeds + (width - 1); if (isOutsideLeftRegion || isOutsideRightRegion) - { return; - } position = pos; active = true; diff --git a/src/games/phase-evasion/driver.cpp b/src/games/phase-evasion/driver.cpp index 1c7c526..98e2e4d 100644 --- a/src/games/phase-evasion/driver.cpp +++ b/src/games/phase-evasion/driver.cpp @@ -13,7 +13,7 @@ namespace Games::PhaseEvasion state.current = Actions::Startup; reset(); wait(500); - gem.wait(5000); + gem.wait(gemRespawnDelay); windDownTimer.wait(windDownLength); } @@ -31,7 +31,7 @@ namespace Games::PhaseEvasion case Actions::ActiveGame: case Actions::WindDown: getUpdates(); - checkChallenge(); + assessDifficulty(); checkCollision(); checkGemCapture(); renderFlare(); @@ -95,7 +95,7 @@ namespace Games::PhaseEvasion void Driver::renderGem() { - if (!gem.isActive()) + if (!gem.isActive() || !gem.isReady()) return; uint16_t gemLeft = std::max(gem.getPosition() - gem.width, 0); @@ -137,27 +137,36 @@ namespace Games::PhaseEvasion { const auto randGemPosition = static_cast((esp_random() % static_cast(SystemCore::Configuration::numLeds)) + gem.width); gem.spawn(randGemPosition); + gemTimeoutTimer.wait(gemCaptureDelay); } - if (gem.isActive()) + if (gem.isActive() && gem.isReady()) { - uint16_t gemStart = std::max(gem.getPosition() - gem.width, 0); - uint16_t gemEnd = std::min(gem.getPosition(), SystemCore::Configuration::numLeds); - - bool hasEnteredRegion = gemStart <= player.getPosition() + player.width; - bool hasNotExitedRegion = gemEnd >= player.getPosition(); - - if (hasEnteredRegion && hasNotExitedRegion) + if (!gemTimeoutTimer.isReady()) + { + uint16_t gemStart = std::max(gem.getPosition() - gem.width, 0); + uint16_t gemEnd = std::min(gem.getPosition(), SystemCore::Configuration::numLeds); + + bool hasEnteredRegion = gemStart <= player.getPosition() + player.width; + bool hasNotExitedRegion = gemEnd >= player.getPosition(); + + if (hasEnteredRegion && hasNotExitedRegion) // player captured the gem + { + gem.capture(); + contextManager->stateManager.getPhaseEvasionGameState().gemsCaptured++; + contextManager->stateManager.displayShouldUpdate = true; + gem.wait(gemRespawnDelay); + } + } + else // gem timed out, player took too long { gem.capture(); - contextManager->stateManager.getPhaseEvasionGameState().gemsCaptured++; - contextManager->stateManager.displayShouldUpdate = true; - gem.wait(5000); + gem.wait(gemRespawnDelay); } } } - void Driver::checkChallenge() + void Driver::assessDifficulty() { if (isReady()) { @@ -232,7 +241,7 @@ namespace Games::PhaseEvasion gap = 1600; speed = 0.4f; gem.capture(); - gem.wait(5000); + gem.wait(gemRespawnDelay); flareManager.reset(); player.setPosition(static_cast(0)); wait(500); From a3bc8c16d1827f34bfd2e53f386c3bc59ae94451 Mon Sep 17 00:00:00 2001 From: Eric McDaniel Date: Thu, 5 Feb 2026 19:18:05 -0600 Subject: [PATCH 17/17] calculate HUD adjusted totals and high score --- src/display/display.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/display/display.cpp b/src/display/display.cpp index 17ea21d..3fcb149 100644 --- a/src/display/display.cpp +++ b/src/display/display.cpp @@ -192,15 +192,14 @@ namespace Display drawHeader("Phase Evasion"); display.setCursor(0, 16); - display.printf("Flares: %3u", flaresEvaded); - - display.setCursor(DISPLAY_WIDTH / 2 + 8, 16); - display.printf("Gems: %3u", gemsCaptured); + display.printf("Flares: %u", flaresEvaded); + display.setCursor(DISPLAY_WIDTH / 2 + 4, 16); + display.printf("Total: %u", (flaresEvaded + (2 * gemsCaptured))); display.setCursor(0, 24); - display.printf(" High: %3u", flaresEvaded); - display.setCursor(DISPLAY_WIDTH / 2 + 8, 24); - display.printf(" %2u", gemsCaptured); + display.printf(" Gems: %u", gemsCaptured); + display.setCursor(DISPLAY_WIDTH / 2 + 4, 24); + display.printf(" High: %u", (flaresEvaded + (2 * gemsCaptured))); display.display(); }