* 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>
104 lines
3.5 KiB
C++
104 lines
3.5 KiB
C++
#include "TestUtil.h"
|
|
#include "modules/games/Breakout.h"
|
|
#include <unity.h>
|
|
|
|
// Pure-logic tests for BreakoutGame: initial serve/brick state, paddle clamping, brick-clearing on
|
|
// a straight-up serve, and the ball staying within the board. No device globals or display stack.
|
|
|
|
static const uint32_t kSeed = 0xC0FFEEu;
|
|
|
|
void test_reset_initialState()
|
|
{
|
|
BreakoutGame game;
|
|
game.reset(kSeed);
|
|
TEST_ASSERT_TRUE(game.isPlaying());
|
|
TEST_ASSERT_EQUAL_UINT8(BreakoutGame::START_LIVES, game.lives());
|
|
TEST_ASSERT_EQUAL_UINT8(1, game.level());
|
|
TEST_ASSERT_EQUAL_UINT32(0, game.score());
|
|
// Every brick present at the start.
|
|
TEST_ASSERT_EQUAL_UINT16(static_cast<uint16_t>(BreakoutGame::BRICK_ROWS) * BreakoutGame::BRICK_COLS, game.bricksRemaining());
|
|
// Paddle centred, ball above it and inside the board.
|
|
TEST_ASSERT_EQUAL_INT16((BreakoutGame::BOARD_W - BreakoutGame::PADDLE_W) / 2, game.paddleX());
|
|
TEST_ASSERT_TRUE(game.ballX() >= 0 && game.ballX() < BreakoutGame::BOARD_W);
|
|
TEST_ASSERT_TRUE(game.ballY() >= 0 && game.ballY() < BreakoutGame::BOARD_H);
|
|
}
|
|
|
|
void test_paddle_clampsToEdges()
|
|
{
|
|
BreakoutGame game;
|
|
game.reset(kSeed);
|
|
for (int i = 0; i < 100; i++)
|
|
game.moveLeft();
|
|
TEST_ASSERT_EQUAL_INT16(0, game.paddleX());
|
|
for (int i = 0; i < 100; i++)
|
|
game.moveRight();
|
|
TEST_ASSERT_EQUAL_INT16(BreakoutGame::BOARD_W - BreakoutGame::PADDLE_W, game.paddleX());
|
|
}
|
|
|
|
void test_serve_clearsABrickAndScores()
|
|
{
|
|
BreakoutGame game;
|
|
game.reset(kSeed);
|
|
// The ball serves upward from just above the paddle straight into the brick field; within a
|
|
// few dozen steps it must clear at least one brick and score.
|
|
for (int i = 0;
|
|
i < 60 && game.bricksRemaining() == static_cast<uint16_t>(BreakoutGame::BRICK_ROWS) * BreakoutGame::BRICK_COLS; i++)
|
|
game.step();
|
|
TEST_ASSERT_TRUE(game.bricksRemaining() < static_cast<uint16_t>(BreakoutGame::BRICK_ROWS) * BreakoutGame::BRICK_COLS);
|
|
TEST_ASSERT_TRUE(game.score() > 0);
|
|
}
|
|
|
|
void test_ball_staysInBounds()
|
|
{
|
|
BreakoutGame game;
|
|
game.reset(kSeed);
|
|
// Drive the paddle to follow the ball so the game keeps going, and check the ball never leaves
|
|
// the board horizontally across a long run.
|
|
for (int i = 0; i < 500 && game.isPlaying(); i++) {
|
|
if (game.ballX() < game.paddleX())
|
|
game.moveLeft();
|
|
else
|
|
game.moveRight();
|
|
game.step();
|
|
TEST_ASSERT_TRUE(game.ballX() >= 0 && game.ballX() < BreakoutGame::BOARD_W);
|
|
TEST_ASSERT_TRUE(game.ballY() >= 0);
|
|
}
|
|
}
|
|
|
|
void test_deadGame_stepIsNoOp()
|
|
{
|
|
BreakoutGame game;
|
|
game.reset(kSeed);
|
|
// Park the paddle in a corner and never move it; the ball is eventually lost every life.
|
|
game.moveLeft();
|
|
for (int i = 0; i < 20000 && game.isPlaying(); i++) {
|
|
for (int j = 0; j < 40; j++) // hold the paddle pinned left
|
|
game.moveLeft();
|
|
game.step();
|
|
}
|
|
TEST_ASSERT_FALSE(game.isPlaying());
|
|
const uint32_t scoreBefore = game.score();
|
|
TEST_ASSERT_FALSE(game.step()); // stays dead, no further change
|
|
TEST_ASSERT_EQUAL_UINT32(scoreBefore, game.score());
|
|
}
|
|
|
|
void setUp(void) {}
|
|
|
|
void tearDown(void) {}
|
|
|
|
extern "C" {
|
|
void setup()
|
|
{
|
|
initializeTestEnvironment();
|
|
UNITY_BEGIN();
|
|
RUN_TEST(test_reset_initialState);
|
|
RUN_TEST(test_paddle_clampsToEdges);
|
|
RUN_TEST(test_serve_clearsABrickAndScores);
|
|
RUN_TEST(test_ball_staysInBounds);
|
|
RUN_TEST(test_deadGame_stepIsNoOp);
|
|
exit(UNITY_END());
|
|
}
|
|
|
|
void loop() {}
|
|
}
|