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;">
← → 移动 ↓ 软加速 ↑ 旋转<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>