A Tetris game in vanilla JavaScript — no build step, no dependencies.
To play: open index.html in a browser. Or grab tetris.html, which is
the whole game inlined into one file you can put anywhere and double-click.
Everything is clickable — an Enter button to start, Pause and Restart beside the board — so no keyboard chart is needed to get going. The controls are listed on the title screen, and restarting mid-run asks first.
Arrow keys or WASD, whichever you prefer.
| Key | Action |
|---|---|
| ← → / A D | Move (hold to auto-repeat) |
| ↓ / S | Soft drop |
| Space | Hard drop |
| ↑ / X / W | Rotate clockwise |
| Z / Ctrl | Rotate counter-clockwise |
| C / Shift | Hold |
| P / Esc | Pause |
| R | Restart |
| Enter | Start / play again |
On phones and tablets an on-screen control pad appears below the board.
Standard guideline behaviour rather than a rough approximation:
- SRS rotation with wall kicks — the full kick tables, so pieces twist out of tight spots the way they should.
- 7-bag randomiser — every seven pieces contain each tetromino once, so you never wait forever for an I.
- Hold with the usual once-per-piece limit, and a five-piece preview.
- Ghost piece showing where a hard drop lands.
- Lock delay of 500 ms with a 15-reset cap, so you can slide a piece into place without being able to stall indefinitely.
- T-spin detection by the three-corner rule, including minis.
- Scoring — the standard line table, T-spin bonuses, back-to-back at 1.5x, combos, soft/hard drop points, and a perfect-clear bonus.
- Levels every five lines, with gravity following the guideline curve — and because that curve bottoms out at level 13, the lock delay tightens from 500ms toward 150ms across levels 13-20 so difficulty keeps climbing instead of flatlining.
- Three modes — Marathon, Sprint 40 (ranked by time) and Ultra 2:00 — with a starting level of 1, 4, 7 or 10.
- Chiptune audio, synthesised from oscillators rather than sound files, so the game stays a single self-contained page.
Scores are kept per mode in localStorage.
Four toggles sit under the board: Boss powers, Sound, Motion and Sigil. Motion honours your system's reduced-motion preference automatically, turning off shake, particles and easing.
A dark mystical forest that deepens as you descend. Behind the playfield stands a boss, and every three levels a new one takes over — recolouring the entire game, shifting the woods, and bringing an ability of its own.
| Boss | Levels | Ability |
|---|---|---|
| The Moss Warden | 1-3 | none — the woods are calm here |
| The Hollow Witch | 4-6 | raises garbage rows from below |
| The Ember Seer | 7-9 | surges gravity in bursts |
| The Frostbound | 10+ | fogs the top of the well, and raises garbage |
Hazards always telegraph before they land, and Boss powers: On/Off turns them off entirely if you would rather just stack.
Each boss is represented by a slowly turning sigil behind the playfield — concentric rings, radiating spokes and an angular glyph in that boss's accent colour, flaring briefly when you land a tetris or T-spin. It is drawn procedurally, so there are no image files to keep alongside the game.
Blocks are cut gems, the panels are dark bark with glowing rune edges, and the forest is generated procedurally from a seeded RNG — each boss's woods look the same every time you meet them, and different from everyone else's. Line clears burst into particles, hard drops land with a trail and a shake, and pieces ease between cells rather than snapping.
| File | Purpose |
|---|---|
engine.js |
The rules — grid, collision, rotation, scoring. No DOM. |
theme.js |
The four bosses — palettes, forests and hazard configs. |
sprites.js |
Block palette, 5x7 bitmap font, and the boss sigils. |
sound.js |
WebAudio synth — every cue generated, no audio files. |
forest.js |
Seeded procedural parallax forest. |
fx.js |
Particle pool, screen shake, motion easing. |
tetris.js |
Canvas rendering, keyboard/touch input, animation loop. |
engine.test.js, fx.test.js |
Tests for the rules and the effects. |
index.html, style.css |
Page and styling. |
build.js |
Inlines everything into the standalone tetris.html. |
The engine is deliberately free of DOM references so the rules can be tested
directly. It loads as a browser global (TetrisEngine) or a CommonJS module.
node --test84 tests. 72 cover the rules — the bag randomiser, every rotation and kick path, line clears and collapse, the scoring table, T-spins, hold, the lock delay and its tightening curve, levelling pace, starting levels, the three modes, and every boss hazard (garbage cadence and its single gap, the stack being pushed up, top-out, the telegraph, surges, and the toggle) — plus a fuzz run that checks random input never corrupts the board, and a bot that plays a full game end to end. The other 12 cover the effects layer: particle pooling and expiry, gravity, shake capping and decay, and frame-rate independent easing.
The rules tests never touch the renderer, so they stay a valid regression net across visual changes.