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/core/context-manager.h b/include/core/context-manager.h index acf2aec..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; - SystemCore::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/controller.h b/include/games/demo/driver.h similarity index 75% rename from include/games/demo/controller.h rename to include/games/demo/driver.h index c750e7e..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}}, player2{Player{ctx}} + Driver(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..cd1b79f 100644 --- a/include/games/demo/player.h +++ b/include/games/demo/player.h @@ -5,15 +5,11 @@ namespace Games::Demo { - class Player : public ::Player::Player + class Player : public ::Player::BasePlayer { public: - Player(SystemCore::ContextManager *ctx) : contextManager{ctx}, ::Player::Player{ctx} {}; + Player(SystemCore::ContextManager *ctx, const uint16_t width) : ::Player::BasePlayer{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/controller.h b/include/games/phase-evasion/controller.h deleted file mode 100644 index 083a9e6..0000000 --- a/include/games/phase-evasion/controller.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include "engine/layer.h" -#include "engine/timer.h" -#include "core/context-manager.h" -#include "games/phase-evasion/player.h" -#include "games/phase-evasion/flare.h" -#include "games/phase-evasion/flare-manager.h" - -namespace Games::PhaseEvasion -{ - class Controller : public Engine::Layer, private Engine::Timer - { - public: - Controller(SystemCore::ContextManager *ctx); - void nextEvent() override; - static constexpr uint16_t playerClearance = 20; - - private: - SystemCore::ContextManager *contextManager; - GameState &state = contextManager->stateManager.getPhaseEvasionGameState(); - Player player; - FlareManager flareManager; - - void getUpdates(); - void renderUserColor(); - void renderFlare(); - void checkCollision(); - void checkGrowth(); - }; -} \ No newline at end of file diff --git a/include/games/phase-evasion/driver.h b/include/games/phase-evasion/driver.h new file mode 100644 index 0000000..0ecb6ba --- /dev/null +++ b/include/games/phase-evasion/driver.h @@ -0,0 +1,54 @@ +#pragma once + +#include "engine/layer.h" +#include "engine/timer.h" +#include "core/context-manager.h" +#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 +{ + class Driver : public Engine::Layer, private Engine::Timer + { + public: + Driver(SystemCore::ContextManager *ctx); + void nextEvent() override; + static constexpr uint16_t playerOffset = 25; + static constexpr uint16_t playerWidth = 5; + static constexpr uint32_t windDownLength = 20'000; + + private: + SystemCore::ContextManager *contextManager; + 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; + + void getUpdates(); + void renderPlayer(); + void renderFlare(); + void renderGem(); + void checkCollision(); + void checkGemCapture(); + void assessDifficulty(); + void muzzleFlash(); + void gameOver(); + void reset(); + }; +} \ 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 e2db21f..db986c7 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; @@ -22,9 +23,11 @@ namespace Games::PhaseEvasion const size_t size() const; void updatePositions(); - void dispatch(); + void dispatch(float speed); + void reset(); 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..453c7e1 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() { reset(); }; static constexpr uint16_t width = 10; void updatePosition(); @@ -26,6 +23,9 @@ namespace Games::PhaseEvasion bool isActive() const { return active; } void activate(Lights::Color color, float speed); void deactivate(); + bool completedCycle = false; + bool impacted = false; + void reset(); private: bool active; diff --git a/include/games/phase-evasion/gem.h b/include/games/phase-evasion/gem.h new file mode 100644 index 0000000..0d60774 --- /dev/null +++ b/include/games/phase-evasion/gem.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include "core/configuration.h" + +namespace Games::PhaseEvasion +{ + + class Gem : public Engine::Timer + { + public: + Gem() : active{false} { spawn(0); } + + static constexpr uint16_t width = 3; + uint16_t getPosition() { return position; } + bool isActive() { return active; } + + 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 + (width - 1); + if (isOutsideLeftRegion || isOutsideRightRegion) + return; + + position = pos; + active = true; + } + + private: + uint16_t position; + bool active; + }; +} \ No newline at end of file diff --git a/include/games/phase-evasion/player.h b/include/games/phase-evasion/player.h index 830fe91..5551044 100644 --- a/include/games/phase-evasion/player.h +++ b/include/games/phase-evasion/player.h @@ -5,21 +5,14 @@ namespace Games::PhaseEvasion { - class Player : public ::Player::Player + class Player : public ::Player::BasePlayer { public: - Player(SystemCore::ContextManager *ctx) : contextManager{ctx}, ::Player::Player{ctx} {}; + Player(SystemCore::ContextManager *ctx, uint16_t width) : ::Player::BasePlayer{ctx, width} {}; void checkColorChangeRequest(); Lights::Color getColor() { return currentColor; } - static constexpr uint16_t width = 10; 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/phase-evasion/state.h b/include/games/phase-evasion/state.h index 4c9778c..7e31976 100644 --- a/include/games/phase-evasion/state.h +++ b/include/games/phase-evasion/state.h @@ -8,16 +8,19 @@ namespace Games::PhaseEvasion { Startup, ActiveGame, + WindDown, + MuzzleFlash, GameOver }; class GameState { public: - GameState() : highScore{0}, spectersDodged{0} {} + GameState() : highScore{0}, flaresEvaded{0}, gemsCaptured{0} {} + uint16_t flaresEvaded; + uint16_t gemsCaptured; uint16_t highScore; - uint16_t spectersDodged; Actions current = Actions::Startup; - void reset() { highScore = spectersDodged = 0; } + void reset() { highScore = flaresEvaded = gemsCaptured = 0; } }; } \ No newline at end of file diff --git a/include/games/recall/controller.h b/include/games/recall/driver.h similarity index 75% rename from include/games/recall/controller.h rename to include/games/recall/driver.h index 81c4777..9f2a75e 100644 --- a/include/games/recall/controller.h +++ b/include/games/recall/driver.h @@ -5,13 +5,14 @@ #include "engine/timer.h" #include "lights/color.h" #include "lights/color-code.h" +#include "player/player.h" 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: @@ -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::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 6ded16e..35bf3b6 100644 --- a/include/player/player.h +++ b/include/player/player.h @@ -4,16 +4,33 @@ namespace Player { - class Player + class BasePlayer { public: - Player(SystemCore::ContextManager *ctx) : contextManager{ctx} {}; - void move(const int distance, const float speed); - uint16_t getPosition() { return position; } + 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); } + 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[] = { + ControllerButton::Cross, + ControllerButton::Square, + ControllerButton::Triangle, + ControllerButton::Circle}; protected: SystemCore::ContextManager *contextManager; - uint16_t position = 0; float positionPrecise = 0.0f; + float responseExponent = 2.0f; + float responseBlend = 0.35f; }; + + inline constexpr auto &availableGameplayButtons = BasePlayer::BasePlayer::availableGameplayButtons; } \ No newline at end of file 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..927bc6b 100644 --- a/src/core/context-manager.cpp +++ b/src/core/context-manager.cpp @@ -1,13 +1,13 @@ #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 { - ContextManager::ContextManager() : leds{config}, display{this} {} + ContextManager::ContextManager() : display{this} {} ContextManager::~ContextManager() { @@ -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/display/display.cpp b/src/display/display.cpp index 59a53b3..3fcb149 100644 --- a/src/display/display.cpp +++ b/src/display/display.cpp @@ -185,14 +185,21 @@ 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("Specters Dodged: "); - display.print(contextManager->stateManager.getPhaseEvasionGameState().spectersDodged); + display.printf("Flares: %u", flaresEvaded); + display.setCursor(DISPLAY_WIDTH / 2 + 4, 16); + display.printf("Total: %u", (flaresEvaded + (2 * gemsCaptured))); + display.setCursor(0, 24); - display.print("High Score: -"); + display.printf(" Gems: %u", gemsCaptured); + display.setCursor(DISPLAY_WIDTH / 2 + 4, 24); + display.printf(" High: %u", (flaresEvaded + (2 * gemsCaptured))); display.display(); } 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/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/demo/player.cpp b/src/games/demo/player.cpp index 645df10..bf9c82b 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) % SystemCore::Configuration::numLeds; 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) % SystemCore::Configuration::numLeds; 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 deleted file mode 100644 index 66ddbfe..0000000 --- a/src/games/phase-evasion/controller.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include "games/phase-evasion/controller.h" -#include "logger.h" - -namespace Games::PhaseEvasion -{ - Controller::Controller(SystemCore::ContextManager *ctx) : contextManager{ctx}, - player{ctx} - { - state = contextManager->stateManager.getPhaseEvasionGameState(); - state.reset(); - state.current = Actions::Startup; - wait(500); - } - - void Controller::nextEvent() - { - switch (state.current) - { - case Actions::Startup: - if (isReady()) - { - state.current = Actions::ActiveGame; - flareManager.dispatch(); - log("Starting new game."); - wait(1000); - } - break; - case Actions::ActiveGame: - getUpdates(); - checkGrowth(); - checkCollision(); - renderFlare(); - renderUserColor(); - break; - case Actions::GameOver: - for (uint16_t i = 0; i < contextManager->config.numLeds; ++i) - { - contextManager->leds.buffer[i] = Lights::ColorCode::GameRed; - } - break; - } - } - - void Controller::getUpdates() - { - player.checkColorChangeRequest(); - flareManager.updatePositions(); - } - - void Controller::renderUserColor() - { - for (uint16_t i = playerClearance; i < playerClearance + player.width; ++i) - { - contextManager->leds.buffer[i] = player.getColor(); - } - } - - void Controller::renderFlare() - { - for (const auto &flare : flareManager) - { - if (!flare.isActive()) - continue; - - uint16_t start = std::max(flare.getPosition() - flare.width, 0); - uint16_t end = std::min(flare.getPosition(), contextManager->config.numLeds); - - for (uint16_t i = start; i < end; ++i) - { - contextManager->leds.buffer[i] = flare.getColor(); - } - } - } - - void Controller::checkCollision() - { - for (const auto &flare : flareManager) - { - if (!flare.isActive()) - continue; - - uint16_t start = std::max(flare.getPosition() - flare.width, 0); - uint16_t end = std::min(flare.getPosition(), contextManager->config.numLeds); - - if (start <= playerClearance + player.width && end >= playerClearance && player.getColor() != flare.getColor()) - { - state.current = Actions::GameOver; - } - } - } - - void Controller::checkGrowth() - { - if (isReady()) - { - flareManager.dispatch(); - wait(1000); - } - } -} \ No newline at end of file diff --git a/src/games/phase-evasion/driver.cpp b/src/games/phase-evasion/driver.cpp new file mode 100644 index 0000000..98e2e4d --- /dev/null +++ b/src/games/phase-evasion/driver.cpp @@ -0,0 +1,249 @@ +#include "games/phase-evasion/driver.h" +#include "player/controller.h" +#include "logger.h" + +namespace Games::PhaseEvasion +{ + Driver::Driver(SystemCore::ContextManager *ctx) : contextManager{ctx}, + player{ctx, playerWidth}, + flareManager{ctx} + { + state = contextManager->stateManager.getPhaseEvasionGameState(); + state.reset(); + state.current = Actions::Startup; + reset(); + wait(500); + gem.wait(gemRespawnDelay); + windDownTimer.wait(windDownLength); + } + + void Driver::nextEvent() + { + switch (state.current) + { + case Actions::Startup: + if (isReady()) + { + state.current = Actions::ActiveGame; + log("Starting new game."); + } + break; + case Actions::ActiveGame: + case Actions::WindDown: + getUpdates(); + assessDifficulty(); + checkCollision(); + checkGemCapture(); + renderFlare(); + renderGem(); + renderPlayer(); + break; + case Actions::MuzzleFlash: + muzzleFlash(); + break; + case Actions::GameOver: + gameOver(); + renderFlare(); + renderGem(); + renderPlayer(); + break; + } + } + + void Driver::getUpdates() + { + player.checkColorChangeRequest(); + flareManager.updatePositions(); + auto leftAnalog = contextManager->controller.leftAnalog(); + player.move(leftAnalog.x, 1.25, false); + } + + void Driver::renderPlayer() + { + for (uint16_t i = 0; i < player.width; ++i) + { + uint16_t index = (player.getPosition() + i) % SystemCore::Configuration::numLeds; + contextManager->leds.buffer[index] = player.getColor(); + } + } + + void Driver::renderFlare() + { + for (const auto &flare : flareManager) + { + 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); + + for (uint16_t i = flareHead; i < flareTail; ++i) + { + uint16_t distance = i - flareHead; + double attenuation = std::clamp(1.0 - 0.08 * static_cast(distance), 0.0, 1.0); + + contextManager->leds.buffer[i] = flare.getColor() * attenuation; + } + } + } + + void Driver::renderGem() + { + if (!gem.isActive() || !gem.isReady()) + 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 (auto &flare : flareManager) + { + if (!flare.isActive()) + continue; + + uint16_t flareStart = std::max(flare.getPosition() - flare.width, 0); + uint16_t flareEnd = std::min(flare.getPosition(), SystemCore::Configuration::numLeds); + + bool isUnmatchingColor = player.getColor() != flare.getColor(); + bool hasEnteredRegion = flareStart <= player.getPosition() + player.width; + bool hasNotExitedRegion = flareEnd >= player.getPosition(); + + if (isUnmatchingColor && hasEnteredRegion && hasNotExitedRegion) + { + flare.impacted = true; + state.current = Actions::MuzzleFlash; + gameOverPhaseShift = static_cast(((SystemCore::Configuration::numLeds / 2) + player.getPosition()) % SystemCore::Configuration::numLeds); + wait(20); + } + } + } + + 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); + gemTimeoutTimer.wait(gemCaptureDelay); + } + + if (gem.isActive() && gem.isReady()) + { + 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(); + gem.wait(gemRespawnDelay); + } + } + } + + void Driver::assessDifficulty() + { + if (isReady()) + { + if (windDownTimer.isReady()) + { + state.current = Actions::WindDown; + } + + if (state.current == Actions::ActiveGame) + { + flareManager.dispatch(speed); + uint32_t timeDelay = static_cast((esp_random() % static_cast(interval)) + gap); + wait(timeDelay); + } + + bool shouldStartNextRound = state.current == Actions::WindDown && flareManager.size() == 2; + if (shouldStartNextRound) + { + windDownTimer.wait(windDownLength); + state.current = Actions::ActiveGame; + speed *= 1.07; + interval *= 0.8; + gap *= 0.82; + } + } + } + + void Driver::muzzleFlash() + { + for (uint16_t i = 0; i < SystemCore::Configuration::numLeds; ++i) + { + contextManager->leds.buffer[i] = Lights::Color::White; + } + + if (isReady()) + { + state.current = Actions::GameOver; + } + } + + void Driver::gameOver() + { + + 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) * 0.95), + static_cast(std::floor(phase) * 0.15), + static_cast(std::floor(phase) * 0.25)}; + } + + gameOverPhaseShift += std::cos(gameOverPhaseOffset) / 16.0f; + gameOverPhaseOffset += 1.0 / 32.0f; + + if (gameOverPhaseShift > SystemCore::Configuration::numLeds) + gameOverPhaseShift = 0.0; + + if (contextManager->controller.wasPressed(::Player::ControllerButton::Start)) + { + state.current = Actions::Startup; + state.reset(); + contextManager->stateManager.displayShouldUpdate = true; + reset(); + windDownTimer.wait(windDownLength); + } + } + + void Driver::reset() + { + interval = 2500; + gap = 1600; + speed = 0.4f; + gem.capture(); + gem.wait(gemRespawnDelay); + flareManager.reset(); + player.setPosition(static_cast(0)); + wait(500); + } +} \ 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 b30a3a4..5b04f2a 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; @@ -28,6 +28,13 @@ namespace Games::PhaseEvasion continue; flare.updatePosition(); + + if (flare.completedCycle) + { + contextManager->stateManager.getPhaseEvasionGameState().flaresEvaded++; + contextManager->stateManager.displayShouldUpdate = true; + flare.completedCycle = false; + } } } @@ -36,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 8dae6a2..0927093 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); @@ -27,4 +26,13 @@ namespace Games::PhaseEvasion { active = false; } + + void Flare::reset() + { + active = false; + color = Lights::Color::WhiteSmoke; + speed = 0.0f; + impacted = false; + positionFloat = static_cast(SystemCore::Configuration::numLeds + width); + } } \ 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/driver.cpp similarity index 81% rename from src/games/recall/controller.cpp rename to src/games/recall/driver.cpp index 55b16ed..b6ac0ab 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(availableGameplayButtons)); + // gameplayColors[i] = static_cast(i % arraySize(Driver::availableGameplayButtons)); - uint16_t colorIndex = static_cast(esp_random()) % arraySize(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()) { @@ -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) @@ -104,7 +105,7 @@ namespace Games::Recall } } - void Controller::pauseComputerPlayback() + void Driver::pauseComputerPlayback() { if (sequenceIndex >= state.round) { @@ -124,7 +125,7 @@ namespace Games::Recall } } - void Controller::evaluateUserRecall() + void Driver::evaluateUserRecall() { if (sequenceIndex > state.round && isReady()) { @@ -140,9 +141,9 @@ namespace Games::Recall evaluateUserButton(gameplayColors[sequenceIndex]); } - void Controller::evaluateUserButton(Player::ControllerButton expectedButton) + void Driver::evaluateUserButton(Player::ControllerButton expectedButton) { - for (auto button : availableGameplayButtons) + for (auto button : Driver::availableGameplayButtons) { if (contextManager->controller.wasPressedAndReleased(button)) { @@ -161,12 +162,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(availableGameplayButtons); ++i) + for (uint16_t i = 0; i < arraySize(Driver::availableGameplayButtons); ++i) { auto btn = static_cast(i); if (contextManager->controller.isDown(btn)) @@ -181,7 +182,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(Driver::availableGameplayButtons))) { auto boundaries = directionBoundaries(static_cast(pressedButtonIndex)); for (uint16_t i = boundaries.first; i <= boundaries.second; ++i) @@ -190,9 +191,9 @@ namespace Games::Recall } } - std::pair Controller::directionBoundaries(Player::ControllerButton button) + std::pair Driver::directionBoundaries(Player::ControllerButton button) { - const auto &boundary = contextManager->config.recallBoundaries; + const auto &boundary = SystemCore::Configuration::recallBoundaries; switch (button) { @@ -203,13 +204,13 @@ 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}; } } - void Controller::prepareComputerPlayback() + void Driver::prepareComputerPlayback() { if (isReady()) { @@ -235,7 +236,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)) { @@ -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()) - gameOverLedPhaseShift = 0; + 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 eab4260..00ccc01 100644 --- a/src/player/player.cpp +++ b/src/player/player.cpp @@ -2,20 +2,49 @@ namespace Player { - void Player::move(const int distance, const float speed) + 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 ledCountF = static_cast(contextManager->leds.size()); - positionPrecise = std::fmod(positionPrecise, ledCountF); + const float adjustedLedCount = static_cast(SystemCore::Configuration::numLeds - width); + if (shouldWrap) + { + positionPrecise = std::fmod(positionPrecise, adjustedLedCount); - if (positionPrecise < 0.0f) - positionPrecise += ledCountF; + if (positionPrecise < 0.0f) + { + positionPrecise += adjustedLedCount; + } + } + else + { + if (positionPrecise >= adjustedLedCount) + { + positionPrecise = adjustedLedCount; + } + else if (positionPrecise < 0.0f) + { + positionPrecise = 0.0f; + } + } + } + + 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); - position = static_cast(std::floor(positionPrecise)) % contextManager->leds.size(); + 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/controller.cpp b/src/scenes/canvas/driver.cpp similarity index 88% rename from src/scenes/canvas/controller.cpp rename to src/scenes/canvas/driver.cpp index cdce2a7..b96d55d 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(); @@ -19,13 +19,13 @@ 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; } } - 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);