422 lines
15 KiB
HTML
422 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 { font-family: Arial; background: #333; color: white; display: flex; flex-direction: column; align-items: center; padding: 20px; }
|
|
.container { max-width: 800px; width: 100%; text-align: center; }
|
|
h1 { margin: 20px 0; color: #ffd700; }
|
|
.game-area { display: flex; justify-content: center; gap: 20px; margin: 20px 0; }
|
|
canvas { background: #000; border: 2px solid #666; }
|
|
.info { background: #222; padding: 15px; border-radius: 10px; }
|
|
.controls { margin: 20px 0; }
|
|
button { background: #4CAF50; color: white; border: none; padding: 10px 20px; margin: 5px; border-radius: 5px; cursor: pointer; font-size: 16px; }
|
|
button:hover { background: #45a049; }
|
|
.log { background: #111; padding: 10px; border-radius: 5px; margin-top: 20px; max-height: 200px; overflow-y: auto; text-align: left; font-family: monospace; font-size: 12px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>🎮 俄罗斯方块 - 简单循环版</h1>
|
|
<div class="game-area">
|
|
<canvas id="game-canvas" width="300" height="600"></canvas>
|
|
<div class="info">
|
|
<div>分数: <span id="score">0</span></div>
|
|
<div>等级: <span id="level">1</span></div>
|
|
<div>消行: <span id="lines">0</span></div>
|
|
<div>下一个: <span id="next-piece">?</span></div>
|
|
<div class="controls">
|
|
<button id="start-btn" onclick="startGame()">开始游戏</button>
|
|
<button onclick="togglePause()">暂停</button>
|
|
<button onclick="restartGame()">重来</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="log" id="log"></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 lastUpdate = 0;
|
|
let gameLoopId = null;
|
|
|
|
// 日志函数
|
|
function log(msg) {
|
|
const logDiv = document.getElementById('log');
|
|
logDiv.innerHTML += `<div>[${new Date().toLocaleTimeString()}] ${msg}</div>`;
|
|
logDiv.scrollTop = logDiv.scrollHeight;
|
|
console.log(msg);
|
|
}
|
|
|
|
// 初始化
|
|
function initGame() {
|
|
log('🎮 初始化游戏...');
|
|
|
|
canvas = document.getElementById('game-canvas');
|
|
ctx = canvas.getContext('2d');
|
|
|
|
if (!ctx) {
|
|
log('❌ 无法获取Canvas上下文');
|
|
return;
|
|
}
|
|
|
|
log('✅ Canvas上下文获取成功');
|
|
|
|
// 初始化游戏板
|
|
initBoard();
|
|
|
|
// 生成预览方块
|
|
generateNextPiece();
|
|
|
|
// 初始渲染
|
|
renderGame();
|
|
|
|
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,
|
|
y: nextPiece.y
|
|
};
|
|
}
|
|
|
|
generateNextPiece();
|
|
|
|
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;
|
|
|
|
const newX = currentPiece.x + dx;
|
|
const newY = currentPiece.y + dy;
|
|
|
|
if (isValidMove(currentPiece.shape, newX, newY)) {
|
|
currentPiece.x = newX;
|
|
currentPiece.y = newY;
|
|
log(`移动方块到: (${newX}, ${newY})`);
|
|
renderGame();
|
|
return true;
|
|
} else if (dy > 0) {
|
|
lockPiece();
|
|
checkLines();
|
|
spawnNewPiece();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function lockPiece() {
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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();
|
|
log(`消除 ${linesCleared} 行,获得 ${points} 分`);
|
|
}
|
|
}
|
|
|
|
// 游戏循环 - 使用setInterval确保兼容性
|
|
function gameLoop() {
|
|
if (!gameRunning || gameOver || gamePaused) return;
|
|
|
|
const now = Date.now();
|
|
const deltaTime = (now - lastUpdate) / 1000;
|
|
|
|
// 每0.5秒下落一次(测试用)
|
|
if (deltaTime > 0.5) {
|
|
log(`[游戏循环] 自动下落,deltaTime: ${deltaTime.toFixed(3)}`);
|
|
const moved = movePiece(0, 1);
|
|
log(`移动结果: ${moved ? '成功' : '失败'}`);
|
|
lastUpdate = now;
|
|
}
|
|
}
|
|
|
|
// 渲染
|
|
function renderGame() {
|
|
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;
|
|
|
|
// 绘制已固定的方块
|
|
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];
|
|
ctx.fillRect(
|
|
offsetX + x * CELL_SIZE,
|
|
offsetY + y * CELL_SIZE,
|
|
CELL_SIZE,
|
|
CELL_SIZE
|
|
);
|
|
|
|
ctx.strokeStyle = 'white';
|
|
ctx.strokeRect(
|
|
offsetX + x * CELL_SIZE,
|
|
offsetY + y * CELL_SIZE,
|
|
CELL_SIZE,
|
|
CELL_SIZE
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 绘制当前方块
|
|
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]) {
|
|
ctx.fillRect(
|
|
offsetX + (currentPiece.x + col) * CELL_SIZE,
|
|
offsetY + (currentPiece.y + row) * CELL_SIZE,
|
|
CELL_SIZE,
|
|
CELL_SIZE
|
|
);
|
|
|
|
ctx.strokeStyle = 'white';
|
|
ctx.strokeRect(
|
|
offsetX + (currentPiece.x + col) * CELL_SIZE,
|
|
offsetY + (currentPiece.y + row) * CELL_SIZE,
|
|
CELL_SIZE,
|
|
CELL_SIZE
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 游戏控制
|
|
window.startGame = function() {
|
|
if (gameRunning) return;
|
|
|
|
log('🎮 开始游戏...');
|
|
|
|
gameRunning = true;
|
|
gamePaused = false;
|
|
gameOver = false;
|
|
score = 0;
|
|
level = 1;
|
|
lines = 0;
|
|
lastUpdate = Date.now();
|
|
|
|
initBoard();
|
|
spawnNewPiece();
|
|
updateUI();
|
|
|
|
// 使用setInterval确保兼容性
|
|
if (gameLoopId) clearInterval(gameLoopId);
|
|
gameLoopId = setInterval(gameLoop, 1000 / 60); // 60 FPS
|
|
|
|
document.getElementById('start-btn').textContent = '暂停';
|
|
|
|
log('✅ 游戏已启动,使用setInterval游戏循环');
|
|
};
|
|
|
|
window.togglePause = function() {
|
|
if (gameOver) return;
|
|
|
|
gamePaused = !gamePaused;
|
|
log(gamePaused ? '⏸️ 游戏暂停' : '▶️ 游戏继续');
|
|
|
|
document.getElementById('start-btn').textContent = gamePaused ? '继续' : '暂停';
|
|
};
|
|
|
|
window.restartGame = function() {
|
|
log('🔄 重新开始游戏...');
|
|
|
|
if (gameLoopId) {
|
|
clearInterval(gameLoopId);
|
|
gameLoopId = null;
|
|
}
|
|
|
|
startGame();
|
|
};
|
|
|
|
function endGame() {
|
|
gameOver = true;
|
|
gameRunning = false;
|
|
|
|
if (gameLoopId) {
|
|
clearInterval(gameLoopId);
|
|
gameLoopId = null;
|
|
}
|
|
|
|
log(`💀 游戏结束,最终分数: ${score}`);
|
|
}
|
|
|
|
function updateUI() {
|
|
document.getElementById('score').textContent = score;
|
|
document.getElementById('level').textContent = level;
|
|
document.getElementById('lines').textContent = lines;
|
|
}
|
|
|
|
// 键盘控制
|
|
document.addEventListener('keydown', function(event) {
|
|
if (!gameRunning || gameOver) return;
|
|
|
|
switch(event.key) {
|
|
case 'ArrowLeft': movePiece(-1, 0); break;
|
|
case 'ArrowRight': movePiece(1, 0); break;
|
|
case 'ArrowDown': movePiece(0, 1); break;
|
|
case ' ':
|
|
case 'ArrowUp':
|
|
// 旋转
|
|
if (!currentPiece) 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;
|
|
log('旋转方块');
|
|
renderGame();
|
|
}
|
|
break;
|
|
case 'p':
|
|
case 'P': togglePause(); break;
|
|
}
|
|
});
|
|
|
|
// 页面加载
|
|
window.addEventListener('load', function() {
|
|
log('📄 页面加载完成');
|
|
initGame();
|
|
log('🪟 Windows兼容性: 使用setInterval确保兼容性');
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |