* 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>
139 lines
5.2 KiB
C++
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
|