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

704 lines
26 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>🎮 俄罗斯方块 | 简单修复版</title>
<link rel="icon" href="favicon.ico" type="image/x-icon">
<!-- 纯Canvas 2D版本,无PlayCanvas依赖 -->
<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: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
padding: 10px;
}
canvas {
display: block;
box-shadow: 0 0 40px rgba(0, 100, 255, 0.3);
border-radius: 8px;
background: #111827;
}
/* 虚拟控制按钮容器 */
.controls-container {
margin-top: 20px;
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
width: 100%;
}
.control-row {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
max-width: 500px;
}
.action-button {
padding: 12px 24px;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
gap: 8px;
min-width: 100px;
justify-content: center;
}
.action-button.start {
background: linear-gradient(145deg, #10b981 0%, #047857 100%);
color: white;
}
.action-button.restart {
background: linear-gradient(145deg, #f59e0b 0%, #d97706 100%);
color: white;
}
.control-button {
width: 65px;
height: 65px;
background: linear-gradient(145deg, #1e293b 0%, #0f172a 100%);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-size: 28px;
color: #fff;
cursor: pointer;
user-select: none;
border: 2px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
transition: all 0.1s ease;
}
.control-row:nth-child(2) {
justify-content: center;
gap: 5px;
}
.info-panel {
width: 100%;
max-width: 500px;
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
padding: 15px;
display: flex;
justify-content: space-around;
margin-top: 10px;
}
.info-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 5px;
}
.info-label {
color: #aaa;
font-size: 12px;
}
.info-value {
color: #fff;
font-weight: bold;
font-size: 20px;
}
.status-message {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 10px 20px;
border-radius: 5px;
font-size: 14px;
z-index: 1000;
display: none;
}
.loading {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: 18px;
text-align: center;
}
.debug-info {
position: fixed;
bottom: 10px;
left: 10px;
background: rgba(0, 0, 0, 0.8);
color: #68d391;
padding: 10px;
border-radius: 5px;
font-family: monospace;
font-size: 12px;
max-width: 300px;
max-height: 200px;
overflow-y: auto;
}
</style>
</head>
<body>
<div class="loading" id="loading">🎮 加载俄罗斯方块...</div>
<div class="status-message" id="status-message"></div>
<div id="game-container">
<canvas id="game-canvas" width="540" height="720"></canvas>
<div class="controls-container">
<!-- 第一行:开始/旋转/重来按钮 -->
<div class="control-row">
<button class="action-button start" id="start-btn">
<span>▶️</span> 开始
</button>
<div class="control-button rotate" id="rotate-btn">↻ 旋转</div>
<button class="action-button restart" id="restart-btn">
<span>🔄</span> 重来
</button>
</div>
<!-- 第二行:方向控制按钮 -->
<div class="control-row">
<div class="control-button left" id="left-btn">←</div>
<div class="control-button down" id="down-btn">↓</div>
<div class="control-button right" id="right-btn">→</div>
</div>
<!-- 游戏信息面板 -->
<div class="info-panel">
<div class="info-item">
<span class="info-label">分数</span>
<span class="info-value" id="score">0</span>
</div>
<div class="info-item">
<span class="info-label">等级</span>
<span class="info-value" id="level">0</span>
</div>
<div class="info-item">
<span class="info-label">消行</span>
<span class="info-value" id="lines">0</span>
</div>
<div class="info-item">
<span class="info-label">速度</span>
<span class="info-value" id="speed">1.0x</span>
</div>
</div>
</div>
</div>
<div class="debug-info" id="debug-info">
<div>调试信息:</div>
</div>
<script>
// ==================== 调试日志 ====================
function debugLog(message) {
const debugEl = document.getElementById('debug-info');
const logEntry = document.createElement('div');
logEntry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
debugEl.appendChild(logEntry);
debugEl.scrollTop = debugEl.scrollHeight;
console.log(message);
}
// ==================== 状态消息 ====================
function showStatus(message, duration = 2000) {
const statusEl = document.getElementById('status-message');
statusEl.textContent = message;
statusEl.style.display = 'block';
setTimeout(() => {
statusEl.style.display = 'none';
}, duration);
}
// ==================== 隐藏加载界面 ====================
function hideLoading() {
document.getElementById('loading').style.display = 'none';
}
// ==================== 俄罗斯方块游戏 ====================
class TetrisGame {
constructor() {
debugLog('🎮 创建TetrisGame实例');
// 获取画布
this.canvas = document.getElementById('game-canvas');
this.ctx = this.canvas.getContext('2d');
if (!this.canvas || !this.ctx) {
debugLog('❌ Canvas元素或上下文未找到');
showStatus('❌ Canvas初始化失败', 5000);
return;
}
debugLog('✅ Canvas初始化成功');
// 游戏配置
this.gridWidth = 10; // 10列
this.gridHeight = 20; // 20行
this.cellSize = 24; // 每个格子大小
// 游戏状态
this.board = [];
this.currentPiece = null;
this.score = 0;
this.level = 0;
this.lines = 0;
this.gameSpeed = 1.0;
this.isPaused = true;
this.isGameOver = false;
this.gameLoop = null;
this.dropTimer = 0;
this.lastFrameTime = 0;
// 方块类型定义
this.pieceTypes = [
{ 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' } // 红色
];
// 初始化游戏
this.initGame();
this.setupControls();
this.setupUI();
debugLog('✅ 游戏初始化完成');
showStatus('✅ 俄罗斯方块已加载,点击开始按钮或按任意键开始游戏', 3000);
hideLoading();
}
initGame() {
debugLog('初始化游戏逻辑');
// 清空游戏场地
this.board = Array(this.gridHeight).fill().map(() => Array(this.gridWidth).fill(0));
// 重置游戏状态
this.score = 0;
this.level = 0;
this.lines = 0;
this.gameSpeed = 1.0;
this.isPaused = false;
this.isGameOver = false;
this.dropTimer = 0;
this.lastFrameTime = 0;
// 生成第一个方块
this.spawnNewPiece();
// 开始游戏循环
this.startGameLoop();
// 更新UI
this.updateUI();
debugLog('🎮 游戏开始!');
}
spawnNewPiece() {
const randomType = this.pieceTypes[Math.floor(Math.random() * this.pieceTypes.length)];
this.currentPiece = {
type: randomType.type,
shape: randomType.shape,
color: randomType.color,
x: Math.floor(this.gridWidth / 2) - Math.floor(randomType.shape[0].length / 2),
y: 0
};
debugLog(`生成新方块: ${this.currentPiece.type}`);
}
startGameLoop() {
if (this.gameLoop) {
cancelAnimationFrame(this.gameLoop);
}
const gameLoop = (timestamp) => {
if (!this.lastFrameTime) this.lastFrameTime = timestamp;
const deltaTime = timestamp - this.lastFrameTime;
// 自动下落
if (!this.isPaused && !this.isGameOver) {
this.dropTimer += deltaTime;
if (this.dropTimer > 1000 / this.gameSpeed) {
this.movePiece(0, 1);
this.dropTimer = 0;
}
}
// 渲染
this.render();
// 继续循环
this.gameLoop = requestAnimationFrame(gameLoop);
this.lastFrameTime = timestamp;
};
this.gameLoop = requestAnimationFrame(gameLoop);
debugLog('✅ 游戏循环已启动');
}
movePiece(dx, dy) {
if (!this.currentPiece || this.isPaused || this.isGameOver) return;
const newX = this.currentPiece.x + dx;
const newY = this.currentPiece.y + dy;
if (this.isValidMove(this.currentPiece.shape, newX, newY)) {
this.currentPiece.x = newX;
this.currentPiece.y = newY;
} else if (dy > 0) {
// 固定方块
this.lockPiece();
this.checkLines();
this.spawnNewPiece();
// 检查游戏是否结束
if (!this.isValidMove(this.currentPiece.shape, this.currentPiece.x, this.currentPiece.y)) {
this.gameOver();
}
}
}
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 >= this.gridWidth || newY >= this.gridHeight) {
return false;
}
if (newY >= 0 && this.board[newY][newX]) {
return false;
}
}
}
}
return true;
}
lockPiece() {
const { shape, x, y, color } = this.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) {
this.board[boardY][boardX] = color;
}
}
}
}
debugLog(`方块固定在位置: (${x}, ${y})`);
}
checkLines() {
let linesCleared = 0;
for (let y = this.gridHeight - 1; y >= 0; y--) {
if (this.board[y].every(cell => cell !== 0)) {
// 移除该行
this.board.splice(y, 1);
// 在顶部添加新行
this.board.unshift(Array(this.gridWidth).fill(0));
linesCleared++;
y++; // 重新检查同一位置
}
}
if (linesCleared > 0) {
this.lines += linesCleared;
this.score += linesCleared * 100 * (this.level + 1);
this.level = Math.floor(this.lines / 10);
this.gameSpeed = 1.0 + this.level * 0.1;
debugLog(`消除 ${linesCleared} 行,得分: ${linesCleared * 100 * (this.level + 1)}`);
showStatus(`🎉 消除 ${linesCleared} 行!`, 1500);
}
}
rotatePiece() {
if (!this.currentPiece || this.isPaused || this.isGameOver) 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(this.currentPiece.shape);
if (this.isValidMove(rotated, this.currentPiece.x, this.currentPiece.y)) {
this.currentPiece.shape = rotated;
debugLog('旋转方块');
}
}
gameOver() {
this.isGameOver = true;
if (this.gameLoop) {
cancelAnimationFrame(this.gameLoop);
this.gameLoop = null;
}
debugLog(`游戏结束!最终得分: ${this.score}`);
showStatus(`🎮 游戏结束!最终得分: ${this.score}`, 5000);
}
togglePause() {
this.isPaused = !this.isPaused;
if (this.isPaused) {
debugLog('游戏暂停');
showStatus('⏸️ 游戏暂停');
} else {
debugLog('游戏继续');
showStatus('▶️ 游戏继续');
this.lastFrameTime = performance.now();
}
this.updateUI();
}
render() {
// 清空画布
this.ctx.fillStyle = '#111827';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
// 绘制网格
this.ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
this.ctx.lineWidth = 1;
for (let x = 0; x <= this.gridWidth; x++) {
this.ctx.beginPath();
this.ctx.moveTo(x * this.cellSize, 0);
this.ctx.lineTo(x * this.cellSize, this.canvas.height);
this.ctx.stroke();
}
for (let y = 0; y <= this.gridHeight; y++) {
this.ctx.beginPath();
this.ctx.moveTo(0, y * this.cellSize);
this.ctx.lineTo(this.canvas.width, y * this.cellSize);
this.ctx.stroke();
}
// 绘制已固定的方块
for (let y = 0; y < this.gridHeight; y++) {
for (let x = 0; x < this.gridWidth; x++) {
if (this.board[y][x]) {
this.ctx.fillStyle = this.board[y][x];
this.ctx.fillRect(x * this.cellSize, y * this.cellSize, this.cellSize, this.cellSize);
this.ctx.strokeStyle = 'white';
this.ctx.lineWidth = 2;
this.ctx.strokeRect(x * this.cellSize, y * this.cellSize, this.cellSize, this.cellSize);
}
}
}
// 绘制当前方块
if (this.currentPiece) {
const { shape, x, y, color } = this.currentPiece;
this.ctx.fillStyle = color;
for (let row = 0; row < shape.length; row++) {
for (let col = 0; col < shape[row].length; col++) {
if (shape[row][col]) {
this.ctx.fillRect((x + col) * this.cellSize, (y + row) * this.cellSize, this.cellSize, this.cellSize);
this.ctx.strokeStyle = 'white';
this.ctx.lineWidth = 2;
this.ctx.strokeRect((x + col) * this.cellSize, (y + row) * this.cellSize, this.cellSize, this.cellSize);
}
}
}
}
}
updateUI() {
document.getElementById('score').textContent = this.score;
document.getElementById('level').textContent = this.level;
document.getElementById('lines').textContent = this.lines;
document.getElementById('speed').textContent = this.gameSpeed.toFixed(1) + 'x';
const startBtn = document.getElementById('start-btn');
if (!this.currentPiece && !this.isGameOver) {
startBtn.innerHTML = '<span>▶️</span> 开始';
startBtn.className = 'action-button start';
} else if (this.isPaused) {
startBtn.innerHTML = '<span>▶️</span> 继续';
startBtn.className = 'action-button start';
} else if (this.isGameOver) {
startBtn.innerHTML = '<span>🔄</span> 重新开始';
startBtn.className = 'action-button restart';
} else {
startBtn.innerHTML = '<span>⏸️</span> 暂停';
startBtn.className = 'action-button start';
}
}
setupControls() {
debugLog('设置控件事件...');
// 开始按钮
document.getElementById('start-btn').addEventListener('click', () => {
debugLog('开始按钮点击');
if (!this.currentPiece && !this.isGameOver) {
this.initGame();
} else if (this.isPaused) {
this.togglePause();
} else if (this.isGameOver) {
this.initGame();
} else {
this.togglePause();
}
});
// 重来按钮
document.getElementById('restart-btn').addEventListener('click', () => {
debugLog('重来按钮点击');
this.initGame();
});
// 方向按钮
document.getElementById('left-btn').addEventListener('click', () => {
debugLog('左按钮点击');
this.movePiece(-1, 0);
});
document.getElementById('right-btn').addEventListener('click', () => {
debugLog('右按钮点击');
this.movePiece(1, 0);
});
document.getElementById('down-btn').addEventListener('click', () => {
debugLog('下按钮点击');
this.movePiece(0, 1);
});
document.getElementById('rotate-btn').addEventListener('click', () => {
debugLog('旋转按钮点击');
this.rotatePiece();
});
// 键盘控制
document.addEventListener('keydown', (e) => {
debugLog(`键盘按键: ${e.key}`);
// 如果游戏还没开始,按任意键开始游戏
if (!this.currentPiece && !this.isGameOver) {
this.initGame();
return;
}
if (this.isPaused || this.isGameOver) return;
switch(e.key) {
case 'ArrowLeft':
this.movePiece(-1, 0);
break;
case 'ArrowRight':
this.movePiece(1, 0);
break;
case 'ArrowDown':
this.movePiece(0, 1);
break;
case 'ArrowUp':
case ' ':
this.rotatePiece();
break;
case 'p':
case 'P':
this.togglePause();
break;
}
});
debugLog('控件事件设置完成');
}
setupUI() {
// 添加按钮按下效果
document.querySelectorAll('.action-button, .control-button').forEach(btn => {
btn.addEventListener('mousedown', () => {
btn.style.transform = 'scale(0.95)';
});
btn.addEventListener('mouseup', () => {
btn.style.transform = 'scale(1)';
});
btn.addEventListener('mouseleave', () => {
btn.style.transform = 'scale(1)';
});
});
}
}
// ==================== 页面加载完成 ====================
window.addEventListener('load', () => {
debugLog('页面加载完成');
debugLog(`浏览器: ${navigator.userAgent}`);
debugLog(`平台: ${navigator.platform}`);
// 创建游戏实例
window.game = new TetrisGame();
});
// ==================== 错误处理 ====================
window.addEventListener('error', (e) => {
console.error('全局错误:', e);
debugLog(`错误: ${e.message}`);
showStatus(`❌ JavaScript错误: ${e.message}`, 5000);
});
</script>
</body>
</html>