nicer tetris

This commit is contained in:
2025-10-12 14:04:06 +02:00
parent 83ba775971
commit 6d8834d9b2

View File

@@ -70,13 +70,13 @@ constexpr Tetromino makeTetromino(std::initializer_list<BlockOffset> baseBlocks)
}
constexpr std::array<Tetromino, 7> 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<int, kBoardWidth * kBoardHeight> 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<int>(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<int>(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<cardboy::sdk::IApp> create(AppContext& context) override {
return std::make_unique<TetrisApp>(context);
}
@@ -616,8 +658,6 @@ public:
} // namespace
std::unique_ptr<cardboy::sdk::IAppFactory> createTetrisAppFactory() {
return std::make_unique<TetrisFactory>();
}
std::unique_ptr<cardboy::sdk::IAppFactory> createTetrisAppFactory() { return std::make_unique<TetrisFactory>(); }
} // namespace apps