diff --git a/include/core/context-manager.h b/include/core/context-manager.h index e35106f..3098325 100644 --- a/include/core/context-manager.h +++ b/include/core/context-manager.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "engine/layer.h" #include "engine/state-manager.h" #include "player/controller.h" @@ -22,6 +24,7 @@ namespace SystemCore Player::Controller controller; Lights::LedStrip leds; Display::OledDisplay display; + Preferences memory; Display::MenuTileNavigation menuNav; void navigateMainMenu(); diff --git a/include/engine/state-manager.h b/include/engine/state-manager.h index 5f3e67c..192846c 100644 --- a/include/engine/state-manager.h +++ b/include/engine/state-manager.h @@ -5,6 +5,12 @@ #include "games/demo/state.h" #include "scenes/canvas/state.h" +namespace SystemCore +{ + // forward declaration because of ContextManager/OledDisplay circular dependency + class ContextManager; +} + namespace Engine { enum class SystemState @@ -51,9 +57,14 @@ namespace Engine class StateManager { public: - StateManager() : systemState{SystemState::Initialize}, - userMainMenuChoice{MainMenuSelection::Games}, - userGameChoice{GameSelection::Recall} {} + StateManager(SystemCore::ContextManager *ctx) : contextManager{ctx}, + phaseEvasionGameState{ctx}, + recallGameState{ctx}, + systemState{SystemState::Initialize}, + userMainMenuChoice{MainMenuSelection::Games}, + userGameChoice{GameSelection::Recall} + { + } bool displayShouldUpdate = true; bool displayIsVisible = true; @@ -85,6 +96,7 @@ namespace Engine Scenes::Canvas::SceneState &getCanvasSceneState() { return canvasSceneState; } private: + SystemCore::ContextManager *contextManager; SystemState systemState; MainMenuSelection userMainMenuChoice; diff --git a/include/games/phase-evasion/driver.h b/include/games/phase-evasion/driver.h index 0ecb6ba..0a3f181 100644 --- a/include/games/phase-evasion/driver.h +++ b/include/games/phase-evasion/driver.h @@ -14,6 +14,7 @@ namespace Games::PhaseEvasion { public: Driver(SystemCore::ContextManager *ctx); + ~Driver() { state.reset(); } void nextEvent() override; static constexpr uint16_t playerOffset = 25; static constexpr uint16_t playerWidth = 5; @@ -41,6 +42,7 @@ namespace Games::PhaseEvasion float gameOverPhaseOffset = 0.0f; void getUpdates(); + void checkIfHighScore(); void renderPlayer(); void renderFlare(); void renderGem(); diff --git a/include/games/phase-evasion/state.h b/include/games/phase-evasion/state.h index 7e31976..522e581 100644 --- a/include/games/phase-evasion/state.h +++ b/include/games/phase-evasion/state.h @@ -2,6 +2,11 @@ #include +namespace SystemCore +{ + class ContextManager; +} + namespace Games::PhaseEvasion { enum class Actions @@ -16,11 +21,19 @@ namespace Games::PhaseEvasion class GameState { public: - GameState() : highScore{0}, flaresEvaded{0}, gemsCaptured{0} {} + GameState(SystemCore::ContextManager *ctx) : contextManager{ctx}, highScore{0}, flaresEvaded{0}, gemsCaptured{0} {} uint16_t flaresEvaded; uint16_t gemsCaptured; uint16_t highScore; Actions current = Actions::Startup; - void reset() { highScore = flaresEvaded = gemsCaptured = 0; } + static constexpr const char *memoryKeyName = "phase-high"; + + void reset(); + uint16_t calculateTotalScore() const; + void checkHighScore(); + void updateHighScore(); + + private: + SystemCore::ContextManager *contextManager; }; } \ No newline at end of file diff --git a/include/games/recall/driver.h b/include/games/recall/driver.h index 9f2a75e..bdf4509 100644 --- a/include/games/recall/driver.h +++ b/include/games/recall/driver.h @@ -13,6 +13,7 @@ namespace Games::Recall { public: Driver(SystemCore::ContextManager *ctx); + ~Driver() { state.reset(); } void nextEvent() override; private: diff --git a/include/games/recall/state.h b/include/games/recall/state.h index 8155674..1cbf4b5 100644 --- a/include/games/recall/state.h +++ b/include/games/recall/state.h @@ -2,6 +2,11 @@ #include +namespace SystemCore +{ + class ContextManager; +} + namespace Games::Recall { enum class Actions @@ -17,11 +22,17 @@ namespace Games::Recall class GameState { public: - GameState() : highScore{0}, round{0} {} + GameState(SystemCore::ContextManager *ctx); uint16_t highScore; uint16_t round; Actions current = Actions::Startup; + static constexpr const char *memoryKeyName = "recall-high"; + + void reset(); + void incrementScore(); + void updateHighScore(); - void reset() { highScore = round = 0; } + private: + SystemCore::ContextManager *contextManager; }; } \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index d121867..6bf75eb 100644 --- a/platformio.ini +++ b/platformio.ini @@ -26,7 +26,7 @@ framework = arduino build_type = debug build_unflags = -std=gnu++11 -build_flags = -g -DVIRTUALIZATION -std=gnu++17 +build_flags = -g -DVIRTUALIZATION -std=gnu++2a [env:debug] platform = espressif32 @@ -34,7 +34,7 @@ board = upesy_wroom framework = arduino build_type = debug build_unflags = -std=gnu++11 -build_flags = -g -DDEBUG -std=gnu++17 +build_flags = -g -DDEBUG -std=gnu++2a [env:release] platform = espressif32 @@ -43,7 +43,7 @@ framework = arduino lib_ignore = debug-fastled build_type = release build_unflags = -std=gnu++11 -build_flags = -Os -DRELEASE -std=gnu++17 +build_flags = -Os -DRELEASE -std=gnu++2a [env:native] platform = native @@ -53,4 +53,4 @@ build_flags = -I.pio/libdeps/native/googletest/googletest/include/gtest -I.pio/libdeps/native/googletest/googlemock/include/gmock -pthread - -std=gnu++17 + -std=gnu++2a diff --git a/src/core/context-manager.cpp b/src/core/context-manager.cpp index 17577c5..983f5b1 100644 --- a/src/core/context-manager.cpp +++ b/src/core/context-manager.cpp @@ -7,7 +7,7 @@ namespace SystemCore { - ContextManager::ContextManager() : display{this}, menuNav{this} {} + ContextManager::ContextManager() : display{this}, stateManager{this}, menuNav{this} {} ContextManager::~ContextManager() { diff --git a/src/display/display.cpp b/src/display/display.cpp index 3fcb149..caa508a 100644 --- a/src/display/display.cpp +++ b/src/display/display.cpp @@ -171,22 +171,28 @@ namespace Display void OledDisplay::drawRecallGameHud() { + const auto recallState = contextManager->stateManager.getRecallGameState(); + const auto round = recallState.round; + const auto highScore = recallState.highScore; + display.clearDisplay(); drawHeader("Recall"); display.setCursor(0, 16); - display.print("Round: "); - display.print(contextManager->stateManager.getRecallGameState().round + 1); + display.printf("Round: %u", round + 1); display.setCursor(0, 24); - display.print("High Score: -"); + display.printf("High Score: %u", highScore + 1); display.display(); } void OledDisplay::drawPhaseEvasionGameHud() { - const auto flaresEvaded = contextManager->stateManager.getPhaseEvasionGameState().flaresEvaded; - const auto gemsCaptured = contextManager->stateManager.getPhaseEvasionGameState().gemsCaptured; + const auto phaseEvasionState = contextManager->stateManager.getPhaseEvasionGameState(); + const auto flaresEvaded = phaseEvasionState.flaresEvaded; + const auto gemsCaptured = phaseEvasionState.gemsCaptured; + const auto totalScore = phaseEvasionState.calculateTotalScore(); + const auto highScore = phaseEvasionState.highScore; display.clearDisplay(); drawHeader("Phase Evasion"); @@ -194,12 +200,12 @@ namespace Display display.setCursor(0, 16); display.printf("Flares: %u", flaresEvaded); display.setCursor(DISPLAY_WIDTH / 2 + 4, 16); - display.printf("Total: %u", (flaresEvaded + (2 * gemsCaptured))); + display.printf("Total: %u", totalScore); display.setCursor(0, 24); display.printf(" Gems: %u", gemsCaptured); display.setCursor(DISPLAY_WIDTH / 2 + 4, 24); - display.printf(" High: %u", (flaresEvaded + (2 * gemsCaptured))); + display.printf(" High: %u", highScore); display.display(); } diff --git a/src/display/menu-navigation.cpp b/src/display/menu-navigation.cpp index ebdc564..03031fa 100644 --- a/src/display/menu-navigation.cpp +++ b/src/display/menu-navigation.cpp @@ -11,7 +11,7 @@ namespace Display Engine::SystemState currentState = contextManager->stateManager.current(); uint16_t displayIndex = SystemCore::Configuration::numLeds - 1; - float inactiveSelectionDimmingScale = currentState == Engine::SystemState::MenuHome ? 1.0f : 0.3f; + float inactiveSelectionDimmingScale = currentState == Engine::SystemState::MenuHome ? 1.0f : 0.6f; constexpr float center = (menuTileWidth - 1) / 2.0f; constexpr double sigma = 3.0f; @@ -20,7 +20,7 @@ namespace Display for (uint16_t i = 0; i < menuTileWidth; ++i) { float x = i - center; - float blend = std::exp(-(x * x) / (2 * sigma * sigma)); // computed gaussian curve + float blend = std::exp(-(x * x) / (4 * sigma * sigma)); // computed gaussian curve if (modeIdx == static_cast(modeSelected)) contextManager->leds.buffer[displayIndex] = Lights::Color{Lights::ColorCode::ThemeGreen} * inactiveSelectionDimmingScale * blend; @@ -41,7 +41,7 @@ namespace Display uint8_t sceneSelected = static_cast(contextManager->stateManager.getUserSceneChoice()); displayIndex = 0; - inactiveSelectionDimmingScale = (currentState == Engine::SystemState::MenuGames || currentState == Engine::SystemState::MenuScenes) ? 1.0f : 0.3f; + inactiveSelectionDimmingScale = (currentState == Engine::SystemState::MenuGames || currentState == Engine::SystemState::MenuScenes) ? 1.0f : 0.6f; uint8_t gamesOrScenesAvailable = modeSelected == Engine::MainMenuSelection::Games ? numGames : numScenes; for (uint8_t modeIdx = 0; modeIdx < gamesOrScenesAvailable; ++modeIdx) @@ -49,7 +49,7 @@ namespace Display for (size_t i = 0; i < menuTileWidth; ++i) { float x = i - center; - float blend = std::exp(-(x * x) / (2 * sigma * sigma)); + float blend = std::exp(-(x * x) / (4 * sigma * sigma)); if (currentState == Engine::SystemState::MenuGames && modeIdx == gameSelected) contextManager->leds.buffer[displayIndex] = Lights::Color{Lights::ColorCode::ThemeBlue} * blend; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 25dcce2..d6af5bb 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1,6 +1,7 @@ #include #include +#include #include "engine/engine.h" #include "lights/color.h" #include "display/menu-navigation.h" @@ -108,6 +109,13 @@ namespace Engine lastRender = micros(); log("Startup process completed. Transitioning to Main Menu"); } + +#ifdef RELEASE + contextManager.memory.begin("lumenlab", false); +#else + contextManager.memory.begin("lumenlab-dev", false); +#endif + randomSeed(esp_random()); } diff --git a/src/engine/state-manager.cpp b/src/engine/state-manager.cpp index bdd2206..645dd32 100644 --- a/src/engine/state-manager.cpp +++ b/src/engine/state-manager.cpp @@ -1,4 +1,5 @@ #include "engine/state-manager.h" +#include "core/context-manager.h" namespace Engine { diff --git a/src/games/phase-evasion/driver.cpp b/src/games/phase-evasion/driver.cpp index 98e2e4d..71e9d68 100644 --- a/src/games/phase-evasion/driver.cpp +++ b/src/games/phase-evasion/driver.cpp @@ -1,3 +1,4 @@ +#include #include "games/phase-evasion/driver.h" #include "player/controller.h" #include "logger.h" @@ -25,6 +26,7 @@ namespace Games::PhaseEvasion if (isReady()) { state.current = Actions::ActiveGame; + state.reset(); log("Starting new game."); } break; @@ -34,6 +36,7 @@ namespace Games::PhaseEvasion assessDifficulty(); checkCollision(); checkGemCapture(); + checkIfHighScore(); renderFlare(); renderGem(); renderPlayer(); @@ -194,6 +197,12 @@ namespace Games::PhaseEvasion } } + void Driver::checkIfHighScore() + { + GameState state = contextManager->stateManager.getPhaseEvasionGameState(); + state.checkHighScore(); + } + void Driver::muzzleFlash() { for (uint16_t i = 0; i < SystemCore::Configuration::numLeds; ++i) @@ -227,8 +236,6 @@ namespace Games::PhaseEvasion if (contextManager->controller.wasPressed(::Player::ControllerButton::Start)) { - state.current = Actions::Startup; - state.reset(); contextManager->stateManager.displayShouldUpdate = true; reset(); windDownTimer.wait(windDownLength); @@ -244,6 +251,9 @@ namespace Games::PhaseEvasion gem.wait(gemRespawnDelay); flareManager.reset(); player.setPosition(static_cast(0)); + state.current = Actions::Startup; + state.reset(); + contextManager->stateManager.displayShouldUpdate = true; 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 5b04f2a..6aca3a9 100644 --- a/src/games/phase-evasion/flare-manager.cpp +++ b/src/games/phase-evasion/flare-manager.cpp @@ -13,6 +13,7 @@ namespace Games::PhaseEvasion { uint16_t colorIndex = static_cast(esp_random()) % arraySize(Lights::colorPalette); flare->activate(Lights::colorPalette[colorIndex], speed); + logf("Flare dispatched: %u", colorIndex); return; } ++flare; diff --git a/src/games/phase-evasion/state.cpp b/src/games/phase-evasion/state.cpp new file mode 100644 index 0000000..4862784 --- /dev/null +++ b/src/games/phase-evasion/state.cpp @@ -0,0 +1,33 @@ +#include "games/phase-evasion/state.h" +#include "core/context-manager.h" +#include "logger.h" + +namespace Games::PhaseEvasion +{ + void GameState::reset() + { + flaresEvaded = gemsCaptured = 0; + highScore = contextManager->memory.getUInt(memoryKeyName); + } + + uint16_t GameState::calculateTotalScore() const + { + return flaresEvaded + (2 * gemsCaptured); + } + + void GameState::checkHighScore() + { + if (calculateTotalScore() > highScore) + { + updateHighScore(); + contextManager->stateManager.displayShouldUpdate = true; + } + } + + void GameState::updateHighScore() + { + highScore = calculateTotalScore(); + contextManager->memory.putUInt(memoryKeyName, highScore); + logf("High score updated: %u", highScore); + } +} \ No newline at end of file diff --git a/src/games/recall/driver.cpp b/src/games/recall/driver.cpp index b6ac0ab..e39b3cd 100644 --- a/src/games/recall/driver.cpp +++ b/src/games/recall/driver.cpp @@ -12,6 +12,7 @@ namespace Games::Recall state = contextManager->stateManager.getRecallGameState(); state.reset(); state.current = Actions::Startup; + contextManager->stateManager.displayShouldUpdate = true; wait(gameplaySpeedIlluminated); } @@ -40,6 +41,8 @@ namespace Games::Recall if (isReady()) { state.current = Actions::ComputerPlaybackOnDisplay; + state.reset(); + contextManager->stateManager.displayShouldUpdate = true; wait(gameplaySpeedIlluminated); } break; @@ -130,8 +133,8 @@ namespace Games::Recall if (sequenceIndex > state.round && isReady()) { state.current = Actions::PlayerResponseVerified; - ++state.round; - contextManager->stateManager.displayShouldUpdate = true; + state.incrementScore(); + contextManager->controller.reset(); wait(gameplaySpeedIlluminated * 2); return; diff --git a/src/games/recall/state.cpp b/src/games/recall/state.cpp new file mode 100644 index 0000000..4506e43 --- /dev/null +++ b/src/games/recall/state.cpp @@ -0,0 +1,36 @@ +#include "games/phase-evasion/state.h" +#include "core/context-manager.h" +#include "logger.h" + +namespace Games::Recall +{ + + GameState::GameState(SystemCore::ContextManager *ctx) : contextManager{ctx} + { + reset(); + } + + void GameState::reset() + { + round = 0; + highScore = contextManager->memory.getUInt(memoryKeyName); + contextManager->stateManager.displayShouldUpdate = true; + } + + void GameState::updateHighScore() + { + highScore = round; + contextManager->memory.putUInt(memoryKeyName, highScore); + logf("High score updated: %u", highScore); + } + + void GameState::incrementScore() + { + ++round; + if (round > highScore) + { + updateHighScore(); + } + contextManager->stateManager.displayShouldUpdate = true; + } +} \ No newline at end of file