mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-14 15:31:31 +10:00
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>
This commit is contained in:
co-authored by
Copilot Autofix powered by AI
Thomas Göttgens
coderabbitai[bot]
Ixitxachitl
parent
d3d02af817
commit
8d1fbbf55f
@@ -0,0 +1,101 @@
|
||||
#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
|
||||
Reference in New Issue
Block a user