DEV Community

Devanshu Biswas
Devanshu Biswas

Posted on

I Built Tetris in 150 Lines of JavaScript — 7 Pieces, Rotation, Line Clear

🌐 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));
Enter fullscreen mode Exit fullscreen mode

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)
];
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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];
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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)