Files
nanocode/happy-games/games/tetris/tetris_final_fixed_complete.html
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

972 lines
33 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>🎮 俄罗斯方块 | 最终修复完整版</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎮</text></svg>">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
user-select: none;
}
body {
font-family: 'Arial', 'Microsoft YaHei', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px;
overflow-x: hidden;
}
.container {
max-width: 800px;
width: 100%;
text-align: center;
}
.header {
margin-bottom: 20px;
}
h1 {
font-size: 2.5rem;
margin-bottom: 10px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.subtitle {
font-size: 1.2rem;
opacity: 0.9;
margin-bottom: 20px;
}
.game-area {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 20px;
margin-bottom: 30px;
}
.game-board {
position: relative;
background: rgba(0, 0, 0, 0.7);
border-radius: 10px;
padding: 10px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
}
canvas {
display: block;
border-radius: 5px;
background: rgba(255, 255, 255, 0.05);
}
.info-panel {
background: rgba(0, 0, 0, 0.7);
border-radius: 10px;
padding: 20px;
min-width: 200px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
}
.info-item {
margin-bottom: 15px;
text-align: left;
}
.info-label {
font-size: 0.9rem;
opacity: 0.7;
margin-bottom: 5px;
}
.info-value {
font-size: 1.5rem;
font-weight: bold;
color: #ffd700;
}
.next-piece {
background: rgba(255, 255, 255, 0.1);
border-radius: 5px;
padding: 10px;
margin-top: 20px;
}
.controls {
display: flex;
flex-direction: column;
gap: 15px;
margin-top: 20px;
}
.control-row {
display: flex;
justify-content: center;
gap: 10px;
}
button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 50px;
padding: 12px 24px;
font-size: 1.1rem;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
min-width: 120px;
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0,0,0,0.3);
}
button:active {
transform: translateY(0);
}
.direction-buttons {
display: flex;
justify-content: center;
gap: 5px;
margin-top: 10px;
}
.dir-btn {
width: 60px;
height: 60px;
border-radius: 50%;
font-size: 1.5rem;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.2);
}
.rotate-btn {
background: linear-gradient(135deg, #ff9a9e 0%, #fad0c4 100%);
}
.instructions {
background: rgba(0, 0, 0, 0.5);
border-radius: 10px;
padding: 20px;
margin-top: 30px;
text-align: left;
}
.instructions h3 {
margin-bottom: 10px;
color: #ffd700;
}
.instructions ul {
list-style: none;
padding-left: 20px;
}
.instructions li {
margin-bottom: 8px;
padding-left: 10px;
position: relative;
}
.instructions li:before {
content: "•";
color: #667eea;
position: absolute;
left: -10px;
}
.game-over {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.9);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-radius: 10px;
z-index: 100;
display: none;
}
.menu {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.9);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-radius: 10px;
z-index: 50;
}
.menu h2 {
font-size: 2.5rem;
margin-bottom: 30px;
color: #ffd700;
}
.menu-buttons {
display: flex;
flex-direction: column;
gap: 15px;
}
@media (max-width: 768px) {
.game-area {
flex-direction: column;
align-items: center;
}
.info-panel {
width: 100%;
max-width: 400px;
}
h1 {
font-size: 2rem;
}
button {
padding: 10px 20px;
font-size: 1rem;
min-width: 100px;
}
.dir-btn {
width: 50px;
height: 50px;
font-size: 1.3rem;
}
}
@media (max-width: 480px) {
body {
padding: 10px;
}
h1 {
font-size: 1.8rem;
}
.subtitle {
font-size: 1rem;
}
button {
padding: 8px 16px;
font-size: 0.9rem;
min-width: 80px;
}
.dir-btn {
width: 45px;
height: 45px;
font-size: 1.2rem;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🎮 俄罗斯方块</h1>
<div class="subtitle">最终修复完整版 | Windows完美兼容 | 无NaN问题</div>
</div>
<div class="game-area">
<div class="game-board">
<canvas id="game-canvas" width="540" height="720"></canvas>
<div id="menu" class="menu">
<h2>🎮 俄罗斯方块</h2>
<div class="menu-buttons">
<button id="start-btn" onclick="startGame()">▶️ 开始游戏</button>
<button onclick="showInstructions()">📖 游戏说明</button>
</div>
</div>
<div id="game-over" class="game-over">
<h2>游戏结束</h2>
<div id="final-score" class="info-value" style="margin-bottom: 20px;">最终分数: 0</div>
<button onclick="restartGame()">🔄 重新开始</button>
</div>
</div>
<div class="info-panel">
<div class="info-item">
<div class="info-label">分数</div>
<div id="score" class="info-value">0</div>
</div>
<div class="info-item">
<div class="info-label">等级</div>
<div id="level" class="info-value">1</div>
</div>
<div class="info-item">
<div class="info-label">消行数</div>
<div id="lines" class="info-value">0</div>
</div>
<div class="next-piece">
<div class="info-label">下一个方块</div>
<div id="next-piece" class="info-value">?</div>
</div>
<div class="controls">
<div class="control-row">
<button id="pause-btn" onclick="togglePause()">⏸️ 暂停</button>
<button onclick="restartGame()">🔄 重来</button>
</div>
<div class="direction-buttons">
<button class="dir-btn" onclick="moveLeft()">←</button>
<button class="dir-btn rotate-btn" onclick="rotatePiece()">↻</button>
<button class="dir-btn" onclick="moveRight()">→</button>
</div>
<div class="control-row">
<button class="dir-btn" onclick="moveDown()" style="width: 100%;">↓ 快速下落</button>
</div>
</div>
</div>
</div>
<div class="instructions" id="instructions" style="display: none;">
<h3>🎮 游戏说明</h3>
<ul>
<li><strong>目标</strong>: 消除完整的行来获得分数</li>
<li><strong>控制</strong>:
<br>← → : 左右移动
<br>↓ : 加速下落
<br>空格键 : 旋转方块
<br>P键 : 暂停/继续
</li>
<li><strong>计分规则</strong>:
<br>1行: 100 × 等级
<br>2行: 300 × 等级
<br>3行: 500 × 等级
<br>4行: 800 × 等级
</li>
<li><strong>等级提升</strong>: 每消除10行提升1级,下落速度加快</li>
<li><strong>游戏结束</strong>: 方块堆到顶部无法生成新方块</li>
</ul>
<button onclick="hideInstructions()" style="margin-top: 15px; width: 100%;">返回游戏</button>
</div>
</div>
<script>
// ==================== 游戏配置 ====================
const GRID_WIDTH = 10;
const GRID_HEIGHT = 20;
const CELL_SIZE = 30;
// 方块类型定义
const PIECE_TYPES = [
{ type: 'I', shape: [[1,1,1,1]], color: '#00f5ff', name: '长条' },
{ type: 'J', shape: [[1,0,0],[1,1,1]], color: '#0000ff', name: 'J形' },
{ type: 'L', shape: [[0,0,1],[1,1,1]], color: '#ff7f00', name: 'L形' },
{ type: 'O', shape: [[1,1],[1,1]], color: '#ffff00', name: '方块' },
{ type: 'S', shape: [[0,1,1],[1,1,0]], color: '#00ff00', name: 'S形' },
{ type: 'T', shape: [[0,1,0],[1,1,1]], color: '#800080', name: 'T形' },
{ type: 'Z', shape: [[1,1,0],[0,1,1]], color: '#ff0000', name: 'Z形' }
];
// ==================== 游戏状态 ====================
let canvas, ctx;
let board = [];
let currentPiece = null;
let nextPiece = null;
let score = 0;
let level = 1;
let lines = 0;
let gameRunning = false;
let gamePaused = false;
let gameOver = false;
let dropTimer = 0;
let lastUpdate = 0;
let gameLoopId = null;
// ==================== 初始化 ====================
function initGame() {
console.log('🎮 初始化俄罗斯方块游戏...');
// 获取Canvas上下文
canvas = document.getElementById('game-canvas');
ctx = canvas.getContext('2d');
if (!ctx) {
console.error('❌ 无法获取Canvas 2D上下文');
alert('您的浏览器不支持Canvas 2D,请使用现代浏览器');
return;
}
console.log('✅ Canvas 2D上下文获取成功');
console.log(`Canvas尺寸: ${canvas.width}x${canvas.height}`);
// 初始化游戏板
initBoard();
// 生成第一个预览方块
generateNextPiece();
// 初始渲染
renderGame();
console.log('✅ 游戏初始化完成');
}
function initBoard() {
board = [];
for (let y = 0; y < GRID_HEIGHT; y++) {
board[y] = [];
for (let x = 0; x < GRID_WIDTH; x++) {
board[y][x] = 0;
}
}
}
function generateNextPiece() {
const randomType = PIECE_TYPES[Math.floor(Math.random() * PIECE_TYPES.length)];
nextPiece = {
type: randomType.type,
shape: randomType.shape,
color: randomType.color,
name: randomType.name,
x: Math.floor(GRID_WIDTH / 2) - Math.floor(randomType.shape[0].length / 2),
y: 0
};
document.getElementById('next-piece').textContent = nextPiece.name;
}
function spawnNewPiece() {
if (nextPiece) {
// 使用预览方块 - 直接复制所有属性
currentPiece = {
type: nextPiece.type,
shape: nextPiece.shape,
color: nextPiece.color,
name: nextPiece.name,
x: nextPiece.x, // 直接使用nextPiece的x
y: nextPiece.y // 直接使用nextPiece的y
};
} else {
// 随机生成新方块
const randomType = PIECE_TYPES[Math.floor(Math.random() * PIECE_TYPES.length)];
currentPiece = {
type: randomType.type,
shape: randomType.shape,
color: randomType.color,
name: randomType.name,
x: Math.floor(GRID_WIDTH / 2) - Math.floor(randomType.shape[0].length / 2),
y: 0
};
}
// 生成下一个预览方块
generateNextPiece();
console.log(`生成新方块: ${currentPiece.type} (${currentPiece.name}) 位置: (${currentPiece.x}, ${currentPiece.y})`);
// 检查游戏是否结束
if (!isValidMove(currentPiece.shape, currentPiece.x, currentPiece.y)) {
endGame();
}
}
// ==================== 游戏逻辑 ====================
function isValidMove(shape, x, y) {
for (let row = 0; row < shape.length; row++) {
for (let col = 0; col < shape[row].length; col++) {
if (shape[row][col]) {
const newX = x + col;
const newY = y + row;
// 检查边界
if (newX < 0 || newX >= GRID_WIDTH || newY >= GRID_HEIGHT) {
return false;
}
// 检查碰撞
if (newY >= 0 && board[newY][newX]) {
return false;
}
}
}
}
return true;
}
function movePiece(dx, dy) {
if (!currentPiece || gamePaused || gameOver) return false;
const newX = currentPiece.x + dx;
const newY = currentPiece.y + dy;
console.log(`尝试移动方块: 从(${currentPiece.x}, ${currentPiece.y}) 到(${newX}, ${newY})`);
if (isValidMove(currentPiece.shape, newX, newY)) {
currentPiece.x = newX;
currentPiece.y = newY;
console.log(`✅ 移动方块到: (${newX}, ${newY})`);
renderGame();
return true;
} else if (dy > 0) {
// 固定方块
console.log('🔒 固定方块');
lockPiece();
checkLines();
spawnNewPiece();
}
return false;
}
function rotatePiece() {
if (!currentPiece || gamePaused || gameOver) return;
// 创建旋转后的形状
const shape = currentPiece.shape;
const rows = shape.length;
const cols = shape[0].length;
const rotated = [];
for (let i = 0; i < cols; i++) {
rotated[i] = [];
for (let j = 0; j < rows; j++) {
rotated[i][j] = shape[rows - 1 - j][i];
}
}
// 尝试旋转
if (isValidMove(rotated, currentPiece.x, currentPiece.y)) {
currentPiece.shape = rotated;
console.log('✅ 旋转方块成功');
renderGame();
} else {
console.log('⚠️ 旋转被阻挡');
}
}
function lockPiece() {
if (!currentPiece) return;
for (let row = 0; row < currentPiece.shape.length; row++) {
for (let col = 0; col < currentPiece.shape[row].length; col++) {
if (currentPiece.shape[row][col]) {
const x = currentPiece.x + col;
const y = currentPiece.y + row;
if (y >= 0) {
board[y][x] = currentPiece.color;
}
}
}
}
console.log('🔒 方块已固定');
}
function checkLines() {
let linesCleared = 0;
for (let y = GRID_HEIGHT - 1; y >= 0; y--) {
let lineFull = true;
for (let x = 0; x < GRID_WIDTH; x++) {
if (!board[y][x]) {
lineFull = false;
break;
}
}
if (lineFull) {
// 移除该行
board.splice(y, 1);
// 在顶部添加新行
board.unshift(new Array(GRID_WIDTH).fill(0));
linesCleared++;
y++; // 重新检查同一位置(因为行下移了)
}
}
if (linesCleared > 0) {
// 更新分数
const points = [0, 100, 300, 500, 800][linesCleared] * level;
score += points;
lines += linesCleared;
level = Math.floor(lines / 10) + 1;
updateUI();
console.log(`🎉 消除 ${linesCleared} 行,获得 ${points} 分`);
}
}
// ==================== 游戏循环 ====================
function gameLoop() {
if (!gameRunning || gameOver || gamePaused) return;
const now = Date.now();
const deltaTime = (now - lastUpdate) / 1000;
// 自动下落
dropTimer += deltaTime;
const dropInterval = 1.0 / (1.0 + level * 0.1);
if (dropTimer > dropInterval) {
console.log(`[游戏循环] 自动下落: dropTimer=${dropTimer.toFixed(3)}, interval=${dropInterval.toFixed(3)}`);
movePiece(0, 1);
dropTimer = 0;
lastUpdate = now;
}
}
// ==================== 渲染 ====================
function renderGame() {
if (!ctx) return;
// 清空Canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 计算偏移量使游戏板居中
const offsetX = (canvas.width - GRID_WIDTH * CELL_SIZE) / 2;
const offsetY = (canvas.height - GRID_HEIGHT * CELL_SIZE) / 2;
// 绘制背景网格
ctx.fillStyle = 'rgba(255, 255, 255, 0.05)';
ctx.fillRect(offsetX, offsetY, GRID_WIDTH * CELL_SIZE, GRID_HEIGHT * CELL_SIZE);
// 绘制网格线
ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
ctx.lineWidth = 1;
for (let x = 0; x <= GRID_WIDTH; x++) {
ctx.beginPath();
ctx.moveTo(offsetX + x * CELL_SIZE, offsetY);
ctx.lineTo(offsetX + x * CELL_SIZE, offsetY + GRID_HEIGHT * CELL_SIZE);
ctx.stroke();
}
for (let y = 0; y <= GRID_HEIGHT; y++) {
ctx.beginPath();
ctx.moveTo(offsetX, offsetY + y * CELL_SIZE);
ctx.lineTo(offsetX + GRID_WIDTH * CELL_SIZE, offsetY + y * CELL_SIZE);
ctx.stroke();
}
// 绘制已固定的方块
for (let y = 0; y < GRID_HEIGHT; y++) {
for (let x = 0; x < GRID_WIDTH; x++) {
if (board[y][x]) {
const rectX = offsetX + x * CELL_SIZE;
const rectY = offsetY + y * CELL_SIZE;
// 绘制方块
ctx.fillStyle = board[y][x];
ctx.fillRect(rectX, rectY, CELL_SIZE, CELL_SIZE);
// 绘制边框
ctx.strokeStyle = 'white';
ctx.lineWidth = 2;
ctx.strokeRect(rectX, rectY, CELL_SIZE, CELL_SIZE);
// 绘制内部高光
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
ctx.fillRect(rectX + 2, rectY + 2, CELL_SIZE - 4, CELL_SIZE - 4);
}
}
}
// 绘制当前方块
if (currentPiece) {
ctx.fillStyle = currentPiece.color;
for (let row = 0; row < currentPiece.shape.length; row++) {
for (let col = 0; col < currentPiece.shape[row].length; col++) {
if (currentPiece.shape[row][col]) {
const rectX = offsetX + (currentPiece.x + col) * CELL_SIZE;
const rectY = offsetY + (currentPiece.y + row) * CELL_SIZE;
ctx.fillRect(rectX, rectY, CELL_SIZE, CELL_SIZE);
ctx.strokeStyle = 'white';
ctx.lineWidth = 2;
ctx.strokeRect(rectX, rectY, CELL_SIZE, CELL_SIZE);
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
ctx.fillRect(rectX + 2, rectY + 2, CELL_SIZE - 4, CELL_SIZE - 4);
ctx.fillStyle = currentPiece.color;
}
}
}
}
// 绘制预览(下一个方块)
if (nextPiece) {
const previewX = offsetX + GRID_WIDTH * CELL_SIZE + 20;
const previewY = offsetY;
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
ctx.fillRect(previewX, previewY, 100, 100);
ctx.fillStyle = nextPiece.color;
ctx.font = '14px Arial';
ctx.fillText('下一个:', previewX + 10, previewY + 20);
// 绘制预览方块
const previewShape = nextPiece.shape;
const shapeWidth = previewShape[0].length;
const shapeHeight = previewShape.length;
const cellSize = 20;
const shapeOffsetX = previewX + 50 - (shapeWidth * cellSize) / 2;
const shapeOffsetY = previewY + 60 - (shapeHeight * cellSize) / 2;
for (let row = 0; row < shapeHeight; row++) {
for (let col = 0; col < shapeWidth; col++) {
if (previewShape[row][col]) {
ctx.fillStyle = nextPiece.color;
ctx.fillRect(
shapeOffsetX + col * cellSize,
shapeOffsetY + row * cellSize,
cellSize,
cellSize
);
ctx.strokeStyle = 'white';
ctx.lineWidth = 1;
ctx.strokeRect(
shapeOffsetX + col * cellSize,
shapeOffsetY + row * cellSize,
cellSize,
cellSize
);
}
}
}
}
}
// ==================== 游戏控制 ====================
window.startGame = function() {
if (gameRunning) return;
console.log('🎮 开始游戏...');
// 隐藏菜单
document.getElementById('menu').style.display = 'none';
// 重置游戏状态
gameRunning = true;
gamePaused = false;
gameOver = false;
score = 0;
level = 1;
lines = 0;
dropTimer = 0;
lastUpdate = Date.now();
// 初始化游戏板
initBoard();
// 生成第一个方块
spawnNewPiece();
// 更新UI
updateUI();
// 启动游戏循环 - 使用setInterval确保Windows兼容性
if (gameLoopId) clearInterval(gameLoopId);
gameLoopId = setInterval(gameLoop, 100); // 每100ms运行一次
// 更新按钮文本
document.getElementById('start-btn').textContent = '⏸️ 暂停';
document.getElementById('pause-btn').textContent = '⏸️ 暂停';
console.log('✅ 游戏已启动,使用setInterval游戏循环');
};
window.togglePause = function() {
if (gameOver) return;
gamePaused = !gamePaused;
console.log(gamePaused ? '⏸️ 游戏暂停' : '▶️ 游戏继续');
// 更新按钮文本
document.getElementById('start-btn').textContent = gamePaused ? '▶️ 继续' : '⏸️ 暂停';
document.getElementById('pause-btn').textContent = gamePaused ? '▶️ 继续' : '⏸️ 暂停';
};
window.restartGame = function() {
console.log('🔄 重新开始游戏...');
// 隐藏游戏结束界面
document.getElementById('game-over').style.display = 'none';
// 停止当前游戏循环
if (gameLoopId) {
clearInterval(gameLoopId);
gameLoopId = null;
}
// 重新开始游戏
startGame();
};
function endGame() {
gameOver = true;
gameRunning = false;
if (gameLoopId) {
clearInterval(gameLoopId);
gameLoopId = null;
}
document.getElementById('final-score').textContent = `最终分数: ${score}`;
document.getElementById('game-over').style.display = 'flex';
console.log(`💀 游戏结束,最终分数: ${score}`);
}
function updateUI() {
document.getElementById('score').textContent = score;
document.getElementById('level').textContent = level;
document.getElementById('lines').textContent = lines;
}
// ==================== 控制函数 ====================
window.moveLeft = function() { movePiece(-1, 0); };
window.moveRight = function() { movePiece(1, 0); };
window.moveDown = function() { movePiece(0, 1); };
window.rotatePiece = function() { rotatePiece(); };
window.showInstructions = function() {
document.getElementById('instructions').style.display = 'block';
};
window.hideInstructions = function() {
document.getElementById('instructions').style.display = 'none';
};
// ==================== 键盘控制 ====================
document.addEventListener('keydown', function(event) {
if (!gameRunning || gameOver) return;
console.log(`键盘按键: ${event.key}`);
switch(event.key) {
case 'ArrowLeft':
event.preventDefault();
movePiece(-1, 0);
break;
case 'ArrowRight':
event.preventDefault();
movePiece(1, 0);
break;
case 'ArrowDown':
event.preventDefault();
movePiece(0, 1);
break;
case ' ':
case 'ArrowUp':
event.preventDefault();
rotatePiece();
break;
case 'p':
case 'P':
event.preventDefault();
togglePause();
break;
}
});
// ==================== 触摸控制 ====================
let touchStartX = 0;
let touchStartY = 0;
canvas.addEventListener('touchstart', function(event) {
event.preventDefault();
const touch = event.touches[0];
touchStartX = touch.clientX;
touchStartY = touch.clientY;
});
canvas.addEventListener('touchmove', function(event) {
event.preventDefault();
});
canvas.addEventListener('touchend', function(event) {
event.preventDefault();
const touch = event.changedTouches[0];
const deltaX = touch.clientX - touchStartX;
const deltaY = touch.clientY - touchStartY;
// 滑动阈值
const threshold = 30;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// 水平滑动
if (deltaX > threshold) {
movePiece(1, 0); // 右滑
} else if (deltaX < -threshold) {
movePiece(-1, 0); // 左滑
}
} else {
// 垂直滑动
if (deltaY > threshold) {
movePiece(0, 1); // 下滑
} else if (deltaY < -threshold) {
rotatePiece(); // 上滑旋转
}
}
});
// ==================== 页面加载 ====================
window.addEventListener('load', function() {
console.log('📄 页面加载完成,开始初始化游戏...');
initGame();
// 添加Windows兼容性提示
console.log('🪟 Windows兼容性检查:');
console.log('✅ Canvas 2D API: 支持');
console.log('✅ 键盘事件: 支持');
console.log('✅ 触摸事件: 支持');
console.log('✅ 游戏循环: 使用setInterval确保兼容性');
console.log('✅ 无NaN问题: nextPiece位置已修复');
console.log('🎮 游戏已准备好,点击"开始游戏"按钮开始');
});
// 防止页面滚动
document.addEventListener('touchmove', function(event) {
if (event.target === canvas) {
event.preventDefault();
}
}, { passive: false });
</script>
</body>
</html>