DeepSeek vs Kimi vs Qwen —— AI 生成俄罗斯方块代码效果横评

DeepSeek vs Kimi vs Qwen ------ AI 生成俄罗斯方块代码效果横评


一、引言

  • 背景:国产大模型崛起,代码生成能力成为核心竞争力之一
  • 测试动机:以经典游戏「俄罗斯方块」为载体,直观对比三款模型的代码生成质量
  • 参测模型版本说明:DeepSeek(DeepSeek-R1 Thinking + Network Search)、Kimi(Kimi 2.5 Agent)、Qwen(Qwen3.5-Plus Thinking)

二、测试方法论

  • 统一 Prompt 设计

    • 使用相同的提示词(中文/英文各一轮)
    • 示例 Prompt:用原生 HTML + CSS + JavaScript 实现一个完整的俄罗斯方块游戏,包含计分、加速、游戏结束逻辑

三、各模型生成结果对比

deepseek效果

kimi效果

qwen效果

对比维度 deepseek.html kimi.html qwen.html
渲染方式 Canvas 2D DOM + CSS Grid Canvas 2D
代码架构 函数式(IIFE闭包) 面向对象(Class) 函数式(全局变量)
视觉效果 ⭐⭐⭐⭐ 深海蓝主题,精美UI ⭐⭐⭐⭐⭐ 霓虹发光效果,最华丽 ⭐⭐ 简洁朴素
幽灵方块(降落位置指示) ❌ 无 ✅ 有 ❌ 无
暂停功能 ❌ 无 ✅ 有 ❌ 无
连续移动 ✅ 支持长按连续移动 ❌ 单次触发 ❌ 单次触发
计分规则 1/3/5/8百分制,不乘等级 1/3/5/8百分制,乘以等级 1/3/6/10十分制,乘以等级
软降加分 ❌ 无 ✅ 软降+1分 硬降+2×距离 ❌ 无
升级机制 每消2行升级 每消10行升级 每消10行升级
速度下限 80ms 100ms 100ms
代码可维护性 中(注释详细但耦合较高) 高(Class封装清晰) 低(全局变量,耦合高)
代码体积 小(最精简)
性能 较好(Canvas渲染) 较差(大量DOM操作) 好(Canvas渲染,代码轻量)

四、总结与推荐

  • 个人直观游戏体验来讲,kimi 2.5 agent生成的游戏玩起来是最舒服的,色彩华丽,功能完整、玩法丰富(包括落地指示,暂停游戏等),但是生成代码速度较慢,画面稍稍有些延迟,UI布局有些不对称
  • deepseek r1色彩比较丰富,玩法比较丰富,游玩体验非常流畅
  • qwen3.5-plus生成的代码行数最少,但只有最基本的玩法,色彩表现方面比较朴素,游戏运行非常流畅(仁者见仁,可能朴素的才是最好玩的)

我自己体验下来,感觉kimi生成的代码是最成熟的。个人认为是其agent模式起到了非常大的作用。kimi在代码生成期间,展示了其工作流程与步骤,显示出其首先进行了规划,制定了todo list,在代码生成前参考了许多设计文档,对代码进行了多次迭代。


五、附录

Deepseek代码

ini 复制代码
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>原生俄罗斯方块 · 经典加速计分版</title>
    <style>
        * {
            box-sizing: border-box;
            user-select: none;
        }
        body {
            background: linear-gradient(145deg, #0b1a2e, #1a2f3f);
            min-height: 100vh;
            margin: 0;
            display: flex;
            justify-content: center;
            align-items: center;
            font-family: 'Segoe UI', Roboto, system-ui, sans-serif;
        }
        .game-container {
            background: #1f3a4b;
            padding: 30px 30px 30px 30px;
            border-radius: 40px;
            box-shadow: 0 20px 30px rgba(0, 0, 0, 0.6), inset 2px 2px 5px rgba(255,255,255,0.1);
            display: flex;
            gap: 30px;
            flex-wrap: wrap;
            justify-content: center;
            border: 2px solid #3d6677;
        }
        .play-area {
            text-align: center;
        }
        #gameCanvas {
            display: block;
            background-color: #0f1c27;
            border-radius: 24px;
            box-shadow: inset 0 0 0 2px #2d4b5e, 0 10px 15px -5px #0b141c;
            width: 300px;
            height: 600px;
        }
        .info-panel {
            background: #17313d;
            border-radius: 30px;
            padding: 25px 20px;
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 25px;
            box-shadow: inset 1px 1px 4px #2c5568, inset -2px -2px 4px #0b1e28, 0 10px 15px -5px #041016;
            border: 1px solid #2f5a6b;
            min-width: 160px;
        }
        .next-piece {
            background: #0f1c27;
            border-radius: 25px;
            padding: 20px 15px 15px 15px;
            text-align: center;
            box-shadow: inset 0 0 0 2px #2d4b5e;
        }
        .next-piece h3 {
            margin: 0 0 15px 0;
            color: #c2dae8;
            font-weight: 400;
            letter-spacing: 2px;
            font-size: 1.2rem;
            text-shadow: 0 2px 3px black;
        }
        #nextCanvas {
            display: block;
            background-color: #0f1c27;
            border-radius: 18px;
            width: 120px;
            height: 120px;
            box-shadow: inset 0 0 0 2px #316980;
        }
        .score-box {
            background: #10232e;
            border-radius: 40px;
            padding: 15px 20px;
            width: 100%;
            text-align: center;
            border: 1px solid #3d7a91;
            box-shadow: inset 1px 1px 5px #1c4353;
        }
        .score-box div {
            color: #f2e9c0;
            font-size: 1.7rem;
            font-weight: 700;
            text-shadow: 0 3px 0 #0a1a22;
            letter-spacing: 1px;
        }
        .score-box .label {
            font-size: 0.9rem;
            color: #9ec7db;
            letter-spacing: 2px;
            margin-top: 5px;
        }
        .level-box {
            background: #10232e;
            border-radius: 40px;
            padding: 10px 20px;
            width: 100%;
            text-align: center;
            border: 1px solid #3d7a91;
        }
        .level-box span {
            color: #d3e8d3;
            font-size: 1.5rem;
            font-weight: 600;
        }
        .level-box .label {
            color: #9ec7db;
            font-size: 0.8rem;
            margin-left: 8px;
        }
        button {
            background: #d4b47c;
            border: none;
            font-size: 1.3rem;
            font-weight: bold;
            padding: 12px 30px;
            border-radius: 50px;
            color: #1d313b;
            letter-spacing: 1.5px;
            cursor: pointer;
            box-shadow: 0 7px 0 #7e5d3e, 0 10px 15px -5px black;
            transition: 0.07s ease;
            margin-top: 15px;
            width: 100%;
            border: 1px solid #eedbaa;
        }
        button:active {
            transform: translateY(5px);
            box-shadow: 0 2px 0 #7e5d3e, 0 8px 12px -5px black;
        }
        .game-over-msg {
            color: #ffbaba;
            font-size: 1.2rem;
            font-weight: 600;
            background: #9e3b3b80;
            padding: 6px 15px;
            border-radius: 40px;
            backdrop-filter: blur(2px);
            border: 1px solid #ffa4a4;
        }
        .status {
            min-height: 40px;
        }
    </style>
</head>
<body>
<div class="game-container">
    <!-- 左侧主游戏区 -->
    <div class="play-area">
        <canvas id="gameCanvas" width="300" height="600"></canvas>
    </div>

    <!-- 右侧信息面板 -->
    <div class="info-panel">
        <div class="next-piece">
            <h3>▶ 下一个</h3>
            <canvas id="nextCanvas" width="120" height="120"></canvas>
        </div>

        <div class="score-box">
            <div id="scoreDisplay">0</div>
            <div class="label">得分</div>
        </div>

        <div class="level-box">
            <span id="levelDisplay">0</span><span class="label">级</span>
        </div>

        <div class="status" id="gameStatus"></div>

        <button id="restartBtn">↻ 重新开始</button>
        <div style="color:#89b9cd; font-size:0.75rem; margin-top:5px;">
            ← → 移动 &nbsp; ↓ 软加速 &nbsp; ↑ 旋转<br>空格 硬到底
        </div>
    </div>
