444 lines
15 KiB
HTML
444 lines
15 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>🎮 俄罗斯方块 | 简单下落测试</title>
|
|
<style>
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body {
|
|
background: #0a0a1a;
|
|
color: white;
|
|
font-family: monospace;
|
|
padding: 20px;
|
|
}
|
|
|
|
#game {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 20px;
|
|
}
|
|
|
|
canvas {
|
|
border: 2px solid white;
|
|
background: #111827;
|
|
}
|
|
|
|
#controls {
|
|
display: flex;
|
|
gap: 10px;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
button {
|
|
padding: 10px 20px;
|
|
background: #667eea;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 5px;
|
|
cursor: pointer;
|
|
font-size: 16px;
|
|
}
|
|
|
|
button:hover {
|
|
background: #764ba2;
|
|
}
|
|
|
|
#debug {
|
|
background: rgba(0,0,0,0.7);
|
|
color: #00ff00;
|
|
padding: 10px;
|
|
border-radius: 5px;
|
|
max-width: 600px;
|
|
max-height: 200px;
|
|
overflow: auto;
|
|
font-size: 12px;
|
|
margin-top: 20px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="game">
|
|
<h1>🎮 俄罗斯方块 - 简单下落测试</h1>
|
|
<canvas id="canvas" width="240" height="480"></canvas>
|
|
|
|
<div id="info">
|
|
<div>分数: <span id="score">0</span></div>
|
|
<div>等级: <span id="level">0</span></div>
|
|
<div>消行: <span id="lines">0</span></div>
|
|
</div>
|
|
|
|
<div id="controls">
|
|
<button onclick="startGame()">开始游戏</button>
|
|
<button onclick="moveLeft()">← 左移</button>
|
|
<button onclick="moveRight()">→ 右移</button>
|
|
<button onclick="moveDown()">↓ 下落</button>
|
|
<button onclick="rotatePiece()">↻ 旋转</button>
|
|
</div>
|
|
|
|
<div id="debug"></div>
|
|
</div>
|
|
|
|
<script>
|
|
// ==================== 调试日志 ====================
|
|
const debugLog = (message) => {
|
|
const timestamp = new Date().toLocaleTimeString();
|
|
console.log(`[${timestamp}] ${message}`);
|
|
|
|
const debugDiv = document.getElementById('debug');
|
|
if (debugDiv) {
|
|
const line = document.createElement('div');
|
|
line.textContent = `[${timestamp}] ${message}`;
|
|
debugDiv.appendChild(line);
|
|
|
|
// 限制显示行数
|
|
while (debugDiv.children.length > 20) {
|
|
debugDiv.removeChild(debugDiv.firstChild);
|
|
}
|
|
|
|
debugDiv.scrollTop = debugDiv.scrollHeight;
|
|
}
|
|
};
|
|
|
|
// ==================== 游戏变量 ====================
|
|
const canvas = document.getElementById('canvas');
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
const GRID_WIDTH = 10;
|
|
const GRID_HEIGHT = 20;
|
|
const CELL_SIZE = 24;
|
|
|
|
let board = Array(GRID_HEIGHT).fill().map(() => Array(GRID_WIDTH).fill(0));
|
|
let currentPiece = null;
|
|
let gameRunning = false;
|
|
let gameLoopId = null;
|
|
let dropTimer = 0;
|
|
let score = 0;
|
|
let level = 0;
|
|
let lines = 0;
|
|
|
|
// 方块类型定义
|
|
const PIECE_TYPES = [
|
|
{ type: 'I', shape: [[1,1,1,1]], color: '#00f5ff' },
|
|
{ type: 'J', shape: [[1,0,0],[1,1,1]], color: '#0000ff' },
|
|
{ type: 'L', shape: [[0,0,1],[1,1,1]], color: '#ff8000' },
|
|
{ type: 'O', shape: [[1,1],[1,1]], color: '#ffff00' },
|
|
{ type: 'S', shape: [[0,1,1],[1,1,0]], color: '#00ff00' },
|
|
{ type: 'T', shape: [[0,1,0],[1,1,1]], color: '#8000ff' },
|
|
{ type: 'Z', shape: [[1,1,0],[0,1,1]], color: '#ff0000' }
|
|
];
|
|
|
|
// ==================== 游戏逻辑 ====================
|
|
function spawnNewPiece() {
|
|
const randomType = PIECE_TYPES[Math.floor(Math.random() * PIECE_TYPES.length)];
|
|
currentPiece = {
|
|
type: randomType.type,
|
|
shape: randomType.shape,
|
|
color: randomType.color,
|
|
x: Math.floor(GRID_WIDTH / 2) - Math.floor(randomType.shape[0].length / 2),
|
|
y: 0
|
|
};
|
|
debugLog(`生成新方块: ${currentPiece.type} 位置: (${currentPiece.x}, ${currentPiece.y})`);
|
|
}
|
|
|
|
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 || !gameRunning) return;
|
|
|
|
const newX = currentPiece.x + dx;
|
|
const newY = currentPiece.y + dy;
|
|
|
|
debugLog(`尝试移动方块: (${currentPiece.x},${currentPiece.y}) -> (${newX},${newY})`);
|
|
|
|
if (isValidMove(currentPiece.shape, newX, newY)) {
|
|
currentPiece.x = newX;
|
|
currentPiece.y = newY;
|
|
debugLog(`移动成功: (${newX}, ${newY})`);
|
|
} else if (dy > 0) {
|
|
// 固定方块
|
|
debugLog(`方块无法下落,准备固定`);
|
|
lockPiece();
|
|
checkLines();
|
|
spawnNewPiece();
|
|
|
|
// 检查游戏是否结束
|
|
if (!isValidMove(currentPiece.shape, currentPiece.x, currentPiece.y)) {
|
|
endGame();
|
|
}
|
|
} else {
|
|
debugLog(`移动失败: 位置无效`);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
debugLog(`方块固定在位置: (${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;
|
|
score += linesCleared * 100 * (level + 1);
|
|
level = Math.floor(lines / 10);
|
|
|
|
debugLog(`消除 ${linesCleared} 行,得分: ${linesCleared * 100 * (level + 1)}`);
|
|
updateUI();
|
|
}
|
|
}
|
|
|
|
function rotatePiece() {
|
|
if (!currentPiece || !gameRunning) 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;
|
|
debugLog('旋转方块');
|
|
renderGame();
|
|
}
|
|
}
|
|
|
|
function endGame() {
|
|
gameRunning = false;
|
|
if (gameLoopId) {
|
|
cancelAnimationFrame(gameLoopId);
|
|
gameLoopId = null;
|
|
}
|
|
debugLog(`游戏结束!最终得分: ${score}`);
|
|
}
|
|
|
|
// ==================== 渲染函数 ====================
|
|
function renderGame() {
|
|
// 清空画布
|
|
ctx.fillStyle = '#0a0a1a';
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
|
|
// 绘制网格背景
|
|
ctx.fillStyle = '#111827';
|
|
ctx.fillRect(0, 0, 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++) {
|
|
const lineX = x * CELL_SIZE;
|
|
ctx.beginPath();
|
|
ctx.moveTo(lineX, 0);
|
|
ctx.lineTo(lineX, GRID_HEIGHT * CELL_SIZE);
|
|
ctx.stroke();
|
|
}
|
|
|
|
for (let y = 0; y <= GRID_HEIGHT; y++) {
|
|
const lineY = y * CELL_SIZE;
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, lineY);
|
|
ctx.lineTo(GRID_WIDTH * CELL_SIZE, lineY);
|
|
ctx.stroke();
|
|
}
|
|
|
|
// 绘制已固定的方块
|
|
let fixedCount = 0;
|
|
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 = x * CELL_SIZE;
|
|
const rectY = 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);
|
|
|
|
fixedCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 绘制当前方块
|
|
if (currentPiece) {
|
|
const { shape, x, y, color } = currentPiece;
|
|
|
|
ctx.fillStyle = color;
|
|
|
|
let currentCount = 0;
|
|
for (let row = 0; row < shape.length; row++) {
|
|
for (let col = 0; col < shape[row].length; col++) {
|
|
if (shape[row][col]) {
|
|
const rectX = (x + col) * CELL_SIZE;
|
|
const rectY = (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);
|
|
|
|
currentCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
debugLog(`渲染: 固定方块=${fixedCount}, 当前方块=${currentCount}, 位置=(${x},${y})`);
|
|
}
|
|
}
|
|
|
|
// ==================== 游戏循环 ====================
|
|
function gameLoop(timestamp) {
|
|
if (!gameRunning) return;
|
|
|
|
// 自动下落
|
|
dropTimer += 0.016; // 假设每帧16ms
|
|
|
|
const dropInterval = 1.0 / (1.0 + level * 0.1); // 等级越高,下落越快
|
|
|
|
if (dropTimer > dropInterval) {
|
|
debugLog(`自动下落: dropTimer=${dropTimer.toFixed(3)} > dropInterval=${dropInterval.toFixed(3)}`);
|
|
movePiece(0, 1);
|
|
dropTimer = 0;
|
|
}
|
|
|
|
gameLoopId = requestAnimationFrame(gameLoop);
|
|
}
|
|
|
|
// ==================== 控制函数 ====================
|
|
window.startGame = function() {
|
|
if (gameRunning) return;
|
|
|
|
debugLog('🎮 开始游戏...');
|
|
|
|
// 重置游戏状态
|
|
board = Array(GRID_HEIGHT).fill().map(() => Array(GRID_WIDTH).fill(0));
|
|
score = 0;
|
|
level = 0;
|
|
lines = 0;
|
|
dropTimer = 0;
|
|
|
|
// 生成第一个方块
|
|
spawnNewPiece();
|
|
|
|
// 开始游戏
|
|
gameRunning = true;
|
|
|
|
updateUI();
|
|
renderGame();
|
|
|
|
// 启动游戏循环
|
|
gameLoopId = requestAnimationFrame(gameLoop);
|
|
};
|
|
|
|
window.moveLeft = function() {
|
|
movePiece(-1, 0);
|
|
};
|
|
|
|
window.moveRight = function() {
|
|
movePiece(1, 0);
|
|
};
|
|
|
|
window.moveDown = function() {
|
|
movePiece(0, 1);
|
|
};
|
|
|
|
// ==================== UI更新 ====================
|
|
function updateUI() {
|
|
document.getElementById('score').textContent = score;
|
|
document.getElementById('level').textContent = level;
|
|
document.getElementById('lines').textContent = lines;
|
|
}
|
|
|
|
// ==================== 键盘控制 ====================
|
|
document.addEventListener('keydown', (e) => {
|
|
if (!gameRunning) 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;
|
|
}
|
|
});
|
|
|
|
// ==================== 初始化 ====================
|
|
debugLog('游戏初始化完成');
|
|
debugLog('点击"开始游戏"按钮开始');
|
|
|
|
// 初始渲染
|
|
renderGame();
|
|
</script>
|
|
</body>
|
|
</html> |