2048: betting on the odds, assuming the worst, and a gate that keeps you alive¶
In 2048 you slide numbered tiles and merge equal ones, and after every move a new tile appears where the game decides. A player that plans for the usual tiles plays well when the tiles are random and badly when they are placed to hurt it. This page shows both kinds of player side by side, in worlds that place the tiles randomly, kindly or hostilely, and a gated player whose one promise (it never gives up guaranteed survival) is proved for every world.
This page is for anyone who wants to see what "probabilistic" and "worst-case" mean on a game they know, and for engineers who want a template for a game with a random environment. It assumes only the rules below; the two ideas it builds on are explained on Seeing the future, assuming the worst, or betting on the odds and Who controls the randomness: worlds.
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.
2048 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_2048 # the report quoted below
make watch-2048 # the gated agent on 4 x 4, live
make play-2048 # you play (w a s d); the gate checks you
make audit-2048 # every gated move judged by a deeper search
The command line calls the game 2048; the Python module is
games/game_2048.py (a module name cannot start with a digit), with the
rules in games/game_2048_rules.py. Tests: tests/test_games_2048.py.
The rules, exactly as implemented¶
| Rule | What the code does |
|---|---|
| Board | 4 x 4 by default; --size WxH from 2 to 8 cells a side. |
| Move | Up, down, left or right: every tile slides as far as it can. A move must change the board. |
| Merge | Two equal tiles that meet become one of twice the value; the new value is added to the score. A tile made by a merge does not merge again in the same move (2 2 2 2 slid left is 4 4 . .). |
| New tile | After every move: a 2 with probability 0.9 or a 4 with probability 0.1, on an empty cell chosen uniformly (in the random world). Two such tiles start the game. |
| End | When no move is legal. The game counts as won if it made a 2048 tile; play continues after 2048, as in the original. --target TILE changes the winning tile (a power of two, at least 4) and --stop-at-target ends the game, won, as soon as it appears. |
The report checks the merge rule on four rows, and the tests compare the fast implementation with a plain one on 1,600 random boards of four sizes:
Sliding a row to the left (a merged tile does not merge again):
2 2 2 2 -> 4 4 . . (+8)
2 2 4 . -> 4 4 . . (+4)
4 . 4 8 -> 8 8 . . (+8)
2 . 2 2 -> 4 2 . . (+4)
How the tiles are drawn. The tile after move \(k\) is decided by a 64-bit number computed from the seed and \(k\) alone (a SplitMix64 hash), never from the moves played: it picks the \(i\)-th empty cell with \(i = \lfloor h \cdot e / 2^{32} \rfloor\) (\(h\) the high 32 bits, \(e\) the number of empty cells) and a 4 when the low 32 bits are below \(2^{32}/10\). Both are uniform up to an error below \(2^{-32}\). So the same seed gives the same sequence of draws whatever the agent does, and a clairvoyant benchmark can be shown them in advance.
Speed. A board is one integer (five bits per cell), and sliding a row is a table look-up that is computed once per distinct row. A 4 x 4 move takes about 1 to 3 microseconds; the expectimax agent decides in about 3 milliseconds.
Who places the tiles: worlds¶
The agent chooses moves; the world chooses the new tiles
(--world MODE):
| World | Where the new tile goes |
|---|---|
random |
The standard game, from the seed. The only world whose future is fixed in advance. |
kind |
The cell and value that leave the player the best position after its best reply. |
evil |
A fixed adversary: the cell and value that leave the worst position after the player's best reply. It does not look at the agent. |
adversarial |
Adapts to the actual agent: takes the 6 placements evil rates worst, asks a private copy of the agent how it would reply to each, and picks the one where that reply ends worst. A placement that leaves no legal move is always taken. |
MODE:p |
Each tile follows MODE with probability p, else random (for example evil:0.3). |
"Position" is scored by the same heuristic the search agents use (below). Ties between equally bad (or good) placements are broken by the seed, so every world is reproducible.
The agents¶
From weakest to strongest; play 2048 --list prints this table and
--explain AGENT the paragraph behind each line.
| Agent | Regime | How it moves |
|---|---|---|
random |
baseline | A uniformly random legal move. |
greedy |
baseline | The move that merges the most points now. |
corner |
baseline | One move ahead: keeps the big tiles in decreasing order along a snake-shaped path from the bottom-left corner (tiles weighted \(4^k\) along the path). |
expectimax |
probabilistic | Searches two moves ahead (three when at most three cells are empty), averaging over every possible new tile with its true probability. |
minimax |
worst-case | The same search, but every new tile is assumed to be the worst one (cell and value). |
gated |
worst-case | expectimax proposes; exact survival checks decide (below). |
clairvoyant |
benchmark | Is shown the next four tile draws and tries every sequence of four moves against them. Defined only in the random world. |
The search agents score positions with the row heuristic of Robert
Xiao's 2048 AI (nneonneo/2048-ai): bonuses for empty cells and for
equal neighbours, penalties for rows and columns that are not monotone
and for large tiles, summed over every row and column.
What each regime may claim. minimax's value is a lower bound on
the heuristic two moves on, whatever the world does. expectimax's value
is an expectation under the random world only; in any other world its
notes, compare and audit say "assumption not met". clairvoyant is
refused outside the random world, because there the tiles depend on its
own moves and there is no fixed future to see:
$ grounded-decision play 2048 --world evil --agents clairvoyant
grounded-decision play: error: the agent clairvoyant is clairvoyant (a benchmark that sees the future), but in the evil world the future depends on the agent's own moves, so there is no fixed future to see; use the random world
The gate: never give up guaranteed survival¶
Call the worst-case survival horizon of a move the largest number of turns \(d\) (up to 3) for which, whatever tiles appear, the player can certainly keep making legal moves after it. The gated agent asks expectimax for a move and checks it:
| Check | Passes when |
|---|---|
legal |
The move changes the board. |
survives_any_spawn |
Every possible new tile after the move still leaves a legal move, whenever some legal move achieves that. |
best_survival_horizon |
The move's horizon equals the best horizon of any legal move. |
If a check fails, the agent plays a legal move of best horizon, choosing among those by the worst-case search, and the decision says so. The report shows the first override it finds (3 x 3, seed 0):
The gate at work: seed 0, move 35 on a 3 x 3 board
2 4 8
8 4 2
32 16 2
expectimax proposed down; the gate played up
[pass] legal: down changes the board
[pass] survives_any_spawn: down leaves a legal reply after every possible spawn
[FAIL] best_survival_horizon: down survives 2 turn(s) whatever tiles appear; the best move survives 3 (checked up to 3)
fallback: up keeps the best worst-case survival horizon (3 of 3 turns) with worst-case value 1,168,188
What is proved. Every move the gated agent plays has the best worst-case survival horizon of any legal move. So in every world, random, kind, evil, adversarial or a mixture: the game never ends at the tile right after a gated move if some move could have survived every possible tile, and if a gated move guarantees \(d \ge 1\) more turns, the next position still has a move guaranteeing \(d - 1\), which the agent will play. Its guaranteed horizon never drops by more than one per turn.
Why it is cheap and exact. A move never reduces the number of empty cells and a new tile fills exactly one; a board with an empty cell and a tile always has a legal move. So a board with at least \(d + 1\) empty cells survives \(d\) turns whatever appears, and the search only has to explore nearly full boards, where there are few tiles to try. The tests compare it with a brute force without that shortcut on hundreds of random positions of 2 x 2, 3 x 2 and 3 x 3 boards.
What is not proved. Scores, game lengths and largest tiles are measured, below. The guarantee is about survival a few turns ahead, not about reaching 2048: the gate gives up expected points to keep its horizon (the audit below prices that).
What happens in each world¶
The report (uv run grounded-decision examples games_2048) plays every
agent on 8 seeds of a 3 x 3 board, where games are short. These tables
come from the same code on 20 seeds:
uv run grounded-decision compare 2048 --agents random,greedy,corner,expectimax,minimax,gated --size 3x3 --seeds 20 --world random,evil,adversarial
| Agent | random: moves | random: score | evil: moves | evil: score | adversarial: moves | adversarial: score |
|---|---|---|---|---|---|---|
random |
32.0 | 193.0 | 15.7 | 77.8 | 12.4 | 43.2 |
greedy |
44.5 | 311.0 | 20.8 | 133.2 | 14.2 | 57.0 |
corner |
52.0 | 408.4 | 15.6 | 81.6 | 13.3 | 51.6 |
expectimax |
210.0 | 2,716.2 | 34.8 | 385.2 | 45.1 | 529.6 |
minimax |
145.9 | 1,739.8 | 48.6 | 634.8 | 34.5 | 362.4 |
gated |
188.9 | 2,416.8 | 60.8 | 850.6 | 47.2 | 563.0 |
Mean moves and mean score per game; the gate overrode 80, 26 and 22
proposals in the three worlds. With the clairvoyant benchmark in the
random world (compare 2048 --agents expectimax,clairvoyant --size 3x3
--seeds 20), seeing the next four tiles takes the mean game from 210.0
to 302.1 moves.
On the standard 4 x 4 board, 10 seeds per agent and world, the largest tile each game ended with:
from grounded_decision.examples.games import game_2048 as g
agents = ("random", "greedy", "corner", "expectimax", "minimax", "gated", "clairvoyant")
for world in ("random", "evil", "adversarial"):
results = g.measure(agents, world=world, size=(4, 4), seeds=range(10))
print(*g.distribution_table(results), sep="\n")
Columns: mean moves, mean score, then how many of the 10 games ended
with each largest tile (. for none).
World random (4 x 4, seeds 0-9)
agent moves score 32 64 128 256 512 1024 2048 4096 8192 16384
random 112.1 986.4 2 3 5 . . . . . . .
greedy 258.6 3109.2 . . 2 7 1 . . . . .
corner 238.3 2886.8 . . 5 4 1 . . . . .
expectimax 2749.7 58446.0 . . . . . 1 4 4 1 .
minimax 1474.5 29089.2 . . . . 1 2 5 2 . .
gated 2629.8 57661.2 . . . . 1 2 2 3 2 .
clairvoyant 7873.9 198551.2 . . . . . . . 1 5 4
World evil
agent moves score 16 32 64 128 256 512
random 51.6 430.4 1 4 4 1 . .
greedy 47.8 391.2 1 4 4 1 . .
corner 63.8 608.8 . 4 5 1 . .
expectimax 110.2 1541.6 . . 2 5 3 .
minimax 165.6 2777.6 . . 1 3 4 2
gated 131.6 1945.6 . . . 5 5 .
World adversarial
agent moves score 8 16 32 64 128 256 512 1024
random 28.6 134.4 2 5 3 . . . . .
greedy 35.5 208.8 . 3 7 . . . . .
corner 37.9 245.2 . 2 7 1 . . . .
expectimax 124.7 1739.6 . . . 2 5 3 . .
minimax 257.7 4924.4 . . . . 1 4 4 1
gated 167.0 2751.2 . . . . 5 2 3 .
In the random world the gated agent made 2048 or more in 7 of 10 games (81 overrides in all), expectimax in 9, minimax in 7 and the clairvoyant benchmark in all 10 (it ends the longest games, up to 13,717 moves).
What the numbers say:
- In the random world, betting on the odds pays.
expectimaxbeatsminimaxclearly: assuming the worst tile every time makes it play too cautiously. - Against hostile tiles, the bet collapses. In the
eviland the adaptiveadversarialworlds on 4 x 4,minimaxlasts 1.5 and 2.1 times as long asexpectimax(165.6 against 110.2 moves, 257.7 against 124.7) and makes larger tiles. On 3 x 3 the adaptive adversary, which tests each agent's actual reply, hurtsminimaxmore thanexpectimax: assuming the worst one move ahead is not the same as surviving an opponent that searches against you. - The gate helps most where survival is tight. On 3 x 3 under
evilthe gated agent lasts longest (60.8 moves against 34.8 for the expectimax it gates); on 4 x 4 it sits between expectimax and minimax. In the random world it gives up a little score for its guarantee. - None of these agents makes 2048 against hostile tiles (the best
4 x 4 game under
adversarialended at 1024). That is measured on these seeds, not proved for every player.
Audit: what the guarantee costs¶
make audit-2048 plays the gated agent on 3 x 3 and judges every
decision with a depth-3 expectimax (regret is in the heuristic's points):
== Audit: gated [worst-case] playing player in 2048, seed 0 ==
Line-up: player (gated). Result: no move left: largest tile 128, score 1,288 after 123 moves.
Oracle: expectimax depth 3. Regret unit: expected heuristic points (nneonneo line heuristic) 3 moves ahead.
Decisions audited: 123
total regret 1.884e+05, mean 1531 per decision
agreed with the oracle: 112/123 (P(agrees with the oracle) ∈ [0.85, 0.95] (95%, 123 trials))
gate overrides: 7
Most of the regret comes from the 7 overrides: the deeper search would
have taken the higher expected value and accepted a shorter guaranteed
horizon. The survival oracle (--oracle survival) measures the other
side: by construction the gated agent has zero regret against it.
The audit also prints the regime table: on 3 x 3 with 20 seeds, the
chance of making a 256 tile in the random world is 1.00 for the
clairvoyant benchmark, 0.80 for expectimax, 0.45 for minimax and 0.70
for the gated agent; under evil, the chance of making 128 is 0.15 for
expectimax, 0.45 for minimax and 0.80 for the gated agent, each with an
exact 95% interval in the output.
Limits¶
- The survival guarantee looks 3 turns ahead (
SURVIVAL_HORIZON); a longer horizon is exact too but costs more on full boards. - The
adversarialworld simulates only the agent's next reply to at most 6 candidate placements, so it is a bounded adversary, not an optimal one. - The heuristic, search depths and every comparison above are measured
on few seeds; the intervals in
compareand the regime table say how little 10 or 20 games can show.