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