</div>

<script>
    (function(){
        // ---------- 游戏配置 ----------
        const COLS = 10;
        const ROWS = 20;
        const CELL_SIZE = 30;          // 画布格子尺寸
        const PREVIEW_SIZE = 24;        // 预览区域格子尺寸 (120/5≈24, 但预览画布小,动态绘制)

        // 基础下落间隔(ms) -> 速度等级调节
        const BASE_INTERVAL = 500;
        const MIN_INTERVAL = 80;
        const SPEED_STEP = 35;          // 每级减少35ms

        // 移动按键间隔(ms)
        const MOVE_SIDE_INTERVAL = 130;  // 左右移动间隔
        const MOVE_DOWN_INTERVAL = 60;   // 软加速移动间隔

        // ---------- 形状定义 ----------
        const SHAPES = [
            // I
            { 
                matrix: [[1,1,1,1]],
                color: '#4ad9d9'   // 青
            },
            // O
            { 
                matrix: [[1,1],[1,1]],
                color: '#f7e476'   // 明黄
            },
            // T
            { 
                matrix: [[0,1,0],[1,1,1]],
                color: '#b484e0'   // 紫
            },
            // S
            { 
                matrix: [[0,1,1],[1,1,0]],
                color: '#7bcc7b'   // 绿
            },
            // Z
            { 
                matrix: [[1,1,0],[0,1,1]],
                color: '#e67e7e'   // 红
            },
            // L
            { 
                matrix: [[1,0,0],[1,1,1]],
                color: '#f0b078'   // 橙
            },
            // J
            { 
                matrix: [[0,0,1],[1,1,1]],
                color: '#7890d4'   // 蓝
            }
        ];

        // ---------- DOM 元素 ----------
        const gameCanvas = document.getElementById('gameCanvas');
        const ctx = gameCanvas.getContext('2d');
        const nextCanvas = document.getElementById('nextCanvas');
        const nextCtx = nextCanvas.getContext('2d');
        const scoreSpan = document.getElementById('scoreDisplay');
        const levelSpan = document.getElementById('levelDisplay');
        const gameStatusDiv = document.getElementById('gameStatus');
        const restartBtn = document.getElementById('restartBtn');

        // ---------- 游戏状态变量 ----------
        let board = [];                 // 二维数组 ROWS x COLS (0表示空)
        let activePiece = null;         // 当前活动方块 { matrix, color, x, y }
        let nextPiece = null;           // 下一个方块 { matrix, color }
        let gameOver = false;
        let score = 0;
        let totalLines = 0;             // 消除总行数用于计算等级
        let level = 0;
        let dropInterval = BASE_INTERVAL; // 当前下落间隔(ms)

        // 按键标志 (用于连续移动)
        const keys = {
            left: false,
            right: false,
            down: false,
        };

        // 时间控制 (用于连续移动和下落)
        let lastTimestamp = null;
        let accumulatedDropTime = 0;      // 下落累计时间
        let lastSideMoveTime = 0;          // 左右移动上次执行时间(毫秒时间戳)
        let lastDownMoveTime = 0;           // 软加速上次执行时间

        // 动画帧ID
        let rafId = null;

        // ---------- 辅助函数 ----------
        function createEmptyBoard() {
            return Array.from({ length: ROWS }, () => Array(COLS).fill(0));
        }

        // 随机获取一个形状
        function getRandomPiece() {
            const idx = Math.floor(Math.random() * SHAPES.length);
            const shape = SHAPES[idx];
            return {
                matrix: shape.matrix.map(row => [...row]), // 深拷贝矩阵
                color: shape.color
            };
        }

        // 初始化新游戏 (或重置)
        function initGame() {
            board = createEmptyBoard();
            // 随机生成第一个active和next
            nextPiece = getRandomPiece();
            spawnNewPiece();
            gameOver = false;
            score = 0;
            totalLines = 0;
            level = 0;
            updateScoreAndLevel();
            gameStatusDiv.innerHTML = '';
            // 重置按键状态
            keys.left = false;
            keys.right = false;
            keys.down = false;
            // 重置时间累积
            accumulatedDropTime = 0;
            lastSideMoveTime = 0;
            lastDownMoveTime = 0;
            // 绘制
            drawAll();
        }

        // 生成新块: 从nextPiece取,然后更新nextPiece
        function spawnNewPiece() {
            if (!nextPiece) {
                nextPiece = getRandomPiece();
            }
            // activePiece 设置为 nextPiece 的副本
            activePiece = {
                matrix: nextPiece.matrix.map(row => [...row]),
                color: nextPiece.color,
                x: Math.floor((COLS - nextPiece.matrix[0].length) / 2),
                y: 0
            };
            // 生成下一个
            nextPiece = getRandomPiece();

            // 如果生成时立即发生碰撞 => game over
            if (collision(activePiece.matrix, activePiece.x, activePiece.y)) {
                gameOver = true;
                gameStatusDiv.innerHTML = '<span class="game-over-msg">💀 GAME OVER 💀</span>';
                activePiece = null;  // 无法操作
            }
        }

        // 碰撞检测: 矩阵+位置 vs board (边界和固定块)
        function collision(matrix, offsetX, offsetY) {
            for (let r = 0; r < matrix.length; r++) {
                for (let c = 0; c < matrix[0].length; c++) {
                    if (matrix[r][c] !== 0) {
                        const boardX = offsetX + c;
                        const boardY = offsetY + r;
                        // 超出左右/下边界 或 上方溢出(但上方溢出只有当y为负可能,但产生时限制,不过旋转可能暂时越界)
                        if (boardX < 0 || boardX >= COLS || boardY >= ROWS || boardY < 0) {
                            return true;
                        }
                        // 与已固定的方块重叠 (boardY>=0 确保)
                        if (boardY >= 0 && board[boardY][boardX] !== 0) {
                            return true;
                        }
                    }
                }
            }
            return false;
        }

        // 固定当前activePiece到board
        function mergePiece() {
            if (!activePiece) return;
            for (let r = 0; r < activePiece.matrix.length; r++) {
                for (let c = 0; c < activePiece.matrix[0].length; c++) {
                    if (activePiece.matrix[r][c] !== 0) {
                        const boardX = activePiece.x + c;
                        const boardY = activePiece.y + r;
                        if (boardY >= 0 && boardY < ROWS && boardX >= 0 && boardX < COLS) {
                            board[boardY][boardX] = activePiece.color;   // 存储颜色值代表填充
                        }
                    }
                }
            }
        }

        // 消除满行并更新分数
        function clearFullRows() {
            let rowsCleared = 0;
            for (let row = ROWS - 1; row >= 0; ) {
                let full = true;
                for (let col = 0; col < COLS; col++) {
                    if (board[row][col] === 0) {
                        full = false;
                        break;
                    }
                }
                if (full) {
                    // 消除该行:将上方所有行下移
                    for (let r = row; r > 0; r--) {
                        board[r] = [...board[r-1]];
                    }
                    board[0] = Array(COLS).fill(0); // 顶部置空
                    rowsCleared++;
                    // 继续检查同一行(不要递增row)
                } else {
                    row--;
                }
            }

            if (rowsCleared > 0) {
                // 计分规则: 1行100, 2行300, 3行500, 4行800
                const points = [0, 100, 300, 500, 800];
                const addScore = points[rowsCleared] || 800;
                score += addScore;
                totalLines += rowsCleared;
                // 更新等级: 每消除2行升一级 (等级越高下落越快)
                const newLevel = Math.floor(totalLines / 2);
                if (newLevel > level) {
                    level = newLevel;
                }
                updateScoreAndLevel();
                // 调整下落间隔
                adjustDropInterval();
            }
        }

        // 根据等级更新下落间隔
        function adjustDropInterval() {
            // 间隔 = max(最小, 基础 - 等级 * 速度步长)
            let newInterval = BASE_INTERVAL - level * SPEED_STEP;
            newInterval = Math.max(MIN_INTERVAL, newInterval);
            dropInterval = newInterval;
        }

        // 更新UI分数/等级显示
        function updateScoreAndLevel() {
            scoreSpan.textContent = score;
            levelSpan.textContent = level;
        }

        // 移动尝试 (dx, dy)
        function movePiece(dx, dy) {
            if (!activePiece || gameOver) return false;
            const newX = activePiece.x + dx;
            const newY = activePiece.y + dy;
            if (!collision(activePiece.matrix, newX, newY)) {
                activePiece.x = newX;
                activePiece.y = newY;
                drawAll();
                return true;
            }
            // 如果是向下移动失败 (dy===1), 代表需要固定
            if (dy === 1) {
                lockPiece();
            }
            return false;
        }

        // 固定当前块 + 消除行 + 生成新块
        function lockPiece() {
            if (!activePiece || gameOver) return;
            mergePiece();
            clearFullRows();

            // 生成下一个方块
            spawnNewPiece();
            drawAll();

            // 如果游戏结束,停止操作
            if (gameOver) {
                gameStatusDiv.innerHTML = '<span class="game-over-msg">💀 GAME OVER 💀</span>';
                drawAll(); // 重绘显示结束状态
            }
        }

        // 旋转 (顺时针)
        function rotatePiece() {
            if (!activePiece || gameOver) return;
            // 矩阵旋转90度
            const matrix = activePiece.matrix;
            const rotated = matrix[0].map((_, index) => matrix.map(row => row[index]).reverse());
            // 踢墙简易处理 --- 先尝试直接旋转,若碰撞则尝试向左/向右微移,只尝试一次偏移
            if (!collision(rotated, activePiece.x, activePiece.y)) {
                activePiece.matrix = rotated;
            } else {
                // 左移一格尝试
                if (!collision(rotated, activePiece.x - 1, activePiece.y)) {
                    activePiece.matrix = rotated;
                    activePiece.x -= 1;
                }
                // 右移一格尝试
                else if (!collision(rotated, activePiece.x + 1, activePiece.y)) {
                    activePiece.matrix = rotated;
                    activePiece.x += 1;
                }
                // 还可以尝试上移(极少数情况),但忽略保持简单
            }
            drawAll();
        }

        // 硬降
        function hardDrop() {
            if (!activePiece || gameOver) return;
            // 一直向下直到碰撞
            while (!collision(activePiece.matrix, activePiece.x, activePiece.y + 1)) {
                activePiece.y++;
            }
            // 固定到board
            lockPiece();
            drawAll();
        }

        // ---------- 绘制 ----------
        function drawBoard() {
            ctx.clearRect(0, 0, gameCanvas.width, gameCanvas.height);
            // 绘制网格线 (淡线)
            ctx.strokeStyle = '#2a4a5a';
            ctx.lineWidth = 0.5;
            for (let i = 0; i <= COLS; i++) {
                ctx.beginPath();
                ctx.strokeStyle = '#2a4a5a';
                ctx.moveTo(i * CELL_SIZE, 0);
                ctx.lineTo(i * CELL_SIZE, gameCanvas.height);
                ctx.stroke();
            }
            for (let i = 0; i <= ROWS; i++) {
                ctx.beginPath();
                ctx.strokeStyle = '#2a4a5a';
                ctx.moveTo(0, i * CELL_SIZE);
                ctx.lineTo(gameCanvas.width, i * CELL_SIZE);
                ctx.stroke();
            }

            // 绘制固定方块
            for (let row = 0; row < ROWS; row++) {
                for (let col = 0; col < COLS; col++) {
                    const color = board[row][col];
                    if (color !== 0) {
                        ctx.fillStyle = color;
                        ctx.fillRect(col * CELL_SIZE, row * CELL_SIZE, CELL_SIZE-1, CELL_SIZE-1);
                        // 加一点高光效果
                        ctx.fillStyle = 'rgba(255,255,255,0.2)';
                        ctx.fillRect(col * CELL_SIZE, row * CELL_SIZE, CELL_SIZE-2, 2);
                    }
                }
            }

            // 绘制活动方块
            if (activePiece && !gameOver) {
                const piece = activePiece;
                for (let r = 0; r < piece.matrix.length; r++) {
                    for (let c = 0; c < piece.matrix[0].length; c++) {
                        if (piece.matrix[r][c] !== 0) {
                            const x = (piece.x + c) * CELL_SIZE;
                            const y = (piece.y + r) * CELL_SIZE;
                            ctx.fillStyle = piece.color;
                            ctx.fillRect(x, y, CELL_SIZE-1, CELL_SIZE-1);
                            ctx.fillStyle = 'rgba(255,255,255,0.35)';
                            ctx.fillRect(x, y, CELL_SIZE-4, 2);
                        }
                    }
                }
            }

            // 如果游戏结束,蒙一层半透明
            if (gameOver) {
                ctx.fillStyle = 'rgba(0,0,0,0.4)';
                ctx.fillRect(0, 0, gameCanvas.width, gameCanvas.height);
            }
        }

        function drawPreview() {
            nextCtx.clearRect(0, 0, nextCanvas.width, nextCanvas.height);
            if (!nextPiece) return;
            const matrix = nextPiece.matrix;
            const color = nextPiece.color;
            const blockSize = 24; // 预览格子大小 (120/5≈24但格子数最多4x4, 居中)
            const cols = matrix[0].length;
            const rows = matrix.length;
            const offsetX = (nextCanvas.width - cols * blockSize) / 2;
            const offsetY = (nextCanvas.height - rows * blockSize) / 2;

            for (let r = 0; r < rows; r++) {
                for (let c = 0; c < cols; c++) {
                    if (matrix[r][c] !== 0) {
                        nextCtx.fillStyle = color;
                        nextCtx.fillRect(offsetX + c * blockSize, offsetY + r * blockSize, blockSize-1, blockSize-1);
                        nextCtx.fillStyle = 'rgba(255,255,255,0.25)';
                        nextCtx.fillRect(offsetX + c * blockSize, offsetY + r * blockSize, blockSize-4, 2);
                    }
                }
            }
        }

        function drawAll() {
            drawBoard();
            drawPreview();
        }

        // ---------- 游戏循环(基于 requestAnimationFrame) ----------
        function gameLoop(now) {
            if (!lastTimestamp) {
                lastTimestamp = now;
                rafId = requestAnimationFrame(gameLoop);
                return;
            }

            // 时间差 (毫秒)
            const deltaTime = Math.min(100, now - lastTimestamp); // 限制最大步长避免瞬移
            lastTimestamp = now;

            if (!gameOver && activePiece) {
                // 1. 处理下落累计时间
                accumulatedDropTime += deltaTime;
                while (accumulatedDropTime >= dropInterval) {
                    // 下落一格 (内部如果无法下落会lock并生成新块)
                    if (!movePiece(0, 1)) {
                        // movePiece(0,1) 返回false且如果是碰撞下方则已调用lockPiece, 并可能改变activePiece/gameOver
                        // 需要重置累计时间? 以防连续多次无效下落; 直接减少一次累积
                    }
                    accumulatedDropTime -= dropInterval;
                    // 如果下落导致游戏结束或activePiece变化, 需要重新绘制并可能退出循环
                    if (gameOver || !activePiece) break;
                }

                // 2. 处理按键连续移动 (左右)
                const nowTime = performance.now(); // 高精度时间
                if (keys.left && !gameOver && activePiece) {
                    if (nowTime - lastSideMoveTime > MOVE_SIDE_INTERVAL) {
                        movePiece(-1, 0);
                        lastSideMoveTime = nowTime;
                    }
                }
                if (keys.right && !gameOver && activePiece) {
                    if (nowTime - lastSideMoveTime > MOVE_SIDE_INTERVAL) {
                        movePiece(1, 0);
                        lastSideMoveTime = nowTime;
                    }
                }
                // 软加速 (下键)
                if (keys.down && !gameOver && activePiece) {
                    if (nowTime - lastDownMoveTime > MOVE_DOWN_INTERVAL) {
                        movePiece(0, 1);  // 向下移动一格 (若不能移动内部会lock)
                        lastDownMoveTime = nowTime;
                    }
                }
            }

            // 绘制 (已经在move等操作中调用drawAll,但为确保流畅可以每帧绘制)
            drawAll();

            rafId = requestAnimationFrame(gameLoop);
        }

        // ---------- 键盘事件 ----------
        function handleKeyDown(e) {
            const key = e.key;
            // 防止页面滚动 / 空格点击
            if (key === 'ArrowUp' || key === 'ArrowDown' || key === 'ArrowLeft' || key === 'ArrowRight' || key === ' ') {
                e.preventDefault();
            }

            if (gameOver) return;

            switch (key) {
                case 'ArrowLeft':
                    keys.left = true;
                    break;
                case 'ArrowRight':
                    keys.right = true;
                    break;
                case 'ArrowDown':
                    keys.down = true;
                    break;
                case 'ArrowUp':
                    // 旋转,利用repeat防止连转
                    if (!e.repeat) {
                        rotatePiece();
                    }
                    break;
                case ' ':
                    // 空格硬降
                    hardDrop();
                    break;
                default: return;
            }
        }

        function handleKeyUp(e) {
            const key = e.key;
            if (key === 'ArrowLeft' || key === 'ArrowRight' || key === 'ArrowDown' || key === 'ArrowUp' || key === ' ') {
                e.preventDefault();
            }
            switch (key) {
                case 'ArrowLeft':
                    keys.left = false;
                    break;
                case 'ArrowRight':
                    keys.right = false;
                    break;
                case 'ArrowDown':
                    keys.down = false;
                    break;
                default: break;
            }
        }

        // 窗口失去焦点时重置按键,避免卡键
        function resetKeys() {
            keys.left = false;
            keys.right = false;
            keys.down = false;
        }

        // ---------- 重新开始 ----------
        function restartGame() {
            resetKeys();
            initGame();
            // 重置时间系统
            lastTimestamp = null;
            accumulatedDropTime = 0;
            lastSideMoveTime = 0;
            lastDownMoveTime = 0;
            drawAll();
        }

        // ---------- 启动 ----------
        window.addEventListener('keydown', handleKeyDown);
        window.addEventListener('keyup', handleKeyUp);
        window.addEventListener('blur', resetKeys);     // 离开标签页重置按键

        restartBtn.addEventListener('click', () => {
            restartGame();
        });

        // 初始化
        initGame();
        rafId = requestAnimationFrame(gameLoop);

        // 清理 (理论上SPA不涉及卸载,但可以保留)
        window.addEventListener('beforeunload', function() {
            if (rafId) cancelAnimationFrame(rafId);
            window.removeEventListener('keydown', handleKeyDown);
            window.removeEventListener('keyup', handleKeyUp);
        });

    })();
