Public Access
The rules of the three Games (issue #101), with no screen and no keyboard in them: time and keys go in, the new state and what happened come out, and a seed decides the dice. With a player for a tune a note at a time, and Korobeiniki's notes. Host-tested. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0162FokPdvY2KsS4NBfwyWPk
82 lines
3.0 KiB
C++
82 lines
3.0 KiB
C++
#pragma once
|
|
|
|
#include <array>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
|
|
#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<size_t>(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<Cell, 4> 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<size_t>(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<uint8_t, kCols * kRows> 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
|