mirror of
https://github.com/usatiuk/cardboy.git
synced 2025-10-28 15:17:48 +01:00
nicer tetris
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user