</script>
</body>
</html>

Kimi生成代码

xml 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>俄罗斯方块 - Tetris</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            padding: 20px;
        }

        .game-container {
            display: flex;
            gap: 30px;
            align-items: flex-start;
            flex-wrap: wrap;
            justify-content: center;
        }

        .game-board-wrapper {
            position: relative;
        }

        .game-board {
            display: grid;
            grid-template-columns: repeat(10, 30px);
            grid-template-rows: repeat(20, 30px);
            gap: 1px;
            background: #0a0a0a;
            border: 3px solid #4a4a6a;
            border-radius: 8px;
            padding: 5px;
            box-shadow: 0 0 30px rgba(0, 150, 255, 0.3), inset 0 0 20px rgba(0, 0, 0, 0.5);
        }

        .cell {
            width: 30px;
            height: 30px;
            border-radius: 3px;
            transition: all 0.1s ease;
        }

        .cell.empty {
            background: #1a1a2a;
            box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.3);
        }

        .cell.I { background: linear-gradient(145deg, #00f5ff, #00a8b5); box-shadow: 0 0 10px rgba(0, 245, 255, 0.5); }
        .cell.O { background: linear-gradient(145deg, #ffeb3b, #c9b600); box-shadow: 0 0 10px rgba(255, 235, 59, 0.5); }
        .cell.T { background: linear-gradient(145deg, #e040fb, #9c00d1); box-shadow: 0 0 10px rgba(224, 64, 251, 0.5); }
        .cell.S { background: linear-gradient(145deg, #76ff03, #4caf00); box-shadow: 0 0 10px rgba(118, 255, 3, 0.5); }
        .cell.Z { background: linear-gradient(145deg, #ff5252, #c00000); box-shadow: 0 0 10px rgba(255, 82, 82, 0.5); }
        .cell.J { background: linear-gradient(145deg, #448aff, #0057c9); box-shadow: 0 0 10px rgba(68, 138, 255, 0.5); }
        .cell.L { background: linear-gradient(145deg, #ff9100, #c56200); box-shadow: 0 0 10px rgba(255, 145, 0, 0.5); }
        .cell.ghost { 
            background: rgba(255, 255, 255, 0.1); 
            border: 1px dashed rgba(255, 255, 255, 0.3);
        }

        .side-panel {
            display: flex;
            flex-direction: column;
            gap: 20px;
            min-width: 200px;
        }

        .panel-box {
            background: rgba(26, 26, 46, 0.9);
            border: 2px solid #4a4a6a;
            border-radius: 10px;
            padding: 20px;
            box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3);
        }

        .panel-box h3 {
            color: #00f5ff;
            font-size: 14px;
            text-transform: uppercase;
            letter-spacing: 2px;
            margin-bottom: 15px;
            text-shadow: 0 0 10px rgba(0, 245, 255, 0.5);
        }

        .score-value {
            color: #fff;
            font-size: 32px;
            font-weight: bold;
            text-shadow: 0 0 10px rgba(255, 255, 255, 0.3);
        }

        .level-value, .lines-value {
            color: #ffeb3b;
            font-size: 24px;
            font-weight: bold;
        }

        .next-piece-board {
            display: grid;
            grid-template-columns: repeat(4, 25px);
            grid-template-rows: repeat(4, 25px);
            gap: 2px;
            justify-content: center;
            margin-top: 10px;
        }

        .next-cell {
            width: 25px;
            height: 25px;
            border-radius: 3px;
        }

        .next-cell.empty {
            background: transparent;
        }

        .controls-info {
            color: #aaa;
            font-size: 12px;
            line-height: 1.8;
        }

        .controls-info span {
            color: #00f5ff;
            font-weight: bold;
        }

        .btn {
            padding: 15px 30px;
            font-size: 16px;
            font-weight: bold;
            border: none;
            border-radius: 8px;
            cursor: pointer;
            transition: all 0.3s ease;
            text-transform: uppercase;
            letter-spacing: 1px;
        }

        .btn-start {
            background: linear-gradient(145deg, #00f5ff, #00a8b5);
            color: #1a1a2e;
            box-shadow: 0 5px 20px rgba(0, 245, 255, 0.4);
        }

        .btn-start:hover {
            transform: translateY(-2px);
            box-shadow: 0 8px 25px rgba(0, 245, 255, 0.6);
        }

        .btn-pause {
            background: linear-gradient(145deg, #ff9100, #c56200);
            color: #fff;
            box-shadow: 0 5px 20px rgba(255, 145, 0, 0.4);
        }

        .btn-pause:hover {
            transform: translateY(-2px);
            box-shadow: 0 8px 25px rgba(255, 145, 0, 0.6);
        }

        .game-over-overlay {
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.85);
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            border-radius: 8px;
            opacity: 0;
            visibility: hidden;
            transition: all 0.3s ease;
        }

        .game-over-overlay.active {
            opacity: 1;
            visibility: visible;
        }

        .game-over-text {
            color: #ff5252;
            font-size: 36px;
            font-weight: bold;
            text-transform: uppercase;
            letter-spacing: 3px;
            margin-bottom: 20px;
            text-shadow: 0 0 20px rgba(255, 82, 82, 0.8);
            animation: pulse 1.5s infinite;
        }

        @keyframes pulse {
            0%, 100% { transform: scale(1); }
            50% { transform: scale(1.05); }
        }

        .final-score {
            color: #fff;
            font-size: 20px;
            margin-bottom: 30px;
        }

        .final-score span {
            color: #00f5ff;
            font-size: 28px;
            font-weight: bold;
        }

        .title {
            text-align: center;
            margin-bottom: 30px;
        }

        .title h1 {
            color: #fff;
            font-size: 42px;
            text-transform: uppercase;
            letter-spacing: 5px;
            text-shadow: 0 0 20px rgba(0, 245, 255, 0.5);
            background: linear-gradient(90deg, #00f5ff, #ffeb3b, #e040fb, #00f5ff);
            background-size: 300% 100%;
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            background-clip: text;
            animation: gradient 3s linear infinite;
        }

        @keyframes gradient {
            0% { background-position: 0% 50%; }
            100% { background-position: 300% 50%; }
        }

        .mobile-controls {
            display: none;
            gap: 10px;
            justify-content: center;
            margin-top: 20px;
        }

        .mobile-btn {
            width: 60px;
            height: 60px;
            border-radius: 50%;
            border: none;
            background: rgba(0, 245, 255, 0.2);
            color: #00f5ff;
            font-size: 24px;
            cursor: pointer;
            border: 2px solid #00f5ff;
            transition: all 0.2s ease;
        }

        .mobile-btn:active {
            background: rgba(0, 245, 255, 0.5);
            transform: scale(0.95);
        }

        @media (max-width: 600px) {
            .game-board {
                grid-template-columns: repeat(10, 25px);
                grid-template-rows: repeat(20, 25px);
            }
            .cell {
                width: 25px;
                height: 25px;
            }
            .side-panel {
                flex-direction: row;
                flex-wrap: wrap;
                justify-content: center;
            }
            .mobile-controls {
                display: flex;
            }
        }
    </style>
<base target="_blank">
</head>
<body>
    <div class="wrapper">
        <div class="title">
            <h1>俄罗斯方块</h1>
        </div>
        <div class="game-container">
            <div class="game-board-wrapper">
                <div class="game-board" id="gameBoard"></div>
                <div class="game-over-overlay" id="gameOverOverlay">
                    <div class="game-over-text">游戏结束</div>
                    <div class="final-score">最终得分: <span id="finalScore">0</span></div>
                    <button class="btn btn-start" onclick="game.restart()">重新开始</button>
                </div>
            </div>
            <div class="side-panel">
                <div class="panel-box">
                    <h3>得分</h3>
                    <div class="score-value" id="score">0</div>
                </div>
                <div class="panel-box">
                    <h3>等级</h3>
                    <div class="level-value" id="level">1</div>
                </div>
                <div class="panel-box">
                    <h3>消除行数</h3>
                    <div class="lines-value" id="lines">0</div>
                </div>
                <div class="panel-box">
                    <h3>下一个</h3>
                    <div class="next-piece-board" id="nextPiece"></div>
                </div>
                <div class="panel-box">
                    <h3>操作说明</h3>
                    <div class="controls-info">
                        <span>←</span> 左移<br>
                        <span>→</span> 右移<br>
                        <span>↓</span> 加速下落<br>
                        <span>↑</span> 旋转<br>
                        <span>空格</span> 直接落地
                    </div>
                </div>
                <button class="btn btn-start" id="startBtn" onclick="game.start()">开始游戏</button>
                <button class="btn btn-pause" id="pauseBtn" onclick="game.togglePause()" style="display: none;">暂停</button>
            </div>
        </div>
        <div class="mobile-controls">
            <button class="mobile-btn" onclick="game.moveLeft()">←</button>
            <button class="mobile-btn" onclick="game.rotate()">↻</button>
            <button class="mobile-btn" onclick="game.moveRight()">→</button>
            <button class="mobile-btn" onclick="game.softDrop()">↓</button>
            <button class="mobile-btn" onclick="game.hardDrop()">⤓</button>
        </div>
    </div>

    <script>
        // 俄罗斯方块游戏类
        class TetrisGame {
            constructor() {
                // 游戏配置
                this.cols = 10;
                this.rows = 20;
                this.board = [];
                this.score = 0;
                this.level = 1;
                this.lines = 0;
                this.isGameOver = false;
                this.isPlaying = false;
                this.isPaused = false;
                this.dropInterval = 1000;
                this.lastDropTime = 0;
                this.animationId = null;

                // 方块形状定义
                this.pieces = {
                    I: {
                        shape: [
                            [0, 0, 0, 0],
                            [1, 1, 1, 1],
                            [0, 0, 0, 0],
                            [0, 0, 0, 0]
                        ],
                        color: 'I'
                    },
                    O: {
                        shape: [
                            [1, 1],
                            [1, 1]
                        ],
                        color: 'O'
                    },
                    T: {
                        shape: [
                            [0, 1, 0],
                            [1, 1, 1],
                            [0, 0, 0]
                        ],
                        color: 'T'
                    },
                    S: {
                        shape: [
                            [0, 1, 1],
                            [1, 1, 0],
                            [0, 0, 0]
                        ],
                        color: 'S'
                    },
                    Z: {
                        shape: [
                            [1, 1, 0],
                            [0, 1, 1],
                            [0, 0, 0]
                        ],
                        color: 'Z'
                    },
                    J: {
                        shape: [
                            [1, 0, 0],
                            [1, 1, 1],
                            [0, 0, 0]
                        ],
                        color: 'J'
                    },
                    L: {
                        shape: [
                            [0, 0, 1],
                            [1, 1, 1],
                            [0, 0, 0]
                        ],
                        color: 'L'
                    }
                };

                this.pieceTypes = Object.keys(this.pieces);
                this.currentPiece = null;
                this.nextPiece = null;
                this.ghostPiece = null;

                // 初始化
                this.initBoard();
                this.renderBoard();
                this.bindEvents();
            }

            // 初始化游戏板
            initBoard() {
                this.board = Array(this.rows).fill(null).map(() => 
                    Array(this.cols).fill(0)
                );
            }

            // 渲染游戏板
            renderBoard() {
                const boardEl = document.getElementById('gameBoard');
                boardEl.innerHTML = '';

                for (let row = 0; row < this.rows; row++) {
                    for (let col = 0; col < this.cols; col++) {
                        const cell = document.createElement('div');
                        cell.className = 'cell empty';
                        cell.dataset.row = row;
                        cell.dataset.col = col;
                        boardEl.appendChild(cell);
                    }
                }
            }

            // 更新显示
            updateDisplay() {
                const cells = document.querySelectorAll('.game-board .cell');

                // 清空所有单元格
                cells.forEach(cell => {
                    cell.className = 'cell empty';
                });

                // 绘制已固定的方块
                for (let row = 0; row < this.rows; row++) {
                    for (let col = 0; col < this.cols; col++) {
                        if (this.board[row][col]) {
                            const index = row * this.cols + col;
                            cells[index].className = `cell ${this.board[row][col]}`;
                        }
                    }
                }

                // 绘制幽灵方块
                if (this.ghostPiece && this.isPlaying && !this.isPaused) {
                    this.ghostPiece.shape.forEach((row, y) => {
                        row.forEach((cell, x) => {
                            if (cell) {
                                const boardX = this.ghostPiece.x + x;
                                const boardY = this.ghostPiece.y + y;
                                if (boardY >= 0 && boardY < this.rows && boardX >= 0 && boardX < this.cols) {
                                    const index = boardY * this.cols + boardX;
                                    if (this.board[boardY][boardX] === 0) {
                                        cells[index].className = 'cell ghost';
                                    }
                                }
                            }
                        });
                    });
                }

                // 绘制当前方块
                if (this.currentPiece && this.isPlaying && !this.isPaused) {
                    this.currentPiece.shape.forEach((row, y) => {
                        row.forEach((cell, x) => {
                            if (cell) {
                                const boardX = this.currentPiece.x + x;
                                const boardY = this.currentPiece.y + y;
                                if (boardY >= 0 && boardY < this.rows && boardX >= 0 && boardX < this.cols) {
                                    const index = boardY * this.cols + boardX;
                                    cells[index].className = `cell ${this.currentPiece.color}`;
                                }
                            }
                        });
                    });
                }

                // 更新分数显示
                document.getElementById('score').textContent = this.score;
                document.getElementById('level').textContent = this.level;
                document.getElementById('lines').textContent = this.lines;
            }

            // 渲染下一个方块
            renderNextPiece() {
                const nextEl = document.getElementById('nextPiece');
                nextEl.innerHTML = '';

                if (!this.nextPiece) return;

                const shape = this.nextPiece.shape;
                const offsetX = Math.floor((4 - shape[0].length) / 2);
                const offsetY = Math.floor((4 - shape.length) / 2);

                for (let row = 0; row < 4; row++) {
                    for (let col = 0; col < 4; col++) {
                        const cell = document.createElement('div');
                        cell.className = 'next-cell empty';

                        const shapeRow = row - offsetY;
                        const shapeCol = col - offsetX;

                        if (shapeRow >= 0 && shapeRow < shape.length && 
                            shapeCol >= 0 && shapeCol < shape[0].length &&
                            shape[shapeRow][shapeCol]) {
                            cell.className = `next-cell ${this.nextPiece.color}`;
                        }

                        nextEl.appendChild(cell);
                    }
                }
            }

            // 生成随机方块
            randomPiece() {
                const type = this.pieceTypes[Math.floor(Math.random() * this.pieceTypes.length)];
                const pieceData = this.pieces[type];
                return {
                    shape: pieceData.shape.map(row => [...row]),
                    color: pieceData.color,
                    x: Math.floor((this.cols - pieceData.shape[0].length) / 2),
                    y: 0
                };
            }

            // 生成新方块
            spawnPiece() {
                if (this.nextPiece) {
                    this.currentPiece = this.nextPiece;
                } else {
                    this.currentPiece = this.randomPiece();
                }
                this.nextPiece = this.randomPiece();
                this.renderNextPiece();
                this.updateGhostPiece();

                // 检查游戏结束
                if (!this.isValidPosition(this.currentPiece, this.currentPiece.x, this.currentPiece.y)) {
                    this.gameOver();
                }
            }

            // 更新幽灵方块位置
            updateGhostPiece() {
                if (!this.currentPiece) return;

                this.ghostPiece = {
                    shape: this.currentPiece.shape,
                    x: this.currentPiece.x,
                    y: this.currentPiece.y,
                    color: 'ghost'
                };

                // 将幽灵方块下落到底
                while (this.isValidPosition(this.ghostPiece, this.ghostPiece.x, this.ghostPiece.y + 1)) {
                    this.ghostPiece.y++;
                }
            }

            // 检查位置是否有效
            isValidPosition(piece, x, y) {
                for (let row = 0; row < piece.shape.length; row++) {
                    for (let col = 0; col < piece.shape[row].length; col++) {
                        if (piece.shape[row][col]) {
                            const newX = x + col;
                            const newY = y + row;

                            if (newX < 0 || newX >= this.cols || newY >= this.rows) {
                                return false;
                            }

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

            // 旋转方块
            rotate() {
                if (!this.isPlaying || this.isPaused || !this.currentPiece) return;

                if (this.currentPiece.color === 'O') return; // O方块不需要旋转

                const rotated = this.currentPiece.shape[0].map((_, i) =>
                    this.currentPiece.shape.map(row => row[i]).reverse()
                );

                const oldShape = this.currentPiece.shape;
                this.currentPiece.shape = rotated;

                // 如果旋转后位置无效,尝试左右移动
                let offset = 0;
                while (!this.isValidPosition(this.currentPiece, this.currentPiece.x, this.currentPiece.y)) {
                    offset = offset > 0 ? -offset : -offset + 1;
                    this.currentPiece.x += offset;
                    if (Math.abs(offset) > 2) {
                        this.currentPiece.shape = oldShape;
                        this.currentPiece.x -= offset;
                        return;
                    }
                }

                this.updateGhostPiece();
                this.updateDisplay();
            }

            // 移动方块
            moveLeft() {
                if (!this.isPlaying || this.isPaused || !this.currentPiece) return;
                if (this.isValidPosition(this.currentPiece, this.currentPiece.x - 1, this.currentPiece.y)) {
                    this.currentPiece.x--;
                    this.updateGhostPiece();
                    this.updateDisplay();
                }
            }

            moveRight() {
                if (!this.isPlaying || this.isPaused || !this.currentPiece) return;
                if (this.isValidPosition(this.currentPiece, this.currentPiece.x + 1, this.currentPiece.y)) {
                    this.currentPiece.x++;
                    this.updateGhostPiece();
                    this.updateDisplay();
                }
            }

            // 软降落(加速)
            softDrop() {
                if (!this.isPlaying || this.isPaused || !this.currentPiece) return;
                if (this.isValidPosition(this.currentPiece, this.currentPiece.x, this.currentPiece.y + 1)) {
                    this.currentPiece.y++;
                    this.score += 1;
                    this.updateDisplay();
                } else {
                    this.lockPiece();
                }
            }

            // 硬降落(直接落地)
            hardDrop() {
                if (!this.isPlaying || this.isPaused || !this.currentPiece) return;

                let dropDistance = 0;
                while (this.isValidPosition(this.currentPiece, this.currentPiece.x, this.currentPiece.y + 1)) {
                    this.currentPiece.y++;
                    dropDistance++;
                }

                this.score += dropDistance * 2;
                this.lockPiece();
            }

            // 自动下落
            autoDrop() {
                if (!this.isPlaying || this.isPaused || !this.currentPiece) return;

                if (this.isValidPosition(this.currentPiece, this.currentPiece.x, this.currentPiece.y + 1)) {
                    this.currentPiece.y++;
                    this.updateDisplay();
                } else {
                    this.lockPiece();
                }
            }

            // 固定方块
            lockPiece() {
                if (!this.currentPiece) return;

                this.currentPiece.shape.forEach((row, y) => {
                    row.forEach((cell, x) => {
                        if (cell) {
                            const boardY = this.currentPiece.y + y;
                            const boardX = this.currentPiece.x + x;
                            if (boardY >= 0) {
                                this.board[boardY][boardX] = this.currentPiece.color;
                            }
                        }
                    });
                });

                this.clearLines();
                this.spawnPiece();
                this.updateDisplay();
            }

            // 消除完整行
            clearLines() {
                let linesCleared = 0;

                for (let row = this.rows - 1; row >= 0; row--) {
                    if (this.board[row].every(cell => cell !== 0)) {
                        this.board.splice(row, 1);
                        this.board.unshift(Array(this.cols).fill(0));
                        linesCleared++;
                        row++;
                    }
                }

                if (linesCleared > 0) {
                    this.lines += linesCleared;

                    // 计分系统
                    const points = [0, 100, 300, 500, 800];
                    this.score += points[linesCleared] * this.level;

                    // 等级提升
                    const newLevel = Math.floor(this.lines / 10) + 1;
                    if (newLevel > this.level) {
                        this.level = newLevel;
                        this.dropInterval = Math.max(100, 1000 - (this.level - 1) * 100);
                    }
                }
            }

            // 游戏循环
            gameLoop(timestamp) {
                if (!this.isPlaying) return;

                if (!this.isPaused) {
                    if (timestamp - this.lastDropTime > this.dropInterval) {
                        this.autoDrop();
                        this.lastDropTime = timestamp;
                    }
                }

                this.animationId = requestAnimationFrame((ts) => this.gameLoop(ts));
            }

            // 开始游戏
            start() {
                if (this.isPlaying) return;

                this.reset();
                this.isPlaying = true;
                this.isGameOver = false;
                this.spawnPiece();

                document.getElementById('startBtn').style.display = 'none';
                document.getElementById('pauseBtn').style.display = 'block';
                document.getElementById('gameOverOverlay').classList.remove('active');

                this.lastDropTime = performance.now();
                this.gameLoop(performance.now());
            }

            // 重置游戏
            reset() {
                this.initBoard();
                this.score = 0;
                this.level = 1;
                this.lines = 0;
                this.dropInterval = 1000;
                this.currentPiece = null;
                this.nextPiece = null;
                this.isGameOver = false;
                this.isPaused = false;

                if (this.animationId) {
                    cancelAnimationFrame(this.animationId);
                }

                this.updateDisplay();
            }

            // 重新开始
            restart() {
                this.reset();
                this.start();
            }

            // 暂停/继续
            togglePause() {
                if (!this.isPlaying || this.isGameOver) return;

                this.isPaused = !this.isPaused;
                document.getElementById('pauseBtn').textContent = this.isPaused ? '继续' : '暂停';

                if (!this.isPaused) {
                    this.lastDropTime = performance.now();
                }

                this.updateDisplay();
            }

            // 游戏结束
            gameOver() {
                this.isGameOver = true;
                this.isPlaying = false;

                if (this.animationId) {
                    cancelAnimationFrame(this.animationId);
                }

                document.getElementById('finalScore').textContent = this.score;
                document.getElementById('gameOverOverlay').classList.add('active');
                document.getElementById('pauseBtn').style.display = 'none';
            }

            // 绑定键盘事件
            bindEvents() {
                document.addEventListener('keydown', (e) => {
                    if (!this.isPlaying) {
                        if (e.code === 'Space' && !this.isGameOver) {
                            e.preventDefault();
                            this.start();
                        }
                        return;
                    }

                    if (this.isPaused && e.code !== 'Escape') return;

                    switch(e.code) {
                        case 'ArrowLeft':
                            e.preventDefault();
                            this.moveLeft();
                            break;
                        case 'ArrowRight':
                            e.preventDefault();
                            this.moveRight();
                            break;
                        case 'ArrowDown':
                            e.preventDefault();
                            this.softDrop();
                            break;
                        case 'ArrowUp':
                            e.preventDefault();
                            this.rotate();
                            break;
                        case 'Space':
                            e.preventDefault();
                            this.hardDrop();
                            break;
                        case 'Escape':
                            e.preventDefault();
                            this.togglePause();
                            break;
                    }
                });
            }
        }

        // 创建游戏实例
        const game = new TetrisGame();
    </script>
</body>
</html>

qwen生成代码

ini 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>原生 JS 俄罗斯方块</title>
    <style>
        /* CSS 样式部分 */
        body {
            background: #202028;
            color: #fff;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            height: 100vh;
            margin: 0;
            overflow: hidden;
        }

        .game-container {
            display: flex;
            gap: 20px;
            padding: 20px;
            background: rgba(0, 0, 0, 0.5);
            border-radius: 10px;
            box-shadow: 0 0 20px rgba(0,0,0,0.5);
        }

        canvas {
            border: 2px solid #333;
            background-color: #000;
            display: block;
        }

        .ui-panel {
            display: flex;
            flex-direction: column;
            min-width: 150px;
        }

        .stat-box {
            background: #333;
            padding: 15px;
            margin-bottom: 15px;
            border-radius: 5px;
            text-align: center;
        }

        .stat-label {
            font-size: 14px;
            color: #aaa;
            margin-bottom: 5px;
            text-transform: uppercase;
        }

        .stat-value {
            font-size: 24px;
            font-weight: bold;
            color: #fff;
        }

        .controls-info {
            margin-top: auto;
            font-size: 12px;
            color: #888;
            line-height: 1.6;
        }

        /* 游戏结束遮罩 */
        #game-over-modal {
            display: none;
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(0, 0, 0, 0.9);
            padding: 40px;
            border: 2px solid #fff;
            text-align: center;
            z-index: 10;
        }

        button {
            background: #4CAF50;
            color: white;
            border: none;
            padding: 10px 20px;
            font-size: 16px;
            cursor: pointer;
            margin-top: 20px;
            border-radius: 5px;
        }

        button:hover {
            background: #45a049;
        }
    </style>
</head>
<body>

    <div class="game-container">
        <!-- 游戏画布 -->
        <canvas id="tetris" width="240" height="400"></canvas>

        <!-- 侧边栏 UI -->
        <div class="ui-panel">
            <div class="stat-box">
                <div class="stat-label">分数 (Score)</div>
                <div class="stat-value" id="score">0</div>
            </div>
            <div class="stat-box">
                <div class="stat-label">等级 (Level)</div>
                <div class="stat-value" id="level">1</div>
            </div>

            <div class="controls-info">
                <p>← → : 移动</p>
                <p>↑ : 旋转</p>
                <p>↓ : 加速下落</p>
            </div>
        </div>
    </div>

    <!-- 游戏结束弹窗 -->
    <div id="game-over-modal">
        <h2>GAME OVER</h2>
        <p>最终得分:<span id="final-score">0</span></p>
        <button onclick="resetGame()">重新开始</button>
    </div>

    <script>
        // JavaScript 逻辑部分
        const canvas = document.getElementById('tetris');
        const context = canvas.getContext('2d');
        const scoreElement = document.getElementById('score');
        const levelElement = document.getElementById('level');
        const modal = document.getElementById('game-over-modal');
        const finalScoreElement = document.getElementById('final-score');

        // 放大倍数,让每个像素点看起来更大 (20px)
        context.scale(20, 20);

        // 定义 7 种方块形状 (Tetrominoes)
        const pieces = 'ILJOTSZ';
        const colors = [
            null,
            '#FF0D72', // T
            '#0DC2FF', // O
            '#0DFF72', // L
            '#F538FF', // J
            '#FF8E0D', // I
            '#FFE138', // S
            '#3877FF', // Z
        ];

        // 创建矩阵(用于游戏区域或方块)
        function createMatrix(w, h) {
            const matrix = [];
            while (h--) {
                matrix.push(new Array(w).fill(0));
            }
            return matrix;
        }

        // 创建具体的方块形状矩阵
        function createPiece(type) {
            if (type === 'I') {
                return [
                    [0, 1, 0, 0],
                    [0, 1, 0, 0],
                    [0, 1, 0, 0],
                    [0, 1, 0, 0],
                ];
            } else if (type === 'L') {
                return [
                    [0, 2, 0],
                    [0, 2, 0],
                    [0, 2, 2],
                ];
            } else if (type === 'J') {
                return [
                    [0, 3, 0],
                    [0, 3, 0],
                    [3, 3, 0],
                ];
            } else if (type === 'O') {
                return [
                    [4, 4],
                    [4, 4],
                ];
            } else if (type === 'Z') {
                return [
                    [5, 5, 0],
                    [0, 5, 5],
                    [0, 0, 0],
                ];
            } else if (type === 'S') {
                return [
                    [0, 6, 6],
                    [6, 6, 0],
                    [0, 0, 0],
                ];
            } else if (type === 'T') {
                return [
                    [0, 7, 0],
                    [7, 7, 7],
                    [0, 0, 0],
                ];
            }
        }

        // 绘制函数
        function draw() {
            // 清空画布
            context.fillStyle = '#000';
            context.fillRect(0, 0, canvas.width, canvas.height);

            // 绘制已固定的方块
            drawMatrix(arena, {x: 0, y: 0});
            // 绘制当前正在下落的方块
            drawMatrix(player.matrix, player.pos);
        }

        // 绘制矩阵通用函数
        function drawMatrix(matrix, offset) {
            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);

                        // 绘制一点高光效果,让它看起来立体一点
                        context.lineWidth = 0.05;
                        context.strokeStyle = 'rgba(255,255,255,0.5)';
                        context.strokeRect(x + offset.x, y + offset.y, 1, 1);
                    }
                });
            });
        }

        // 合并方块到游戏区域(当方块落地时)
        function merge(arena, player) {
            player.matrix.forEach((row, y) => {
                row.forEach((value, x) => {
                    if (value !== 0) {
                        arena[y + player.pos.y][x + player.pos.x] = value;
                    }
                });
            });
        }

        // 矩阵旋转
        function rotate(matrix, dir) {
            // 转置
            for (let y = 0; y < matrix.length; ++y) {
                for (let x = 0; x < y; ++x) {
                    [
                        matrix[x][y],
                        matrix[y][x],
                    ] = [
                        matrix[y][x],
                        matrix[x][y],
                    ];
                }
            }
            // 如果是顺时针,反转每一行
            if (dir > 0) {
                matrix.forEach(row => row.reverse());
            } else {
                matrix.reverse();
            }
        }

        // 碰撞检测
        function collide(arena, player) {
            const m = player.matrix;
            const o = player.pos;
            for (let y = 0; y < m.length; ++y) {
                for (let x = 0; x < m[y].length; ++x) {
                    if (m[y][x] !== 0 &&
                       (arena[y + o.y] && arena[y + o.y][x + o.x]) !== 0) {
                        return true;
                    }
                }
            }
            return false;
        }

        // 消除行逻辑
        function arenaSweep() {
            let rowCount = 0;
            outer: for (let y = arena.length -1; y > 0; --y) {
                for (let x = 0; x < arena[y].length; ++x) {
                    if (arena[y][x] === 0) {
                        continue outer; // 如果这一行有空位,跳过,检查下一行
                    }
                }

                // 如果执行到这里,说明这一行满了
                const row = arena.splice(y, 1)[0].fill(0); // 移除该行并清空
                arena.unshift(row); // 在顶部加一个空行
                ++y; // 因为删了一行,索引要回退,重新检查当前索引位置的新行
                rowCount++;
            }

            if (rowCount > 0) {
                // 计分规则:1 行 10,2 行 30,3 行 60,4 行 100
                const lineScores = [0, 10, 30, 60, 100];
                player.score += lineScores[rowCount] * (player.level + 1);
                updateScore();

                // 每消除 10 行升一级
                player.lines += rowCount;
                const newLevel = Math.floor(player.lines / 10) + 1;
                if (newLevel > player.level) {
                    player.level = newLevel;
                    // 加速:减少 dropInterval,最低 100ms
                    dropInterval = Math.max(100, 1000 - (player.level - 1) * 100); 
                    updateScore();
                }
            }
        }

        // 玩家重置(生成新方块)
        function playerReset() {
            const piecesStr = 'ILJOTSZ';
            player.matrix = createPiece(piecesStr[piecesStr.length * Math.random() | 0]);
            player.pos.y = 0;
            player.pos.x = (arena[0].length / 2 | 0) - (player.matrix[0].length / 2 | 0);

            // 如果生成新方块时就碰撞,游戏结束
            if (collide(arena, player)) {
                gameOver = true;
                modal.style.display = 'block';
                finalScoreElement.innerText = player.score;
            }
        }

        // 玩家下落
        function playerDrop() {
            player.pos.y++;
            if (collide(arena, player)) {
                player.pos.y--; // 退回一步
                merge(arena, player); // 固定方块
                playerReset(); // 生成新方块
                arenaSweep(); // 检查消除
            }
            dropCounter = 0;
        }

        // 玩家移动
        function playerMove(dir) {
            player.pos.x += dir;
            if (collide(arena, player)) {
                player.pos.x -= dir;
            }
        }

        // 玩家旋转
        function playerRotate(dir) {
            const pos = player.pos.x;
            let offset = 1;
            rotate(player.matrix, dir);
            // 旋转后如果碰撞(比如在墙边),尝试左右推挤(Wall Kick 简易版)
            while (collide(arena, player)) {
                player.pos.x += offset;
                offset = -(offset + (offset > 0 ? 1 : -1));
                if (offset > player.matrix[0].length) {
                    rotate(player.matrix, -dir); // 转不回来就转回去
                    player.pos.x = pos;
                    return;
                }
            }
        }

        // 更新 UI
        function updateScore() {
            scoreElement.innerText = player.score;
            levelElement.innerText = player.level;
        }

        // 游戏主循环
        let dropCounter = 0;
        let dropInterval = 1000; // 初始下落间隔 1 秒
        let lastTime = 0;
        let gameOver = false;

        function update(time = 0) {
            if (gameOver) return;

            const deltaTime = time - lastTime;
            lastTime = time;

            dropCounter += deltaTime;
            if (dropCounter > dropInterval) {
                playerDrop();
            }

            draw();
            requestAnimationFrame(update);
        }

        // 游戏状态对象
        const arena = createMatrix(12, 20); // 宽 12,高 20
        const player = {
            pos: {x: 0, y: 0},
            matrix: null,
            score: 0,
            level: 1,
            lines: 0,
        };

        // 键盘监听
        document.addEventListener('keydown', event => {
            if (gameOver) return;

            if (event.keyCode === 37) { // Left
                playerMove(-1);
            } else if (event.keyCode === 39) { // Right
                playerMove(1);
            } else if (event.keyCode === 40) { // Down
                playerDrop();
            } else if (event.keyCode === 38) { // Up (Rotate)
                playerRotate(1);
            }
        });

        // 重新开始游戏
        function resetGame() {
            arena.forEach(row => row.fill(0));
            player.score = 0;
            player.level = 1;
            player.lines = 0;
            dropInterval = 1000;
            gameOver = false;
            modal.style.display = 'none';
            updateScore();
            playerReset();
            update();
        }

        // 启动游戏
        playerReset();
        update();

    </script>
</body>
</html>
相关推荐
Mintopia2 小时前
OpenClaw 是什么?为什么节后热度如此之高?
人工智能
爱可生开源社区2 小时前
DBA 的未来?八位行业先锋的年度圆桌讨论
人工智能·dba
叁两5 小时前
用opencode打造全自动公众号写作流水线,AI 代笔太香了!
前端·人工智能·agent
前端付豪5 小时前
LangChain记忆:通过Memory记住上次的对话细节
人工智能·python·langchain
strayCat232555 小时前
Clawdbot 源码解读 7: 扩展机制
人工智能·开源
王鑫星5 小时前
SWE-bench 首次突破 80%:Claude Opus 4.5 发布,Anthropic 的野心不止于写代码
人工智能
lnix5 小时前
当“大龙虾”养在本地:我们离“反SaaS”的AI未来还有多远?
人工智能·aigc
泉城老铁5 小时前
Dify知识库如何实现多关键词AND检索?
人工智能