Files
meshtastic_firmware/src/modules/games/Tetris.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

157 lines
5.2 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma once
#include <stdint.h>
#include <string.h>
/**
* 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 &current() 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<TetrisEntry> scores_{"/prefs/tetris.dat", 0x54455452u, 1, "Tetris"};
};
#endif // HAS_SCREEN && BASEUI_HAS_GAMES