667 lines
24 KiB
HTML
667 lines
24 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">
|
|
|
|
<!-- 使用贪吃蛇相同的PlayCanvas版本 -->
|
|
<script src="https://code.playcanvas.com/playcanvas-1.66.0.min.js"></script>
|
|
|
|
<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;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.loading {
|
|
position: fixed;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
color: white;
|
|
font-size: 18px;
|
|
text-align: center;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="loading" id="loading">🎮 加载俄罗斯方块...</div>
|
|
<div class="status-message" id="status-message"></div>
|
|
|
|
<div id="game-container">
|
|
<canvas id="application" 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实例');
|
|
|
|
// 画布尺寸 (540x720 竖屏)
|
|
this.width = 540;
|
|
this.height = 720;
|
|
|
|
// 获取画布
|
|
this.canvas = document.getElementById('application');
|
|
if (!this.canvas) {
|
|
debugLog('❌ Canvas元素未找到');
|
|
showStatus('❌ Canvas元素未找到', 5000);
|
|
return;
|
|
}
|
|
|
|
debugLog('✅ Canvas元素找到');
|
|
|
|
// 初始化 PlayCanvas
|
|
try {
|
|
this.app = new pc.Application(this.canvas, {
|
|
mouse: new pc.Mouse(this.canvas),
|
|
touch: new pc.TouchDevice(this.canvas),
|
|
elementInput: new pc.ElementInput(this.canvas)
|
|
});
|
|
debugLog('✅ PlayCanvas 应用创建成功');
|
|
} catch (e) {
|
|
debugLog(`❌ PlayCanvas 初始化失败: ${e.message}`);
|
|
showStatus(`❌ 游戏引擎初始化失败: ${e.message}`, 5000);
|
|
return;
|
|
}
|
|
|
|
// 启动PlayCanvas
|
|
try {
|
|
this.app.start();
|
|
debugLog('✅ PlayCanvas 已启动');
|
|
} catch (e) {
|
|
debugLog(`❌ PlayCanvas 启动失败: ${e.message}`);
|
|
showStatus(`❌ 游戏引擎启动失败: ${e.message}`, 5000);
|
|
return;
|
|
}
|
|
|
|
// 设置画布填充模式(使用贪吃蛇相同的设置)
|
|
this.app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW);
|
|
this.app.setCanvasResolution(pc.RESOLUTION_AUTO);
|
|
|
|
debugLog('✅ 画布设置完成');
|
|
|
|
// 游戏配置
|
|
this.gridWidth = 10; // 10列
|
|
this.gridHeight = 20; // 20行
|
|
this.cellSize = 24; // 每个格子大小
|
|
|
|
// 游戏状态
|
|
this.board = [];
|
|
this.currentPiece = null;
|
|
this.nextPiece = null;
|
|
this.score = 0;
|
|
this.level = 0;
|
|
this.lines = 0;
|
|
this.gameSpeed = 1.0;
|
|
this.isPaused = true;
|
|
this.isGameOver = false;
|
|
this.gameLoop = null;
|
|
this.lastDropTime = 0;
|
|
|
|
// 方块类型定义(简化版)
|
|
this.pieceTypes = ['I', 'O', 'T', 'L', 'J', 'S', 'Z'];
|
|
|
|
// 延迟初始化游戏逻辑
|
|
setTimeout(() => {
|
|
this.initGame();
|
|
this.setupControls();
|
|
this.setupUI();
|
|
debugLog('✅ 游戏初始化完成');
|
|
showStatus('✅ 俄罗斯方块已加载,点击开始按钮或按任意键开始游戏', 3000);
|
|
}, 500);
|
|
}
|
|
|
|
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.lastDropTime = 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,
|
|
x: Math.floor(this.gridWidth / 2) - 1,
|
|
y: 0
|
|
};
|
|
debugLog(`生成新方块: ${this.currentPiece.type}`);
|
|
}
|
|
|
|
startGameLoop() {
|
|
if (this.gameLoop) {
|
|
cancelAnimationFrame(this.gameLoop);
|
|
}
|
|
|
|
const gameLoop = (timestamp) => {
|
|
if (!this.lastDropTime) this.lastDropTime = timestamp;
|
|
|
|
const deltaTime = timestamp - this.lastDropTime;
|
|
|
|
// 自动下落
|
|
if (!this.isPaused && !this.isGameOver && deltaTime > 1000 / this.gameSpeed) {
|
|
this.movePiece(0, 1);
|
|
this.lastDropTime = timestamp;
|
|
}
|
|
|
|
// 渲染
|
|
this.render();
|
|
|
|
// 继续循环
|
|
this.gameLoop = requestAnimationFrame(gameLoop);
|
|
};
|
|
|
|
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(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.x, this.currentPiece.y)) {
|
|
this.gameOver();
|
|
}
|
|
}
|
|
}
|
|
|
|
isValidMove(x, y) {
|
|
// 简单边界检查
|
|
if (x < 0 || x >= this.gridWidth || y >= this.gridHeight) {
|
|
return false;
|
|
}
|
|
|
|
// 检查是否与已有方块重叠
|
|
if (y >= 0 && this.board[y] && this.board[y][x]) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
lockPiece() {
|
|
if (!this.currentPiece) return;
|
|
|
|
const { x, y, type } = this.currentPiece;
|
|
|
|
// 简单固定逻辑
|
|
if (y >= 0) {
|
|
this.board[y] = this.board[y] || [];
|
|
this.board[y][x] = type;
|
|
}
|
|
|
|
debugLog(`方块固定在位置: (${x}, ${y})`);
|
|
}
|
|
|
|
checkLines() {
|
|
let linesCleared = 0;
|
|
|
|
for (let y = this.gridHeight - 1; y >= 0; y--) {
|
|
if (this.board[y] && 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;
|
|
|
|
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.lastDropTime = performance.now();
|
|
}
|
|
|
|
this.updateUI();
|
|
}
|
|
|
|
render() {
|
|
// 清空画布
|
|
const ctx = this.app.graphicsDevice.gl;
|
|
if (!ctx) return;
|
|
|
|
// 设置背景色
|
|
ctx.clearColor(0.1, 0.1, 0.15, 1.0);
|
|
ctx.clear(ctx.COLOR_BUFFER_BIT);
|
|
|
|
// 简单渲染逻辑
|
|
// 实际游戏中需要更复杂的3D渲染
|
|
}
|
|
|
|
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}`);
|
|
|
|
// 检查PlayCanvas是否加载
|
|
if (typeof pc === 'undefined') {
|
|
debugLog('❌ PlayCanvas 未加载');
|
|
showStatus('❌ 游戏引擎加载失败,请刷新页面', 5000);
|
|
return;
|
|
}
|
|
|
|
debugLog('✅ PlayCanvas 已加载');
|
|
|
|
// 创建游戏实例
|
|
window.game = new TetrisGame();
|
|
|
|
// 隐藏加载界面
|
|
hideLoading();
|
|
});
|
|
|
|
// 错误处理
|
|
window.addEventListener('error', (e) => {
|
|
console.error('全局错误:', e);
|
|
debugLog(`错误: ${e.message}`);
|
|
showStatus(`❌ JavaScript错误: ${e.message}`, 5000);
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |