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

690 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>🎮 俄罗斯方块 | 纯PlayCanvas版</title>
<link rel="icon" href="favicon.ico" type="image/x-icon">
<!-- PlayCanvas 1.66.0 - 和贪吃蛇完全一样 -->
<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;
}
#application {
width: 100vw;
height: 100vh;
display: block;
}
/* UI层 */
#ui {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 100;
}
#score {
position: absolute;
top: 20px;
left: 20px;
color: white;
font-size: 24px;
font-weight: bold;
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
}
#level {
position: absolute;
top: 50px;
left: 20px;
color: #aaa;
font-size: 14px;
}
#lines {
position: absolute;
top: 70px;
left: 20px;
color: #aaa;
font-size: 14px;
}
#menu {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
pointer-events: auto;
}
#menu h1 {
color: #00d4ff;
font-size: 48px;
margin-bottom: 20px;
text-shadow: 0 0 20px rgba(0,212,255,0.5);
}
.btn {
padding: 15px 40px;
font-size: 20px;
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
border: none;
border-radius: 30px;
cursor: pointer;
margin: 10px;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn:hover {
transform: scale(1.05);
box-shadow: 0 10px 30px rgba(102,126,234,0.4);
}
#gameOver {
display: none;
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;
pointer-events: auto;
}
#gameOver h2 {
color: #ff6b6b;
font-size: 36px;
margin-bottom: 20px;
}
#finalScore {
color: white;
font-size: 24px;
margin-bottom: 30px;
}
/* 虚拟控制按钮 */
.control-button {
position: absolute;
width: 80px;
height: 80px;
background: rgba(255, 255, 255, 0.1);
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
color: white;
font-size: 32px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
pointer-events: auto;
user-select: none;
transition: all 0.1s;
}
.control-button:active {
background: rgba(255, 255, 255, 0.2);
transform: scale(0.95);
}
#left-btn {
bottom: 100px;
left: 50px;
}
#right-btn {
bottom: 100px;
right: 50px;
}
#down-btn {
bottom: 100px;
left: 50%;
transform: translateX(-50%);
}
#rotate-btn {
bottom: 200px;
left: 50%;
transform: translateX(-50%);
font-size: 24px;
}
#start-btn {
bottom: 300px;
left: 50%;
transform: translateX(-50%);
width: 120px;
height: 50px;
border-radius: 25px;
font-size: 18px;
background: linear-gradient(135deg, #10b981, #047857);
}
#restart-btn {
bottom: 370px;
left: 50%;
transform: translateX(-50%);
width: 120px;
height: 50px;
border-radius: 25px;
font-size: 18px;
background: linear-gradient(135deg, #f59e0b, #d97706);
}
/* 移动端适配 */
@media (max-width: 768px) {
.control-button {
width: 70px;
height: 70px;
font-size: 28px;
}
#left-btn {
bottom: 80px;
left: 30px;
}
#right-btn {
bottom: 80px;
right: 30px;
}
#down-btn {
bottom: 80px;
}
#rotate-btn {
bottom: 170px;
}
#start-btn, #restart-btn {
width: 100px;
height: 45px;
font-size: 16px;
}
}
</style>
</head>
<body>
<canvas id="application" style="display:block;width:100%;height:100%;"></canvas>
<div id="ui">
<div id="score">分数: 0</div>
<div id="level">等级: 0</div>
<div id="lines">消行: 0</div>
<div id="menu">
<h1>🎮 俄罗斯方块</h1>
<button class="btn" onclick="startGame()">开始游戏</button>
<div style="margin-top: 30px; padding: 20px; background: rgba(0,0,0,0.6); border-radius: 15px; max-width: 320px; text-align: left;">
<h3 style="color: #00d4ff; margin-bottom: 15px; text-align: center;">📜 游戏规则</h3>
<div style="color: #ccc; font-size: 14px; line-height: 1.8;">
<p style="margin-bottom: 8px;"><span style="color: #4ecdc4;">🎮 控制:</span> 键盘方向键或虚拟按钮</p>
<p style="margin-bottom: 8px;"><span style="color: #ffeaa7;">⬆️ 旋转:</span> 上箭头或旋转按钮</p>
<p style="margin-bottom: 8px;"><span style="color: #ff6b6b;">⚠️ 目标:</span> 填满整行消除得分</p>
<p style="margin-bottom: 8px;"><span style="color: #fd79a8;">🎯 技巧:</span> 一次消除多行得分更高</p>
<p style="margin-bottom: 0;"><span style="color: #a29bfe;">⭐ 提示:</span> 等级越高,方块下落越快!</p>
</div>
</div>
</div>
<div id="gameOver">
<h2>游戏结束</h2>
<div id="finalScore">最终分数: 0</div>
<button class="btn" onclick="restartGame()">重新开始</button>
</div>
<!-- 虚拟控制按钮 -->
<div class="control-button" id="left-btn">←</div>
<div class="control-button" id="right-btn">→</div>
<div class="control-button" id="down-btn">↓</div>
<div class="control-button" id="rotate-btn">↻ 旋转</div>
<div class="control-button" id="start-btn">▶️ 开始</div>
<div class="control-button" id="restart-btn">🔄 重来</div>
</div>
<script>
// ==================== 调试日志 ====================
function debugLog(message) {
console.log(`[${new Date().toLocaleTimeString()}] ${message}`);
}
// ==================== 游戏变量 ====================
let app = null;
let gameRunning = false;
let gamePaused = false;
let gameOver = false;
let score = 0;
let level = 0;
let lines = 0;
let dropTimer = 0;
let lastFrameTime = 0;
// 游戏场地
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;
// 方块类型定义
const PIECE_TYPES = [
{ type: 'I', shape: [[1,1,1,1]], color: new pc.Color(0, 0.96, 1) }, // 青色
{ type: 'J', shape: [[1,0,0],[1,1,1]], color: new pc.Color(0, 0, 1) }, // 蓝色
{ type: 'L', shape: [[0,0,1],[1,1,1]], color: new pc.Color(1, 0.5, 0) }, // 橙色
{ type: 'O', shape: [[1,1],[1,1]], color: new pc.Color(1, 1, 0) }, // 黄色
{ type: 'S', shape: [[0,1,1],[1,1,0]], color: new pc.Color(0, 1, 0) }, // 绿色
{ type: 'T', shape: [[0,1,0],[1,1,1]], color: new pc.Color(0.5, 0, 1) }, // 紫色
{ type: 'Z', shape: [[1,1,0],[0,1,1]], color: new pc.Color(1, 0, 0) } // 红色
];
// ==================== UI更新 ====================
function updateUI() {
document.getElementById('score').textContent = `分数: ${score}`;
document.getElementById('level').textContent = `等级: ${level}`;
document.getElementById('lines').textContent = `消行: ${lines}`;
}
// ==================== 游戏逻辑 ====================
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}`);
}
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;
} else if (dy > 0) {
// 固定方块
lockPiece();
checkLines();
spawnNewPiece();
// 检查游戏是否结束
if (!isValidMove(currentPiece.shape, currentPiece.x, currentPiece.y)) {
endGame();
}
}
}
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 || 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;
debugLog('旋转方块');
}
}
function endGame() {
gameOver = true;
gameRunning = false;
document.getElementById('finalScore').textContent = `最终分数: ${score}`;
document.getElementById('gameOver').style.display = 'block';
debugLog(`游戏结束!最终得分: ${score}`);
}
function restartGame() {
// 重置游戏状态
board = Array(GRID_HEIGHT).fill().map(() => Array(GRID_WIDTH).fill(0));
score = 0;
level = 0;
lines = 0;
gameOver = false;
gamePaused = false;
dropTimer = 0;
lastFrameTime = 0;
// 生成第一个方块
spawnNewPiece();
// 隐藏游戏结束界面
document.getElementById('gameOver').style.display = 'none';
document.getElementById('menu').style.display = 'none';
// 开始游戏
gameRunning = true;
updateUI();
debugLog('🎮 游戏重新开始!');
}
// ==================== 渲染函数 ====================
function renderGame() {
if (!app || !gameRunning) return;
// 清空画布
app.graphicsDevice.clear({
color: [0.04, 0.04, 0.1, 1], // 深蓝色背景
depth: 1,
flags: pc.CLEARFLAG_COLOR_DEPTH
});
// 计算游戏区域位置(居中)
const canvasWidth = app.graphicsDevice.width;
const canvasHeight = app.graphicsDevice.height;
const gameWidth = GRID_WIDTH * CELL_SIZE;
const gameHeight = GRID_HEIGHT * CELL_SIZE;
const offsetX = (canvasWidth - gameWidth) / 2;
const offsetY = (canvasHeight - gameHeight) / 2;
// 绘制网格背景
app.graphicsDevice.setColor(0.07, 0.07, 0.15); // 深灰色网格背景
app.graphicsDevice.fillRect(offsetX, offsetY, gameWidth, gameHeight);
// 绘制网格线
app.graphicsDevice.setColor(1, 1, 1, 0.1);
for (let x = 0; x <= GRID_WIDTH; x++) {
const lineX = offsetX + x * CELL_SIZE;
app.graphicsDevice.drawLine(lineX, offsetY, lineX, offsetY + gameHeight);
}
for (let y = 0; y <= GRID_HEIGHT; y++) {
const lineY = offsetY + y * CELL_SIZE;
app.graphicsDevice.drawLine(offsetX, lineY, offsetX + gameWidth, lineY);
}
// 绘制已固定的方块
for (let y = 0; y < GRID_HEIGHT; y++) {
for (let x = 0; x < GRID_WIDTH; x++) {
if (board[y][x]) {
const color = board[y][x];
app.graphicsDevice.setColor(color.r, color.g, color.b);
const rectX = offsetX + x * CELL_SIZE;
const rectY = offsetY + y * CELL_SIZE;
// 绘制方块
app.graphicsDevice.fillRect(rectX, rectY, CELL_SIZE, CELL_SIZE);
// 绘制边框
app.graphicsDevice.setColor(1, 1, 1, 0.5);
app.graphicsDevice.drawRect(rectX, rectY, CELL_SIZE, CELL_SIZE);
}
}
}
// 绘制当前方块
if (currentPiece) {
const { shape, x, y, color } = currentPiece;
app.graphicsDevice.setColor(color.r, color.g, color.b);
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;
// 绘制方块
app.graphicsDevice.fillRect(rectX, rectY, CELL_SIZE, CELL_SIZE);
// 绘制边框
app.graphicsDevice.setColor(1, 1, 1, 0.8);
app.graphicsDevice.drawRect(rectX, rectY, CELL_SIZE, CELL_SIZE);
}
}
}
}
}
// ==================== 游戏循环 ====================
function gameUpdate(dt) {
if (!gameRunning || gamePaused || gameOver) return;
// 自动下落
dropTimer += dt;
const dropInterval = 1.0 / (1.0 + level * 0.1); // 等级越高,下落越快
if (dropTimer > dropInterval) {
movePiece(0, 1);
dropTimer = 0;
}
// 渲染游戏
renderGame();
}
// ==================== 初始化游戏 ====================
function initGame() {
debugLog('🎮 初始化 PlayCanvas...');
const canvas = document.getElementById('application');
if (!canvas) {
debugLog('❌ Canvas 元素未找到!');
alert('游戏加载失败:Canvas 未找到');
return;
}
try {
app = new pc.Application(canvas, {
mouse: new pc.Mouse(canvas),
keyboard: new pc.Keyboard(window),
touch: new pc.TouchDevice(canvas), // 移动端支持
elementInput: new pc.ElementInput(canvas)
});
debugLog('✅ PlayCanvas 应用创建成功');
} catch (e) {
debugLog('❌ PlayCanvas 初始化失败:', e);
alert('游戏引擎初始化失败: ' + e.message);
throw e;
}
app.start();
debugLog('✅ PlayCanvas 已启动');
app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW);
app.setCanvasResolution(pc.RESOLUTION_AUTO);
window.addEventListener('resize', () => app.resizeCanvas());
// 设置游戏循环
app.on('update', gameUpdate);
// 初始化游戏状态
restartGame();
debugLog('✅ 游戏初始化完成');
}
// ==================== 控制函数 ====================
window.startGame = function() {
debugLog('🎮 开始游戏...');
document.getElementById('menu').style.display = 'none';
gameRunning = true;
gamePaused = false;
};
function togglePause() {
if (gameOver) return;
gamePaused = !gamePaused;
debugLog(gamePaused ? '⏸️ 游戏暂停' : '▶️ 游戏继续');
}
// ==================== 设置控件事件 ====================
function setupControls() {
debugLog('设置控件事件...');
// 虚拟按钮
document.getElementById('start-btn').addEventListener('click', () => {
if (!gameRunning) {
startGame();
} else {
togglePause();
}
});
document.getElementById('restart-btn').addEventListener('click', restartGame);
document.getElementById('left-btn').addEventListener('click', () => movePiece(-1, 0));
document.getElementById('right-btn').addEventListener('click', () => movePiece(1, 0));
document.getElementById('down-btn').addEventListener('click', () => movePiece(0, 1));
document.getElementById('rotate-btn').addEventListener('click', rotatePiece);
// 键盘控制
document.addEventListener('keydown', (e) => {
debugLog(`键盘按键: ${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;
}
});
debugLog('控件事件设置完成');
}
// ==================== 页面加载完成 ====================
window.addEventListener('load', () => {
debugLog('页面加载完成');
debugLog(`浏览器: ${navigator.userAgent}`);
debugLog(`平台: ${navigator.platform}`);
// 设置控件
setupControls();
// 初始化游戏
initGame();
});
// ==================== 错误处理 ====================
window.addEventListener('error', (e) => {
console.error('全局错误:', e);
debugLog(`错误: ${e.message}`);
alert(`❌ JavaScript错误: ${e.message}`);
});
</script>
</body>
</html>