#pragma once #include #include #include #include "game_rng.h" namespace roro::games { // What the player asks of Tetris in one step: the first three for as long as the key is held, // the others once for each press. struct TetrisInput { bool left = false, right = false, soft = false; bool rotate = false, rotateBack = false, hardDrop = false; }; // Tetris (issue #101): a well of 10 x 20, the seven pieces dealt from a bag (each once in every // seven), the next one shown, a turn that shifts away from a wall, a soft and a hard drop, and a // fall that quickens every ten lines. No hold, no ghost. Time comes in milliseconds. class Tetris { public: static constexpr int kCols = 10, kRows = 20; static constexpr uint32_t kSlowestMs = 800, kFastestMs = 80; static constexpr uint32_t kSoftMs = 90; // a row, falling faster: quick, and still steered static constexpr uint32_t kRepeatAfterMs = 170, kRepeatEveryMs = 50; // a held left or right static constexpr int kLinesALevel = 10; enum Event : uint8_t { Moved = 1, Turned = 2, Locked = 4, Lines = 8, LevelUp = 16, Over = 32 }; // The pieces, 1 to 7, in the order I O T S Z J L; 0 is an empty cell. struct Piece { uint8_t kind = 0; uint8_t turn = 0; // 0 to 3, quarter turns clockwise int8_t col = 0, row = 0; // of the top left of its 4 x 4 box; the row may be above the well }; struct Cell { int8_t col, row; }; explicit Tetris(uint32_t seed) : rng_(seed) { start(); } void start(); uint8_t step(uint32_t ms, const TetrisInput& in); bool over() const { return over_; } uint8_t cell(int col, int row) const { return well_[static_cast(row * kCols + col)]; } const Piece& piece() const { return piece_; } uint8_t next() const { return next_; } int score() const { return score_; } int lines() const { return lines_; } int level() const { return lines_ / kLinesALevel; } uint32_t fallMs() const; // between two rows of fall, at this level int lastCleared() const { return lastCleared_; } // by the last piece locked // The four cells a piece covers. static std::array cells(const Piece& p); // For the tests: a cell of the well set, and the piece in play replaced. void put(int col, int row, uint8_t kind) { well_[static_cast(row * kCols + col)] = kind; } void give(uint8_t kind); private: bool fits(const Piece& p) const; bool shift(int dc, int dr); bool rotate(int by); uint8_t lock(); uint8_t spawn(uint8_t kind); uint8_t deal(); Rng rng_; std::array well_{}; Piece piece_; uint8_t next_ = 1; uint8_t bag_[7] = {}; int bagLeft_ = 0; int score_ = 0, lines_ = 0, lastCleared_ = 0; uint32_t fallSince_ = 0, heldMs_ = 0; int heldDir_ = 0; bool repeating_ = false, over_ = false; bool softSpent_ = false; // the key that brought the last piece down is still held }; } // namespace roro::games