Free Tetris Game Source Code in HTML, CSS & JavaScript

Here is free Tetris-style game source code built with HTML, CSS and JavaScript. Learn how to create a falling-block puzzle game and customize its features.

The idea is simple: different shapes fall from the top of the screen, and you need to move and rotate them to complete horizontal lines. When a line is completed, it disappears and you earn points.

This project uses HTML, CSS, and JavaScript with the HTML5 Canvas element. You do not need to install a game engine or add any external libraries. Everything is included in one HTML file.

Whether you are learning JavaScript or building a collection of browser games, this project gives you a practical way to understand how game mechanics work.

What Is a Tetris-Style Game?

A Tetris-style game is a puzzle game in which different shapes fall into a playing area.

The player can move the shapes left or right and rotate them to fit into empty spaces. When blocks fill an entire horizontal row, that row disappears and the blocks above it move down.

The goal is to clear as many lines as possible before the blocks reach the top of the board.

Building a game like this helps you practice JavaScript arrays, loops, keyboard events, canvas drawing, collision detection, and score management.

Features of This Tetris Game

This version includes:

  • Falling puzzle shapes
  • Left and right movement
  • Shape rotation
  • Automatic falling
  • Line-clearing system
  • Score counter
  • Increasing difficulty
  • Game over detection
  • Restart button
  • Keyboard controls
  • HTML5 Canvas graphics
  • Responsive layout
  • No external libraries
  • Single-file source code

Free Tetris Game Source Code

