diff --git a/assets/lumenlab-logo-dark-inkscape.svg b/assets/lumenlab-logo-dark-inkscape.svg index 368079b..8162b42 100644 --- a/assets/lumenlab-logo-dark-inkscape.svg +++ b/assets/lumenlab-logo-dark-inkscape.svg @@ -9,7 +9,7 @@ id="svg1" inkscape:version="1.4.2 (f4327f4, 2025-05-13)" sodipodi:docname="lumenlab-logo-dark-inkscape.svg" - inkscape:export-filename="lumenlab-logo.png" + inkscape:export-filename="lumenlab-logo-dark.png" inkscape:export-xdpi="100" inkscape:export-ydpi="100" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" @@ -26,9 +26,9 @@ inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" inkscape:document-units="mm" - inkscape:zoom="1.4142136" - inkscape:cx="375.82724" - inkscape:cy="59.750521" + inkscape:zoom="2.8284272" + inkscape:cx="236.88077" + inkscape:cy="58.866638" inkscape:window-width="1920" inkscape:window-height="1009" inkscape:window-x="-8" @@ -178,11 +178,14 @@ d="m 15.547371,29.625582 v 0.0031 c -1.930042,0.02513 -3.75304,1.51844 -4.516003,3.944463 -0.911293,2.897672 -2.6715849,8.039926 -1.2262817,10.841198 0.1420569,0.275334 0.3025437,0.536947 0.4800737,0.780831 0.785838,1.079559 2.780759,2.04945 3.947046,1.919263 0.508484,-0.05676 1.005062,-0.214636 1.471745,-0.464055 0.846205,-0.302708 1.679659,-0.838646 2.420007,-1.604036 l 1.858285,-1.920813 H 32.49104 l 1.869653,1.932699 c 0.543171,0.561542 1.136056,0.999537 1.747697,1.312581 1.21472,0.843306 2.696605,1.031074 4.080371,0.358118 1.860903,-0.904999 3.002922,-3.102796 3.045809,-5.478736 0.025,-1.385036 -0.673371,-3.461405 -1.072802,-4.731494 l -0.926559,-2.947107 c -0.772398,-2.456023 -2.631393,-3.955859 -4.587834,-3.944462 v -0.0015 z" /> + d="m 16.920414,34.019629 c -0.1662,0 -0.30024,0.13404 -0.30024,0.30024 v 1.720825 h -1.744079 c -0.1662,0 -0.300241,0.133523 -0.300241,0.299723 v 1.650029 c 0,0.1662 0.134041,0.30024 0.300241,0.30024 h 1.744079 v 1.648478 c 0,0.1662 0.13404,0.30024 0.30024,0.30024 h 1.650029 c 0.1662,0 0.299723,-0.13404 0.299723,-0.30024 v -1.648478 h 1.625224 c 0.1662,0 0.30024,-0.13404 0.30024,-0.30024 v -1.650029 c 0,-0.1662 -0.13404,-0.299723 -0.30024,-0.299723 h -1.625224 v -1.720825 c 0,-0.1662 -0.133523,-0.30024 -0.299723,-0.30024 z" + inkscape:export-filename="lumenlab-logo-dark.png" + inkscape:export-xdpi="100" + inkscape:export-ydpi="100" /> ionPool; + }; +} \ No newline at end of file diff --git a/include/games/chain-reaction/ion.h b/include/games/chain-reaction/ion.h new file mode 100644 index 0000000..88ff879 --- /dev/null +++ b/include/games/chain-reaction/ion.h @@ -0,0 +1,34 @@ +#pragma once + +#include "engine/timer.h" +#include "lights/color.h" + +namespace Games::ChainReaction +{ + class Ion : public Engine::Timer + { + public: + Ion() { Ion(maxIonColors); } + Ion(uint8_t size); + ~Ion() = default; + + static constexpr uint8_t maxIonColors = 3; + + const uint8_t getSize() const { return colors.size(); } + + uint16_t getPosition() const { return static_cast(positionFloat); } + void updatePosition(); + + const std::vector &getColors() const { return colors; }; + void assignColors(uint8_t s); + + const bool isActive() const { return active; } + + std::vector colors; + + private: + float positionFloat; + float speed; + bool active = true; + }; +} \ No newline at end of file diff --git a/include/games/chain-reaction/player.h b/include/games/chain-reaction/player.h new file mode 100644 index 0000000..aa96df3 --- /dev/null +++ b/include/games/chain-reaction/player.h @@ -0,0 +1,18 @@ +#pragma once + +#include "core/context-manager.h" +#include "player/player.h" + +namespace Games::ChainReaction +{ + class Player + { + public: + Player(SystemCore::ContextManager *ctx) {}; + void checkAccelerator(); + + // void checkColorChangeRequest(); + + private: + }; +} \ No newline at end of file diff --git a/include/games/chain-reaction/state.h b/include/games/chain-reaction/state.h new file mode 100644 index 0000000..dfc3283 --- /dev/null +++ b/include/games/chain-reaction/state.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +namespace SystemCore +{ + class ContextManager; +} + +namespace Games::ChainReaction +{ + enum class Actions + { + Startup, + Dispatch, + ActiveDrop, + GameOver + }; + + class GameState + { + public: + GameState(SystemCore::ContextManager *ctx) : contextManager{ctx} {} + uint16_t reactions; + + Actions current = Actions::Startup; + static constexpr const char *memoryKeyName = "high_chain"; + + void reset(); + + private: + SystemCore::ContextManager *contextManager; + }; +} \ No newline at end of file diff --git a/include/games/phase-evasion/state.h b/include/games/phase-evasion/state.h index dd2f687..54aa67a 100644 --- a/include/games/phase-evasion/state.h +++ b/include/games/phase-evasion/state.h @@ -26,7 +26,7 @@ namespace Games::PhaseEvasion uint16_t gemsCaptured; uint16_t highScore; Actions current = Actions::Startup; - static constexpr const char *memoryKeyName = "phase-high"; + static constexpr const char *memoryKeyName = "high_phase"; void reset(); void loadHighScore(); diff --git a/include/games/recall/state.h b/include/games/recall/state.h index c57c0c9..34c76fa 100644 --- a/include/games/recall/state.h +++ b/include/games/recall/state.h @@ -26,7 +26,7 @@ namespace Games::Recall uint16_t highScore; uint16_t round; Actions current = Actions::Startup; - static constexpr const char *memoryKeyName = "recall-high"; + static constexpr const char *memoryKeyName = "high_recall"; void reset(); void loadHighScore(); diff --git a/include/lights/led-luminance.h b/include/lights/led-luminance.h index d5637b6..6bb7221 100644 --- a/include/lights/led-luminance.h +++ b/include/lights/led-luminance.h @@ -12,7 +12,7 @@ namespace Lights static constexpr float MAX_LED_BRIGHTNESS = 255.0f; static constexpr int MAX_ADC_READING = 4095; - int getLuminance() { return currentLuminance; } + int getLuminance(); void adjustLuminance(); static uint8_t applyGamma(uint8_t value); diff --git a/src/core/configuration.cpp b/src/core/configuration.cpp index 383e4ea..8cd15d9 100644 --- a/src/core/configuration.cpp +++ b/src/core/configuration.cpp @@ -2,7 +2,7 @@ #include #ifndef LUMENLAB_VERSION -#define LUMENLAB_VERSION "v999.999.999" +#define LUMENLAB_VERSION "v99.99.99" #endif #ifdef USE_PS3 #define PS_CONTROLLER_TYPE "PS3" diff --git a/src/core/context-manager.cpp b/src/core/context-manager.cpp index 0e7fe00..b726813 100644 --- a/src/core/context-manager.cpp +++ b/src/core/context-manager.cpp @@ -1,8 +1,9 @@ #include "core/context-manager.h" #include "player/controller-properties.h" -#include "games/demo/driver.h" #include "games/recall/driver.h" #include "games/phase-evasion/driver.h" +#include "games/chain-reaction/driver.h" +#include "games/demo/driver.h" #include "scenes/canvas/driver.h" #include "logger.h" @@ -31,6 +32,7 @@ namespace SystemCore SystemCore::Configuration::load(memory); stateManager.getPhaseEvasionGameState().loadHighScore(); stateManager.getRecallGameState().loadHighScore(); + // TODO: Add loadHighScore for Chain Reaction } void ContextManager::checkExitRequest() @@ -111,6 +113,9 @@ namespace SystemCore case Engine::GameSelection::PhaseEvasion: stateManager.setNext(Engine::SystemState::GamePhaseEvasion); break; + case Engine::GameSelection::ChainReaction: + stateManager.setNext(Engine::SystemState::GameChainReaction); + break; case Engine::GameSelection::Demo: stateManager.setNext(Engine::SystemState::GameDemo); break; @@ -176,6 +181,10 @@ namespace SystemCore application = new Games::PhaseEvasion::Driver{this}; logf("Transitioning to Phase Evasion (Game)"); break; + case Engine::SystemState::GameChainReaction: + application = new Games::ChainReaction::Driver{this}; + logf("Transitioning to Chain Reaction (Game)"); + break; case Engine::SystemState::GameDemo: application = new Games::Demo::Driver{this}; logf("Transitioning to Demo (Game)"); diff --git a/src/display/display.cpp b/src/display/display.cpp index ef23ac3..364ed72 100644 --- a/src/display/display.cpp +++ b/src/display/display.cpp @@ -16,16 +16,17 @@ namespace Display void OledDisplay::updateDisplay() { - if (!contextManager->stateManager.displayIsVisible && contextManager->stateManager.displayShouldUpdate) + Engine::StateManager &state = contextManager->stateManager; + if (!state.displayIsVisible && state.displayShouldUpdate) { clearDisplay(); - contextManager->stateManager.displayShouldUpdate = false; + state.displayShouldUpdate = false; return; } - if (contextManager->stateManager.displayShouldUpdate) + if (state.displayShouldUpdate) { - switch (contextManager->stateManager.current()) + switch (state.current()) { case Engine::SystemState::Initialize: drawBootScreen(); @@ -48,6 +49,9 @@ namespace Display case Engine::SystemState::GamePhaseEvasion: drawPhaseEvasionGameHud(); break; + case Engine::SystemState::GameChainReaction: + drawChainReactionGameHud(); + break; case Engine::SystemState::SceneCanvas: drawCanvasSceneHud(); break; @@ -58,7 +62,7 @@ namespace Display drawBootScreen(); break; } - contextManager->stateManager.displayShouldUpdate = false; + state.displayShouldUpdate = false; } } @@ -116,23 +120,25 @@ namespace Display void OledDisplay::drawGamesMenu() { - display.clearDisplay(); - drawHeader("Games Menu"); - + constexpr int itemsPerPage = 3; + constexpr int numGames = static_cast(Engine::GameSelection::COUNT); uint8_t selectedOptionIndex = static_cast(contextManager->stateManager.getUserGameChoice()); + auto currentPage = selectedOptionIndex / itemsPerPage; char nameBuffer[32] = ""; - display.setCursor(0, 8); - sprintf(nameBuffer, "%c %s", selectedOption(0, selectedOptionIndex), contextManager->stateManager.printGameName(0)); - display.print(nameBuffer); + display.clearDisplay(); + sprintf(nameBuffer, "Games Menu (%d/%d)", currentPage + 1, (numGames + itemsPerPage - 1) / itemsPerPage); + drawHeader(nameBuffer); - display.setCursor(0, 16); - sprintf(nameBuffer, "%c %s", selectedOption(1, selectedOptionIndex), contextManager->stateManager.printGameName(1)); - display.print(nameBuffer); + int startIndex = currentPage * itemsPerPage; + int endIndex = std::min(startIndex + itemsPerPage, numGames); - display.setCursor(0, 24); - sprintf(nameBuffer, "%c %s", selectedOption(2, selectedOptionIndex), contextManager->stateManager.printGameName(2)); - display.print(nameBuffer); + for (int i = startIndex; i < endIndex; ++i) + { + display.setCursor(0, 8 * (i - startIndex + 1)); + sprintf(nameBuffer, "%c %s", selectedOption(i, selectedOptionIndex), contextManager->stateManager.printGameName(i)); + display.print(nameBuffer); + } display.display(); } @@ -207,6 +213,22 @@ namespace Display display.display(); } + void OledDisplay::drawChainReactionGameHud() + { + display.clearDisplay(); + drawHeader("Chain Reaction"); + + const auto chainReactionState = contextManager->stateManager.getChainReactionGameState(); + const auto reactions = chainReactionState.reactions; + + display.setCursor(0, 16); + display.printf("Reactions: %u", reactions); + display.setCursor(0, 24); + display.printf("Total: %u", reactions); + + display.display(); + } + void OledDisplay::drawCanvasSceneHud() { display.clearDisplay(); diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 9f43c42..63579e5 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -44,6 +44,7 @@ namespace Engine break; case SystemState::GameRecall: case SystemState::GamePhaseEvasion: + case SystemState::GameChainReaction: case SystemState::GameDemo: case SystemState::SceneCanvas: contextManager.application->nextEvent(); diff --git a/src/engine/state-manager.cpp b/src/engine/state-manager.cpp index 645dd32..d82dced 100644 --- a/src/engine/state-manager.cpp +++ b/src/engine/state-manager.cpp @@ -47,9 +47,10 @@ namespace Engine const char *StateManager::printGameName(uint8_t index) { - static constexpr const char gameNames[3][20] = { + static constexpr const char gameNames[static_cast(Engine::GameSelection::COUNT)][20] = { "Recall", "Phase Evasion", + "Chain Reaction", "Demo"}; return gameNames[index]; } diff --git a/src/games/chain-reaction/driver.cpp b/src/games/chain-reaction/driver.cpp new file mode 100644 index 0000000..e57504c --- /dev/null +++ b/src/games/chain-reaction/driver.cpp @@ -0,0 +1,84 @@ +#include "games/chain-reaction/driver.h" +#include "logger.h" + +namespace Games::ChainReaction +{ + Driver::Driver(SystemCore::ContextManager *ctx) : contextManager{ctx}, + state{ctx->stateManager.getChainReactionGameState()}, + cannon{ctx} + { + reset(); + wait(500); + } + + void Driver::nextEvent() + { + switch (state.current) + { + case Actions::Startup: + if (isReady()) + { + reset(); + state.current = Actions::Dispatch; + log("Starting new game."); + } + break; + case Actions::Dispatch: + handleDispatch(); + break; + case Actions::ActiveDrop: + advanceActiveIon(); + renderIons(); + break; + default: + break; + } + } + + void Driver::advanceActiveIon() + { + for (auto &ion : cannon) + { + if (!ion.isActive()) + continue; + + ion.updatePosition(); + } + } + + void Driver::renderIons() + { + logf("# of Ions: %u", cannon.size()); + int ct = 1; + for (const auto &ion : cannon) + { + logf("# of colors per ion: %u", ion.getSize()); + for (uint16_t i = 0; i < ion.getSize(); ++i) + { + + uint16_t segmentHead = std::max(ion.getPosition() - ((ion.getSize() - i) * cannon.ionWidth), 0); + uint16_t segmentTail = std::min(static_cast(ion.getPosition() - ((ion.getSize() - i - 1) * cannon.ionWidth)), SystemCore::Configuration::numLeds()); + + for (uint16_t j = segmentHead; j < segmentTail; ++j) + { + contextManager->leds.buffer[j] = ion.getColors().at(i); + } + logf("Ion %u - Color: %u", ct, ion.getColors().at(i)); + } + ct++; + } + } + + void Driver::reset() + { + state.reset(); + state.current = Actions::Startup; + cannon.reset(); + } + + void Driver::handleDispatch() + { + cannon.dispatch(); + state.current = Actions::ActiveDrop; + } +} \ No newline at end of file diff --git a/src/games/chain-reaction/ion-cannon.cpp b/src/games/chain-reaction/ion-cannon.cpp new file mode 100644 index 0000000..1ea2b7e --- /dev/null +++ b/src/games/chain-reaction/ion-cannon.cpp @@ -0,0 +1,23 @@ +#include "games/chain-reaction/ion-cannon.h" +#include "logger.h" + +namespace Games::ChainReaction +{ + + void IonCannon::dispatch() + { + ionPool.emplace_back(2); // remove hardcode when possible + logf("New color dispatched"); + } + + void IonCannon::reset() + { + ionPool.clear(); + } + + const size_t IonCannon::shouldDispatch() const + { + return std::count_if(ionPool.begin(), ionPool.end(), [](const Ion &ion) + { return ion.isActive(); }); + } +} \ No newline at end of file diff --git a/src/games/chain-reaction/ion.cpp b/src/games/chain-reaction/ion.cpp new file mode 100644 index 0000000..6bdbcda --- /dev/null +++ b/src/games/chain-reaction/ion.cpp @@ -0,0 +1,30 @@ +#include "games/chain-reaction/ion.h" +#include "lights/color.h" +#include "core/configuration.h" +#include "common.h" + +namespace Games::ChainReaction +{ + Ion::Ion(uint8_t s) : positionFloat{static_cast(SystemCore::Configuration::numLeds())}, + speed{0.40f} + { + colors.reserve(s); + assignColors(s); + } + + void Ion::updatePosition() + { + positionFloat -= speed; + // return here to add logic to deactivate when stacked + } + + void Ion::assignColors(uint8_t s) + { + for (uint8_t i = 0; i < s; ++i) + { + // uint16_t colorIndex = static_cast(esp_random()) % arraySize(Lights::colorPalette); + // colors.push_back(Lights::colorPalette[colorIndex]); + colors.push_back(Lights::colorPalette[i]); + } + } +} \ No newline at end of file diff --git a/src/games/chain-reaction/state.cpp b/src/games/chain-reaction/state.cpp new file mode 100644 index 0000000..d181c0e --- /dev/null +++ b/src/games/chain-reaction/state.cpp @@ -0,0 +1,9 @@ +#include "games/chain-reaction/state.h" + +namespace Games::ChainReaction +{ + void GameState::reset() + { + reactions = 0; + } +} \ No newline at end of file diff --git a/src/lights/led-luminance.cpp b/src/lights/led-luminance.cpp index f1854b0..f006dca 100644 --- a/src/lights/led-luminance.cpp +++ b/src/lights/led-luminance.cpp @@ -4,6 +4,16 @@ namespace Lights { + + int LedLuminance::getLuminance() + { +#ifdef VIRTUALIZATION + return LedLuminance::MAX_LED_BRIGHTNESS; +#else + return currentLuminance; +#endif + } + void LedLuminance::adjustLuminance() { int dialReading = analogRead(SystemCore::Configuration::ledDimmerGpio); diff --git a/src/lights/led-strip.cpp b/src/lights/led-strip.cpp index 3a45cea..0aff085 100644 --- a/src/lights/led-strip.cpp +++ b/src/lights/led-strip.cpp @@ -27,18 +27,9 @@ namespace Lights void LedStrip::adjustLuminance() { +#ifdef RELEASE luminance.adjustLuminance(); float brightnessScale = static_cast(luminance.getLuminance()) / LedLuminance::MAX_LED_BRIGHTNESS; - -#ifdef VIRTUALIZATION - - for (uint16_t i = 0; i < size(); ++i) - { - buffer[i] *= brightnessScale; - } - -#else - constexpr float redGain = 0.90f; constexpr float greenGain = 0.75f; constexpr float blueGain = 0.60f; @@ -63,7 +54,6 @@ namespace Lights c.g = LedLuminance::applyGamma(static_cast(g)); c.b = LedLuminance::applyGamma(static_cast(b)); } - #endif } } \ No newline at end of file