All questions

Tetris

Premium

Tetris

Build a playable Tetris as a single React component. Tetris is the falling-block puzzle: tetrominoes (four-cell shapes) drop down a grid on a timer, you slide and rotate them as they fall, and completing a horizontal row clears it for points. It's a compact game that leans hard on three things React developers reach for constantly — a timer loop, keyboard input, and derived rendering — and it exposes the classic stale-closure trap when the loop reads state it didn't capture.

What you'll build

The starter App.tsx renders the board in its resting state — an empty 16-row by 10-column grid with the first piece resting at the top, score 0, and a Start button — with no logic. The piece definitions and an empty-board builder are already there. Make it playable:

  1. Hold the game in state. The board (16x10 of 0 or a color index 1..7), the active piece (four cells + a color), plus score, running, and over in useState.
  2. Run a gravity loop. On Start, setInterval(step, 600). Each step tries to move the piece down one row; if it can't, lock it into the board, clear any full rows, and spawn the next piece.
  3. Spawn in a fixed order. The seven pieces (I, O, T, S, Z, J, L) cycle in that exact order — no randomness — so every run is deterministic.
  4. Steer with the keyboard. A window keydown listener maps ArrowLeft/ArrowRight to a shift, ArrowDown to a soft-drop, and ArrowUp to a 90-degree rotation.
  5. Reject illegal moves. Any move — shift, drop, or rotate — is applied only if all four resulting cells stay on the board and off the settled blocks.
  6. Read the latest state. The tick and the key handler must act on the current board and piece — a value captured in the interval closure goes stale. Hold them in refs.
  7. Clean up. Clear the interval on game over and in the effect cleanup.

Examples

  • Load: the board is empty with the cyan I-piece across the top and score 0. Nothing falls until you press Start.
  • Press Start: the piece drops one row every 600ms. ArrowLeft/ArrowRight slide it, ArrowUp rotates it, ArrowDown drops it faster.
  • A piece that can't fall locks in place; a full row clears, the rows above drop down, and the score jumps by 100.
  • When a freshly spawned piece has nowhere to go, a Game Over overlay covers the board. Restart resets everything.

Behavioral contract

  • Use a 10-column by 16-row board, a 600ms gravity tick, and the fixed cyclic piece order I, O, T, S, Z, J, L. The initial cyan I-piece occupies (3,0) through (6,0); the game starts paused at score 0.
  • Start and Restart both reset the board, score, piece sequence, and active piece before starting exactly one interval. The first spawned piece is I and the next is O.
  • A gravity tick moves the active piece down exactly one row when legal. When blocked, lock its four cells, clear every full row, add 100 points per cleared row, then spawn the next piece.
  • While running, Left/Right shift one column, Down soft-drops one row, and Up rotates 90 degrees clockwise about the declared pivot. Prevent page scrolling for handled arrow keys; ignore illegal candidates and all other keys.
  • A newly spawned piece that collides ends the game, stops gravity, and shows Game Over. Clear the interval on game over, restart, and teardown.

Accessibility contract

  • Keep Start/Restart as a native button with a truthful visible label.
  • Expose the visual cell matrix as one named 10-column by 16-row board graphic; mark its 160 decorative cells hidden from assistive technology.
  • Announce score changes politely and maintain a separate polite status message for ready, running, and game-over states, including the keyboard instructions and final score.
  • Do not rely on tetromino colors alone: the accessible board name, score, status, and Game Over text carry the essential state.

Notes

  • Separate the piece from the board. The settled board only changes when a piece locks; the active piece moves freely above it. Render the piece overlaid on the board.
  • Collision is one function. Off the board or onto a settled cell — the same check gates shifting, dropping, and rotating.
  • Rotation is a transform, not a lookup. Rotate each cell 90 degrees about the piece's pivot; if the result collides, keep the old piece.
  • The tick must read the latest state. A captured piece/board freezes at its first value — hold them in refs so the interval always sees the current game.
  • Always clear the interval. On game over and on unmount, or a stray loop keeps ticking after the component is gone.
WE’RE LISTENING

Help make UIReady better

Found a bug or have an idea? Tell us about it.

Up to 3 files · 5 MiB each · JPG, PNG, WebP, MP4, WebM, MP3, M4A or WAV