Files
meshtastic_firmware/src/modules/games/Breakout.h
T
Jonathan BennettGitHubThomas Göttgenscoderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>IxitxachitlCopilot Autofix powered by AI
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

139 lines
5.2 KiB
C++

#pragma once
#include <stddef.h>
#include <stdint.h>
#include <string.h>
/**
* Pure, self-contained Breakout game logic.
*
* No Arduino/display dependencies - designed to be unit-tested natively (see test/test_breakout)
* and reused by the Breakout adapter below without pulling in the display stack.
*
* The board is a fixed BOARD_W x BOARD_H pixel field. A grid of bricks sits near the top, a paddle
* slides along the bottom, and a ball bounces between them. Ball position/velocity are tracked in
* fixed-point sub-pixels (SUBPX per pixel) so movement is smooth and deterministic; everything is
* integer math and statically sized (no heap).
*/
class BreakoutGame
{
public:
// Playfield, in pixels (a standard 128x64 OLED in landscape).
static constexpr int16_t BOARD_W = 128;
static constexpr int16_t BOARD_H = 64;
// Brick grid.
static constexpr uint8_t BRICK_COLS = 8;
static constexpr uint8_t BRICK_ROWS = 5;
static constexpr int16_t BRICK_W = BOARD_W / BRICK_COLS; // 16
static constexpr int16_t BRICK_H = 4;
static constexpr int16_t BRICK_TOP = 12; // top of the first course; leaves room for the score row
// Paddle.
static constexpr int16_t PADDLE_W = 24;
static constexpr int16_t PADDLE_H = 2;
static constexpr int16_t PADDLE_Y = BOARD_H - 3; // top edge of the paddle
static constexpr int16_t PADDLE_STEP = 6; // pixels moved per input event
static constexpr uint8_t START_LIVES = 3;
static constexpr uint8_t POINTS_PER_BRICK = 10;
/** (Re)start a full game: rebuild bricks, reset lives/score, serve the ball. `seed` drives the
* xorshift32 RNG used for the initial serve direction. */
void reset(uint32_t seed);
/** Advance the simulation by one tick (move the ball, resolve collisions). Returns true if the
* game is still in progress, false once the last life is lost. No-op returning false once dead. */
bool step();
/** Slide the paddle; the ball is unaffected. moveLeft/moveRight use the default per-press step;
* movePaddle takes an explicit signed pixel delta (used when polling a held joystick). */
void movePaddle(int16_t dxPx);
void moveLeft();
void moveRight();
bool isPlaying() const { return alive; }
uint32_t score() const { return points; }
uint8_t lives() const { return livesLeft; }
uint8_t level() const { return levelNum; }
uint16_t bricksRemaining() const { return bricksLeft; }
// --- Rendering accessors (all in board pixels) ---
int16_t paddleX() const { return paddleLeft; }
int16_t ballX() const { return static_cast<int16_t>(ballPxX / SUBPX); }
int16_t ballY() const { return static_cast<int16_t>(ballPxY / SUBPX); }
bool brickAt(uint8_t row, uint8_t col) const { return row < BRICK_ROWS && col < BRICK_COLS && bricks[row][col]; }
private:
static constexpr int32_t SUBPX = 16; // fixed-point sub-pixels per pixel
static constexpr int32_t BALL_VY = 40; // vertical ball speed (sub-pixels/step)
static constexpr int16_t BALL_SIZE = 2; // ball is drawn BALL_SIZE x BALL_SIZE
void buildBricks();
void serveBall();
void nextLevel();
uint32_t nextRandom();
uint8_t bricks[BRICK_ROWS][BRICK_COLS] = {0}; // 0 == cleared, 1 == present
uint16_t bricksLeft = 0;
int16_t paddleLeft = 0; // left edge of the paddle, in pixels
int32_t ballPxX = 0, ballPxY = 0; // ball centre, in sub-pixels
int32_t ballVx = 0, ballVy = 0; // ball velocity, in sub-pixels/step
uint32_t points = 0;
uint8_t livesLeft = 0;
uint8_t levelNum = 1;
uint32_t rng = 1; // xorshift32 state (never 0)
bool alive = false;
bool ballTick = false; // ball advances on every other step() (see step())
};
#include "configuration.h"
#if HAS_SCREEN && BASEUI_HAS_GAMES
#include "Game.h"
#include "HighScoreTable.h"
/**
* Breakout as a hosted Game. Wraps the pure BreakoutGame logic above and supplies the attract art,
* the playfield renderer, the paddle input, the level-based speed curve, and its own local
* high-score table. No mesh protocol (scores are local-only).
*/
class Breakout : public Game
{
public:
Breakout();
const char *name() const override { return "Breakout"; }
void start(uint32_t seed) override { game.reset(seed); }
bool tick() override; // polls a held joystick for the paddle, then advances the ball
bool isPlaying() const override { return game.isPlaying(); }
uint32_t score() const override { return game.score(); }
int32_t tickIntervalMs() const override;
void handleInput(input_broker_event ev) override;
void drawAttract(OLEDDisplay *display, int16_t x, int16_t y) override;
void drawPlaying(OLEDDisplay *display, int16_t x, int16_t y) override;
HighScoreTableBase &scores() override { return scores_; }
private:
// On-disk high-score record. New file (magic 'BRKT', version 1); layout mirrors Snake's.
struct BreakoutEntry {
uint32_t score;
uint32_t nodeNum;
char shortName[5]; // three characters, NUL-terminated
uint32_t epoch; // getValidTime(), 0 if no RTC
} __attribute__((packed));
BreakoutGame game;
HighScoreTable<BreakoutEntry> scores_{"/prefs/breakout.dat", 0x424B5254u, 1, "Breakout"};
};
#endif // HAS_SCREEN && BASEUI_HAS_GAMES