Tetris was designed by Alexey Pajitnov in 1984 at the Soviet Academy of Sciences on a Soviet Electronika 60 computer. Since then it has been ported to over 65 platforms and is one of the most recognised games in history. Beyond its cultural impact, Tetris is an exceptional programming exercise: implementing it correctly requires understanding 2D array manipulation, rotation matrices, collision detection, game loop timing, score systems, and level progression. These are real, transferable programming skills. This guide explains every component of a Tetris implementation, using JavaScript and the HTML Canvas API as the implementation context.
Tetriminoes: The Seven Pieces
Tetris uses seven distinct pieces called tetriminoes (tetrominoes), each composed of four square cells arranged in a connected configuration. They are traditionally identified by the letters they resemble: I (four in a line), O (two-by-two square), T (T-shape), S (S-shape), Z (Z-shape, mirror of S), L (L-shape), J (J-shape, mirror of L). Each has a designated color in the official Tetris guidelines: cyan, yellow, purple, green, red, orange, and blue respectively.
Each tetrimino is represented as a 2D matrix (or a flat array with row-length arithmetic). The I piece in its horizontal orientation: a 4×1 array [1,1,1,1]. The O piece: a 2×2 array [[1,1],[1,1]]. The T piece in its default orientation: [[0,1,0],[1,1,1]]. These matrix representations are the data structure from which all rendering and collision detection derives. A "1" indicates a filled cell; a "0" indicates empty space within the bounding box.
The seven-bag randomisation system — used in all official Tetris games since Tetris Guideline (2001) — prevents long droughts of any single piece. Rather than selecting the next piece with uniform random probability (which could, by chance, produce 15 S and Z pieces in a row), the system fills a "bag" with one copy of each of the seven pieces, shuffles the bag, and delivers them in that order. When the bag is empty, it refills and reshuffles. This guarantees each piece appears at least once per seven pieces, creating a more balanced and fair experience.
Rotation System
Rotating a tetrimino is implemented as a matrix transpose followed by a column reversal (for clockwise rotation) or a row reversal followed by a transpose (for counterclockwise). For an N×N matrix, the clockwise rotation algorithm: create a new N×N matrix, then for each cell at (row, col) in the original, place it at (col, N-1-row) in the new matrix. This operation works correctly for all seven tetriminoes.
The official Tetris Guideline uses "Super Rotation System" (SRS) to handle rotation near walls and other pieces. When a basic rotation would result in the piece overlapping a wall or existing blocks, SRS attempts a series of "wall kicks" — specific offset positions defined in tables for each piece and each rotation state. If any wall kick position is valid (no overlap), the piece rotates to that position. If all wall kick attempts fail, the rotation is blocked. This system enables T-spins and other advanced techniques that form the basis of competitive Tetris strategy.
Implementing SRS correctly is the most complex part of a complete Tetris implementation. A simplified rotation system that merely attempts the basic rotation and cancels it if invalid (no wall kicks) is significantly simpler to implement and produces a playable game, but lacks the feel and technique possibilities of the full SRS. Our browser-based Tetris implementation uses simplified rotation for accessibility, prioritising immediate playability over competitive correctness.
Collision Detection
Collision detection determines whether a tetrimino can move to a proposed position. It checks two conditions: the proposed position is within the grid boundaries, and no cell of the tetrimino overlaps a filled cell in the grid. For each filled cell in the tetrimino matrix, compute the corresponding grid coordinate (tetrimino row + piece top + cell row, tetrimino column + piece left + cell column). If any coordinate is outside the grid or occupies a filled grid cell, the move is invalid.
This check must be performed before every move operation: left shift, right shift, downward movement, and rotation. If a downward movement fails the collision check, the piece "locks" in its current position — its cells are written into the permanent grid, and a new piece is spawned. This locking behaviour is the mechanism by which the stack grows over time.
A subtlety in modern Tetris implementations: "lock delay." When a piece can no longer move down (it has landed on the stack or the floor), a countdown timer begins before it locks. During this window, the player can still rotate or shift the piece horizontally. This gives experienced players more control over precise piece placement. Lock delay timers typically range from 500ms to 1 second, resetting each time the piece successfully moves or rotates during the window.
Line Clearing Algorithm
After a piece locks, the game checks every row in the grid for completion — a row is complete if every cell in that row is filled. Completed rows are removed and all rows above them shift down by the number of cleared rows. The score is awarded based on how many rows were cleared simultaneously: one row (Single), two rows (Double), three rows (Triple), or four rows (Tetris). The Tetris clear (four rows simultaneously with the I piece) awards the most points and is the highest-scoring standard move.
The array shift operation for cleared lines is conceptually simple: filter out completed rows, then prepend the required number of empty rows at the top of the grid. In JavaScript: grid = grid.filter(row => row.some(cell => cell === 0)); while (grid.length < ROWS) grid.unshift(new Array(COLS).fill(0));. This concise operation handles any number of simultaneous clears (1–4 rows) correctly in a single pass.
Scoring and Level Progression
The classic Tetris scoring formula (used in the NES version) awards points based on lines cleared × current level: Single = 40 × (level + 1), Double = 100 × (level + 1), Triple = 300 × (level + 1), Tetris = 1200 × (level + 1). The level increases every ten lines cleared, and each level increase raises the game speed — the interval at which pieces automatically move down decreases. Early levels are slow enough for any player; Level 15 and above move faster than most humans can react without intensive training.
The piece speed formula typically follows an exponential curve: the drop interval in frames at Level 0 is 48, Level 1 is 43, and the interval decreases by varying amounts at each level, reaching a minimum of 1 frame per drop at Level 29 in the NES version — approximately 60 drops per second, which is effectively invisible. Modern implementations often use a smoother progression curve and allow the maximum speed to be configured for accessibility.
The Game Loop
A Tetris game loop runs continuously while the game is active. Using requestAnimationFrame, the loop calls a render function every frame (typically 60fps). The render function draws the current state: the permanent grid (locked pieces), the active piece at its current position, the ghost piece (a projection of where the piece will land if dropped), the "next piece" preview, and the score/level display.
The fall timer is separate from the frame rate — piece gravity operates on a separate time budget. Accumulate elapsed time per frame and advance the piece downward when the accumulated time exceeds the current level's fall interval. This decouples visual rendering (runs at 60fps) from game logic (runs at level-dependent speed), which is the correct architecture for any time-dependent game.