pull down to refresh

HTML code to play my vibe-coded Tetris game (239 lines):
<!DOCTYPE html>
<html>
<head>
    <title>Tetris - Game Boy Style</title>
    <style>
        body {
            background-color: #9bbc0f; /* Game Boy screen greenish tint */
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            font-family: 'Courier New', Courier, monospace;
        }
        #gameContainer {
            background-color: #8bac0f; /* Slightly darker green */
            padding: 20px;
            border: 4px solid #0f380f; /* Dark border like Game Boy */
            border-radius: 10px;
            display: flex;
            align-items: flex-start;
        }
        #gameCanvas {
            background-color: #8bac0f;
            image-rendering: pixelated;
            border: 2px solid #306230; /* Visible border around playfield */
        }
        #previewCanvas {
            background-color: #8bac0f;
            image-rendering: pixelated;
            border: 2px solid #306230;
            width: 64px; /* 4 blocks wide */
            height: 64px; /* 4 blocks tall */
            margin-left: 20px;
        }
        #scoreboard {
            color: #0f380f;
            font-size: 16px;
            margin-left: 20px;
            text-align: left;
        }
    </style>
</head>
<body>
    <div id="gameContainer">
        <canvas id="gameCanvas" width="160" height="320"></canvas>
        <div>
            <canvas id="previewCanvas" width="64" height="64"></canvas>
            <div id="scoreboard">
                Score: 0<br>
                Lines: 0<br>
                Level: 1
            </div>
        </div>
    </div>

    <script>
        const canvas = document.getElementById('gameCanvas');
        const ctx = canvas.getContext('2d');
        const previewCanvas = document.getElementById('previewCanvas');
        const previewCtx = previewCanvas.getContext('2d');
        const scoreboard = document.getElementById('scoreboard');

        // Game constants
        const BLOCK_SIZE = 16;
        const COLS = 10;
        const ROWS = 20;
        const canvasWidth = COLS * BLOCK_SIZE;
        const canvasHeight = ROWS * BLOCK_SIZE;
        canvas.width = canvasWidth;
        canvas.height = canvasHeight;

        const TETROMINOES = [
            [[1,1,1,1]], // I
            [[1,1],[1,1]], // O
            [[0,1,0],[1,1,1]], // T
            [[0,1,1],[1,1,0]], // S
            [[1,1,0],[0,1,1]], // Z
            [[1,0,0],[1,1,1]], // J
            [[0,0,1],[1,1,1]]  // L
        ];
        const COLORS = ['#0f380f']; // Dark green for blocks

        let board = Array(ROWS).fill().map(() => Array(COLS).fill(0));
        let score = 0;
        let linesCleared = 0;
        let level = 1;
        let currentPiece, currentX, currentY;
        let nextPiece;
        let gameOver = false;

        // Piece spawning
        function spawnPiece() {
            if (!nextPiece) {
                nextPiece = TETROMINOES[Math.floor(Math.random() * TETROMINOES.length)];
            }
            currentPiece = nextPiece;
            nextPiece = TETROMINOES[Math.floor(Math.random() * TETROMINOES.length)];
            currentX = Math.floor(COLS / 2) - Math.floor(currentPiece[0].length / 2);
            currentY = 0;
            if (!canMove(0, 0)) {
                gameOver = true;
                alert(`Game Over! Score: ${score}, Lines: ${linesCleared}, Level: ${level}`);
                document.location.reload();
            }
            drawPreview();
        }

        // Drawing
        function drawBlock(x, y, context = ctx) {
            context.fillStyle = COLORS[0];
            context.fillRect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE - 1, BLOCK_SIZE - 1);
            context.strokeStyle = '#306230'; // Lighter green for outline
            context.strokeRect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE - 1, BLOCK_SIZE - 1);
        }

        function drawBoard() {
            ctx.clearRect(0, 0, canvasWidth, canvasHeight);
            for (let r = 0; r < ROWS; r++) {
                for (let c = 0; c < COLS; c++) {
                    if (board[r][c]) drawBlock(c, r);
                }
            }
            for (let r = 0; r < currentPiece.length; r++) {
                for (let c = 0; c < currentPiece[r].length; c++) {
                    if (currentPiece[r][c]) drawBlock(currentX + c, currentY + r);
                }
            }
        }

        function drawPreview() {
            previewCtx.clearRect(0, 0, previewCanvas.width, previewCanvas.height);
            const offsetX = (4 - nextPiece[0].length) / 2; // Center horizontally
            const offsetY = (4 - nextPiece.length) / 2; // Center vertically
            for (let r = 0; r < nextPiece.length; r++) {
                for (let c = 0; c < nextPiece[r].length; c++) {
                    if (nextPiece[r][c]) drawBlock(c + offsetX, r + offsetY, previewCtx);
                }
            }
        }

        // Movement and collision
        function canMove(dx, dy, newPiece = currentPiece) {
            for (let r = 0; r < newPiece.length; r++) {
                for (let c = 0; c < newPiece[r].length; c++) {
                    if (!newPiece[r][c]) continue;
                    const newX = currentX + c + dx;
                    const newY = currentY + r + dy;
                    if (newX < 0 || newX >= COLS || newY >= ROWS) return false;
                    if (newY >= 0 && board[newY][newX]) return false;
                }
            }
            return true;
        }

        function mergePiece() {
            for (let r = 0; r < currentPiece.length; r++) {
                for (let c = 0; c < currentPiece[r].length; c++) {
                    if (currentPiece[r][c]) {
                        board[currentY + r][currentX + c] = 1;
                    }
                }
            }
        }

        function clearLines() {
            let linesThisTime = 0;
            for (let r = ROWS - 1; r >= 0; r--) {
                if (board[r].every(cell => cell)) {
                    board.splice(r, 1);
                    board.unshift(Array(COLS).fill(0));
                    linesThisTime++;
                    r++; // Check the same row again after shifting
                }
            }
            if (linesThisTime > 0) {
                linesCleared += linesThisTime;
                score += [0, 40, 100, 300, 1200][linesThisTime] * level; // Level multiplier
                level = Math.floor(linesCleared / 10) + 1; // Level up every 10 lines
                updateScoreboard();
            }
        }

        function updateScoreboard() {
            scoreboard.innerHTML = `Score: ${score}<br>Lines: ${linesCleared}<br>Level: ${level}`;
        }

        // Rotation
        function rotatePiece() {
            const rotated = currentPiece[0].map((_, i) => 
                currentPiece.map(row => row[i]).reverse()
            );
            if (canMove(0, 0, rotated)) currentPiece = rotated;
        }

        // Game loop
        let dropCounter = 0;
        function getDropInterval() {
            const baseSpeed = 800; // Starting speed in ms (Level 1)
            return baseSpeed / (1 + (level - 1) * 0.2); // 20% faster per level
        }

        function update() {
            if (gameOver) return;
            dropCounter += 16.67; // ~60 FPS
            const dropInterval = getDropInterval();
            if (dropCounter >= dropInterval) {
                if (canMove(0, 1)) {
                    currentY++;
                } else {
                    mergePiece();
                    clearLines();
                    spawnPiece();
                }
                dropCounter = 0;
            }
            drawBoard();
            requestAnimationFrame(update);
        }

        // Controls
        document.addEventListener('keydown', e => {
            if (gameOver) return;
            switch (e.key) {
                case 'ArrowLeft': if (canMove(-1, 0)) currentX--; break;
                case 'ArrowRight': if (canMove(1, 0)) currentX++; break;
                case 'ArrowDown': if (canMove(0, 1)) currentY++; break;
                case 'ArrowUp': rotatePiece(); break;
            }
            drawBoard();
        });

        // Start game
        spawnPiece(); // Initial spawn sets both current and next
        updateScoreboard();
        update();
    </script>
</body>
</html>