871 lines
30 KiB
HTML
871 lines
30 KiB
HTML
<!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>🎮 俄罗斯方块 | 最终Canvas 2D版</title>
|
|
<link rel="icon" href="favicon.ico" type="image/x-icon">
|
|
|
|
<style>
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
html, body {
|
|
width: 100%;
|
|
height: 100%;
|
|
overflow: hidden;
|
|
background: #0a0a1a;
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
touch-action: none;
|
|
}
|
|
|
|
#game-container {
|
|
width: 100vw;
|
|
height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 20px;
|
|
}
|
|
|
|
canvas {
|
|
border: 2px solid rgba(255, 255, 255, 0.2);
|
|
border-radius: 10px;
|
|
background: #111827;
|
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
|
|
}
|
|
|
|
#info-panel {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
width: 540px;
|
|
margin-top: 20px;
|
|
padding: 15px;
|
|
background: rgba(0, 0, 0, 0.6);
|
|
border-radius: 10px;
|
|
color: white;
|
|
}
|
|
|
|
.info-item {
|
|
text-align: center;
|
|
}
|
|
|
|
.info-label {
|
|
font-size: 14px;
|
|
color: #aaa;
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.info-value {
|
|
font-size: 24px;
|
|
font-weight: bold;
|
|
color: #00d4ff;
|
|
}
|
|
|
|
#controls {
|
|
display: flex;
|
|
gap: 15px;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.control-btn {
|
|
padding: 12px 25px;
|
|
font-size: 18px;
|
|
background: linear-gradient(135deg, #667eea, #764ba2);
|
|
color: white;
|
|
border: none;
|
|
border-radius: 25px;
|
|
cursor: pointer;
|
|
transition: transform 0.2s, box-shadow 0.2s;
|
|
}
|
|
|
|
.control-btn:hover {
|
|
transform: scale(1.05);
|
|
box-shadow: 0 10px 30px rgba(102,126,234,0.4);
|
|
}
|
|
|
|
.control-btn:active {
|
|
transform: scale(0.95);
|
|
}
|
|
|
|
#start-btn {
|
|
background: linear-gradient(135deg, #10b981, #047857);
|
|
}
|
|
|
|
#restart-btn {
|
|
background: linear-gradient(135deg, #f59e0b, #d97706);
|
|
}
|
|
|
|
#pause-btn {
|
|
background: linear-gradient(135deg, #8b5cf6, #7c3aed);
|
|
}
|
|
|
|
#menu {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
text-align: center;
|
|
background: rgba(0, 0, 0, 0.8);
|
|
padding: 40px;
|
|
border-radius: 20px;
|
|
backdrop-filter: blur(10px);
|
|
z-index: 100;
|
|
}
|
|
|
|
#menu h1 {
|
|
color: #00d4ff;
|
|
font-size: 48px;
|
|
margin-bottom: 20px;
|
|
text-shadow: 0 0 20px rgba(0,212,255,0.5);
|
|
}
|
|
|
|
#menu p {
|
|
color: #ccc;
|
|
margin-bottom: 30px;
|
|
max-width: 400px;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
#game-over {
|
|
display: none;
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
text-align: center;
|
|
background: rgba(0, 0, 0, 0.9);
|
|
padding: 40px;
|
|
border-radius: 20px;
|
|
backdrop-filter: blur(10px);
|
|
z-index: 100;
|
|
}
|
|
|
|
#game-over h2 {
|
|
color: #ff6b6b;
|
|
font-size: 36px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
#final-score {
|
|
color: white;
|
|
font-size: 24px;
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
/* 移动端虚拟按钮 */
|
|
#mobile-controls {
|
|
display: none;
|
|
position: absolute;
|
|
bottom: 50px;
|
|
left: 0;
|
|
right: 0;
|
|
justify-content: center;
|
|
gap: 20px;
|
|
z-index: 50;
|
|
}
|
|
|
|
.mobile-btn {
|
|
width: 70px;
|
|
height: 70px;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
border-radius: 50%;
|
|
color: white;
|
|
font-size: 28px;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
cursor: pointer;
|
|
user-select: none;
|
|
touch-action: manipulation;
|
|
}
|
|
|
|
.mobile-btn:active {
|
|
background: rgba(255, 255, 255, 0.2);
|
|
transform: scale(0.95);
|
|
}
|
|
|
|
/* 响应式设计 */
|
|
@media (max-width: 768px) {
|
|
canvas {
|
|
width: 300px;
|
|
height: 400px;
|
|
}
|
|
|
|
#info-panel {
|
|
width: 300px;
|
|
flex-wrap: wrap;
|
|
gap: 10px;
|
|
}
|
|
|
|
.info-item {
|
|
flex: 1;
|
|
min-width: 80px;
|
|
}
|
|
|
|
#controls {
|
|
flex-wrap: wrap;
|
|
justify-content: center;
|
|
}
|
|
|
|
.control-btn {
|
|
padding: 10px 20px;
|
|
font-size: 16px;
|
|
}
|
|
|
|
#mobile-controls {
|
|
display: flex;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 480px) {
|
|
canvas {
|
|
width: 280px;
|
|
height: 380px;
|
|
}
|
|
|
|
#info-panel {
|
|
width: 280px;
|
|
}
|
|
|
|
#menu, #game-over {
|
|
width: 90%;
|
|
padding: 20px;
|
|
}
|
|
|
|
#menu h1 {
|
|
font-size: 36px;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="game-container">
|
|
<canvas id="game-canvas" width="540" height="720"></canvas>
|
|
|
|
<div id="info-panel">
|
|
<div class="info-item">
|
|
<div class="info-label">分数</div>
|
|
<div class="info-value" id="score">0</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">等级</div>
|
|
<div class="info-value" id="level">0</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">消行</div>
|
|
<div class="info-value" id="lines">0</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">下一个</div>
|
|
<div class="info-value" id="next-piece">?</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="controls">
|
|
<button class="control-btn" id="start-btn">▶️ 开始</button>
|
|
<button class="control-btn" id="pause-btn">⏸️ 暂停</button>
|
|
<button class="control-btn" id="restart-btn">🔄 重来</button>
|
|
</div>
|
|
|
|
<!-- 移动端虚拟按钮 -->
|
|
<div id="mobile-controls">
|
|
<div class="mobile-btn" id="mobile-left">←</div>
|
|
<div class="mobile-btn" id="mobile-down">↓</div>
|
|
<div class="mobile-btn" id="mobile-right">→</div>
|
|
<div class="mobile-btn" id="mobile-rotate">↻</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 开始菜单 -->
|
|
<div id="menu">
|
|
<h1>🎮 俄罗斯方块</h1>
|
|
<p>经典的下落式方块游戏。使用方向键移动和旋转方块,填满整行消除得分!</p>
|
|
<button class="control-btn" onclick="startGame()">开始游戏</button>
|
|
<div style="margin-top: 30px; color: #888; font-size: 14px;">
|
|
<p>🎮 控制:键盘方向键或虚拟按钮</p>
|
|
<p>⬆️ 旋转:上箭头或旋转按钮</p>
|
|
<p>⏸️ 暂停:P键或暂停按钮</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 游戏结束界面 -->
|
|
<div id="game-over">
|
|
<h2>游戏结束</h2>
|
|
<div id="final-score">最终分数: 0</div>
|
|
<button class="control-btn" onclick="restartGame()">重新开始</button>
|
|
</div>
|
|
|
|
<script>
|
|
// ==================== 游戏配置 ====================
|
|
const GRID_WIDTH = 10;
|
|
const GRID_HEIGHT = 20;
|
|
const CELL_SIZE = 24;
|
|
|
|
// 方块类型定义
|
|
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: '#ff8000', 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: '#8000ff', 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 gameRunning = false;
|
|
let gamePaused = false;
|
|
let gameOver = false;
|
|
let score = 0;
|
|
let level = 0;
|
|
let lines = 0;
|
|
let dropTimer = 0;
|
|
let gameLoopId = null;
|
|
let lastTime = 0;
|
|
|
|
// ==================== 初始化 ====================
|
|
function init() {
|
|
console.log('🎮 初始化俄罗斯方块游戏...');
|
|
|
|
// 获取Canvas和上下文
|
|
canvas = document.getElementById('game-canvas');
|
|
ctx = canvas.getContext('2d');
|
|
|
|
if (!ctx) {
|
|
console.error('❌ 无法获取Canvas 2D上下文!');
|
|
alert('游戏初始化失败:无法获取Canvas上下文');
|
|
return;
|
|
}
|
|
|
|
console.log('✅ Canvas 2D上下文获取成功');
|
|
console.log(`Canvas尺寸: ${canvas.width}x${canvas.height}`);
|
|
|
|
// 初始化游戏板
|
|
resetBoard();
|
|
|
|
// 生成第一个方块
|
|
spawnNewPiece();
|
|
generateNextPiece();
|
|
|
|
// 设置事件监听器
|
|
setupEventListeners();
|
|
|
|
// 初始渲染
|
|
renderGame();
|
|
|
|
console.log('✅ 游戏初始化完成');
|
|
}
|
|
|
|
function resetBoard() {
|
|
board = Array(GRID_HEIGHT).fill().map(() => Array(GRID_WIDTH).fill(0));
|
|
}
|
|
|
|
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})`);
|
|
}
|
|
|
|
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 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;
|
|
|
|
const newX = currentPiece.x + dx;
|
|
const newY = currentPiece.y + dy;
|
|
|
|
if (isValidMove(currentPiece.shape, newX, newY)) {
|
|
currentPiece.x = newX;
|
|
currentPiece.y = newY;
|
|
console.log(`移动方块到: (${newX}, ${newY})`);
|
|
} else if (dy > 0) {
|
|
// 固定方块
|
|
lockPiece();
|
|
checkLines();
|
|
spawnNewPiece();
|
|
|
|
// 检查游戏是否结束
|
|
if (!isValidMove(currentPiece.shape, currentPiece.x, currentPiece.y)) {
|
|
endGame();
|
|
}
|
|
}
|
|
|
|
renderGame();
|
|
}
|
|
|
|
function lockPiece() {
|
|
const { shape, x, y, color } = currentPiece;
|
|
|
|
for (let row = 0; row < shape.length; row++) {
|
|
for (let col = 0; col < shape[row].length; col++) {
|
|
if (shape[row][col]) {
|
|
const boardX = x + col;
|
|
const boardY = y + row;
|
|
|
|
if (boardY >= 0) {
|
|
board[boardY][boardX] = color;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log(`方块固定在位置: (${x}, ${y})`);
|
|
}
|
|
|
|
function checkLines() {
|
|
let linesCleared = 0;
|
|
|
|
for (let y = GRID_HEIGHT - 1; y >= 0; y--) {
|
|
if (board[y].every(cell => cell !== 0)) {
|
|
// 移除该行
|
|
board.splice(y, 1);
|
|
// 在顶部添加新行
|
|
board.unshift(Array(GRID_WIDTH).fill(0));
|
|
linesCleared++;
|
|
y++; // 重新检查同一位置
|
|
}
|
|
}
|
|
|
|
if (linesCleared > 0) {
|
|
lines += linesCleared;
|
|
|
|
// 计算得分:一次消除多行得分更高
|
|
const lineScores = [0, 100, 300, 500, 800]; // 0-4行的得分
|
|
score += lineScores[Math.min(linesCleared, 4)] * (level + 1);
|
|
level = Math.floor(lines / 10);
|
|
|
|
console.log(`消除 ${linesCleared} 行,得分: ${lineScores[Math.min(linesCleared, 4)] * (level + 1)}`);
|
|
updateUI();
|
|
}
|
|
}
|
|
|
|
function rotatePiece() {
|
|
if (!currentPiece || gamePaused || gameOver) return;
|
|
|
|
// 旋转矩阵
|
|
const rotateMatrix = (matrix) => {
|
|
const result = [];
|
|
for (let i = 0; i < matrix[0].length; i++) {
|
|
result[i] = [];
|
|
for (let j = 0; j < matrix.length; j++) {
|
|
result[i][j] = matrix[matrix.length - 1 - j][i];
|
|
}
|
|
}
|
|
return result;
|
|
};
|
|
|
|
const rotated = rotateMatrix(currentPiece.shape);
|
|
if (isValidMove(rotated, currentPiece.x, currentPiece.y)) {
|
|
currentPiece.shape = rotated;
|
|
console.log('旋转方块');
|
|
renderGame();
|
|
}
|
|
}
|
|
|
|
// ==================== 渲染函数 ====================
|
|
function renderGame() {
|
|
// 清空画布
|
|
ctx.fillStyle = '#0a0a1a';
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
|
|
// 计算游戏区域位置(居中)
|
|
const gameWidth = GRID_WIDTH * CELL_SIZE;
|
|
const gameHeight = GRID_HEIGHT * CELL_SIZE;
|
|
const offsetX = (canvas.width - gameWidth) / 2;
|
|
const offsetY = (canvas.height - gameHeight) / 2;
|
|
|
|
// 绘制网格背景
|
|
ctx.fillStyle = '#111827';
|
|
ctx.fillRect(offsetX, offsetY, gameWidth, gameHeight);
|
|
|
|
// 绘制网格线
|
|
ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
|
|
ctx.lineWidth = 1;
|
|
|
|
for (let x = 0; x <= GRID_WIDTH; x++) {
|
|
const lineX = offsetX + x * CELL_SIZE;
|
|
ctx.beginPath();
|
|
ctx.moveTo(lineX, offsetY);
|
|
ctx.lineTo(lineX, offsetY + gameHeight);
|
|
ctx.stroke();
|
|
}
|
|
|
|
for (let y = 0; y <= GRID_HEIGHT; y++) {
|
|
const lineY = offsetY + y * CELL_SIZE;
|
|
ctx.beginPath();
|
|
ctx.moveTo(offsetX, lineY);
|
|
ctx.lineTo(offsetX + gameWidth, lineY);
|
|
ctx.stroke();
|
|
}
|
|
|
|
// 绘制已固定的方块
|
|
for (let y = 0; y < GRID_HEIGHT; y++) {
|
|
for (let x = 0; x < GRID_WIDTH; x++) {
|
|
if (board[y][x]) {
|
|
ctx.fillStyle = board[y][x];
|
|
const rectX = offsetX + x * CELL_SIZE;
|
|
const rectY = offsetY + y * 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.2)';
|
|
ctx.fillRect(rectX + 2, rectY + 2, CELL_SIZE - 4, CELL_SIZE - 4);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 绘制当前方块
|
|
if (currentPiece) {
|
|
const { shape, x, y, color } = currentPiece;
|
|
|
|
ctx.fillStyle = color;
|
|
|
|
for (let row = 0; row < shape.length; row++) {
|
|
for (let col = 0; col < shape[row].length; col++) {
|
|
if (shape[row][col]) {
|
|
const rectX = offsetX + (x + col) * CELL_SIZE;
|
|
const rectY = offsetY + (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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 绘制预览(下一个方块)
|
|
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
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ==================== 游戏循环 ====================
|
|
function gameLoop(currentTime) {
|
|
if (!gameRunning || gameOver) return;
|
|
|
|
// 计算时间增量
|
|
const deltaTime = (currentTime - lastTime) / 1000; // 转换为秒
|
|
lastTime = currentTime;
|
|
|
|
if (!gamePaused) {
|
|
// 自动下落
|
|
dropTimer += deltaTime;
|
|
const dropInterval = 1.0 / (1.0 + level * 0.1); // 等级越高,下落越快
|
|
|
|
if (dropTimer > dropInterval) {
|
|
movePiece(0, 1);
|
|
dropTimer = 0;
|
|
}
|
|
}
|
|
|
|
gameLoopId = requestAnimationFrame(gameLoop);
|
|
}
|
|
|
|
// ==================== 游戏控制 ====================
|
|
window.startGame = function() {
|
|
if (gameRunning) return;
|
|
|
|
console.log('🎮 开始游戏...');
|
|
|
|
// 隐藏菜单
|
|
document.getElementById('menu').style.display = 'none';
|
|
|
|
// 重置游戏状态
|
|
resetBoard();
|
|
score = 0;
|
|
level = 0;
|
|
lines = 0;
|
|
gameOver = false;
|
|
gamePaused = false;
|
|
dropTimer = 0;
|
|
|
|
// 生成方块
|
|
spawnNewPiece();
|
|
generateNextPiece();
|
|
|
|
// 开始游戏
|
|
gameRunning = true;
|
|
lastTime = performance.now();
|
|
|
|
updateUI();
|
|
renderGame();
|
|
|
|
// 启动游戏循环
|
|
gameLoopId = requestAnimationFrame(gameLoop);
|
|
|
|
// 更新按钮文本
|
|
document.getElementById('start-btn').textContent = '⏸️ 暂停';
|
|
};
|
|
|
|
function togglePause() {
|
|
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) {
|
|
cancelAnimationFrame(gameLoopId);
|
|
gameLoopId = null;
|
|
}
|
|
|
|
// 重新开始游戏
|
|
startGame();
|
|
};
|
|
|
|
function endGame() {
|
|
gameOver = true;
|
|
gameRunning = false;
|
|
|
|
if (gameLoopId) {
|
|
cancelAnimationFrame(gameLoopId);
|
|
gameLoopId = null;
|
|
}
|
|
|
|
document.getElementById('final-score').textContent = `最终分数: ${score}`;
|
|
document.getElementById('game-over').style.display = 'block';
|
|
|
|
console.log(`游戏结束!最终得分: ${score}`);
|
|
}
|
|
|
|
// ==================== UI更新 ====================
|
|
function updateUI() {
|
|
document.getElementById('score').textContent = score;
|
|
document.getElementById('level').textContent = level;
|
|
document.getElementById('lines').textContent = lines;
|
|
}
|
|
|
|
// ==================== 事件监听器 ====================
|
|
function setupEventListeners() {
|
|
// 键盘控制
|
|
document.addEventListener('keydown', (e) => {
|
|
console.log(`键盘按键: ${e.key}`);
|
|
|
|
// 如果游戏还没开始,按任意键开始游戏
|
|
if (!gameRunning && !gameOver) {
|
|
startGame();
|
|
return;
|
|
}
|
|
|
|
if (gamePaused || gameOver) return;
|
|
|
|
switch(e.key) {
|
|
case 'ArrowLeft':
|
|
movePiece(-1, 0);
|
|
break;
|
|
case 'ArrowRight':
|
|
movePiece(1, 0);
|
|
break;
|
|
case 'ArrowDown':
|
|
movePiece(0, 1);
|
|
break;
|
|
case 'ArrowUp':
|
|
case ' ':
|
|
rotatePiece();
|
|
break;
|
|
case 'p':
|
|
case 'P':
|
|
togglePause();
|
|
break;
|
|
}
|
|
});
|
|
|
|
// 按钮控制
|
|
document.getElementById('start-btn').addEventListener('click', () => {
|
|
if (!gameRunning) {
|
|
startGame();
|
|
} else {
|
|
togglePause();
|
|
}
|
|
});
|
|
|
|
document.getElementById('pause-btn').addEventListener('click', togglePause);
|
|
document.getElementById('restart-btn').addEventListener('click', restartGame);
|
|
|
|
// 移动端虚拟按钮
|
|
document.getElementById('mobile-left').addEventListener('click', () => movePiece(-1, 0));
|
|
document.getElementById('mobile-right').addEventListener('click', () => movePiece(1, 0));
|
|
document.getElementById('mobile-down').addEventListener('click', () => movePiece(0, 1));
|
|
document.getElementById('mobile-rotate').addEventListener('click', rotatePiece);
|
|
|
|
// 触摸事件(移动端)
|
|
let touchStartX = 0;
|
|
let touchStartY = 0;
|
|
|
|
canvas.addEventListener('touchstart', (e) => {
|
|
e.preventDefault();
|
|
const touch = e.touches[0];
|
|
touchStartX = touch.clientX;
|
|
touchStartY = touch.clientY;
|
|
});
|
|
|
|
canvas.addEventListener('touchmove', (e) => {
|
|
e.preventDefault();
|
|
});
|
|
|
|
canvas.addEventListener('touchend', (e) => {
|
|
e.preventDefault();
|
|
const touch = e.changedTouches[0];
|
|
const deltaX = touch.clientX - touchStartX;
|
|
const deltaY = touch.clientY - touchStartY;
|
|
|
|
// 滑动控制
|
|
if (Math.abs(deltaX) > Math.abs(deltaY)) {
|
|
// 水平滑动
|
|
if (deltaX > 30) movePiece(1, 0); // 右滑
|
|
else if (deltaX < -30) movePiece(-1, 0); // 左滑
|
|
} else {
|
|
// 垂直滑动
|
|
if (deltaY > 30) movePiece(0, 1); // 下滑
|
|
else if (deltaY < -30) rotatePiece(); // 上滑(旋转)
|
|
}
|
|
});
|
|
|
|
// 窗口大小改变时调整Canvas
|
|
window.addEventListener('resize', () => {
|
|
renderGame();
|
|
});
|
|
}
|
|
|
|
// ==================== 页面加载完成 ====================
|
|
window.addEventListener('load', () => {
|
|
console.log('页面加载完成');
|
|
console.log(`浏览器: ${navigator.userAgent}`);
|
|
console.log(`平台: ${navigator.platform}`);
|
|
|
|
init();
|
|
});
|
|
|
|
// ==================== 错误处理 ====================
|
|
window.addEventListener('error', (e) => {
|
|
console.error('全局错误:', e);
|
|
alert(`❌ JavaScript错误: ${e.message}`);
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |