Tetris: exact look-ahead checks, and worlds that choose the pieces¶
In Tetris you never control what comes next: a generator picks every piece, and you only choose where it goes. That makes it a clean place to ask what a player can honestly promise. This page shows a player that looks one piece ahead, a gate that checks every proposal exactly against the pieces already on screen, and what happens when the piece generator is fair, rigged, or hostile. It also says precisely which "you will lose" theorems are known, for which board widths, and which results here are proved rather than measured.
This page is for anyone who wants to see the grounded pattern (propose, check, then act or degrade) in a game where the future is random, and for readers who want the published Tetris results stated with their limits. It assumes you know roughly what Tetris looks like; the next section gives the exact rules.
Try it in your browser
The board below plays this game's recorded example with no download; Play live runs this package itself in your browser, in Pyodide (about 7 MB, downloaded once), and the play page has every setting.
Tetris runs here when JavaScript is on; the play page has it too, and the commands below run it in a terminal.
Run it¶
uv run grounded-decision examples games_tetris # the report below
uv run grounded-decision play tetris # watch the gated player, live
uv run grounded-decision play tetris --you --guarded # place pieces yourself, with the gate checking you
Source: src/grounded_decision/examples/games/tetris_rules.py (pieces,
placements, line clears, features, the exact survival search) and
tetris.py (the game, worlds, players, gate and report). Tests:
tests/test_games_tetris.py.
The rules, exactly as implemented¶
By default the game is turn-based: there is no falling timer. Each turn
you choose where the current piece lands. The real-time mode
(--mode realtime) plays tick by tick with gravity instead.
| Rule | What the code does |
|---|---|
| Well | width x height cells, 10 x 20 by default; any size from 4 x 4 to 40 x 100 (--size 6x12). |
| Pieces | The seven tetrominoes I O T S Z J L in their standard starting orientations. |
| A move | A rotation and a column, written T r1 c4: piece T, turned one quarter clockwise, its leftmost cell in column 4 (columns count from 1). Only distinct rotations exist: O has one, I, S and Z two, T, J and L four. |
| Legal | The piece, in that rotation, fits in the top rows at the centre (where pieces appear), and can slide along the top to that column without hitting anything. It then drops straight down as far as it can. No wall kicks, no sliding under overhangs, no T-spins. Every move here is a move the real game allows too. |
| Lines | Full rows disappear and everything above moves down. |
| Score | Single 100, double 300, triple 500, four rows ("tetris") 800, times the level 1 + lines / 10 (rounded down). No points for dropping. |
| Preview | You see the current piece and the next one (--preview N, 0 to 3; configure(preview=2) in Python). |
| Lost | The piece to place has no legal move ("topped out"). |
| Won | 500 pieces placed (--pieces N; configure(pieces=N) in Python). Tetris has no natural end, so the goal is to survive; the score column of compare is lines cleared. |
On a fresh 10-wide well the legal moves are: I 17, O 9, S and Z 17 each, T, J and L 34 each (a test checks these counts).
Worlds: who picks the pieces¶
A world is the rule that chooses each new piece (--world MODE). It
is separate from what a player assumes about the pieces (its
information regime); see Who controls the randomness.
| World | Picks | Depends on your moves? |
|---|---|---|
random |
Each piece uniformly from the seven, independently (the classic generator; the default). | No |
bag |
The modern 7-bag: every block of seven pieces is a shuffle of all seven. | No |
sz |
Only S and Z, alternating (the seed picks which comes first). | No |
adversarial |
Bastet-style: after each move, the piece whose best placement (by the same evaluation the players use) leaves the worst board. Ties broken by a seeded coin. | Yes |
kind |
The mirror image: the piece whose best placement leaves the best board. | Yes |
MODE:p mixes a mode with random: each piece follows the mode with
probability p. The adversarial world is a heuristic adversary: it
best-responds to the shared evaluation, it does not simulate the
particular player, and it looks only one piece deep. It is not the
strongest possible adversary.
The players¶
| Player | What it does | Regime |
|---|---|---|
random |
A uniformly random legal move. | baseline |
greedy |
Pierre Dellacherie's one-piece controller: scores every move by six features of the resulting board and plays the best. Ignores the preview. | worst-case |
lookahead |
For the ten best moves of the current piece, adds the best move of the previewed piece and scores the two-piece line. | worst-case |
expectimax |
lookahead, then the average over the seven pieces of the best move of the first piece not yet shown. It bets that pieces are uniform. |
probabilistic |
gated |
lookahead proposes; the gate below decides. |
worst-case |
clairvoyant |
Reads the true future sequence from the seed and searches four pieces deep (beam of 6). A benchmark, never a real player; refused in the adversarial and kind worlds, which have no fixed future. |
clairvoyant |
The evaluation is the weighted sum of landing height (weight -1), eroded
piece cells (+1: rows cleared times the piece's cells in them), row
transitions (-1), column transitions (-1), holes (-4) and cumulative
wells (-1), with the weights of Dellacherie's controller as documented by
Thiery and Scherrer ("Building Controllers for Tetris", ICGA Journal
32(1), 2009). How exactly each feature is counted (walls and floor count
as filled, the space above the well as empty) is in the
tetris_rules.py module docstring; a test checks the fast bit-level
code against a cell-by-cell reference.
The gate: what it proves¶
The gate checks every proposal with three exact checks. "Exact" means an exhaustive search over every way to place the pieces already on screen, which no world can change any more.
| Check | Passes when |
|---|---|
legal |
The move is legal. |
fits_preview |
After it, every previewed piece can still be placed, in order. |
unseen_piece_fits |
After it, the previewed pieces can be placed so that the top four rows end empty. Then the first piece not yet shown fits whatever it is, because no piece is taller than four rows (the clear-top lemma). |
If every check passes, the proposal is played (act). Otherwise the
fallback plays the move that best keeps these properties: first
unseen_piece_fits, then how many previewed pieces can still be placed,
then the evaluation (degrade). When nothing passes, the decision's note
starts with WARNING.
Theorem (worst-case: it holds in every world).
- If some move passes every check, the gated player plays one.
- The gated player cannot top out while every move it has made passed
unseen_piece_fits. - Whenever it does top out, its record carries a
WARNINGat leastpreview + 1pieces before the end (unless the warning came on the very first piece).
Why. (1) is how the gate is built. (2): a move that passes
unseen_piece_fits leaves a way to place the previewed pieces and the
next new piece; the first step of that plan passes fits_preview on
the next turn, and the fallback never picks a worse move than that. (3):
on the first turn with no move passing unseen_piece_fits, some move
still passes fits_preview (by (2) for the turn before), so all shown
pieces can be placed, and each turn the number of shown pieces that can
be placed drops by at most one.
The tests check all three statements on real games: (1) by comparing the
chosen move with every legal move, (2) and (3) on every lost game in the
adversarial, sz and random worlds with previews 0, 1 and 2.
What the gate does not promise: to survive. It looks exactly as far as the screen shows, plus one piece. It does not make the player stronger in the long run either (see the measurements below): its fallback trades board quality for the guarantee.
Nobody can promise to survive forever¶
These are the known results, as stated in the literature, with their exact scope.
- Brzustowski (1992), Can you win at Tetris? (M.Sc. thesis,
University of British Columbia): if the machine may choose each next
piece after seeing the player's moves, it can force a loss on a well
of any size using only S and Z pieces. So the
adversarialworld, if it played optimally, beats every player; ours is a weaker heuristic. - Burgiel (1997), "How to lose at Tetris", The Mathematical Gazette
81(491): on a well of width
2nwithnodd (6, 10, 14, ...: the standard 10 included), the fixed sequence S Z S Z ... forces a loss however the pieces are placed. Consequently, if pieces are drawn independently and S and Z each have a positive chance (therandomworld), every game on such a width ends with probability one. This is a statement about eventually, not about how soon.
Both results are about the real game. Every move here is a real move, and our game ends no later than the real one would, so a loss the real game forces is forced here too. They are summarised, with these widths, in Demaine, Hohenberger and Liben-Nowell, "Tetris is hard, even to approximate" (COCOON 2003, arXiv:cs/0210020). We cite them; we did not re-check the proofs, and the code does not verify them.
What we prove ourselves is narrower and exact: for the rules on this
page, tetris_rules.survival(width, height) builds every reachable
(well, next piece) pair under S Z S Z ... and decides whether some
strategy survives forever (a reachable cycle exists) or, if not, the most
pieces any strategy can place. Real output from the report:
Exact (exhaustive search over hard-drop placements): can S Z S Z ... be survived forever?
4x6, S Z S Z ...: survivable forever (229 reachable states)
5x5, S Z S Z ...: every strategy tops out; the best places 6 pieces (203 reachable states)
6x4, S Z S Z ...: every strategy tops out; the best places 7 pieces (175 reachable states)
6x5, S Z S Z ...: every strategy tops out; the best places 14 pieces (2,077 reachable states)
6x6, S Z S Z ...: every strategy tops out; the best places 21 pieces (23,907 reachable states)
8x4, S Z S Z ...: every strategy tops out; the best places 10 pieces (1,664 reachable states)
8x5, S Z S Z ...: survivable forever (109,182 reachable states)
10x4, S Z S Z ...: every strategy tops out; the best places 15 pieces (25,154 reachable states)
Further runs of the same search (not in the report; they take up to half a minute each): 6 x 7 tops out after at most 32 pieces, 6 x 8 after at most 59; 7 x 5, 7 x 6, 9 x 5 and 12 x 4 also top out; 8 x 6 and 4 x 4 to 4 x 8 are survivable forever. Widths 6 and 10 behave as Burgiel's theorem says they must. Widths divisible by four (4, 8, 12) are outside his theorem; on the small heights searched some are survivable and some are not, and we make no claim about them beyond the boards searched (a short well can lose for reasons unrelated to the S Z argument). Tests cross-check the search against a plain depth-first search.
Results (measured)¶
The report, real output (uv run grounded-decision examples games_tetris;
three seeds per world, so it is an illustration, not a statistic):
Agents on a 10 x 20 well, preview 1, 3 seeds per world, goal 150 pieces (measured):
world agent survived mean lines mean pieces overrides
random random 0/3 0.0 23.7 0
random greedy 3/3 57.7 150.0 0
random lookahead 3/3 58.3 150.0 0
random gated 3/3 58.3 150.0 0
sz random 0/3 0.0 24.7 0
sz greedy 3/3 48.3 150.0 0
sz lookahead 0/3 27.3 107.0 0
sz gated 0/3 30.7 116.0 61
adversarial random 0/3 0.0 20.0 0
adversarial greedy 0/3 1.3 36.7 0
adversarial lookahead 0/3 12.3 71.0 0
adversarial gated 0/3 16.3 80.0 45
One gated game in the adversarial world (seed 0): topped out: no room for the O after 88 pieces (20 lines); 17 overrides.
O: proposed O r0 c9, played O r0 c3 (failed: unseen_piece_fits). fallback: the best placement that keeps the next unseen piece placeable
O: proposed O r0 c9, played O r0 c3 (failed: unseen_piece_fits). fallback: the best placement that keeps the next unseen piece placeable
O: proposed O r0 c9, played O r0 c5 (failed: unseen_piece_fits). fallback: the best placement that keeps the next unseen piece placeable
First WARNING at move 67 (the game lasted 88); the theorem guarantees that move and the next 1 still fit.
On the standard 10 x 20 well with fair pieces, every non-random player survives 150 pieces. Under S Z S Z ... the greedy player survives 150 pieces here but not forever (it tops out after 186 on average below), as Burgiel's theorem requires on width 10. The adversarial world ends every game within about 90 pieces.
A narrow 6 x 12 well separates the players. Real output of
uv run grounded-decision compare tetris --agents random,greedy,lookahead,expectimax,gated,clairvoyant --size 6x12 --seeds 20
(goal 500 pieces; "score" is lines cleared):
Comparison: tetris, solo, 20 seed(s); seats: player (assigned by the comparison); world: random
agent games won drawn lost cut points moves score overrides win rate (95% interval)
clairvoyant 20 20 0 0 0 20.0 500.0 331.8 0 1.00 [0.83, 1.00]
lookahead 20 18 0 2 0 18.0 476.6 315.4 0 0.90 [0.68, 0.99]
gated 20 17 0 3 0 17.0 461.4 304.9 74 0.85 [0.62, 0.97]
expectimax 20 14 0 6 0 14.0 423.2 277.9 0 0.70 [0.46, 0.88]
greedy 20 1 0 19 0 1.0 131.5 79.8 0 0.05 [0.00, 0.25]
random 20 0 0 20 0 0.0 8.2 0.2 0 0.00 [0.00, 0.17]
The same with --world adversarial (clairvoyant left out: there is no
fixed future to see):
Comparison: tetris, solo, 20 seed(s); seats: player (assigned by the comparison); world: adversarial
agent games won drawn lost cut points moves score overrides win rate (95% interval)
random 20 0 0 20 0 0.0 7.3 0.0 0 0.00 [0.00, 0.17]
greedy 20 0 0 20 0 0.0 12.0 1.0 0 0.00 [0.00, 0.17]
gated 20 0 0 20 0 0.0 23.2 7.9 154 0.00 [0.00, 0.17]
lookahead 20 0 0 20 0 0.0 23.6 8.0 0 0.00 [0.00, 0.17]
expectimax 20 0 0 20 0 0.0 27.4 10.2 0 0.00 [0.00, 0.17]
points: win 1, draw or cut-off shares 1 among the seats, loss 0. moves: mean moves per game (all players).
Note: assumption not met: probabilistic agents model the random world, but this run uses the adversarial world, so their high-probability statements do not apply here (worst-case guarantees still do).
And --world sz on the standard 10 x 20 well (--seeds 2: the sequence
is fixed, the seed only picks S or Z first, so two seeds are every
game):
agent games won drawn lost cut points moves score overrides win rate (95% interval)
lookahead 2 0 0 2 0 0.0 110.5 28.5 0 0.00 [0.00, 0.84]
gated 2 0 0 2 0 0.0 121.0 32.5 45 0.00 [0.00, 0.84]
expectimax 2 0 0 2 0 0.0 174.0 53.0 0 0.00 [0.00, 0.84]
greedy 2 0 0 2 0 0.0 186.0 58.5 0 0.00 [0.00, 0.84]
clairvoyant 2 0 0 2 0 0.0 241.5 80.0 0 0.00 [0.00, 0.84]
What these measurements say, and what they do not:
- Seeing one piece ahead matters a lot on a narrow well:
lookaheadsurvives 500 pieces in 18 of 20 games,greedyin 1. Seeing the whole sequence (clairvoyant) survived all 20; the interval for the difference is wide at 20 seeds. - The gate does not improve survival here (17 of 20 against 18; the intervals overlap almost entirely). Its value is the guarantee and the advance warning, not a stronger player.
expectimax, whose model (uniform pieces) is exactly right in therandomworld, did worse thanlookahead(14 of 20). Averaging the same one-piece evaluation over unseen pieces is not a better evaluation; we report it as measured.- Against the adversarial world everyone loses after fewer than 30 pieces on average on 6 x 12. Under S Z S Z ... on width 10 everyone loses, as the theorem says, and the order of players is different from the order under fair pieces.
What you'll see¶
In a terminal, play tetris animates every move: the piece appears at
the top centre in its starting orientation, turns, slides along the top
to its column, falls row by row, lands (highlighted green), and any full
rows flash (amber, then a burst of !, ✶ in the unicode style)
before they disappear. While the piece is on its way, a soft highlight
(the ghost; a light outline with --zoom 2 or more, not drawn at zoom 1
without colour) marks where it will land, and the panel to the right of
the well shows only the pieces still to come, each in a fixed slot of
three rows; when the piece has landed, the new piece joins the end of
the queue.
A real sequence, captured from the game's own transition frames
(6 x 8 well, greedy, seed 1, move 3: L r1 c2, which clears one row),
drawn in the ascii style without colour, left to right, then the next
line; the last picture is the position after the move:
. . . L . . . . L . . . . L . . . . . . . . . . . . . . . . . . . . . .
. L L L . . . . L . . . . L . . . . . L . . . . . . . . . . . . . . . .
. . . . . . . . L L . . . L L . . . . L . . . . . L . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . L L . . . . L . . . . . L . . . .
I . . . . . I . . . . . I . . . . . I . . . . . I L L . . . I L . . . .
I . . . . . I . . . . . I . . . . . I . . . . . I . . . . . I L L . . .
I . . . . L I . . . . L I . . . . L I . . . . L I . . . . L I . . . . L
I . . L L L I . . L L L I . . L L L I . . L L L I . . L L L I . . L L L
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
I L . . . . I . . . . . I . . . . . I . . . . . . . . . . .
I L . . . . I L . . . . I L . . . . I L . . . . I . . . . .
I L L . . L I L . . . L I L . . . L I L . . . L I L . . . .
I . . L L L I L L L L L I L L L L L ! ! ! ! ! ! I L . . . L
Spawn; one turn (r1); a slide of one column; four rows of fall
(meanwhile the ghost marks the landing cells, in colour or zoomed); landed (green in colour); the full row flashes
(amber in colour, the same picture without it); the burst; and the
position after the move. The panel's event lines name what a move
cleared: 3. L r1 c2 · clears 1 (single, +100), bold for a tetris
(clears 4 (tetris, +800)). Reproduce the sequence in Python:
from grounded_decision.examples.games import tetris
from grounded_decision.examples.games.engine import play_match
game = tetris.configure(6, 8)
record = play_match(game, [tetris.GreedyAgent()], seed=1, max_steps=20)
step = next(s for s in record.steps if s.after.lines > s.before.lines)
frames = game.transition(step.before, step.decision.move, step.after)
What is decided and what is animated. The player (and the gate) decides once per piece, choosing a placement: a rotation and a column. The animation is drawn afterwards and only illustrates that placement being carried out the way the rules define it (turn at the spawn position, slide along the top, hard drop). It is not a sequence of decisions, and nothing in it can change the move: no frame is a position of the game, and the record, the JSON view and every count are the same with or without it. Every placement is a real move: a human with a real Tetris could make the same turn, slide and hard drop (the rules page above says why the move set is a subset of the real game's), so the animation never shows a move the real game forbids. The turns are drawn clockwise and skipped when an intermediate orientation would overlap the stack (the rules only require the final one to fit).
Timing. The live view spends 60% of each move's delay on these
frames and draws no more of them than the frame cap allows in that time
(--fps, 60 by default), thinning them out evenly; a long slide or fall
is already capped at 4 and 8 frames. Without --delay, Tetris waits
0.25 s per move in the live view (about nine frames); --delay 1 shows
every frame. Nothing is animated with --delay 0, --fps max,
--view none, --view json or history, or when the output is not a
terminal, so throughput and printed output do not change.
Watching the gate warn¶
A real frame (uv run grounded-decision play tetris --size 6x12 --world adversarial --agents gated --view history --style ascii --color never --max-steps 14 --frame-every 14 --seed 0).
The right-hand column shows the current piece and the preview (one slot
of three rows each), the event lines the last three line clears; the
proposal fails unseen_piece_fits, the fallback finds nothing better,
so the decision degrades (here to the same move) and says so:
──────── after move 14 ────────
tetris gated [worst-case] move 14/14
. . . . . . O O move 14 player (gated · worst-case): O r0 c1
. . . . . . O O OVERRIDE proposed O r0 c1 -> played O r0 c1
. . . . . . why: no way to place the preview that leaves the top four rows empty: the …
. . . . . . S S ✓ legal the piece fits at the top and drops there
. . . S . . S S ✓ fits_preview every previewed piece (O) can still be placed
O O . S S . ✗ unseen_piece_fits no way to place the preview that leaves the top f…
O O . T S . note: WARNING: no placement keeps the top clear through the preview; this …
T . T T T .
T T L L L .
T Z L J . .
Z Z J J J . moves 14 · acts 13 · overrides 1 · piece O · next S · pieces …
Z S S S S . · 11. Z r1 c1 · clears 2 (double, +300)
· 8. Z r1 c1 · clears 1 (single, +100)
· 4. Z r1 c1 · clears 1 (single, +100)
since the last frame: 14 moves, 1 overrides
height ▁▁▃▂▅▅▆▅▆█▆███
In a terminal the pieces are drawn in their usual colours (cyan I,
yellow O, purple T, green S, red Z, blue J, orange L), the last piece
placed is highlighted, and --style blocks packs two rows per line.
Playing yourself¶
uv run grounded-decision play tetris --you seats you. Before each move
you see the piece, the preview and the evaluation's five favourite moves.
Type r1 c4 (or 1 4, 1,4, c4 for rotation 0, T r1 c4), or a
number to take that entry of the ranking (1 is the evaluation's
favourite). With --guarded your move is only the proposal: the gate
checks it, and if it fails, plays its fallback and tells you why.
--coords (on by default for a human) numbers the columns.
The regime table¶
uv run grounded-decision audit tetris --agent gated ends with the
information-regime table: each regime's player on a 6 x 12 well, in the
random and adversarial worlds, 12 seeds each, event "survive 300
pieces" (it takes about half a minute). In the table, "W/D/L" counts a
top-out as L (a finished one-player game without a win is a loss, as
in compare) and only a game cut off by the move budget as D; the
rate column is the survival rate. The
probabilistic row in the adversarial world carries the "assumption not
met" note.
Limits¶
- Placement mode (the default) is a turn-based simplification: no gravity, no kicks, no tucks or spins, no hold piece. A real player has more moves. The falling animation does not change that: it draws the hard drop that was already chosen. The real-time mode adds gravity, kicks, tucks and spins.
- The evaluation's weights are Dellacherie's; we did not re-tune them for the two-piece line or for narrow wells.
- The adversarial world is a one-piece-deep heuristic, so "survived N
pieces against
adversarial" says nothing about an optimal adversary (which, by Brzustowski, always wins). - Survival rates on 20 seeds have wide intervals; the tables show them.
Real-time mode¶
--mode realtime plays the same wells, worlds, preview and survival goal
the way the arcade game does: one tick at a time.
uv run grounded-decision play tetris --mode realtime # watch the gated player
uv run grounded-decision play tetris --mode realtime --agents you # key it yourself
uv run grounded-decision play tetris --mode realtime --level 15 # close to 20G
Source: src/grounded_decision/examples/games/tetris_realtime.py (the
tick, SRS, T-spins, scoring, the planner and the searches) and the
real-time classes in tetris.py (RealtimeTetrisGame, the pilot, the
gate). Tests: tests/test_games_tetris_realtime.py.
The rules of a tick¶
| Rule | What the code does |
|---|---|
| Tick | A tenth of a second of game time. On every tick the mover chooses one action: left, right, rotate_cw, rotate_ccw, soft_drop, hard_drop or wait. Then gravity acts, then the lock rule. |
| Moves | left/right move one column if the piece fits there. Turns follow SRS: the piece turns about the centre of its box, and if it overlaps, up to four other offsets (the wall kicks) are tried in order, exactly the tables of the Tetris Wiki's "Super Rotation System" page (a test compares them entry by entry); O does not turn. soft_drop moves one row down (1 point), hard_drop drops the piece as far as it goes and locks it at once (2 points a row). An action that cannot be carried out does nothing. |
| Gravity | Level 1: one row every 10 ticks. The guideline's curve, (0.8 − 0.007 (level − 1))^(level − 1) seconds per row (as the Tetris Wiki's "Marathon" page gives it), rounded to whole ticks: one row per tick from level 8, two from level 10, the whole height ("20G") from level 16 on a 20-row well. --gravity 20g or --gravity N (one row every N ticks) replace the curve. |
| Lock delay | A piece resting on the stack locks once it has rested more than 5 ticks (--lock-delay) since it last reached a row lower than any before. Shifts and turns never restart the timer, and neither does a turn that kicks the piece up and lets it fall back (so a piece cannot be kept alive forever by turning it). This is a step reset; the guideline's default, move reset limited to 15 moves, is not implemented. |
| Spawn | The new piece appears at the top centre in its spawn orientation (on a 10-wide well, I in columns 4-7, O in 5-6, the others in 4-6). There are no hidden rows above the well. The game is lost when the new piece does not fit there. |
| T-spins | The guideline's three-corner rule as the Tetris Wiki's "T-Spin" page documents it: a T whose last change of position was a turn, with at least three of the four corners of its 3 x 3 box occupied (walls and floor count). Full if both corners on the side it points to are occupied or the turn used the fifth kick test; otherwise mini. |
| Score | Times the level: single 100, double 300, triple 500, tetris 800; T-spin mini 100, mini single 200, mini double 400; T-spin 400, single 800, double 1200, triple 1600 (the guideline table on the Tetris Wiki's "Scoring" page). A tetris or a T-spin that clears rows right after another is a back-to-back, worth 1.5 times. Consecutive clearing locks add a combo bonus of 50 x combo x level. |
| Levels | --level N to start (1 by default); one level more every 10 lines. |
| Won | 500 pieces locked (--pieces). The score column of compare is lines cleared, the moves column is ticks. |
The worlds are the same as in placement mode: the adversarial and
kind worlds judge pieces with the placement-mode evaluation in both
modes, so a seed gives the same world either way.
The players in real time¶
Every player keeps its evaluation and still decides once per piece, but it now chooses among every resting place the piece can reach by shifts, turns with kicks and drops if there were time enough (tucks under overhangs and T-spin slots included). A pilot turns the choice into keys with the planner below and keys them one per tick. When no key sequence gets there before the piece locks, an ungated player keys the moves anyway (it does not model the clock), and when gravity pushes the piece off that path it chooses again from where the piece is (at most 8 times per piece; then it hard-drops).
The gated player checks the lookahead's proposal once per piece, on the piece's first tick, with four checks:
| Check | Passes when |
|---|---|
legal |
The piece can reach that resting place (shifts, turns, drops). |
executable_before_lock |
The planned keys lock the piece there before it locks anywhere else, played tick by tick under the gravity of the level and the lock delay (exact). |
fits_preview |
After the lock, every previewed piece can still be placed. |
unseen_piece_fits |
The previewed pieces can be placed leaving the top four rows empty. |
If all pass, the plan is played. Otherwise the fallback plays the safest
lock the planner can still reach in time, ranked as in placement mode.
The ticks that carry out a checked plan show no verdict in the panel
(no gate on this move): there is nothing new to check. A person
(--agents you --guarded) is checked key by key instead: a key passes
executable_before_lock when a lock as safe as the safest one before it
can still be reached in time.
What is proved, and what is measured¶
Proved, exactly, for the rules above.
- The planner is exact. The whole future of a falling piece depends
only on its rotation, position, the tick within the gravity period,
the lock timer and the lowest row it has reached: a finite set. The
planner searches it breadth-first with the game's own tick function,
so a key sequence that locks the piece in a given place exists if
and only if the planner finds one, and the one it finds is a
shortest one (ties go to the first in the order
left,right,rotate_cw,rotate_ccw,hard_drop,soft_drop,wait). The tests compare it with all 7^5 key sequences on small wells, and replay every plan tick by tick. - The candidates are complete. The resting places the players choose from are the closure of single moves (a test compares them with an independent search); everything the clock allows is among them.
- The gate's theorem holds in real time with the same three
statements, "move" meaning the lock of a piece: if the proposal passes
every check it is played; the gated player cannot top out while every
lock passed
unseen_piece_fits; and every loss is announced by aWARNINGat leastpreview + 1pieces ahead. It carries over because the previewed pieces are searched with drop placements (turn at the spawn, slide, hard drop), each played on the clock of its own level, so each is executable, and the fallback ranges over every lock the planner can reach from the spawn, which includes them. The tests check it on lost games in theadversarialandszworlds at levels 1, 9 and 12 with previews 0 and 1, and on games at level 15 with preview 2.
The preview search uses drop placements only, so it is sound but not complete: it may miss a tuck that would have kept the top clear, and then warn earlier than needed.
Measured. Real output of
uv run grounded-decision compare tetris --agents random,greedy,lookahead,expectimax,gated,clairvoyant --size 6x12 --seeds 20
in both modes (the same 20 seeds, goal 500 pieces):
Placement mode (the table above):
agent games won drawn lost cut points moves score overrides win rate (95% interval)
clairvoyant 20 20 0 0 0 20.0 500.0 331.8 0 1.00 [0.83, 1.00]
lookahead 20 18 0 2 0 18.0 476.6 315.4 0 0.90 [0.68, 0.99]
gated 20 17 0 3 0 17.0 461.4 304.9 74 0.85 [0.62, 0.97]
expectimax 20 14 0 6 0 14.0 423.2 277.9 0 0.70 [0.46, 0.88]
greedy 20 1 0 19 0 1.0 131.5 79.8 0 0.05 [0.00, 0.25]
random 20 0 0 20 0 0.0 8.2 0.2 0 0.00 [0.00, 0.17]
--mode realtime ("moves" are ticks):
agent games won drawn lost cut points moves score overrides win rate (95% interval)
clairvoyant 20 20 0 0 0 20.0 1552.5 331.9 0 1.00 [0.83, 1.00]
gated 20 19 0 1 0 19.0 1502.5 320.4 151 0.95 [0.75, 1.00]
expectimax 20 16 0 4 0 16.0 1526.6 316.4 0 0.80 [0.56, 0.94]
lookahead 20 13 0 7 0 13.0 1430.8 297.2 0 0.65 [0.41, 0.85]
random 20 0 0 20 0 0.0 31.1 0.5 0 0.00 [0.00, 0.17]
greedy 20 0 0 20 0 0.0 546.9 108.7 0 0.00 [0.00, 0.17]
--mode realtime --level 15 (pieces fall the whole height in a tick from
the start; on 6 x 12 that is 20G):
agent games won drawn lost cut points moves score overrides win rate (95% interval)
clairvoyant 20 20 0 0 0 20.0 1489.2 331.9 0 1.00 [0.83, 1.00]
expectimax 20 19 0 1 0 19.0 1515.7 328.6 0 0.95 [0.75, 1.00]
gated 20 18 0 2 0 18.0 1462.8 328.8 311 0.90 [0.68, 0.99]
lookahead 20 16 0 4 0 16.0 1486.9 324.4 0 0.80 [0.56, 0.94]
random 20 0 0 20 0 0.0 27.2 0.3 0 0.00 [0.00, 0.17]
greedy 20 0 0 20 0 0.0 409.9 84.2 0 0.00 [0.00, 0.17]
What these say, and what they do not (20 seeds: the intervals are wide):
- The extra moves do not help a one-piece player:
greedysurvived 1 of 20 games in placement mode and none in real time, though it cleared more lines before topping out (108.7 on average, against 79.8). We have not analysed why. - The gate gained most: 19 of 20 in real time against 17 in placement mode, with 151 overrides (74 before). The lookahead it gates dropped from 18 to 13. We have not separated which check does the work; the intervals of the gated and lookahead rows overlap only slightly ([0.75, 1.00] and [0.41, 0.85]), and this is one experiment.
- Starting at level 15 did not lower survival for these players on a 12-row well (16 to 19 of 20 for lookahead, expectimax and gated); the gate overrode twice as often (311 times).
clairvoyantsurvived every game in all three settings.
Limits of the real-time mode¶
- No hold piece, no hidden rows above the well, no move reset with a 15-move limit (the guideline's default), no auto-repeat: one shift per tick. A soft drop moves one row per tick, not 20 times the gravity.
- In a terminal the clock advances one tick per key you enter: you
type a key (arrow keys arrive as escape sequences, so they work too)
and press Enter, and nothing falls while you think. The session
protocol takes the same action names (
left,hard_drop…), so a front end can send them; key bindings on the site's play page are a follow-up. - The players' evaluation is still Dellacherie's, which knows nothing of T-spins: the T-spins they score are by-products of good placements, not set up on purpose.