From 6d8834d9b26d6d06f2cda17aee829dea2862362a Mon Sep 17 00:00:00 2001 From: Stepan Usatiuk Date: Sun, 12 Oct 2025 14:04:06 +0200 Subject: [PATCH] nicer tetris --- Firmware/sdk/apps/tetris/src/tetris_app.cpp | 168 ++++++++++++-------- 1 file changed, 104 insertions(+), 64 deletions(-) diff --git a/Firmware/sdk/apps/tetris/src/tetris_app.cpp b/Firmware/sdk/apps/tetris/src/tetris_app.cpp index 7d16203..a9ec9c6 100644 --- a/Firmware/sdk/apps/tetris/src/tetris_app.cpp +++ b/Firmware/sdk/apps/tetris/src/tetris_app.cpp @@ -70,13 +70,13 @@ constexpr Tetromino makeTetromino(std::initializer_list baseBlocks) } constexpr std::array kPieces = {{ - makeTetromino({{-1, 0}, {0, 0}, {1, 0}, {2, 0}}), // I - makeTetromino({{-1, 0}, {0, 0}, {1, 0}, {1, 1}}), // J - makeTetromino({{-1, 1}, {-1, 0}, {0, 0}, {1, 0}}), // L - makeTetromino({{0, 0}, {1, 0}, {0, 1}, {1, 1}}), // O - makeTetromino({{-1, 0}, {0, 0}, {0, 1}, {1, 1}}), // S - makeTetromino({{-1, 0}, {0, 0}, {1, 0}, {0, 1}}), // T - makeTetromino({{-1, 1}, {0, 1}, {0, 0}, {1, 0}}), // Z + makeTetromino({{-1, 0}, {0, 0}, {1, 0}, {2, 0}}), // I + makeTetromino({{-1, 0}, {0, 0}, {1, 0}, {1, 1}}), // J + makeTetromino({{-1, 1}, {-1, 0}, {0, 0}, {1, 0}}), // L + makeTetromino({{0, 0}, {1, 0}, {0, 1}, {1, 1}}), // O + makeTetromino({{-1, 0}, {0, 0}, {0, 1}, {1, 1}}), // S + makeTetromino({{-1, 0}, {0, 0}, {1, 0}, {0, 1}}), // T + makeTetromino({{-1, 1}, {0, 1}, {0, 0}, {1, 0}}), // Z }}; class RandomBag { @@ -107,22 +107,22 @@ private: }; struct ActivePiece { - int type = 0; - int rotation = 0; - int x = 0; - int y = 0; + int type = 0; + int rotation = 0; + int x = 0; + int y = 0; }; struct GameState { std::array board{}; - ActivePiece current{}; - int nextPiece = 0; - int level = 1; - int linesCleared = 0; - int score = 0; - int highScore = 0; - bool paused = false; - bool gameOver = false; + ActivePiece current{}; + int nextPiece = 0; + int level = 1; + int linesCleared = 0; + int score = 0; + int highScore = 0; + bool paused = false; + bool gameOver = false; }; [[nodiscard]] std::uint32_t randomSeed(AppContext& ctx) { @@ -161,21 +161,21 @@ public: } private: - AppContext& context; + AppContext& context; typename AppContext::Framebuffer& framebuffer; - GameState state; - RandomBag bag; - InputState lastInput{}; - bool dirty = false; - AppTimerHandle dropTimer = cardboy::sdk::kInvalidAppTimer; - AppTimerHandle softTimer = cardboy::sdk::kInvalidAppTimer; + GameState state; + RandomBag bag; + InputState lastInput{}; + bool dirty = false; + AppTimerHandle dropTimer = cardboy::sdk::kInvalidAppTimer; + AppTimerHandle softTimer = cardboy::sdk::kInvalidAppTimer; void reset() { cancelTimers(); - int oldHigh = state.highScore; - state = {}; - state.highScore = oldHigh; + int oldHigh = state.highScore; + state = {}; + state.highScore = oldHigh; state.current.type = bag.next(); state.nextPiece = bag.next(); state.current.x = kBoardWidth / 2; @@ -190,7 +190,7 @@ private: } void handleButtons(const AppButtonEvent& evt) { - const auto& cur = evt.current; + const auto& cur = evt.current; const auto& prev = evt.previous; lastInput = cur; @@ -260,7 +260,7 @@ private: void scheduleDropTimer() { cancelDropTimer(); const std::uint32_t interval = dropIntervalMs(); - dropTimer = context.scheduleRepeatingTimer(interval); + dropTimer = context.scheduleRepeatingTimer(interval); } void cancelDropTimer() { @@ -276,8 +276,8 @@ private: } [[nodiscard]] std::uint32_t dropIntervalMs() const { - const int base = 700; - const int step = 50; + const int base = 700; + const int step = 50; int interval = base - (state.level - 1) * step; if (interval < 120) interval = 120; @@ -322,7 +322,7 @@ private: void rotate(int direction) { int nextRot = state.current.rotation + (direction >= 0 ? 1 : -1); - nextRot = ((nextRot % 4) + 4) % 4; + nextRot = ((nextRot % 4) + 4) % 4; if (canPlace(state.current.x, state.current.y, nextRot)) { state.current.rotation = nextRot; dirty = true; @@ -484,11 +484,10 @@ private: for (int y = 0; y < kBoardHeight; ++y) { for (int x = 0; x < kBoardWidth; ++x) { if (int value = cellAt(x, y); value != 0) - drawCell(originX, originY, x, y, value, true); + drawCell(originX, originY, x, y, value - 1, true); } } - - drawGuides(originX, originY); + drawBoardFrame(originX, originY); } void drawActivePiece() { @@ -502,32 +501,76 @@ private: int gy = state.current.y + block.y; if (gy < 0) continue; - drawCell(originX, originY, gx, gy, state.current.type + 1, false); + drawCell(originX, originY, gx, gy, state.current.type, false); } } - void drawCell(int originX, int originY, int cx, int cy, int value, bool solid) { - const int x0 = originX + cx * kCellSize; - const int y0 = originY + cy * kCellSize; - for (int dy = 0; dy < kCellSize; ++dy) { - for (int dx = 0; dx < kCellSize; ++dx) { - bool on = solid ? true : (dx == 0 || dx == kCellSize - 1 || dy == 0 || dy == kCellSize - 1); + static bool patternPixel(int pieceIndex, int dx, int dy) { + const int idx = std::clamp(pieceIndex, 0, static_cast(kPieces.size()) - 1); + const int mx = dx & 0x3; + const int my = dy & 0x3; + switch (idx) { + case 0: // I - vertical stripes + return mx < 2; + case 1: // J - horizontal stripes + return my < 2; + case 2: { // L - forward diagonal + const int sum = (mx + my) & 0x3; + return sum < 2; + } + case 3: // O - diamond centerpiece + return (mx == 1 && my == 1) || (mx == 2 && my == 1) || (mx == 1 && my == 2) || (mx == 2 && my == 2); + case 4: // S - checkerboard + return ((mx ^ my) & 0x1) == 0; + case 5: { // T - cross + return (mx == 0) || (my == 0); + } + case 6: { // Z - backward diagonal + int diff = mx - my; + if (diff < 0) + diff += 4; + diff &= 0x3; + return diff < 2; + } + } + return true; + } + + void drawPatternBlock(int x0, int y0, int size, int pieceIndex, bool locked) { + const int idx = std::clamp(pieceIndex, 0, static_cast(kPieces.size()) - 1); + for (int dy = 0; dy < size; ++dy) { + for (int dx = 0; dx < size; ++dx) { + const bool border = dx == 0 || dx == size - 1 || dy == 0 || dy == size - 1; + bool fill = patternPixel(idx, dx, dy); + if (!locked && !border) + fill = fill && (((dx + dy) & 0x1) == 0); + const bool on = border || fill; framebuffer.drawPixel(x0 + dx, y0 + dy, on); } } - (void) value; // value currently unused (monochrome display) } - void drawGuides(int originX, int originY) { - for (int y = 0; y <= kBoardHeight; ++y) { - const int py = originY + y * kCellSize; - for (int x = 0; x < kBoardWidth * kCellSize; ++x) - framebuffer.drawPixel(originX + x, py, (y % 5) == 0); + void drawCell(int originX, int originY, int cx, int cy, int pieceIndex, bool locked) { + const int x0 = originX + cx * kCellSize; + const int y0 = originY + cy * kCellSize; + drawPatternBlock(x0, y0, kCellSize, pieceIndex, locked); + } + + void drawBoardFrame(int originX, int originY) { + const int widthPixels = kBoardWidth * kCellSize; + const int heightPixels = kBoardHeight * kCellSize; + const int x0 = originX; + const int y0 = originY; + const int x1 = originX + widthPixels - 1; + const int y1 = originY + heightPixels - 1; + + for (int x = x0; x <= x1; ++x) { + framebuffer.drawPixel(x, y0, true); + framebuffer.drawPixel(x, y1, true); } - for (int x = 0; x <= kBoardWidth; ++x) { - const int px = originX + x * kCellSize; - for (int y = 0; y < kBoardHeight * kCellSize; ++y) - framebuffer.drawPixel(px, originY + y, (x % 5) == 0); + for (int y = y0; y <= y1; ++y) { + framebuffer.drawPixel(x0, y, true); + framebuffer.drawPixel(x1, y, true); } } @@ -539,15 +582,14 @@ private: for (int dy = 0; dy < boxSize; ++dy) for (int dx = 0; dx < boxSize; ++dx) - framebuffer.drawPixel(originX + dx, originY + dy, (dy == 0 || dy == boxSize - 1 || dx == 0 || dx == boxSize - 1)); + framebuffer.drawPixel(originX + dx, originY + dy, + (dy == 0 || dy == boxSize - 1 || dx == 0 || dx == boxSize - 1)); const auto& piece = kPieces[state.nextPiece]; for (const auto& block: piece.rotations[0]) { - const int px = originX + (block.x + 1) * blockSize; - const int py = originY + (block.y + 1) * blockSize; - for (int dy = 1; dy < blockSize - 1; ++dy) - for (int dx = 1; dx < blockSize - 1; ++dx) - framebuffer.drawPixel(px + dx, py + dy, true); + const int px = originX + (block.x + 1) * blockSize + 1; + const int py = originY + (block.y + 1) * blockSize + 1; + drawPatternBlock(px, py, blockSize - 2, state.nextPiece, true); } } @@ -608,7 +650,7 @@ private: class TetrisFactory final : public cardboy::sdk::IAppFactory { public: - const char* name() const override { return kTetrisAppName; } + const char* name() const override { return kTetrisAppName; } std::unique_ptr create(AppContext& context) override { return std::make_unique(context); } @@ -616,8 +658,6 @@ public: } // namespace -std::unique_ptr createTetrisAppFactory() { - return std::make_unique(); -} +std::unique_ptr createTetrisAppFactory() { return std::make_unique(); } } // namespace apps