From adb979325a1b9fcd479f9eec7aa6fbc91eeac3d9 Mon Sep 17 00:00:00 2001 From: Jan-Petter Gundersen Date: Tue, 1 Sep 2026 19:20:00 +0200 Subject: [PATCH 01/15] Refactor Snake mode to fix heap fragmentation issues --- firmware/include/modes/SnakeMode.h | 20 ++-- firmware/src/modes/SnakeMode.cpp | 151 ++++++++++++++++------------- 2 files changed, 92 insertions(+), 79 deletions(-) diff --git a/firmware/include/modes/SnakeMode.h b/firmware/include/modes/SnakeMode.h index 7d281886..454bfb21 100644 --- a/firmware/include/modes/SnakeMode.h +++ b/firmware/include/modes/SnakeMode.h @@ -12,28 +12,28 @@ class SnakeMode final : public ModeModule { private: - struct Pixel + enum class Stage : uint8_t // NOLINT(performance-enum-size) { - uint8_t x{0U}; // NOLINT(misc-non-private-member-variables-in-classes) - uint8_t y{0U}; // NOLINT(misc-non-private-member-variables-in-classes) - bool operator==(const Pixel &pixel) const { return x == pixel.x && y == pixel.y; } - bool operator!=(const Pixel &pixel) const { return x != pixel.x || y != pixel.y; } - bool operator<(const Pixel &pixel) const { return y < pixel.y || (y == pixel.y && x < pixel.x); } + READY, + MOVE, + DEATH, + REMOVE, }; unsigned long lastMillis{0UL}; uint8_t blinkCount{0U}; - uint8_t stage{0U}; - Pixel target; + size_t target{0U}; - std::deque snake{}; + std::deque snake{}; std::unique_ptr clock{}; + Stage stage{0U}; + void idle(); - [[nodiscard]] std::optional next() const; + [[nodiscard]] std::optional next() const; void move(); void blink(); void clean(); diff --git a/firmware/src/modes/SnakeMode.cpp b/firmware/src/modes/SnakeMode.cpp index d91de932..7dd084bb 100644 --- a/firmware/src/modes/SnakeMode.cpp +++ b/firmware/src/modes/SnakeMode.cpp @@ -9,9 +9,7 @@ #include "services/DeviceService.h" #include "services/DisplayService.h" -#include #include -#include static_assert(GRID_COLUMNS >= 16U, __STRING(MODE_SNAKE) " is not compatible with this device's display size."); static_assert(GRID_ROWS >= 7U, __STRING(MODE_SNAKE) " is not compatible with this device's display size."); @@ -50,7 +48,7 @@ void SnakeMode::begin() nvs_close(handle); } Display.fillFrame(0U); - stage = 0U; + stage = Stage::READY; } /** @@ -64,17 +62,18 @@ void SnakeMode::handle() } switch (stage) { - case 1U: + case Stage::READY: + idle(); + break; + case Stage::MOVE: move(); break; - case 2U: + case Stage::DEATH: blink(); break; - case 3U: + case Stage::REMOVE: clean(); break; - default: - idle(); } } @@ -83,12 +82,10 @@ void SnakeMode::handle() */ void SnakeMode::idle() { - const uint8_t x{static_cast(random(GRID_COLUMNS))}; - const uint8_t y{static_cast(random(clock == nullptr ? 0 : 5, GRID_ROWS))}; - snake = {{x, y}}; - Display.setPixel(x, y, UINT8_MAX); + snake = {static_cast((random(clock == nullptr ? 0 : 5, GRID_ROWS) * GRID_COLUMNS) + random(GRID_COLUMNS))}; + Display.setPixel(snake.front(), static_cast(random(1, 0b1U << 8U))); setTarget(); - stage = 1U; + stage = Stage::MOVE; } /** @@ -96,80 +93,92 @@ void SnakeMode::idle() * * @return The next unoccupied position, or `std::nullopt` when no adjacent position is available. */ -std::optional SnakeMode::next() const +std::optional SnakeMode::next() const { - Pixel start{snake.back()}; - std::map from; - std::queue frontier; - frontier.push(start); - from[start] = start; + const uint8_t yMin{static_cast(clock == nullptr ? 0U : 5U)}; + std::array from{}; + std::array frontier{}; + std::array visited{}; bool pathFound{false}; - while (!frontier.empty()) + size_t frontierHead{0U}; + size_t frontierTail{0U}; + const size_t start{snake.back()}; + frontier[frontierTail++] = start; + from[start] = start; + visited[start] = true; + while (frontierHead < frontierTail) { - Pixel current{frontier.front()}; - frontier.pop(); + const size_t current{frontier[frontierHead++]}; if (current == target) { pathFound = true; break; } - std::vector neighbors; - if (current.x != 0U) + std::array neighbors{}; + size_t neighborCount{0U}; + const uint8_t currentX{static_cast(current % GRID_COLUMNS)}; + if (currentX != 0U) { - neighbors.push_back(Pixel{static_cast(current.x - 1U), current.y}); + neighbors[neighborCount++] = current - 1U; } - if (current.y > (clock == nullptr ? 0U : 5U)) + if (currentX < GRID_COLUMNS - 1U) { - neighbors.push_back(Pixel{current.x, static_cast(current.y - 1U)}); + neighbors[neighborCount++] = current + 1U; } - if (current.x < GRID_COLUMNS - 1U) + const uint8_t currentY{static_cast(current / GRID_COLUMNS)}; + if (currentY > yMin) { - neighbors.push_back(Pixel{static_cast(current.x + 1U), current.y}); + neighbors[neighborCount++] = current - GRID_COLUMNS; } - if (current.y < GRID_ROWS - 1U) + if (currentY < GRID_ROWS - 1U) { - neighbors.push_back(Pixel{current.x, static_cast(current.y + 1U)}); + neighbors[neighborCount++] = current + GRID_COLUMNS; } - for (const Pixel &neighbor : neighbors) + for (size_t idx{0U}; idx < neighborCount; ++idx) { - if (std::find(snake.begin(), snake.end(), neighbor) == snake.end() && from.find(neighbor) == from.end()) + if (visited[neighbors[idx]] || std::find(snake.begin(), snake.end(), neighbors[idx]) != snake.end()) { - frontier.push(neighbor); - from[neighbor] = current; + continue; } + visited[neighbors[idx]] = true; + from[neighbors[idx]] = current; + frontier[frontierTail++] = neighbors[idx]; } } if (pathFound) { - Pixel step{target}; - while (from[step] != start) + size_t stepIndex{target}; + while (from[stepIndex] != start) { - step = from[step]; + stepIndex = from[stepIndex]; } - return step; + return stepIndex; } - std::vector fallback; - if (start.y > (clock == nullptr ? 0U : 5U)) + std::array fallback{}; + size_t fallbackCount{0U}; + const uint8_t startX{static_cast(start % GRID_COLUMNS)}; + if (startX != 0U) { - fallback.push_back(Pixel{start.x, static_cast(start.y - 1U)}); + fallback[fallbackCount++] = start - 1U; } - if (start.x < GRID_COLUMNS - 1U) + if (startX < GRID_COLUMNS - 1U) { - fallback.push_back(Pixel{static_cast(start.x + 1U), start.y}); + fallback[fallbackCount++] = start + 1U; } - if (start.y < GRID_ROWS - 1U) + const uint8_t startY{static_cast(start / GRID_COLUMNS)}; + if (startY > yMin) { - fallback.push_back(Pixel{start.x, static_cast(start.y + 1U)}); + fallback[fallbackCount++] = start - GRID_COLUMNS; } - if (start.x != 0U) + if (startY < GRID_ROWS - 1U) { - fallback.push_back(Pixel{static_cast(start.x - 1U), start.y}); + fallback[fallbackCount++] = start + GRID_COLUMNS; } - for (const Pixel &option : fallback) + for (size_t idx{0U}; idx < fallbackCount; ++idx) { - if (std::find(snake.begin(), snake.end(), option) == snake.end()) + if (std::find(snake.begin(), snake.end(), fallback[idx]) == snake.end()) { - return option; + return fallback[idx]; } } return std::nullopt; @@ -183,15 +192,15 @@ std::optional SnakeMode::next() const */ void SnakeMode::move() { - if (millis() - lastMillis > INT8_MAX + snake.size()) + if (millis() - lastMillis > snake.size() + INT8_MAX) { - std::optional step{next()}; + std::optional step{next()}; if (step.has_value()) { snake.push_back(step.value()); if (snake.back() == target) { - Display.setPixel(target.x, target.y, UINT8_MAX); + Display.setPixel(target, UINT8_MAX); setTarget(); } else @@ -199,9 +208,9 @@ void SnakeMode::move() const uint8_t step{static_cast(UINT8_MAX / snake.size())}; for (size_t idx{0U}; idx < snake.size(); ++idx) { - Display.setPixel(snake[idx].x, snake[idx].y, step * (idx + 1U)); + Display.setPixel(snake[idx], step * (idx + 1U)); } - Display.setPixel(snake.front().x, snake.front().y, 0U); + Display.setPixel(snake.front(), 0U); snake.pop_front(); } } @@ -209,7 +218,7 @@ void SnakeMode::move() { lastMillis = millis(); blinkCount = 0U; - stage = 2U; + stage = Stage::DEATH; } lastMillis = millis(); } @@ -223,13 +232,13 @@ void SnakeMode::blink() if (millis() - lastMillis > UINT8_MAX) { const uint8_t brightness{static_cast((blinkCount & 0b1U) == 0U ? 0U : UINT8_MAX)}; - for (const Pixel &pixel : snake) + for (const size_t &pixel : snake) { - Display.setPixel(pixel.x, pixel.y, brightness); + Display.setPixel(pixel, brightness); } if (++blinkCount >= 6U) { - stage = 3U; + stage = Stage::REMOVE; } lastMillis = millis(); } @@ -239,14 +248,14 @@ void SnakeMode::clean() { if (millis() - lastMillis > INT8_MAX && !snake.empty()) { - Display.setPixel(snake.front().x, snake.front().y, 0U); + Display.setPixel(snake.front(), 0U); snake.pop_front(); lastMillis = millis(); } else if (snake.empty()) { - Display.setPixel(target.x, target.y, 0U); - stage = 0U; + Display.setPixel(target, 0U); + stage = Stage::READY; } } @@ -259,12 +268,16 @@ void SnakeMode::clean() void SnakeMode::setTarget() { const uint8_t yMin{static_cast(clock == nullptr ? 0U : 5U)}; - do // NOLINT(cppcoreguidelines-avoid-do-while) + for (uint16_t idx{0U}; idx < GRID_COLUMNS * (GRID_ROWS - yMin); ++idx) { - target.x = static_cast(random(GRID_COLUMNS)); - target.y = static_cast(random(yMin, GRID_ROWS)); - } while (Display.getPixel(target.x, target.y) != 0U); - Display.setPixel(target.x, target.y, static_cast(random(1, 0b1U << 8U))); + target = static_cast((random(yMin, GRID_ROWS) * GRID_COLUMNS) + random(GRID_COLUMNS)); + if (Display.getPixel(target) == 0U) + { + Display.setPixel(target, static_cast(random(1, 0b1U << 8U))); + return; + } + } + stage = Stage::DEATH; } /** @@ -287,7 +300,7 @@ void SnakeMode::setClock(bool _clock) if (_clock) { clock = std::make_unique(); - Display.setPixel(target.x, target.y, 0U); + Display.setPixel(target, 0U); setTarget(); } else if (clock != nullptr) From 2e328bc4debd1ed248654ee1c33d0da95484a4c6 Mon Sep 17 00:00:00 2001 From: Jan-Petter Gundersen Date: Tue, 1 Sep 2026 20:02:28 +0200 Subject: [PATCH 02/15] =?UTF-8?q?Fisher=E2=80=93Yates=20shuffle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- firmware/src/modes/SnakeMode.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/firmware/src/modes/SnakeMode.cpp b/firmware/src/modes/SnakeMode.cpp index 7dd084bb..24865583 100644 --- a/firmware/src/modes/SnakeMode.cpp +++ b/firmware/src/modes/SnakeMode.cpp @@ -174,6 +174,11 @@ std::optional SnakeMode::next() const { fallback[fallbackCount++] = start + GRID_COLUMNS; } + for (size_t idx{fallbackCount}; idx > 1U; --idx) + { + const size_t swapIndex{static_cast(random(idx))}; + std::swap(fallback[idx - 1U], fallback[swapIndex]); + } for (size_t idx{0U}; idx < fallbackCount; ++idx) { if (std::find(snake.begin(), snake.end(), fallback[idx]) == snake.end()) From 3e7d9580e5b11bfa0257be2d990d39e62044e174 Mon Sep 17 00:00:00 2001 From: Jan-Petter Gundersen Date: Tue, 1 Sep 2026 20:07:00 +0200 Subject: [PATCH 03/15] Complexity reducing refactor --- firmware/include/modes/SnakeMode.h | 3 ++- firmware/src/modes/SnakeMode.cpp | 41 ++++++++++++++++-------------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/firmware/include/modes/SnakeMode.h b/firmware/include/modes/SnakeMode.h index 454bfb21..a4ec2c58 100644 --- a/firmware/include/modes/SnakeMode.h +++ b/firmware/include/modes/SnakeMode.h @@ -33,7 +33,8 @@ class SnakeMode final : public ModeModule Stage stage{0U}; void idle(); - [[nodiscard]] std::optional next() const; + [[nodiscard]] std::optional findStepPath() const; + [[nodiscard]] std::optional findStepAny() const; void move(); void blink(); void clean(); diff --git a/firmware/src/modes/SnakeMode.cpp b/firmware/src/modes/SnakeMode.cpp index 24865583..134f3a76 100644 --- a/firmware/src/modes/SnakeMode.cpp +++ b/firmware/src/modes/SnakeMode.cpp @@ -93,7 +93,7 @@ void SnakeMode::idle() * * @return The next unoccupied position, or `std::nullopt` when no adjacent position is available. */ -std::optional SnakeMode::next() const +std::optional SnakeMode::findStepPath() const { const uint8_t yMin{static_cast(clock == nullptr ? 0U : 5U)}; std::array from{}; @@ -116,21 +116,19 @@ std::optional SnakeMode::next() const } std::array neighbors{}; size_t neighborCount{0U}; - const uint8_t currentX{static_cast(current % GRID_COLUMNS)}; - if (currentX != 0U) + if (current % GRID_COLUMNS != 0U) { neighbors[neighborCount++] = current - 1U; } - if (currentX < GRID_COLUMNS - 1U) + if (current % GRID_COLUMNS < GRID_COLUMNS - 1U) { neighbors[neighborCount++] = current + 1U; } - const uint8_t currentY{static_cast(current / GRID_COLUMNS)}; - if (currentY > yMin) + if (current / GRID_COLUMNS > yMin) { neighbors[neighborCount++] = current - GRID_COLUMNS; } - if (currentY < GRID_ROWS - 1U) + if (current / GRID_COLUMNS < GRID_ROWS - 1U) { neighbors[neighborCount++] = current + GRID_COLUMNS; } @@ -147,30 +145,35 @@ std::optional SnakeMode::next() const } if (pathFound) { - size_t stepIndex{target}; - while (from[stepIndex] != start) + size_t step{target}; + while (from[step] != start) { - stepIndex = from[stepIndex]; + step = from[step]; } - return stepIndex; + return step; } + return findStepAny(); +} + +std::optional SnakeMode::findStepAny() const +{ + const uint8_t yMin{static_cast(clock == nullptr ? 0U : 5U)}; + const size_t start{snake.back()}; std::array fallback{}; size_t fallbackCount{0U}; - const uint8_t startX{static_cast(start % GRID_COLUMNS)}; - if (startX != 0U) + if (start % GRID_COLUMNS != 0U) { fallback[fallbackCount++] = start - 1U; } - if (startX < GRID_COLUMNS - 1U) + if (start % GRID_COLUMNS < GRID_COLUMNS - 1U) { fallback[fallbackCount++] = start + 1U; } - const uint8_t startY{static_cast(start / GRID_COLUMNS)}; - if (startY > yMin) + if (start / GRID_COLUMNS > yMin) { fallback[fallbackCount++] = start - GRID_COLUMNS; } - if (startY < GRID_ROWS - 1U) + if (start / GRID_COLUMNS < GRID_ROWS - 1U) { fallback[fallbackCount++] = start + GRID_COLUMNS; } @@ -199,7 +202,7 @@ void SnakeMode::move() { if (millis() - lastMillis > snake.size() + INT8_MAX) { - std::optional step{next()}; + const std::optional step{findStepPath()}; if (step.has_value()) { snake.push_back(step.value()); @@ -273,7 +276,7 @@ void SnakeMode::clean() void SnakeMode::setTarget() { const uint8_t yMin{static_cast(clock == nullptr ? 0U : 5U)}; - for (uint16_t idx{0U}; idx < GRID_COLUMNS * (GRID_ROWS - yMin); ++idx) + for (size_t idx{0U}; idx < GRID_COLUMNS * (GRID_ROWS - yMin); ++idx) { target = static_cast((random(yMin, GRID_ROWS) * GRID_COLUMNS) + random(GRID_COLUMNS)); if (Display.getPixel(target) == 0U) From a570b568f6be0d28f0c72c3067d3b06de5735d6b Mon Sep 17 00:00:00 2001 From: Jan-Petter Gundersen Date: Tue, 1 Sep 2026 20:36:01 +0200 Subject: [PATCH 04/15] Minor refactor --- firmware/src/modes/SnakeMode.cpp | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/firmware/src/modes/SnakeMode.cpp b/firmware/src/modes/SnakeMode.cpp index 134f3a76..c4127ed0 100644 --- a/firmware/src/modes/SnakeMode.cpp +++ b/firmware/src/modes/SnakeMode.cpp @@ -99,7 +99,6 @@ std::optional SnakeMode::findStepPath() const std::array from{}; std::array frontier{}; std::array visited{}; - bool pathFound{false}; size_t frontierHead{0U}; size_t frontierTail{0U}; const size_t start{snake.back()}; @@ -111,8 +110,12 @@ std::optional SnakeMode::findStepPath() const const size_t current{frontier[frontierHead++]}; if (current == target) { - pathFound = true; - break; + size_t step{target}; + while (from[step] != start) + { + step = from[step]; + } + return step; } std::array neighbors{}; size_t neighborCount{0U}; @@ -143,15 +146,6 @@ std::optional SnakeMode::findStepPath() const frontier[frontierTail++] = neighbors[idx]; } } - if (pathFound) - { - size_t step{target}; - while (from[step] != start) - { - step = from[step]; - } - return step; - } return findStepAny(); } @@ -179,8 +173,7 @@ std::optional SnakeMode::findStepAny() const } for (size_t idx{fallbackCount}; idx > 1U; --idx) { - const size_t swapIndex{static_cast(random(idx))}; - std::swap(fallback[idx - 1U], fallback[swapIndex]); + std::swap(fallback[idx - 1U], fallback[static_cast(random(static_cast(idx)))]); } for (size_t idx{0U}; idx < fallbackCount; ++idx) { From f6e50f997f072a4c26bb98a0cb37c6dd00811f18 Mon Sep 17 00:00:00 2001 From: Jan-Petter Gundersen Date: Tue, 1 Sep 2026 21:19:54 +0200 Subject: [PATCH 05/15] Nolint false positive --- firmware/src/modes/SnakeMode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/src/modes/SnakeMode.cpp b/firmware/src/modes/SnakeMode.cpp index c4127ed0..2a68dff3 100644 --- a/firmware/src/modes/SnakeMode.cpp +++ b/firmware/src/modes/SnakeMode.cpp @@ -115,7 +115,7 @@ std::optional SnakeMode::findStepPath() const { step = from[step]; } - return step; + return step; // NOLINT(bugprone-narrowing-conversions) } std::array neighbors{}; size_t neighborCount{0U}; From bc783f5229eec2f69a85b6122dcfb878108ecaa5 Mon Sep 17 00:00:00 2001 From: Jan-Petter Gundersen Date: Tue, 1 Sep 2026 22:14:14 +0200 Subject: [PATCH 06/15] Fix Stage initialization value Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- firmware/include/modes/SnakeMode.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/include/modes/SnakeMode.h b/firmware/include/modes/SnakeMode.h index a4ec2c58..131318dc 100644 --- a/firmware/include/modes/SnakeMode.h +++ b/firmware/include/modes/SnakeMode.h @@ -30,7 +30,7 @@ class SnakeMode final : public ModeModule std::unique_ptr clock{}; - Stage stage{0U}; + Stage stage{Stage::READY}; void idle(); [[nodiscard]] std::optional findStepPath() const; From 8bea6b59b5a1c886ea892596018568969891cd97 Mon Sep 17 00:00:00 2001 From: Jan-Petter Gundersen Date: Tue, 1 Sep 2026 22:17:43 +0200 Subject: [PATCH 07/15] Add existing transitive includes Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- firmware/src/modes/SnakeMode.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/firmware/src/modes/SnakeMode.cpp b/firmware/src/modes/SnakeMode.cpp index 2a68dff3..63f6e9e7 100644 --- a/firmware/src/modes/SnakeMode.cpp +++ b/firmware/src/modes/SnakeMode.cpp @@ -9,7 +9,9 @@ #include "services/DeviceService.h" #include "services/DisplayService.h" -#include +#include +#include +#include = 16U, __STRING(MODE_SNAKE) " is not compatible with this device's display size."); static_assert(GRID_ROWS >= 7U, __STRING(MODE_SNAKE) " is not compatible with this device's display size."); From 80bb48ff37f54de6bc3df35cf58121fb8cf739a8 Mon Sep 17 00:00:00 2001 From: Jan-Petter Gundersen Date: Tue, 1 Sep 2026 22:18:57 +0200 Subject: [PATCH 08/15] Fix death animation Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- firmware/src/modes/SnakeMode.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/firmware/src/modes/SnakeMode.cpp b/firmware/src/modes/SnakeMode.cpp index 63f6e9e7..c089fc0c 100644 --- a/firmware/src/modes/SnakeMode.cpp +++ b/firmware/src/modes/SnakeMode.cpp @@ -280,6 +280,8 @@ void SnakeMode::setTarget() return; } } + blinkCount = 0U; + lastMillis = millis(); stage = Stage::DEATH; } From 5fe73b1801e44109809f8e5da0a0e1a944b22fc8 Mon Sep 17 00:00:00 2001 From: Jan-Petter Gundersen Date: Tue, 1 Sep 2026 23:27:55 +0200 Subject: [PATCH 09/15] Refactoring --- firmware/include/modes/SnakeMode.h | 1 + firmware/src/modes/SnakeMode.cpp | 34 ++++++++++++++++++------------ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/firmware/include/modes/SnakeMode.h b/firmware/include/modes/SnakeMode.h index 131318dc..786e44f2 100644 --- a/firmware/include/modes/SnakeMode.h +++ b/firmware/include/modes/SnakeMode.h @@ -40,6 +40,7 @@ class SnakeMode final : public ModeModule void clean(); void setClock(bool _clock); + void setDead(); void setTarget(); void transmit(); diff --git a/firmware/src/modes/SnakeMode.cpp b/firmware/src/modes/SnakeMode.cpp index c089fc0c..e4b2ff06 100644 --- a/firmware/src/modes/SnakeMode.cpp +++ b/firmware/src/modes/SnakeMode.cpp @@ -11,7 +11,7 @@ #include #include -#include static_assert(GRID_COLUMNS >= 16U, __STRING(MODE_SNAKE) " is not compatible with this device's display size."); static_assert(GRID_ROWS >= 7U, __STRING(MODE_SNAKE) " is not compatible with this device's display size."); @@ -86,7 +86,8 @@ void SnakeMode::idle() { snake = {static_cast((random(clock == nullptr ? 0 : 5, GRID_ROWS) * GRID_COLUMNS) + random(GRID_COLUMNS))}; Display.setPixel(snake.front(), static_cast(random(1, 0b1U << 8U))); - setTarget(); + target = static_cast(random(static_cast(clock == nullptr ? 0U : 5U * GRID_COLUMNS), + static_cast(GRID_COLUMNS * GRID_ROWS))); stage = Stage::MOVE; } @@ -219,9 +220,7 @@ void SnakeMode::move() } else { - lastMillis = millis(); - blinkCount = 0U; - stage = Stage::DEATH; + setDead(); } lastMillis = millis(); } @@ -262,6 +261,13 @@ void SnakeMode::clean() } } +void SnakeMode::setDead() +{ + blinkCount = 0U; + lastMillis = millis(); + stage = Stage::DEATH; +} + /** * @brief Selects an unoccupied display position as the snake's target. * @@ -270,19 +276,21 @@ void SnakeMode::clean() */ void SnakeMode::setTarget() { - const uint8_t yMin{static_cast(clock == nullptr ? 0U : 5U)}; - for (size_t idx{0U}; idx < GRID_COLUMNS * (GRID_ROWS - yMin); ++idx) + const size_t offset{static_cast(clock == nullptr ? 0U : 5U * GRID_COLUMNS)}; + for (size_t idx{offset}; idx < GRID_COLUMNS * GRID_ROWS; ++idx) { - target = static_cast((random(yMin, GRID_ROWS) * GRID_COLUMNS) + random(GRID_COLUMNS)); - if (Display.getPixel(target) == 0U) + if (Display.getPixel(idx) == 0U) { - Display.setPixel(target, static_cast(random(1, 0b1U << 8U))); + while (Display.getPixel(target) != 0U) + { + target = + static_cast(random(static_cast(offset), static_cast(GRID_COLUMNS * GRID_ROWS))); + } + Display.setPixel(target, static_cast(random(1L, static_cast(0b1U << 8U)))); return; } } - blinkCount = 0U; - lastMillis = millis(); - stage = Stage::DEATH; + setDead(); } /** From e2e75ef30be96bc073bee57d46208232cd09e2e7 Mon Sep 17 00:00:00 2001 From: Jan-Petter Gundersen Date: Tue, 1 Sep 2026 23:45:44 +0200 Subject: [PATCH 10/15] Attempt fixing false positive Clang-tidy finding --- firmware/src/modes/SnakeMode.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/firmware/src/modes/SnakeMode.cpp b/firmware/src/modes/SnakeMode.cpp index e4b2ff06..08ae1ecf 100644 --- a/firmware/src/modes/SnakeMode.cpp +++ b/firmware/src/modes/SnakeMode.cpp @@ -118,7 +118,7 @@ std::optional SnakeMode::findStepPath() const { step = from[step]; } - return step; // NOLINT(bugprone-narrowing-conversions) + return std::optional{step}; } std::array neighbors{}; size_t neighborCount{0U}; @@ -182,7 +182,7 @@ std::optional SnakeMode::findStepAny() const { if (std::find(snake.begin(), snake.end(), fallback[idx]) == snake.end()) { - return fallback[idx]; + return std::optional{fallback[idx]}; } } return std::nullopt; From 835ba8d0b98331524c447338554958594d785c9d Mon Sep 17 00:00:00 2001 From: Jan-Petter Gundersen Date: Tue, 1 Sep 2026 23:56:37 +0200 Subject: [PATCH 11/15] Potential fix for another Clang-tidy false positive --- firmware/src/modes/SnakeMode.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/firmware/src/modes/SnakeMode.cpp b/firmware/src/modes/SnakeMode.cpp index 08ae1ecf..06ff38ff 100644 --- a/firmware/src/modes/SnakeMode.cpp +++ b/firmware/src/modes/SnakeMode.cpp @@ -110,7 +110,8 @@ std::optional SnakeMode::findStepPath() const visited[start] = true; while (frontierHead < frontierTail) { - const size_t current{frontier[frontierHead++]}; + const size_t current{frontier[frontierHead]}; + ++frontierHead; if (current == target) { size_t step{target}; @@ -146,7 +147,8 @@ std::optional SnakeMode::findStepPath() const } visited[neighbors[idx]] = true; from[neighbors[idx]] = current; - frontier[frontierTail++] = neighbors[idx]; + frontier[frontierTail] = neighbors[idx]; + ++frontierTail; } } return findStepAny(); From 11834865b267ff48311eec48bd5e4729e332f09b Mon Sep 17 00:00:00 2001 From: Jan-Petter Gundersen Date: Wed, 2 Sep 2026 00:13:12 +0200 Subject: [PATCH 12/15] Refactor --- firmware/include/modes/SnakeMode.h | 10 +++++----- firmware/src/modes/SnakeMode.cpp | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/firmware/include/modes/SnakeMode.h b/firmware/include/modes/SnakeMode.h index 786e44f2..6445a309 100644 --- a/firmware/include/modes/SnakeMode.h +++ b/firmware/include/modes/SnakeMode.h @@ -32,18 +32,18 @@ class SnakeMode final : public ModeModule Stage stage{Stage::READY}; - void idle(); - [[nodiscard]] std::optional findStepPath() const; - [[nodiscard]] std::optional findStepAny() const; - void move(); void blink(); void clean(); - + void idle(); + void move(); void setClock(bool _clock); void setDead(); void setTarget(); void transmit(); + [[nodiscard]] std::optional findStepAvailable() const; + [[nodiscard]] std::optional findStepPath() const; + public: static constexpr std::string_view name{"Snake"}; diff --git a/firmware/src/modes/SnakeMode.cpp b/firmware/src/modes/SnakeMode.cpp index 06ff38ff..b61f4431 100644 --- a/firmware/src/modes/SnakeMode.cpp +++ b/firmware/src/modes/SnakeMode.cpp @@ -99,12 +99,12 @@ void SnakeMode::idle() std::optional SnakeMode::findStepPath() const { const uint8_t yMin{static_cast(clock == nullptr ? 0U : 5U)}; + const size_t start{snake.back()}; std::array from{}; std::array frontier{}; std::array visited{}; size_t frontierHead{0U}; size_t frontierTail{0U}; - const size_t start{snake.back()}; frontier[frontierTail++] = start; from[start] = start; visited[start] = true; @@ -151,10 +151,10 @@ std::optional SnakeMode::findStepPath() const ++frontierTail; } } - return findStepAny(); + return findStepAvailable(); } -std::optional SnakeMode::findStepAny() const +std::optional SnakeMode::findStepAvailable() const { const uint8_t yMin{static_cast(clock == nullptr ? 0U : 5U)}; const size_t start{snake.back()}; From c7c82d740147f43e33959e54c5323db25cf01fea Mon Sep 17 00:00:00 2001 From: Jan-Petter Gundersen Date: Wed, 2 Sep 2026 00:36:41 +0200 Subject: [PATCH 13/15] Fix potential initialization issue --- firmware/src/modes/SnakeMode.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/firmware/src/modes/SnakeMode.cpp b/firmware/src/modes/SnakeMode.cpp index b61f4431..8c9bba6e 100644 --- a/firmware/src/modes/SnakeMode.cpp +++ b/firmware/src/modes/SnakeMode.cpp @@ -50,6 +50,8 @@ void SnakeMode::begin() nvs_close(handle); } Display.fillFrame(0U); + target = static_cast(random(clock == nullptr ? 0L : static_cast(5U * GRID_COLUMNS), + static_cast(GRID_COLUMNS * GRID_ROWS))); stage = Stage::READY; } @@ -84,10 +86,10 @@ void SnakeMode::handle() */ void SnakeMode::idle() { - snake = {static_cast((random(clock == nullptr ? 0 : 5, GRID_ROWS) * GRID_COLUMNS) + random(GRID_COLUMNS))}; - Display.setPixel(snake.front(), static_cast(random(1, 0b1U << 8U))); - target = static_cast(random(static_cast(clock == nullptr ? 0U : 5U * GRID_COLUMNS), - static_cast(GRID_COLUMNS * GRID_ROWS))); + snake = {static_cast(random(clock == nullptr ? 0L : static_cast(5U * GRID_COLUMNS), + static_cast(GRID_COLUMNS * GRID_ROWS)))}; + Display.setPixel(snake.front(), static_cast(random(1L, static_cast(0b1U << 8U)))); + setTarget(); stage = Stage::MOVE; } From bb1469becae1b65b7a360c265d8864cfc11b9cd1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 1 Sep 2026 23:38:44 +0000 Subject: [PATCH 14/15] Replace Snake deque with fixed-capacity ring buffer Co-authored-by: JanPetterMG <11933090+JanPetterMG@users.noreply.github.com> --- firmware/include/modes/SnakeMode.h | 15 ++++- firmware/src/modes/SnakeMode.cpp | 102 +++++++++++++++++++++++------ 2 files changed, 94 insertions(+), 23 deletions(-) diff --git a/firmware/include/modes/SnakeMode.h b/firmware/include/modes/SnakeMode.h index 6445a309..3a1d4818 100644 --- a/firmware/include/modes/SnakeMode.h +++ b/firmware/include/modes/SnakeMode.h @@ -5,8 +5,8 @@ #include "handlers/ClockHandler.h" // NOLINT(misc-include-cleaner) #include "modules/ModeModule.h" +#include #include -#include #include class SnakeMode final : public ModeModule @@ -26,7 +26,11 @@ class SnakeMode final : public ModeModule size_t target{0U}; - std::deque snake{}; + static constexpr size_t snakeCapacity{GRID_COLUMNS * GRID_ROWS}; + std::array snake{}; + std::array snakeOccupied{}; + size_t snakeHeadIndex{0U}; + size_t snakeLength{0U}; std::unique_ptr clock{}; @@ -39,6 +43,13 @@ class SnakeMode final : public ModeModule void setClock(bool _clock); void setDead(); void setTarget(); + void snakeReset(size_t start); + void snakeClear(); + bool snakePushBack(size_t pixel); + size_t snakePopFront(); + [[nodiscard]] size_t snakeHead() const; + [[nodiscard]] size_t snakeAt(size_t index) const; + [[nodiscard]] bool snakeContains(size_t pixel) const; void transmit(); [[nodiscard]] std::optional findStepAvailable() const; diff --git a/firmware/src/modes/SnakeMode.cpp b/firmware/src/modes/SnakeMode.cpp index 8c9bba6e..995ebe9c 100644 --- a/firmware/src/modes/SnakeMode.cpp +++ b/firmware/src/modes/SnakeMode.cpp @@ -50,6 +50,7 @@ void SnakeMode::begin() nvs_close(handle); } Display.fillFrame(0U); + snakeClear(); target = static_cast(random(clock == nullptr ? 0L : static_cast(5U * GRID_COLUMNS), static_cast(GRID_COLUMNS * GRID_ROWS))); stage = Stage::READY; @@ -86,9 +87,9 @@ void SnakeMode::handle() */ void SnakeMode::idle() { - snake = {static_cast(random(clock == nullptr ? 0L : static_cast(5U * GRID_COLUMNS), - static_cast(GRID_COLUMNS * GRID_ROWS)))}; - Display.setPixel(snake.front(), static_cast(random(1L, static_cast(0b1U << 8U)))); + snakeReset(static_cast(random(clock == nullptr ? 0L : static_cast(5U * GRID_COLUMNS), + static_cast(GRID_COLUMNS * GRID_ROWS)))); + Display.setPixel(snakeHead(), static_cast(random(1L, static_cast(0b1U << 8U)))); setTarget(); stage = Stage::MOVE; } @@ -101,7 +102,7 @@ void SnakeMode::idle() std::optional SnakeMode::findStepPath() const { const uint8_t yMin{static_cast(clock == nullptr ? 0U : 5U)}; - const size_t start{snake.back()}; + const size_t start{snakeHead()}; std::array from{}; std::array frontier{}; std::array visited{}; @@ -143,7 +144,7 @@ std::optional SnakeMode::findStepPath() const } for (size_t idx{0U}; idx < neighborCount; ++idx) { - if (visited[neighbors[idx]] || std::find(snake.begin(), snake.end(), neighbors[idx]) != snake.end()) + if (visited[neighbors[idx]] || snakeContains(neighbors[idx])) { continue; } @@ -159,7 +160,7 @@ std::optional SnakeMode::findStepPath() const std::optional SnakeMode::findStepAvailable() const { const uint8_t yMin{static_cast(clock == nullptr ? 0U : 5U)}; - const size_t start{snake.back()}; + const size_t start{snakeHead()}; std::array fallback{}; size_t fallbackCount{0U}; if (start % GRID_COLUMNS != 0U) @@ -184,7 +185,7 @@ std::optional SnakeMode::findStepAvailable() const } for (size_t idx{0U}; idx < fallbackCount; ++idx) { - if (std::find(snake.begin(), snake.end(), fallback[idx]) == snake.end()) + if (!snakeContains(fallback[idx])) { return std::optional{fallback[idx]}; } @@ -200,26 +201,28 @@ std::optional SnakeMode::findStepAvailable() const */ void SnakeMode::move() { - if (millis() - lastMillis > snake.size() + INT8_MAX) + if (millis() - lastMillis > snakeLength + INT8_MAX) { const std::optional step{findStepPath()}; if (step.has_value()) { - snake.push_back(step.value()); - if (snake.back() == target) + if (!snakePushBack(step.value())) + { + setDead(); + } + else if (snakeHead() == target) { Display.setPixel(target, UINT8_MAX); setTarget(); } else { - const uint8_t step{static_cast(UINT8_MAX / snake.size())}; - for (size_t idx{0U}; idx < snake.size(); ++idx) + const uint8_t step{static_cast(UINT8_MAX / snakeLength)}; + for (size_t idx{0U}; idx < snakeLength; ++idx) { - Display.setPixel(snake[idx], step * (idx + 1U)); + Display.setPixel(snakeAt(idx), step * (idx + 1U)); } - Display.setPixel(snake.front(), 0U); - snake.pop_front(); + Display.setPixel(snakePopFront(), 0U); } } else @@ -238,9 +241,9 @@ void SnakeMode::blink() if (millis() - lastMillis > UINT8_MAX) { const uint8_t brightness{static_cast((blinkCount & 0b1U) == 0U ? 0U : UINT8_MAX)}; - for (const size_t &pixel : snake) + for (size_t idx{0U}; idx < snakeLength; ++idx) { - Display.setPixel(pixel, brightness); + Display.setPixel(snakeAt(idx), brightness); } if (++blinkCount >= 6U) { @@ -252,19 +255,76 @@ void SnakeMode::blink() void SnakeMode::clean() { - if (millis() - lastMillis > INT8_MAX && !snake.empty()) + if (millis() - lastMillis > INT8_MAX && snakeLength > 0U) { - Display.setPixel(snake.front(), 0U); - snake.pop_front(); + Display.setPixel(snakePopFront(), 0U); lastMillis = millis(); } - else if (snake.empty()) + else if (snakeLength == 0U) { Display.setPixel(target, 0U); stage = Stage::READY; } } +void SnakeMode::snakeReset(size_t start) +{ + snakeClear(); + snake[0U] = start; + snakeOccupied[start] = true; + snakeHeadIndex = 0U; + snakeLength = 1U; +} + +void SnakeMode::snakeClear() +{ + snakeOccupied.fill(false); + snakeHeadIndex = 0U; + snakeLength = 0U; +} + +bool SnakeMode::snakePushBack(size_t pixel) +{ + if (snakeLength >= snakeCapacity) + { + return false; + } + snakeHeadIndex = (snakeHeadIndex + 1U) % snakeCapacity; + snake[snakeHeadIndex] = pixel; + snakeOccupied[pixel] = true; + ++snakeLength; + return true; +} + +size_t SnakeMode::snakePopFront() +{ + const size_t tailIndex{(snakeHeadIndex + snakeCapacity - (snakeLength - 1U)) % snakeCapacity}; + const size_t tail{snake[tailIndex]}; + snakeOccupied[tail] = false; + --snakeLength; + if (snakeLength == 0U) + { + snakeHeadIndex = 0U; + } + return tail; +} + +size_t SnakeMode::snakeHead() const +{ + return snake[snakeHeadIndex]; +} + +size_t SnakeMode::snakeAt(size_t index) const +{ + const size_t tailIndex{(snakeHeadIndex + snakeCapacity - (snakeLength - 1U)) % snakeCapacity}; + return snake[(tailIndex + index) % snakeCapacity]; +} + +bool SnakeMode::snakeContains(size_t pixel) const +{ + return snakeOccupied[pixel]; +} + void SnakeMode::setDead() { blinkCount = 0U; From 4d38adbdce6a702ba5c1e28b3fed06c6d017db84 Mon Sep 17 00:00:00 2001 From: Jan-Petter Gundersen Date: Sat, 5 Sep 2026 17:15:10 +0200 Subject: [PATCH 15/15] WIP --- firmware/include/modes/SnakeMode.h | 19 ++-- firmware/src/modes/SnakeMode.cpp | 156 ++++++++++++++--------------- 2 files changed, 84 insertions(+), 91 deletions(-) diff --git a/firmware/include/modes/SnakeMode.h b/firmware/include/modes/SnakeMode.h index 3a1d4818..9c418b86 100644 --- a/firmware/include/modes/SnakeMode.h +++ b/firmware/include/modes/SnakeMode.h @@ -24,13 +24,12 @@ class SnakeMode final : public ModeModule uint8_t blinkCount{0U}; + size_t head{0U}; + size_t length{0U}; size_t target{0U}; - static constexpr size_t snakeCapacity{GRID_COLUMNS * GRID_ROWS}; - std::array snake{}; - std::array snakeOccupied{}; - size_t snakeHeadIndex{0U}; - size_t snakeLength{0U}; + std::array snake{}; + std::array occupied{}; std::unique_ptr clock{}; @@ -45,13 +44,13 @@ class SnakeMode final : public ModeModule void setTarget(); void snakeReset(size_t start); void snakeClear(); - bool snakePushBack(size_t pixel); - size_t snakePopFront(); - [[nodiscard]] size_t snakeHead() const; - [[nodiscard]] size_t snakeAt(size_t index) const; - [[nodiscard]] bool snakeContains(size_t pixel) const; void transmit(); + [[nodiscard]] bool snakePushBack(size_t pixel); + + [[nodiscard]] size_t snakeAt(size_t index) const; + [[nodiscard]] size_t snakePopFront(); + [[nodiscard]] std::optional findStepAvailable() const; [[nodiscard]] std::optional findStepPath() const; diff --git a/firmware/src/modes/SnakeMode.cpp b/firmware/src/modes/SnakeMode.cpp index 995ebe9c..34f07024 100644 --- a/firmware/src/modes/SnakeMode.cpp +++ b/firmware/src/modes/SnakeMode.cpp @@ -89,7 +89,7 @@ void SnakeMode::idle() { snakeReset(static_cast(random(clock == nullptr ? 0L : static_cast(5U * GRID_COLUMNS), static_cast(GRID_COLUMNS * GRID_ROWS)))); - Display.setPixel(snakeHead(), static_cast(random(1L, static_cast(0b1U << 8U)))); + Display.setPixel(snake[head], static_cast(random(1L, static_cast(0b1U << 8U)))); setTarget(); stage = Stage::MOVE; } @@ -102,15 +102,14 @@ void SnakeMode::idle() std::optional SnakeMode::findStepPath() const { const uint8_t yMin{static_cast(clock == nullptr ? 0U : 5U)}; - const size_t start{snakeHead()}; std::array from{}; std::array frontier{}; std::array visited{}; size_t frontierHead{0U}; size_t frontierTail{0U}; - frontier[frontierTail++] = start; - from[start] = start; - visited[start] = true; + frontier[frontierTail++] = snake[head]; + from[snake[head]] = snake[head]; + visited[snake[head]] = true; while (frontierHead < frontierTail) { const size_t current{frontier[frontierHead]}; @@ -118,33 +117,33 @@ std::optional SnakeMode::findStepPath() const if (current == target) { size_t step{target}; - while (from[step] != start) + while (from[step] != snake[head]) { step = from[step]; } return std::optional{step}; } std::array neighbors{}; - size_t neighborCount{0U}; + size_t count{0U}; if (current % GRID_COLUMNS != 0U) { - neighbors[neighborCount++] = current - 1U; + neighbors[count++] = current - 1U; } if (current % GRID_COLUMNS < GRID_COLUMNS - 1U) { - neighbors[neighborCount++] = current + 1U; + neighbors[count++] = current + 1U; } if (current / GRID_COLUMNS > yMin) { - neighbors[neighborCount++] = current - GRID_COLUMNS; + neighbors[count++] = current - GRID_COLUMNS; } if (current / GRID_COLUMNS < GRID_ROWS - 1U) { - neighbors[neighborCount++] = current + GRID_COLUMNS; + neighbors[count++] = current + GRID_COLUMNS; } - for (size_t idx{0U}; idx < neighborCount; ++idx) + for (size_t idx{0U}; idx < count; ++idx) { - if (visited[neighbors[idx]] || snakeContains(neighbors[idx])) + if (visited[neighbors[idx]] || occupied[neighbors[idx]]) { continue; } @@ -159,38 +158,43 @@ std::optional SnakeMode::findStepPath() const std::optional SnakeMode::findStepAvailable() const { - const uint8_t yMin{static_cast(clock == nullptr ? 0U : 5U)}; - const size_t start{snakeHead()}; - std::array fallback{}; - size_t fallbackCount{0U}; - if (start % GRID_COLUMNS != 0U) - { - fallback[fallbackCount++] = start - 1U; - } - if (start % GRID_COLUMNS < GRID_COLUMNS - 1U) + std::array available{}; + size_t count{0U}; + if (snake[head] % GRID_COLUMNS > 0U) { - fallback[fallbackCount++] = start + 1U; + available[count++] = snake[head] - 1U; } - if (start / GRID_COLUMNS > yMin) + if (snake[head] % GRID_COLUMNS < GRID_COLUMNS - 1U) { - fallback[fallbackCount++] = start - GRID_COLUMNS; + available[count++] = snake[head] + 1U; } - if (start / GRID_COLUMNS < GRID_ROWS - 1U) + if (snake[head] / GRID_COLUMNS > (clock == nullptr ? 0U : 5U)) { - fallback[fallbackCount++] = start + GRID_COLUMNS; + available[count++] = snake[head] - GRID_COLUMNS; } - for (size_t idx{fallbackCount}; idx > 1U; --idx) + if (snake[head] / GRID_COLUMNS < GRID_ROWS - 1U) { - std::swap(fallback[idx - 1U], fallback[static_cast(random(static_cast(idx)))]); + available[count++] = snake[head] + GRID_COLUMNS; } - for (size_t idx{0U}; idx < fallbackCount; ++idx) + std::optional best{}; + size_t bestDistance{SIZE_MAX}; + for (size_t idx{0U}; idx < count; ++idx) { - if (!snakeContains(fallback[idx])) + if (occupied[available[idx]]) + { + continue; + } + const size_t distance{static_cast(std::abs(static_cast(available[idx] % GRID_COLUMNS) - + static_cast(target % GRID_COLUMNS))) + + static_cast(std::abs(static_cast(available[idx] / GRID_COLUMNS) - + static_cast(target / GRID_COLUMNS)))}; + if (distance < bestDistance) { - return std::optional{fallback[idx]}; + best = available[idx]; + bestDistance = distance; } } - return std::nullopt; + return best; } /** @@ -201,7 +205,7 @@ std::optional SnakeMode::findStepAvailable() const */ void SnakeMode::move() { - if (millis() - lastMillis > snakeLength + INT8_MAX) + if (millis() - lastMillis > length + INT8_MAX) { const std::optional step{findStepPath()}; if (step.has_value()) @@ -210,15 +214,15 @@ void SnakeMode::move() { setDead(); } - else if (snakeHead() == target) + else if (snake[head] == target) { Display.setPixel(target, UINT8_MAX); setTarget(); } else { - const uint8_t step{static_cast(UINT8_MAX / snakeLength)}; - for (size_t idx{0U}; idx < snakeLength; ++idx) + const uint8_t step{static_cast(UINT8_MAX / length)}; + for (size_t idx{0U}; idx < length; ++idx) { Display.setPixel(snakeAt(idx), step * (idx + 1U)); } @@ -241,7 +245,7 @@ void SnakeMode::blink() if (millis() - lastMillis > UINT8_MAX) { const uint8_t brightness{static_cast((blinkCount & 0b1U) == 0U ? 0U : UINT8_MAX)}; - for (size_t idx{0U}; idx < snakeLength; ++idx) + for (size_t idx{0U}; idx < length; ++idx) { Display.setPixel(snakeAt(idx), brightness); } @@ -255,12 +259,12 @@ void SnakeMode::blink() void SnakeMode::clean() { - if (millis() - lastMillis > INT8_MAX && snakeLength > 0U) + if (millis() - lastMillis > INT8_MAX && length > 0U) { Display.setPixel(snakePopFront(), 0U); lastMillis = millis(); } - else if (snakeLength == 0U) + else if (length == 0U) { Display.setPixel(target, 0U); stage = Stage::READY; @@ -271,58 +275,47 @@ void SnakeMode::snakeReset(size_t start) { snakeClear(); snake[0U] = start; - snakeOccupied[start] = true; - snakeHeadIndex = 0U; - snakeLength = 1U; + occupied[start] = true; + head = 0U; + length = 1U; } void SnakeMode::snakeClear() { - snakeOccupied.fill(false); - snakeHeadIndex = 0U; - snakeLength = 0U; + occupied.fill(false); + head = 0U; + length = 0U; } bool SnakeMode::snakePushBack(size_t pixel) { - if (snakeLength >= snakeCapacity) + if (length >= GRID_COLUMNS * GRID_ROWS) { return false; } - snakeHeadIndex = (snakeHeadIndex + 1U) % snakeCapacity; - snake[snakeHeadIndex] = pixel; - snakeOccupied[pixel] = true; - ++snakeLength; + head = (head + 1U) % (GRID_COLUMNS * GRID_ROWS); + snake[head] = pixel; + occupied[pixel] = true; + ++length; return true; } size_t SnakeMode::snakePopFront() { - const size_t tailIndex{(snakeHeadIndex + snakeCapacity - (snakeLength - 1U)) % snakeCapacity}; - const size_t tail{snake[tailIndex]}; - snakeOccupied[tail] = false; - --snakeLength; - if (snakeLength == 0U) + const size_t tail{((GRID_COLUMNS * GRID_ROWS) + head - (length - 1U)) % (GRID_COLUMNS * GRID_ROWS)}; + occupied[snake[tail]] = false; + --length; + if (length == 0U) { - snakeHeadIndex = 0U; + head = 0U; } - return tail; -} - -size_t SnakeMode::snakeHead() const -{ - return snake[snakeHeadIndex]; + return snake[tail]; } size_t SnakeMode::snakeAt(size_t index) const { - const size_t tailIndex{(snakeHeadIndex + snakeCapacity - (snakeLength - 1U)) % snakeCapacity}; - return snake[(tailIndex + index) % snakeCapacity]; -} - -bool SnakeMode::snakeContains(size_t pixel) const -{ - return snakeOccupied[pixel]; + const size_t tail{((GRID_COLUMNS * GRID_ROWS) + head - (length - 1U)) % (GRID_COLUMNS * GRID_ROWS)}; + return snake[(tail + index) % (GRID_COLUMNS * GRID_ROWS)]; } void SnakeMode::setDead() @@ -340,21 +333,22 @@ void SnakeMode::setDead() */ void SnakeMode::setTarget() { - const size_t offset{static_cast(clock == nullptr ? 0U : 5U * GRID_COLUMNS)}; - for (size_t idx{offset}; idx < GRID_COLUMNS * GRID_ROWS; ++idx) + std::array available{}; + size_t count{0U}; + for (size_t idx{static_cast(clock == nullptr ? 0U : 5U * GRID_COLUMNS)}; idx < available.size(); ++idx) { - if (Display.getPixel(idx) == 0U) + if (!occupied[idx]) { - while (Display.getPixel(target) != 0U) - { - target = - static_cast(random(static_cast(offset), static_cast(GRID_COLUMNS * GRID_ROWS))); - } - Display.setPixel(target, static_cast(random(1L, static_cast(0b1U << 8U)))); - return; + available[count++] = idx; } } - setDead(); + if (count == 0U) + { + setDead(); + return; + } + target = available[static_cast(random(static_cast(count)))]; + Display.setPixel(target, static_cast(random(1L, static_cast(0b1U << 8U)))); } /**