Copy the complete code below into a file named index.html. Save it and open the file in a modern browser.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Tetris-Style Puzzle Game</title>

    <style>
        * {
            box-sizing: border-box;
        }

        body {
            margin: 0;
            padding: 20px;
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            font-family: Arial, sans-serif;
            color: #fff;
            background: linear-gradient(135deg, #0f172a, #312e81);
        }

        .game-container {
            width: 100%;
            max-width: 520px;
            text-align: center;
        }

        h1 {
            margin: 0 0 8px;
            font-size: 36px;
        }

        .description {
            color: #cbd5e1;
            margin: 0 0 18px;
            line-height: 1.5;
        }

        .game-layout {
            display: flex;
            justify-content: center;
            align-items: flex-start;
            gap: 14px;
        }

        canvas {
            display: block;
            width: 250px;
            max-width: 65vw;
            height: auto;
            background: #020617;
            border: 2px solid #64748b;
            border-radius: 8px;
        }

        .sidebar {
            min-width: 100px;
            text-align: left;
        }

        .score-box {
            padding: 12px;
            margin-bottom: 12px;
            border-radius: 8px;
            background: rgba(255,255,255,0.1);
            line-height: 1.8;
        }

        .score-box strong {
            color: #93c5fd;
        }

        button {
            padding: 12px 20px;
            border: 0;
            border-radius: 8px;
            background: #22c55e;
            color: white;
            font-size: 16px;
            font-weight: bold;
            cursor: pointer;
        }

        button:hover {
            background: #16a34a;
        }

        .controls {
            margin-top: 18px;
            color: #cbd5e1;
            line-height: 1.8;
            font-size: 14px;
        }

        @media (max-width: 420px) {
            body {
                padding: 12px;
            }

            h1 {
                font-size: 30px;
            }

            .game-layout {
                gap: 8px;
            }

            .sidebar {
                min-width: 80px;
                font-size: 13px;
            }

            .score-box {
                padding: 8px;
            }

            canvas {
                width: 230px;
            }
        }
    </style>
</head>

<body>

<div class="game-container">

    <h1>Tetris Puzzle</h1>

    <p class="description">
        Fit the falling shapes together and clear complete lines.
    </p>

    <div class="game-layout">

        <canvas id="game" width="250" height="500"></canvas>

        <div class="sidebar">

            <div class="score-box">
                <strong>Score</strong><br>
                <span id="score">0</span>
            </div>

            <div class="score-box">
                <strong>Lines</strong><br>
                <span id="lines">0</span>
            </div>

            <div class="score-box">
                <strong>Level</strong><br>
                <span id="level">1</span>
            </div>

        </div>

    </div>

    <button id="restart" style="margin-top:18px;">
        Start / Restart
    </button>

    <div class="controls">
        ← → Move   |   ↓ Faster<br>
        ↑ Rotate   |   Space: Drop
    </div>

</div>

<script>
    const canvas = document.getElementById("game");
    const ctx = canvas.getContext("2d");

    const scoreElement = document.getElementById("score");
    const linesElement = document.getElementById("lines");
    const levelElement = document.getElementById("level");
    const restartButton = document.getElementById("restart");

    const COLS = 10;
    const ROWS = 20;
    const SIZE = 25;

    const COLORS = [
        null,
        "#22d3ee",
        "#3b82f6",
        "#f97316",
        "#facc15",
        "#a855f7",
        "#22c55e",
        "#ef4444"
    ];

    const SHAPES = [
        [],
        [[1, 1, 1, 1]],
        [[2, 0, 0], [2, 2, 2]],
        [[0, 0, 3], [3, 3, 3]],
        [[4, 4], [4, 4]],
        [[0, 5, 5], [5, 5, 0]],
        [[0, 6, 0], [6, 6, 6]],
        [[7, 7, 0], [0, 7, 7]]
    ];

    let board;
    let piece;
    let score = 0;
    let lines = 0;
    let level = 1;
    let running = false;
    let lastTime = 0;
    let dropCounter = 0;
    let dropInterval = 800;
    let animationId = null;

    function createBoard() {
        return Array.from(
            { length: ROWS },
            () => Array(COLS).fill(0)
        );
    }

    function randomPiece() {
        const type = Math.floor(Math.random() * 7) + 1;
        const shape = SHAPES[type].map(row => row.slice());

        return {
            shape,
            x: Math.floor((COLS - shape[0].length) / 2),
            y: 0
        };
    }

    function drawCell(x, y, color) {
        ctx.fillStyle = color;
        ctx.fillRect(x * SIZE, y * SIZE, SIZE, SIZE);

        ctx.strokeStyle = "#0f172a";
        ctx.lineWidth = 1;
        ctx.strokeRect(x * SIZE, y * SIZE, SIZE, SIZE);

        ctx.fillStyle = "rgba(255,255,255,0.12)";
        ctx.fillRect(
            x * SIZE + 3,
            y * SIZE + 3,
            SIZE - 6,
            4
        );
    }

    function drawBoard() {
        ctx.fillStyle = "#020617";
        ctx.fillRect(0, 0, canvas.width, canvas.height);

        for (let y = 0; y < ROWS; y++) {
            for (let x = 0; x < COLS; x++) {
                if (board[y][x]) {
                    drawCell(x, y, COLORS[board[y][x]]);
                } else {
                    ctx.strokeStyle = "#172033";
                    ctx.strokeRect(
                        x * SIZE,
                        y * SIZE,
                        SIZE,
                        SIZE
                    );
                }
            }
        }

        if (piece) {
            piece.shape.forEach((row, y) => {
                row.forEach((value, x) => {
                    if (value) {
                        drawCell(
                            piece.x + x,
                            piece.y + y,
                            COLORS[value]
                        );
                    }
                });
            });
        }
    }

    function collides(shape, offsetX, offsetY) {
        for (let y = 0; y < shape.length; y++) {
            for (let x = 0; x < shape[y].length; x++) {
                if (!shape[y][x]) continue;

                const newX = offsetX + x;
                const newY = offsetY + y;

                if (
                    newX < 0 ||
                    newX >= COLS ||
                    newY >= ROWS
                ) {
                    return true;
                }

                if (newY >= 0 && board[newY][newX]) {
                    return true;
                }
            }
        }

        return false;
    }

    function mergePiece() {
        piece.shape.forEach((row, y) => {
            row.forEach((value, x) => {
                if (value) {
                    const boardY = piece.y + y;
                    const boardX = piece.x + x;

                    if (boardY >= 0) {
                        board[boardY][boardX] = value;
                    }
                }
            });
        });
    }

    function clearLines() {
        let cleared = 0;

        for (let y = ROWS - 1; y >= 0; y--) {
            if (board[y].every(cell => cell !== 0)) {
                board.splice(y, 1);
                board.unshift(Array(COLS).fill(0));
                cleared++;
                y++;
            }
        }

        if (cleared > 0) {
            const points = [0, 100, 300, 500, 800];

            score += (points[cleared] || cleared * 200) * level;
            lines += cleared;
            level = Math.floor(lines / 10) + 1;
            dropInterval = Math.max(120, 800 - (level - 1) * 65);

            updateStats();
        }
    }

    function updateStats() {
        scoreElement.textContent = score;
        linesElement.textContent = lines;
        levelElement.textContent = level;
    }

    function spawnPiece() {
        piece = randomPiece();

        if (collides(piece.shape, piece.x, piece.y)) {
            endGame();
        }
    }

    function lockPiece() {
        mergePiece();
        clearLines();
        spawnPiece();
    }

    function moveDown() {
        if (collides(piece.shape, piece.x, piece.y + 1)) {
            lockPiece();
        } else {
            piece.y++;
        }

        dropCounter = 0;
    }

    function moveHorizontal(direction) {
        if (!collides(
            piece.shape,
            piece.x + direction,
            piece.y
        )) {
            piece.x += direction;
        }
    }

    function rotatePiece() {
        const rotated = piece.shape[0].map((_, index) =>
            piece.shape.map(row => row[index]).reverse()
        );

        if (!collides(rotated, piece.x, piece.y)) {
            piece.shape = rotated;
        }
    }

    function hardDrop() {
        while (!collides(piece.shape, piece.x, piece.y + 1)) {
            piece.y++;
            score += 2;
        }

        updateStats();
        lockPiece();
    }

    function update(time = 0) {
        if (!running) return;

        const delta = time - lastTime;
        lastTime = time;
        dropCounter += delta;

        if (dropCounter > dropInterval) {
            moveDown();
        }

        drawBoard();
        animationId = requestAnimationFrame(update);
    }

    function startGame() {
        if (animationId !== null) {
            cancelAnimationFrame(animationId);
        }

        board = createBoard();
        score = 0;
        lines = 0;
        level = 1;
        dropInterval = 800;
        dropCounter = 0;
        lastTime = 0;
        running = true;

        updateStats();
        spawnPiece();
        drawBoard();

        if (running) {
            animationId = requestAnimationFrame(update);
        }
    }

    function endGame() {
        running = false;

        drawBoard();

        ctx.fillStyle = "rgba(0,0,0,0.75)";
        ctx.fillRect(15, 205, canvas.width - 30, 90);

        ctx.fillStyle = "#ffffff";
        ctx.textAlign = "center";
        ctx.font = "bold 25px Arial";
        ctx.fillText("Game Over", canvas.width / 2, 242);

        ctx.font = "15px Arial";
        ctx.fillText(
            "Final Score: " + score,
            canvas.width / 2,
            270
        );
    }

    document.addEventListener("keydown", event => {
        if (!running) return;

        if (
            ["ArrowLeft", "ArrowRight", "ArrowDown", "ArrowUp", " "]
            .includes(event.key) ||
            event.code === "Space"
        ) {
            event.preventDefault();
        }

        if (event.key === "ArrowLeft") {
            moveHorizontal(-1);
        } else if (event.key === "ArrowRight") {
            moveHorizontal(1);
        } else if (event.key === "ArrowDown") {
            moveDown();
        } else if (event.key === "ArrowUp") {
            rotatePiece();
        } else if (event.code === "Space") {
            hardDrop();
        }

        drawBoard();
    });

    restartButton.addEventListener("click", startGame);

    startGame();
</script>

</body>
</html>

How to Run the Game

You can run the game without installing any additional software.

Step 1: Create a File

Open Notepad, VS Code, or another code editor.

Create a new file called:

index.html

Step 2: Add the Code

Copy the complete source code above and paste it into the file.

Save your changes.

Step 3: Open the Game

Open index.html in Chrome, Edge, Firefox, or another modern browser.

The game will start automatically. Use the keyboard to move and rotate the falling shapes.

How user Play

The controls are straightforward:

  • Left Arrow: Move the shape left
  • Right Arrow: Move the shape right
  • Down Arrow: Move the shape down faster
  • Up Arrow: Rotate the shape
  • Space: Drop the shape quickly

Try to fit the falling shapes together without leaving gaps.

When an entire horizontal line is filled, it disappears and you receive points. Clearing several lines together gives you more points.

The game becomes faster as you clear more lines, so you will need to plan your moves carefully.