oneshotlm
Compare all models on this prompt →

Tetris · deepseek-v4-flash

Build a playable Tetris on a canvas with all seven tetrominoes, rotation, line clears, increasing speed, a next-piece preview, and score.

⛨ sandboxed Open in new tab ↗
transcript · 6 messages
user

Build a playable Tetris on a canvas with all seven tetrominoes, rotation, line clears, increasing speed, a next-piece preview, and score.

assistant
thinking
The user wants me to build a complete Tetris game in a single HTML file with a canvas element. Let me plan out all the features:

1. All 7 tetrominoes (I, O, T, S, Z, J, L)
2. Rotation (with wall kicks)
3. Line clears
4. Increasing speed
5. Next piece preview
6. Score display
7. Canvas-based rendering

Let me build this comprehensively.
write /workspace/index.html (13706 bytes)
toolResult

Successfully wrote 13706 bytes to /workspace/index.html

assistant
thinking
Let me verify the file was written correctly and check for any issues.

Let me verify the file is present and well-formed:

bash {"command":"wc -l /workspace/index.html && head -5 /workspace/index.html && echo \"---\" && tail -5 /workspace/index.html"}
toolResult

453 /workspace/index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> --- resetGame(); })(); </script> </body> </html>

assistant
thinking
The file is 453 lines and looks well-formed. Let me double-check the game logic once more quickly by reviewing the key parts to make sure everything is correct.

Actually, let me also check if there's a subtle issue: when the game starts, `resetGame()` calls `spawnNext()`, which sets `current` and `next`. But `spawnNext()` checks `if (!next)` - on the first call, `next` is null, so it assigns `next = randomPiece()`. Then it sets `current = spawnPiece(next)` and `next = randomPiece()`. That's correct.

Wait, there's a bug in `spawnNext()`: the first time it's called, `next` is null, so it sets `next = randomPiece()`. Then `current = spawnPiece(next)` and `next = randomPiece()`. That's fine - current gets a piece, next gets a new random piece.

But wait, let me re-read:
```
function spawnNext() {
    if (!next) {
      next = randomPiece();
    }
    current = spawnPiece(next);
    next = randomPiece();
    ...
}
```

First call: next is null, so next = randomPiece(). Then current = spawnPiece(next). Then next = randomPiece(). Good.

Second call: next is not null (it was set in the previous call). So we skip the if. current = spawnPiece(next). Then next = randomPiece(). Good.

But actually, wait. In `resetGame()`, we call `spawnNext()`. But we also set `next = null` before that. So on the first call:
- next is null, so next = randomPiece() (first piece)
- current = spawnPiece(next) (current gets the piece)
- next = randomPiece() (next gets a new piece)

That's correct.

In `lockPiece()`, we call `spawnNext()` again. At that point, next has a piece from the previous call. So:
- next is not null, skip the if
- current = spawnPiece(next) (current gets the waiting piece)
- next = randomPiece() (next gets a new piece)

That's correct! The bag randomizer works well.

One thing I notice: I should also check that the animation loop keeps running. The `render()` function calls `requestAnimationFrame(render)` at the end, so it will keep running once started. The `resetGame()` function calls `render()` only if `animationId` is falsy, which it will be on the first call. After that, the loop continues.

Let me also think about the `valid` function for the ghost piece - when the current piece is at the top and some rows are above the board (y < 0), the `isValid` function checks `if (by < 0) continue;` which is fine for collision detection.

The game looks correct. Let me also verify the file is complete by reading it.

Actually, I think the file is good. The game has all the requested features:
- All 7 tetrominoes ✓
- Rotation (with wall kicks for I piece and others) ✓
- Line clears ✓
- Increasing speed (level up every 10 lines, drop interval decreases) ✓
- Next-piece preview ✓
- Score (100/300/500/800 points for 1-4 lines, multiplied by level, +2 per hard drop row) ✓
- Ghost piece ✓
- Bag randomizer for fair piece distribution ✓
- Game over detection ✓
- Restart button ✓

I'm satisfied with the implementation.

The file is complete at 453 lines. Here's what's implemented: **Tetris — all in one `index.html` file** | Feature | Details | |---|---| | **7 tetrominoes** | I, O, T, S, Z, J, L — each with distinct colors | | **Rotation** | 90° CW with wall kicks (8 kick offsets, including I-piece adjustments) | | **Line clears** | 1–4 lines at once with scoring: 100/300/500/800 × level | | **Increasing speed** | Level up every 10 lines; drop interval goes from 1000ms → min 100ms | | **Next-piece preview** | 120×120 canvas showing the upcoming piece | | **Score** | Displayed alongside level and lines cleared | | **Ghost piece** | Translucent outline showing where the piece will land | | **Hard drop** | Spacebar — locks instantly, +2 pts per row dropped | | **Soft drop** | ↓ key — move down faster | | **Bag randomizer** | 7-bag system ensures fair piece distribution | | **Game over** | Detected when a new piece can't spawn; overlay + restart button | | **Controls** | ← → ↓ ↑ Space, with on-screen key legend | To play, open `index.html` in any browser — no network or dependencies needed.

TAKE IT TO CODE

A single self-contained HTML file — copy it and open in any browser, or reuse the prompt with your own pi-agent run.

RUN

modeldeepseek-v4-flash
vendorDeepSeek
harnesspi-agent
generated2026-07-10

EVALUATION

4/5 working

Functional Tetris with score, level, lines, next-piece preview; pieces fall and respond to keyboard controls.

Vision grade · anthropic/claude-sonnet-5

MEASUREMENTS

Raw readings from the capture, not a verdict — the grade above is the only judgement. Percentages are how much of the screen changed; the measure under-reports sparse motion on a dark background.

idle motion1.0%
on click1.0%
on drag1.5%
on wheel1.3%
on enter + space1.4%
on arrow keys0.9%
on w/a/s/d0.9%
frame spread15.2 / 255
console errors0
js errors none

METRICS

runtime54.8s
tokens in13k
tokens out5.7k
cached1.0k
cost / run$0.0022