Benchmarks

    We Made Two LLMs Play Tetris. The Winner Wasn't the Better Player.

    Claude Opus 4.8 vs DeepSeek V3.1 at turn-based Tetris, both served through Mesh API. Placement is basically solved, so the contest measures something more interesting: temperament.

    ← All posts
    9 min readBy Raushan Sharma

    Two Tetris boards, Opus in indigo and DeepSeek in teal, mid-matchTetris Contest. Claude Opus 4.8 vs DeepSeek V3.1, both served through Mesh API.

    Claude Opus 4.8 vs DeepSeek V3.1, both served through Mesh API

    We wanted a clean, slightly silly way to compare a frontier model against a cheaper open one. Not on trivia. Not on a leaderboard everyone has already gamed. On something that rewards planning and judgment, and that ends with one unambiguous winner.

    So we made two language models play Tetris against each other, and the result was more interesting than "the expensive one won." The model that placed pieces better, and often scored higher, was frequently the one that lost. What separated the two wasn't skill. It was disposition.

    Here's the whole experiment: how we built it, every result, and the bug that nearly faked the entire thing.

    TL;DR

    • Claude Opus 4.8 and DeepSeek V3.1 (both called through Mesh API) compete at Tetris reframed as a turn-based, numeric decision game. No reflexes, pure decision quality.
    • LLMs are genuinely bad at imagining where a piece lands from a text grid. Once the engine pre-computes every legal placement and the model just chooses, both play near-optimally: ~0.40 lines per piece, against a theoretical max of 0.40.
    • Because placement is basically solved, the contest ends up measuring risk temperament. Each model designs its own scoring policy, and the policies are consistently different: Opus plays survival-first; DeepSeek plays line-greedy.
    • Played to a real top-out, last model standing wins, Opus wins roughly 6 to 7 of 10. The win rate is noisy. The behavioral difference is not.
    • The recurring, almost poetic result: you can outscore your opponent and still lose by topping out first.
    • Given the chance to re-tune mid-game, Opus fiddles with its weights ~4 to 5x more than DeepSeek, and it barely changes who wins.

    Why Tetris

    Most LLM benchmarks reward recall. We wanted one that rewards judgment: spatial reasoning, trade-offs, and the discipline to not blow yourself up. Tetris is a good fit, it's spatial, adversarial, and a single number settles it.

    The obvious problem: real Tetris is a reflex game, and LLMs have no reflexes. Asking a model to react to falling blocks in real time is a non-starter. So we reframed it as a turn-based placement problem:

    • Both players receive the identical piece stream (a seeded 7-bag randomizer), so outcomes reflect decisions, not luck.
    • For each piece, a model decides where it goes, a rotation and a column. The engine hard-drops it, clears full rows, and scores it.
    • Everything is numbers in, numbers out. No images, no canvas. The board goes to the model as text; the answer comes back as a couple of integers.

    That turns "playing Tetris" into a clean test of strategy you can run headless, thousands of times, and replay afterward for humans to watch.

    Both models are reached the same way, through Mesh API, which is OpenAI-compatible, so one endpoint and one key serve anthropic/claude-opus-4.8 and deepseek/deepseek-chat-v3.1 alike. The key never touches the browser; a small Node process holds it. Any Mesh slug can be swapped in, which made running variants trivial.

    The first attempt failed badly

    Our first design sent the board as a compact numeric summary, per-column heights and holes, plus the current and next piece, and asked for two integers: rotation column.

    It was a disaster. Models built pathological towers, left unfillable holes, and topped out within about ten pieces, never clearing a line. DeepSeek would dump pieces into the same middle column until it spiked to the ceiling.

    The reason is worth sitting with, because it's the actual finding hiding in a "bug." Asking an LLM to mentally simulate "where does a Z at rotation 1 land in column 2, and which row does that complete" is exactly the 2D physics these models are worst at. They hallucinate the geometry. The information we gave them was sufficient, a five-line heuristic clears lines easily from the same inputs. The models simply couldn't do the spatial arithmetic in their heads.

    The fix: let the engine do the physics, let the model do the strategy

    Instead of making the model imagine the drop, we had the engine enumerate every legal placement and simulate its outcome, then present them as a menu. The model just picks:

    Current piece Z. Your options:  1) rotation 0, column 0 -> lines cleared 0, holes +1, max height 7, bumpiness 9  2) rotation 1, column 2 -> lines cleared 1, holes +0, max height 5, bumpiness 4  3) rotation 0, column 6 -> lines cleared 0, holes +0, max height 6, bumpiness 6  ... (all legal placements, in neutral order)Reply with the option number.

    This offloads the physics the model can't do, while keeping the strategy, which trade-off to take, entirely the model's. The effect was night and day. Both models started clearing lines and surviving for hundreds of pieces.

    The result is almost anticlimactic: both models now place near-optimally, ~0.39 to 0.40 lines per piece, where 0.40 is the theoretical maximum (4 cells per piece / 10 cells per line). Placement quality is effectively solved. It is no longer the thing that separates them.

    Which raised the real question: if both models pack pieces equally well, what is actually different about how they play?

    Policy mode: design a strategy once, then live with it

    Running a live decision per piece is slow, one API call per piece, and since placement is near-solved, most of those calls just confirm the model can read a menu. So we added policy mode.

    Each model is asked, once, for a scoring policy: a set of weights over five board features measured after a placement.

    featuremeaninggood direction
    linesrows this placement clearshigher = better
    holesempty cells trapped under filled onesfewer = better
    aggtotal stack height across columnslower = better
    bumpbumpiness (height differences between neighbors)lower = better
    maxtallest columnlower = better

    The engine then plays the whole match locally, scoring every legal placement as the weighted sum of those features and playing the max. A thousand pieces resolve in seconds with just two API calls total, one policy per model. The model becomes the strategist; the engine is its hands.

    This is where the experiment got interesting, because the policies the two models choose are consistently, measurably different.

    Across ten matches, we logged the weight each model assigned to lines, its appetite for clearing rows aggressively versus playing it safe. Opus's average line-greed was 1.5. DeepSeek's was 6.3, more than four times as aggressive. Opus leaned on heavy hole and height penalties; DeepSeek wanted lines, and weighted the things that keep you alive far more lightly.

    In plain terms: Opus designs survival-first strategies. DeepSeek designs to score. Neither was told to. It's a stable behavioral signature that falls out of the same prompt, every time.

    The results, and the headline that surprised us

    Played to a real top-out, with weights locked for the match:

    Opus 6, DeepSeek 4. Average score gap +20.3%.

    MatchPiecesWinnerHow it endedOpus (pts / lines)DeepSeek (pts / lines)
    11628DeepSeekOpus topped out2,581,300 / 6352,529,200 / 644
    2164OpusDeepSeek topped out33,100 / 6523,700 / 51
    3587DeepSeekOpus topped out320,400 / 220323,100 / 233
    4131OpusDeepSeek topped out19,200 / 5012,000 / 38
    5311OpusDeepSeek topped out106,500 / 12382,800 / 108
    6655DeepSeekOpus topped out419,700 / 246411,200 / 256
    7479OpusDeepSeek topped out225,600 / 189197,900 / 174
    8470OpusDeepSeek topped out216,800 / 182163,500 / 170
    92054OpusDeepSeek topped out4,050,600 / 8204,159,400 / 805
    10415DeepSeekOpus topped out144,600 / 150174,000 / 164

    All ten policy-mode matches side by side. Each card shows the final Opus and DeepSeek boards, who topped out, and the score gap.Figure 1. Policy tournament. 10 matches, final board states and score gaps.

    Look at Match 9. DeepSeek scored more, 4.16M to Opus's 4.05M, and was leading on points when it died. But it topped out first, so Opus won. The win condition is survival, not score. A model that plays aggressively can rack up a bigger number and still be the one who hits the ceiling first.

    That's the whole experiment in one match. The better-scoring player lost because being the better-scoring player is what killed it.

    Adaptive mode: can a model notice it's in trouble?

    To probe meta-reasoning, we added a harder variant. The board tops out the instant any column exceeds height 12, far less margin. And every 40 pieces the simulation pauses, shows each surviving model its live board, current weights, and recent stats, and asks one question: keep your weights, or change them?

    Now a model can notice it's spiraling and re-tune. The behavioral split got even cleaner:

    Opus 7, DeepSeek 3. Total re-tunes: Opus 32x vs DeepSeek 7x.

    Adaptive Arena results grid: ten matches at top-out height 12, with final boards and score gaps.Figure 3. Adaptive Arena. 10 matches at top-out height 12, with re-tunes allowed every 40 pieces.

    Opus is a tinkerer. Given the freedom to adjust, it adjusted constantly, 32 times across ten matches. DeepSeek mostly set its strategy and rode it out. But here's the kicker: the win split (7 to 3) sits in the same band as the locked-weights run (6 to 4). The ability to adapt did not change who tends to win. All that mid-game fiddling was a personality trait, not a performance lever.

    Weight timeline for each adaptive match. Highlighted checkpoints mark where a model changed its scoring weights mid-game; Opus pivots far more often than DeepSeek.Figure 4. Adaptive weight timeline. Highlighted checkpoints mark where each model changed its scoring weights. Opus 32 changes, DeepSeek 7.

    A real bug we hit (and what it taught us)

    When we first re-ran the full tournament, every single match came back a perfect tie, identical scores down to the point.

    The cause: the tournament fired all weight requests concurrently, ten matches x two models = twenty simultaneous calls. The burst got rate-limited, every call errored, and both models silently fell back to the same default weights. Identical policies produce identical games on an identical piece stream. Twenty fake ties.

    The fix was straightforward: run matches sequentially with only two calls in flight, add retry-with-backoff on the weight calls, and, most importantly, log every fallback loudly instead of swallowing it. The lesson generalizes well beyond Tetris: a silent fallback turns an API hiccup into a confident, wrong result. If your system can degrade quietly, it eventually will, and you won't know. Make failure visible.

    The honest caveats

    We like this benchmark, so we want to be careful not to oversell it.

    • Small N, high variance. Ten seeds per run, weights re-sampled at temperature 0.4. Treat the win counts as samples from each model's strategy distribution, not ground truth. Across several runs we saw 8 to 2, a re-run 9 to 1, then fresh 6 to 4 and 7 to 3. The defensible claim is "Opus wins more often than not, usually by surviving longer, with a score edge averaging ~20 to 28%", not a tidy 9/10.
    • It's a fun benchmark, not a rigorous eval. No significance testing.
    • Placement is engine-assisted. Models choose among pre-computed options; they don't compute geometry. That's deliberate, it's what makes them competent, but it means we're testing strategic judgment, not raw spatial reasoning.
    • Two models, one task. This is a finding about Opus 4.8 and DeepSeek V3.1 on Tetris. It is not a general capability claim.

    What it actually says

    Give a frontier model and a cheaper model the same Tetris, let them design how to play, and the cheaper one is genuinely close. DeepSeek V3.1 places just as well and frequently scores higher. What separates them isn't skill, it's temperament. Opus plays to survive and wins more often; DeepSeek plays to score and dies sooner.

    On a benchmark where the rule is last one standing, discipline beats firepower. But not by nearly as much as a single lucky run would have you believe, and that gap between "the impressive number" and "the honest number" is, in the end, the most useful thing the experiment produced.

    Reproduce it

    Every run writes a JSON log and a self-contained HTML replay, open it and watch the boards play back at 1x to 20x, winners tagged, score gaps shown.

    bash
    cd tetris_contest_v2# Mesh API key in .env as MESH_API_KEY (or --demo for no-API heuristic runs) node multi.js --runs 10 --pieces 10000          # policy tournamentnode adaptive-multi.js --runs 10 --pieces 1000  # adaptive tournamentnode policy.js --pieces 1000 --seed 7           # a single policy matchnode adaptive.js --seed 7                        # a single adaptive match + timeline

    Both models in this experiment were served through one OpenAI-compatible endpoint. If you want to run your own model-vs-model experiments without juggling a different SDK per provider, point your existing OpenAI client at Mesh API and swap the model slug. Same code, any model.