#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 // 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, 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 *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 inputObserver = CallbackObserver(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 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