🌐 Live demo (LOOK · UNDERSTAND · BUILD): https://dev48v.infy.uk/game/day3-tetris.html
Day 3 of my GameFromZero series. 50 playable browser games in 50 days. All open the page → play instantly.
Today: Tetris. Iconic. Older than most readers. Famously simple to start, infinitely deep to master.
The whole game is 150 lines of vanilla JS — same game-loop skeleton as Snake (Day 1) and Pong (Day 2), just with a richer state and a rotation algorithm.
The 4 ideas
Idea 1 — Board is a 2D grid of color numbers
10 columns × 20 rows. Each cell holds 0 (empty) or 1-7 (the color of the tetromino that landed there).
let board = Array.from({ length: 20 }, () => Array(10).fill(0));
That's the whole world state. Every piece eventually merges into this grid.
Idea 2 — 7 tetrominoes as little matrices
Each piece is a small 2D array of 0s and the piece's color number. Rotate the matrix to rotate the piece.
const SHAPES = [
[[1,1,1,1]], // I (cyan)
[[2,0,0],[2,2,2]], // J (blue)
[[0,0,3],[3,3,3]], // L (orange)
[[4,4],[4,4]], // O (yellow)
[[0,5,5],[5,5,0]], // S (green)
[[0,6,0],[6,6,6]], // T (purple)
[[7,7,0],[0,7,7]], // Z (red)
];
Idea 3 — Matrix rotation by transpose
To rotate a matrix 90° clockwise: column k of the original becomes row k of the rotated. Five lines:
function rotate(s) {
const N = s.length, M = s[0].length;
const out = Array.from({ length: M }, () => Array(N).fill(0));
for (let r = 0; r < N; r++)
for (let c = 0; c < M; c++)
out[c][N - 1 - r] = s[r][c];
return out;
}
Idea 4 — Line clear
After a piece merges, scan rows bottom-up. Any row that's fully non-zero gets splice()'d out and replaced with an empty row at the top. Award points.
for (let y = ROWS - 1; y >= 0; y--) {
if (board[y].every(v => v)) {
board.splice(y, 1);
board.unshift(Array(COLS).fill(0));
cleared++;
y++; // re-check same index
}
}
// Original NES scoring
score += [0, 40, 100, 300, 1200][cleared];
Four lines at once = "Tetris" = 1200 points. The exponential reward is what makes the game tense.
Collision check
Before any move (left/right/down/rotate), check if the resulting position overlaps the board or goes out of bounds:
function collide(p, dx = 0, dy = 0, shape = null) {
const s = shape || p.shape;
for (let r = 0; r < s.length; r++)
for (let c = 0; c < s[r].length; c++) {
if (!s[r][c]) continue;
const x = p.x + c + dx, y = p.y + r + dy;
if (x < 0 || x >= COLS || y >= ROWS) return true;
if (y >= 0 && board[y][x]) return true;
}
return false;
}
Same function handles all four motions because we pass dx/dy and optionally a rotated shape to "preview" the move.
The game loop
Same pattern as every game in this series. requestAnimationFrame for smooth rendering + a gravity tick at fixed intervals.
let last = 0;
function loop(t) {
if (!gameOver) {
if (t - last > 600) {
softDrop(); // try to move down 1
last = t;
draw();
}
}
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
softDrop() either moves the piece down by 1 OR (if it can't) merges into the board, clears any complete lines, and spawns the next piece.
Hard drop (the satisfying one)
Space = drop piece as far as it goes in a single tick. The most-used shortcut in competitive Tetris.
function hardDrop() {
while (!collide(piece, 0, 1)) piece.y++;
softDrop(); // triggers merge + line-clear
}
Two lines. Maximum drama.
What this unlocks
Same skeleton works for every falling-block puzzle:
- Puyo Puyo — swap rectangular shapes for paired blobs, add cascade physics
- Dr. Mario — different piece set, color-matching match-3
- Columns — vertical-only pieces, match-3 instead of full rows
- Lumines — Tetris with a beat-synced "timeline" sweeper
Board + pieces + collide + rotate + line-clear + gravity tick. That's the pattern. Everything else is variation.
Try it now
Three tabs on one page:
https://dev48v.infy.uk/game/day3-tetris.html
- LOOK — playable Tetris (with next-piece preview + scoring)
- UNDERSTAND — 9 click-through steps with diagrams + WHY for each piece of math
- BUILD — copy the HTML, save, open, play
What's next in GameFromZero
Day 4: Breakout. Same loop, one paddle, replace right wall with a brick grid.
Series: 50 playable browser games · zero install · open + play.
🌐 All games: https://dev48v.infy.uk/gamefromzero.php
Top comments (0)