#pragma once #include #include /** * Pure, self-contained Tetris game logic. * * No Arduino/display dependencies - designed to be unit-tested natively and reused by the Tetris * adapter below without pulling in the display stack. * * Board coordinate system: col=0 is leftmost, row=0 is top (gravity goes toward higher rows). * board[row][col] holds 0 (empty) or 1..7 (locked piece colour index). * No dynamic allocation: total struct size is ~260 bytes. */ class TetrisGame { public: static constexpr uint8_t BOARD_COLS = 10; static constexpr uint8_t BOARD_ROWS = 16; // 16×4 px = 64 px - fills a standard 64px OLED static constexpr uint8_t PIECE_TYPES = 7; // I O T S Z J L struct Piece { int8_t col; // left column of the 4×4 bounding box (may be negative during spawn) int8_t row; // top row of the 4×4 bounding box (may be negative during spawn) uint8_t type; // 0..6 uint8_t rot; // 0..3 }; // board[row][col]: 0 = empty, 1..7 = locked piece colour uint8_t board[BOARD_ROWS][BOARD_COLS]; /** Start (or restart) the game. seed drives the xorshift32 RNG. */ void reset(uint32_t seed); /** Shift the current piece left one column. Returns true if it moved. */ bool moveLeft(); /** Shift the current piece right one column. Returns true if it moved. */ bool moveRight(); /** * Rotate the current piece CW. Tries a basic wall-kick (±1, ±2 column) if the * natural rotation overlaps a wall or locked cell. Returns true if it rotated. */ bool rotate(); /** * Move the current piece down one row. If it cannot fall it is locked, * lines are cleared, the next piece becomes current, and a new next is generated. * Returns false after locking (game may or may not be over). */ bool softDrop(); /** * Instantly drop the current piece to where it would land and lock it. * Awards 2 pts per row dropped. */ void hardDrop(); /** * Gravity tick: same as softDrop() - move down one row, lock if needed. * Returns true while the game is alive, false after game-over. */ bool step(); bool isPlaying() const { return alive; } uint32_t score() const { return pts; } uint8_t level() const { return lvl; } uint16_t linesCleared() const { return lines; } const Piece ¤t() const { return cur; } const Piece &next() const { return nxt; } /** * Returns the top row the current piece would occupy if instantly dropped. * Used by the renderer to show a ghost/shadow piece. */ int8_t ghostRow() const; /** * Returns true if cell (pr, pc) within the 4×4 bounding box is filled for * the given piece type and rotation. Safe for any (type, rot, pr, pc). */ static bool pieceCell(uint8_t type, uint8_t rot, uint8_t pr, uint8_t pc); private: Piece cur = {}; Piece nxt = {}; uint32_t pts = 0; uint8_t lvl = 1; uint16_t lines = 0; uint32_t rng = 1; // xorshift32 state - must never be 0 bool alive = false; uint32_t nextRandom(); Piece spawnPiece(uint8_t type) const; void advanceNext(); // lock cur, clear lines, shift nxt→cur, spawn new nxt bool canPlace(const Piece &p) const; void lockPiece(); int clearLines(); // returns number of lines cleared (0..4) // Shape table: SHAPES[type][rot][row] = 4-bit column bitmask. // Bit 0 = leftmost column (col 0), bit 3 = rightmost (col 3) of the 4×4 box. static const uint8_t SHAPES[PIECE_TYPES][4][4]; }; #include "configuration.h" #if HAS_SCREEN && BASEUI_HAS_GAMES #include "Game.h" #include "HighScoreTable.h" /** * Tetris as a hosted Game. Wraps the pure TetrisGame logic above and supplies the attract art, the * portrait playfield renderer, the rotate/move/drop input, the level-based speed curve, and its * own high-score table. The new-high-score mesh announcement is shared by all games and lives in * GamesModule. */ class Tetris : public Game { public: Tetris(); const char *name() const override { return "Tetris"; } void start(uint32_t seed) override { game.reset(seed); } bool tick() override { return game.step(); } 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; const char *gameOverHint() const override { return "SEL: scores BCK: exit"; } HighScoreTableBase &scores() override { return scores_; } private: // On-disk high-score record; layout preserved from the original TetrisModule so tetris.dat // keeps loading. Magic 'TETR', file version 1. struct TetrisEntry { uint32_t score; char shortName[5]; // NUL-terminated 3-char display name uint32_t nodeNum; uint32_t epoch; } __attribute__((packed)); TetrisGame game; HighScoreTable scores_{"/prefs/tetris.dat", 0x54455452u, 1, "Tetris"}; }; #endif // HAS_SCREEN && BASEUI_HAS_GAMES