Files
firmware/src/modules/games/GamesModule.h
T
8d1fbbf55f Snake! (#10936)
* Snake!

* Add spiLock to snake score saving

* Check fixes

* More careful locking

* WIP: Big Display Node

* Update src/graphics/HUB75Display.cpp

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Add HUB75 Native

* Add Tetris game module with core logic and UI integration

- Implement TetrisGame class for game logic, including piece movement, rotation, and line clearing.
- Create TetrisModule class to manage game state, input handling, and rendering on OLED display.
- Introduce high score tracking with persistence and optional mesh broadcasting.
- Define UI states for title, playing, paused, game over, and high scores.
- Implement input handling for game controls and state transitions.
- Add rendering functions for the game board, high scores, and title screen.

* feat(snake): add snake graphics and update display logic in SnakeModule

* Prompt for Initials for Tetris, too

* refactor games module

* Games refactor

* hub75 native double buffer

* Games tuning

* Make joystick repeat events on held button

* Add clouds and colors

* Fix breakout and more color

* difficulty tuning

* trunk

* refactor game announcements, etc

* Scale chirpy gravity as game speed increases

* Portduino, check for hub75 display before reading in hub75 options

* Final(?) games tuning

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Properly ignore input when games screen not shown

* Fail gracefully when HUB75 is selected but not supported.

---------

Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Ixitxachitl <kramerfm@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-16 18:35:33 -05:00

102 lines
4.5 KiB
C++

#pragma once
#include "configuration.h"
#if HAS_SCREEN && BASEUI_HAS_GAMES
#include "Game.h"
#include "Observer.h"
#include "concurrency/OSThread.h"
#include "input/InputBroker.h"
#include "mesh/SinglePortModule.h"
#include <vector>
// Broadcasting a new all-time #1 to the mesh is a COMPILE-TIME option, default OFF. Spending shared
// airtime must be opted into at build time with -DGAMES_ANNOUNCE_HIGH_SCORE=1; when disabled (the
// default) the announcement code is compiled out entirely -- there is no runtime toggle. The
// announcement is shared by every hosted game, with the game's name() spliced into the message.
#ifndef GAMES_ANNOUNCE_HIGH_SCORE
#define GAMES_ANNOUNCE_HIGH_SCORE 0
#endif
#ifndef GAMES_HIGH_SCORE_STRING
#define GAMES_HIGH_SCORE_STRING "New %s high score %lu by %s!"
#endif
enum GamesUiState : uint8_t {
GAMES_IDLE, // attract screen of the selected game; OSThread idle (unless a game broadcasts)
GAMES_PLAYING, // active game running; tick thread ticking
GAMES_PAUSED, // paused mid-game
GAMES_GAMEOVER, // final score / new-high notice
GAMES_HISCORES, // top-5 table of the active/selected game
};
/**
* The single host for all BaseUI games. It owns the always-present "games" frame (drawn through
* Screen's trampoline right after home), the shared UI state machine, the game-tick OSThread, the
* initials picker + high-score persistence flow, and the PRIVATE_APP mesh port. Individual games
* are self-contained Game subclasses registered in the constructor (see src/modules/games/); the
* attract screen cycles between them with UP/DOWN and SELECT plays the shown game.
*/
class GamesModule : public SinglePortModule, public Observable<const UIFrameEvent *>, private concurrency::OSThread
{
public:
GamesModule();
/// Start the currently-selected game (invoked when SELECT is pressed on the games frame). The
/// games frame is already current, so this just begins play.
void launchGame();
// Drawn through the games-frame trampoline, and queried by Screen's input gating / nav-bar, so
// these are public. While a game is active we own the D-pad; on the attract screen the D-pad
// cycles games (UP/DOWN) and otherwise navigates between frames as usual.
void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
virtual bool interceptingKeyboardInput() override { return uiState != GAMES_IDLE; }
/// Mesh passthrough for hosted games (a Game is not itself a MeshModule).
meshtastic_MeshPacket *gameAllocDataPacket() { return allocDataPacket(); }
protected:
virtual int32_t runOnce() override; // game tick + idle mesh scheduling
virtual Observable<const UIFrameEvent *> *getUIFrameObservable() override { return this; }
virtual bool wantUIFrame() override { return false; } // shares the games frame; no own slot
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
private:
int handleInputEvent(const InputEvent *event);
CallbackObserver<GamesModule, const InputEvent *> inputObserver =
CallbackObserver<GamesModule, const InputEvent *>(this, &GamesModule::handleInputEvent);
// === State transitions ===
void startPlaying();
void enterGameOver();
void exitToIdle();
void requestRedraw();
void kickTick();
// === Shared game-over / high-score flow ===
void promptForInitials();
void recordHighScore(const char *initials);
#if GAMES_ANNOUNCE_HIGH_SCORE
// Broadcast a new all-time #1 as a plain text message, shared by every game (name() spliced in).
void announceHighScore(const char *initials, uint32_t score);
#endif
// === Shared rendering ===
void drawCenteredLines(OLEDDisplay *display, int16_t x, int16_t y, const char *const *lines, uint8_t count);
void drawHighScores(OLEDDisplay *display, int16_t x, int16_t y, HighScoreTableBase &scores);
std::vector<Game *> games;
uint8_t selected = 0; // attract-screen cursor (index into games)
Game *active = nullptr; // game currently playing / whose scores are shown; null when idle
GamesUiState uiState = GAMES_IDLE;
uint32_t lastScore = 0; // score of the just-finished game (for the GAME OVER screen)
int lastRank = -1; // rank achieved last game (-1 == didn't place)
bool lastWasNewTop = false; // last game set a new all-time #1
uint32_t lastAwakeKickMs = 0; // throttles the power-FSM wake nudge during long runs
};
extern GamesModule *gamesModule;
#endif // HAS_SCREEN && BASEUI_HAS_GAMES