(Part 3/Final) Building Tetris in TypeScript: Completing the Game (Scoring, Line Clearing, and Game Loop)
Wrap up your Tetris game by adding scoring, line-clearing logic, and game over detection. This tutorial ties everything together with a polished game loop and a simple UI to track player progress.
Introduction
On Part 2, we implemented Tetrominoes, added movement logic, and handled collision detection. Now, we will complete our Tetris game by adding line clearing, a scoring system, and the game loop to make the gameplay dynamic. In this final part of our TypeScript Tetris tutorial series, we’ll complete the core game logic by implementing line clearing, scoring, and game over detection.
We’ll also polish the game loop for a smoother experience and set the stage for future enhancements like levels and UI.
By the end of this tutorial, you’ll have:
- A working scoring system
- Logic to clear full rows
- A game over state
- A responsive game loop using
requestAnimationFrame
Let’s wrap up the core game mechanics!
Updated Project Structure
We’ll add one new file:
tetris-ts/
├── src/
│ ├── game.ts # Game loop and scoring logic
Step 1: Add Line Clearing and Score Logic
Create src/game.ts
:
import { Matrix } from './tetromino';
import { Player } from './player';
export class Game {
private arena: Matrix;
private player: Player;
private score: number = 0;
private scoreElement: HTMLElement;
constructor(arena: Matrix, player: Player, scoreSelector: string) {
this.arena = arena;
this.player = player;
this.scoreElement = document.querySelector(scoreSelector)!;
this.updateScore();
}
sweepArena(): void {
outer: for (let y = this.arena.length - 1; y >= 0; y-=1) {
for (let x = 0; x < this.arena[y].length; x+=1) {
if (this.arena[y][x] === 0) {
continue outer;
}
}
const row = this.arena.splice(y, 1)[0].fill(0);
this.arena.unshift(row);
y++;
this.score += 10;
}
this.updateScore();
}
updateScore(): void {
this.scoreElement.textContent = `Score: ${this.score}`;
}
checkGameOver(): boolean {
return this.player.collide();
}
}
Step 2: Add Score UI to index.html
Update index.html
to include a score display:
<body>
<div style="text-align: center; color: white; font-family: monospace; font-size: 18px;">
<div id="score">Score: 0</div>
</div>
<canvas id="tetris"></canvas>
<script type="module" src="./dist/main.js"></script>
</body>
Step 3: Update main.ts for Full Game Logic
Update main.ts
to handle line clearing, score updates, and game over:
import { Grid, COLS, ROWS } from './grid';
import { Player } from './player';
import { COLORS } from './tetromino';
import { Game } from './game';
function createMatrix(w: number, h: number): number[][] {
const matrix = [];
for (let i = 0; i < h; i+=1) {
matrix.push(new Array(w).fill(0));
}
return matrix;
}
const canvas = document.getElementById('tetris') as HTMLCanvasElement;
const context = canvas.getContext('2d')!;
const grid = new Grid(context);
const arena = createMatrix(COLS, ROWS);
const player = new Player(arena);
const game = new Game(arena, player, '#score');
function drawMatrix(matrix: number[][], offset: { x: number; y: number }) {
matrix.forEach((row, y) => {
row.forEach((value, x) => {
if (value !== 0) {
context.fillStyle = COLORS[value];
context.fillRect(x + offset.x, y + offset.y, 1, 1);
}
});
});
}
function draw() {
grid.clear();
drawMatrix(arena, { x: 0, y: 0 });
drawMatrix(player.matrix, player.pos);
}
let dropCounter = 0;
let dropInterval = 1000;
let lastTime = 0;
function update(time = 0) {
const deltaTime = time - lastTime;
lastTime = time;
dropCounter += deltaTime;
if (dropCounter > dropInterval) {
player.drop();
if (player.collide()) {
game.checkGameOver();
} else {
game.sweepArena();
}
dropCounter = 0;
}
draw();
requestAnimationFrame(update);
}
window.addEventListener('keydown', e => {
if (e.key === 'ArrowLeft') player.move(-1);
if (e.key === 'ArrowRight') player.move(1);
if (e.key === 'ArrowDown' || e.key === 'Enter') player.drop();
if (e.key === 'q' || event.key === 's') player.rotate(-1);
if (e.key === 'w' || event.key === ' ') player.rotate(1);
});
update();
Tetris Demo
Summary
On this final part, you:
- Implemented line clearing and scoring
- Added a simple UI element to track score
- Polished the game loop using
requestAnimationFrame
- Handled game over by clearing the arena
Congratulations! You’ve built a complete Tetris game in TypeScript.
Optional Enhancements:
- Add levels and increase speed as score increases
- Display next Tetromino preview
- Add sound effects and music
- Create a start/reset menu screen
- Persist high scores using
localStorage
Thanks for following along. Happy coding!
In case you missed previous posts


Discussion