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

972 lines
35 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="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎮</text></svg>">
<!-- crypto.randomUUID 兼容性修复 -->
<script>
if (!crypto.randomUUID) {
crypto.randomUUID = function() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
};
}
</script>
<!-- Poki SDK -->
<script src="https://game-cdn.poki.com/scripts/v2/poki-sdk.js" async></script>
<!-- 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;
-webkit-tap-highlight-color: transparent;
user-select: none;
}
body {
overflow: hidden;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
touch-action: none;
margin: 0;
padding: 0;
}
/* 手机端:上下布局,按钮在棋盘正下方 */
#game-container {
display: none;
}
#application {
width: 100vw;
height: 100vh;
display: block;
}
#ui {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 100;
}
/* PC宽屏布局 - 使用固定位置避免重叠 */
#game-layout {
display: none; /* PC端不使用这个容器 */
}
/* PC端:分数面板在左侧 */
#score-panel {
position: fixed;
left: 30px;
top: 50%;
transform: translateY(-50%);
color: white;
font-size: 20px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
background: rgba(0,0,0,0.6);
padding: 25px;
border-radius: 15px;
min-width: 160px;
z-index: 101;
}
#score-panel div {
margin-bottom: 15px;
}
#score-panel div:last-child {
margin-bottom: 0;
}
.label {
color: #aaa;
font-size: 14px;
display: block;
}
.value {
color: #ffd700;
font-weight: bold;
font-size: 28px;
}
/* PC端:控制按钮在右下方(菜单下方) */
#controls-pc {
position: fixed;
right: 30px;
bottom: 30px;
display: flex;
flex-direction: column;
gap: 20px;
align-items: center;
z-index: 101;
}
#controls-mobile {
display: none;
}
/* 菜单放在右侧 */
#menu {
position: fixed;
right: 30px;
top: 20px;
text-align: center;
pointer-events: auto;
z-index: 101;
background: rgba(0,0,0,0.7);
padding: 25px;
border-radius: 20px;
max-width: 280px;
}
#menu h1 {
color: #00d4ff;
font-size: 28px;
margin-bottom: 20px;
text-shadow: 0 0 20px rgba(0,212,255,0.5);
}
.btn {
padding: 18px 50px;
font-size: 24px;
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
border: none;
border-radius: 35px;
cursor: pointer;
margin: 10px;
transition: transform 0.2s, box-shadow 0.2s;
pointer-events: auto;
font-weight: bold;
}
.btn:hover {
transform: scale(1.08);
box-shadow: 0 12px 35px rgba(102,126,234,0.5);
}
/* 游戏结束放在左侧 */
#gameOver {
display: none;
position: fixed;
left: 30px;
top: 50%;
transform: translateY(-50%);
text-align: center;
background: rgba(0,0,0,0.9);
padding: 40px;
border-radius: 20px;
pointer-events: auto;
z-index: 102;
min-width: 250px;
}
#gameOver h2 {
color: #ff6b6b;
font-size: 36px;
margin-bottom: 20px;
}
/* 虚拟按键 */
#controls {
position: absolute;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
pointer-events: auto;
}
.control-row {
display: flex;
gap: 15px;
}
.control-btn {
width: 110px;
height: 110px;
border-radius: 50%;
background: rgba(255,255,255,0.15);
border: 4px solid rgba(255,255,255,0.3);
color: white;
font-size: 44px;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
.control-btn:hover {
background: rgba(255,255,255,0.25);
transform: scale(1.1);
}
.control-btn:active {
background: rgba(255,255,255,0.4);
transform: scale(0.95);
}
.rotate-btn {
width: 120px;
height: 120px;
font-size: 52px;
background: rgba(255,107,107,0.3);
border-color: rgba(255,107,107,0.5);
}
/* 手机端布局 - 严格三层:记分牌-棋盘-按钮 */
@media (max-width: 900px) {
body {
margin: 0;
padding: 0;
overflow: hidden;
background: #1a1a2e;
}
/* Canvas全屏渲染 */
#application {
position: fixed !important;
top: 0;
left: 0;
width: 100% !important;
height: 100% !important;
z-index: 1;
}
/* UI层 */
#ui {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 10;
}
#ui > * {
pointer-events: auto;
}
/* 记分牌 - 顶部固定 */
#score-panel {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 45px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
gap: 18px;
font-size: 11px;
padding: 0 10px;
background: rgba(0,0,0,0.9);
z-index: 20;
}
#score-panel div {
margin: 0;
text-align: center;
min-width: 50px;
}
#score-panel .label {
font-size: 9px;
}
#score-panel .value {
font-size: 15px;
}
/* PC按钮隐藏 */
#controls-pc {
display: none !important;
}
/* 手机按钮 - 底部固定 */
#controls-mobile {
display: flex !important;
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 175px;
flex-direction: column;
justify-content: center;
gap: 6px;
padding: 8px;
background: rgba(0,0,0,0.9);
z-index: 20;
}
#controls-mobile .control-row {
gap: 12px;
justify-content: center;
}
.control-btn {
width: 60px;
height: 60px;
font-size: 24px;
border-width: 2px;
}
.rotate-btn {
width: 60px;
height: 60px;
font-size: 28px;
}
#mobile-start-btn, #mobile-restart-btn {
width: 60px;
height: 60px;
border-radius: 50%;
border: 2px solid rgba(255,255,255,0.5);
color: white;
font-size: 11px;
font-weight: bold;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
line-height: 1.2;
padding: 0;
}
#mobile-start-btn {
background: linear-gradient(135deg, #667eea, #764ba2);
}
#mobile-restart-btn {
background: linear-gradient(135deg, #56ab2f, #a8e063);
}
#mobile-rules {
color: #aaa;
font-size: 10px;
text-align: center;
padding: 4px 10px;
line-height: 1.4;
}
#mobile-rules span {
color: #ffd700;
}
#menu {
display: none !important;
}
#gameOver {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
padding: 25px;
min-width: 180px;
z-index: 100;
}
#gameOver h2 {
font-size: 26px;
}
#gameOver .btn {
padding: 12px 25px;
font-size: 16px;
}
}
</style>
</head>
<body>
<canvas id="application"></canvas>
<div id="ui">
<!-- 菜单和结束画面 -->
<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; color: #ccc; font-size: 14px; line-height: 1.8;">
<h3 style="color: #00d4ff; margin-bottom: 15px; text-align: center;">📜 游戏规则</h3>
<p><span style="color: #4ecdc4;">🎮 控制:</span> 键盘方向键或点击按钮</p>
<p><span style="color: #ffeaa7;">← →:</span> 左右移动</p>
<p><span style="color: #ff6b6b;">↓:</span> 加速下落</p>
<p><span style="color: #a29bfe;">↑/空格:</span> 旋转方块</p>
<p><span style="color: #fd79a8;">目标:</span> 填满整行消除得分</p>
</div>
</div>
<div id="gameOver">
<h2>游戏结束!</h2>
<div style="color: white; font-size: 24px; margin-bottom: 20px;">最终分数: <span id="finalScore">0</span></div>
<button class="btn" onclick="restartGame()">再玩一次</button>
</div>
<!-- 分数面板:PC在左侧,手机在顶部 -->
<div id="score-panel">
<div><span class="label">分数</span> <span id="score" class="value">0</span></div>
<div><span class="label">等级</span> <span id="level" class="value">0</span></div>
<div><span class="label">消行</span> <span id="lines" class="value">0</span></div>
<div><span class="label">速度</span> <span id="speed" class="value">1.0</span></div>
</div>
<!-- PC端控制按钮(在右侧) -->
<div id="controls-pc">
<div class="control-row">
<button class="control-btn rotate-btn" onclick="rotatePiece()">↻</button>
</div>
<div class="control-row">
<button class="control-btn" onclick="moveLeft()">←</button>
<button class="control-btn" onclick="moveDown()">↓</button>
<button class="control-btn" onclick="moveRight()">→</button>
</div>
</div>
<!-- 手机端控制按钮(在底部) -->
<div id="controls-mobile">
<!-- 第一行:开始 + 旋转 + 重新开始 -->
<div class="control-row">
<button id="mobile-start-btn" onclick="startGame()">开始<br>游戏</button>
<button class="control-btn rotate-btn" onclick="rotatePiece()">↻</button>
<button id="mobile-restart-btn" onclick="restartGame()">重新<br>开始</button>
</div>
<!-- 第二行:左 下 右 -->
<div class="control-row">
<button class="control-btn" onclick="moveLeft()">←</button>
<button class="control-btn" onclick="moveDown()">↓</button>
<button class="control-btn" onclick="moveRight()">→</button>
</div>
<!-- 第三行:规则 -->
<div id="mobile-rules">
<span>← →</span> 移动 <span>↓</span> 加速 <span>↻</span> 旋转
</div>
</div>
</div>
<script>
// ==================== Poki SDK ====================
let pokiReady = false;
function initPoki() {
if (typeof PokiSDK !== 'undefined') {
return PokiSDK.init().then(() => {
console.log('✅ Poki SDK 初始化成功');
pokiReady = true;
PokiSDK.gameLoadingStart();
setTimeout(() => PokiSDK.gameLoadingFinished(), 500);
}).catch(err => {
console.log('⚠️ Poki SDK 未就绪:', err);
return Promise.resolve();
});
}
return Promise.resolve();
}
// ==================== 游戏配置 ====================
const GRID_W = 10;
const GRID_H = 20;
const CELL_SIZE = 1.5;
const COLORS = {
I: new pc.Color(0, 0.9, 0.9),
O: new pc.Color(0.9, 0.9, 0),
T: new pc.Color(0.6, 0, 0.6),
L: new pc.Color(1, 0.5, 0),
J: new pc.Color(0, 0, 0.9),
S: new pc.Color(0, 0.9, 0),
Z: new pc.Color(0.9, 0, 0)
};
// 方块形状定义
const SHAPES = {
I: [[1,1,1,1]],
O: [[1,1],[1,1]],
T: [[0,1,0],[1,1,1]],
L: [[1,0,0],[1,1,1]],
J: [[0,0,1],[1,1,1]],
S: [[0,1,1],[1,1,0]],
Z: [[1,1,0],[0,1,1]]
};
// ==================== 全局变量 ====================
let app, gameRunning = false, gamePaused = false;
let board = [], currentPiece = null;
let score = 0, level = 0, lines = 0; // 规则:从Level 0开始
let dropTimer = 0, dropInterval = 1.0, lockTimer = 0, lockDelay = 0.5;
let isLocking = false; // 正在锁定延迟中
let boardEntities = [], pieceEntities = [];
// ==================== 初始化 ====================
document.addEventListener('DOMContentLoaded', () => {
initGame();
});
function initGame() {
console.log('🎮 初始化 PlayCanvas...');
const canvas = document.getElementById('application');
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)
});
app.start();
app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW);
app.setCanvasResolution(pc.RESOLUTION_AUTO);
window.addEventListener('resize', () => app.resizeCanvas());
setupScene();
setupInput();
initBoard();
// 设置游戏循环(必须在app创建后)
setupGameLoop();
console.log('✅ PlayCanvas 初始化成功');
} catch (e) {
console.error('❌ 初始化失败:', e);
alert('游戏加载失败: ' + e.message);
}
}
function setupGameLoop() {
app.on('update', (dt) => {
if (!gameRunning || gamePaused) return;
// 锁定延迟处理
if (isLocking) {
lockTimer += dt;
if (lockTimer >= lockDelay) {
// 延迟结束,确认锁定
lockAndSpawn();
isLocking = false;
lockTimer = 0;
}
return; // 锁定期间不处理自然下落
}
dropTimer += dt;
if (dropTimer > dropInterval) {
if (currentPiece) {
if (!currentPiece.move(0, 1)) {
// 规则:无法下落,进入锁定延迟(0.5秒)
isLocking = true;
lockTimer = 0;
}
}
dropTimer = 0;
}
});
}
function setupScene() {
// 根据屏幕尺寸计算最佳相机视野
const isMobile = window.innerWidth <= 900;
// 手机端:根据可视区域比例调整相机
const orthoHeight = isMobile ? 32 : 35;
// 相机
const camera = new pc.Entity('Camera');
camera.addComponent('camera', {
clearColor: new pc.Color(0.102, 0.102, 0.18), // #1a1a2e 与body背景一致
orthographic: true,
orthoHeight: orthoHeight,
nearClip: 0.1,
farClip: 100
});
// 手机端:完全俯视,棋盘微调向右,大小再90%
if (isMobile) {
camera.setPosition(6.55, 30, 11); // 左移半个格子(回退)
camera.setEulerAngles(-90, 0, 0); // 完全俯视
camera.camera.orthoHeight = 21.9; // 棋盘再缩90%
} else {
camera.setPosition(5, 20, 20);
camera.setEulerAngles(-60, 0, 0);
}
app.root.addChild(camera);
// 灯光
const light = new pc.Entity('Light');
light.addComponent('light', {
type: 'directional',
color: new pc.Color(1, 1, 1),
intensity: 1.2
});
light.setEulerAngles(45, 45, 0);
app.root.addChild(light);
// 创建游戏区域背景 - 使用与body一致的颜色 #1a1a2e
const bg = new pc.Entity('Background');
bg.addComponent('render', { type: 'box' });
bg.setLocalScale(GRID_W * CELL_SIZE + 0.5, 0.1, GRID_H * CELL_SIZE + 0.5);
bg.setPosition(GRID_W * CELL_SIZE / 2 - CELL_SIZE/2, -0.1, GRID_H * CELL_SIZE / 2 - CELL_SIZE/2);
const bgMat = new pc.StandardMaterial();
bgMat.diffuse = new pc.Color(0.102, 0.102, 0.18); // #1a1a2e
bgMat.update();
bg.render.material = bgMat;
app.root.addChild(bg);
// 网格线
createGridLines();
}
function createGridLines() {
const lineMat = new pc.StandardMaterial();
lineMat.diffuse = new pc.Color(0.2, 0.2, 0.25);
lineMat.update();
// 垂直线
for (let x = 0; x <= GRID_W; x++) {
const line = new pc.Entity();
line.addComponent('render', { type: 'box' });
line.setLocalScale(0.05, 0.1, GRID_H * CELL_SIZE);
line.setPosition(x * CELL_SIZE - CELL_SIZE/2, 0.01, GRID_H * CELL_SIZE / 2 - CELL_SIZE/2);
line.render.material = lineMat;
app.root.addChild(line);
}
// 水平线
for (let z = 0; z <= GRID_H; z++) {
const line = new pc.Entity();
line.addComponent('render', { type: 'box' });
line.setLocalScale(GRID_W * CELL_SIZE, 0.1, 0.05);
line.setPosition(GRID_W * CELL_SIZE / 2 - CELL_SIZE/2, 0.01, z * CELL_SIZE - CELL_SIZE/2);
line.render.material = lineMat;
app.root.addChild(line);
}
}
function initBoard() {
board = [];
for (let y = 0; y < GRID_H; y++) {
board[y] = [];
for (let x = 0; x < GRID_W; x++) {
board[y][x] = null;
}
}
}
// ==================== 方块类 ====================
class Piece {
constructor(type) {
this.type = type;
this.shape = JSON.parse(JSON.stringify(SHAPES[type]));
this.color = COLORS[type];
// 规则:I型出生在x=3,其他在x=4,y=0
this.x = (type === 'I') ? 3 : 4;
this.y = 0;
this.entities = [];
this.createEntities();
}
createEntities() {
// 清理旧实体 - 修复:e是{item}对象,需要访问e.entity
this.entities.forEach(item => {
if (item.entity && item.entity.parent) {
item.entity.destroy();
}
});
this.entities = [];
for (let row = 0; row < this.shape.length; row++) {
for (let col = 0; col < this.shape[row].length; col++) {
if (this.shape[row][col]) {
const block = new pc.Entity();
block.addComponent('render', { type: 'box' });
block.setLocalScale(CELL_SIZE * 0.9, 0.5, CELL_SIZE * 0.9);
const mat = new pc.StandardMaterial();
mat.diffuse = this.color;
mat.emissive = new pc.Color(this.color.r * 0.3, this.color.g * 0.3, this.color.b * 0.3);
mat.update();
block.render.material = mat;
app.root.addChild(block);
this.entities.push({ entity: block, row: row, col: col });
}
}
}
this.updatePositions();
}
updatePositions() {
this.entities.forEach(item => {
const worldX = (this.x + item.col) * CELL_SIZE;
const worldZ = (this.y + item.row) * CELL_SIZE;
item.entity.setPosition(worldX, 0.25, worldZ);
});
}
move(dx, dy) {
const newX = this.x + dx;
const newY = this.y + dy;
if (this.isValidPosition(newX, newY, this.shape)) {
this.x = newX;
this.y = newY;
this.updatePositions();
return true;
}
return false;
}
rotate() {
const rows = this.shape.length;
const cols = this.shape[0].length;
const rotated = [];
for (let i = 0; i < cols; i++) {
rotated[i] = [];
for (let j = 0; j < rows; j++) {
rotated[i][j] = this.shape[rows - 1 - j][i];
}
}
if (this.isValidPosition(this.x, this.y, rotated)) {
this.shape = rotated;
this.createEntities();
return true;
}
return false;
}
isValidPosition(x, y, shape) {
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_W || newY >= GRID_H) return false;
if (newY >= 0 && board[newY][newX]) return false;
}
}
}
return true;
}
lock() {
// 将方块固定到游戏板
for (let row = 0; row < this.shape.length; row++) {
for (let col = 0; col < this.shape[row].length; col++) {
if (this.shape[row][col]) {
const x = this.x + col;
const y = this.y + row;
if (y >= 0) {
const block = new pc.Entity();
block.addComponent('render', { type: 'box' });
block.setLocalScale(CELL_SIZE * 0.9, 0.5, CELL_SIZE * 0.9);
block.setPosition(x * CELL_SIZE, 0.25, y * CELL_SIZE);
const mat = new pc.StandardMaterial();
mat.diffuse = this.color;
mat.emissive = new pc.Color(this.color.r * 0.2, this.color.g * 0.2, this.color.b * 0.2);
mat.update();
block.render.material = mat;
app.root.addChild(block);
board[y][x] = block;
}
}
}
}
// 清理当前方块实体
this.entities.forEach(item => item.entity.destroy());
}
}
// ==================== 游戏逻辑 ====================
function spawnPiece() {
const types = Object.keys(SHAPES);
const type = types[Math.floor(Math.random() * types.length)];
currentPiece = new Piece(type);
// 检查游戏结束
if (!currentPiece.isValidPosition(currentPiece.x, currentPiece.y, currentPiece.shape)) {
gameOver();
return false;
}
return true;
}
function checkLines() {
let linesCleared = 0;
for (let y = GRID_H - 1; y >= 0; y--) {
let full = true;
for (let x = 0; x < GRID_W; x++) {
if (!board[y][x]) { full = false; break; }
}
if (full) {
// 删除这一行
for (let x = 0; x < GRID_W; x++) {
if (board[y][x]) board[y][x].destroy();
}
// 上方行下移
for (let moveY = y; moveY > 0; moveY--) {
board[moveY] = board[moveY - 1];
// 更新实体位置
for (let x = 0; x < GRID_W; x++) {
if (board[moveY][x]) {
board[moveY][x].setPosition(x * CELL_SIZE, 0.25, moveY * CELL_SIZE);
}
}
}
board[0] = new Array(GRID_W).fill(null);
linesCleared++;
y++; // 重新检查当前行
}
}
if (linesCleared > 0) {
// 规则:基础分 × (等级+1),等级0时×1,等级1时×2...
const basePoints = [0, 100, 300, 500, 800][linesCleared];
const multiplier = level + 1;
score += basePoints * multiplier;
lines += linesCleared;
// 规则:每消除10行升1级
level = Math.floor(lines / 10);
// 速度随等级增加
dropInterval = Math.max(0.05, 1.0 - level * 0.05);
updateUI();
}
}
// ==================== 游戏控制 ====================
window.startGame = function() {
if (gameRunning) return;
document.getElementById('menu').style.display = 'none';
// 清理旧游戏
for (let y = 0; y < GRID_H; y++) {
for (let x = 0; x < GRID_W; x++) {
if (board[y][x]) {
board[y][x].destroy();
board[y][x] = null;
}
}
}
if (currentPiece) {
currentPiece.entities.forEach(item => item.entity.destroy());
currentPiece = null;
}
gameRunning = true;
gamePaused = false;
score = 0;
level = 0; // 规则:从Level 0开始
lines = 0;
dropInterval = 1.0;
dropTimer = 0;
lockTimer = 0;
isLocking = false;
spawnPiece();
updateUI();
if (pokiReady && typeof PokiSDK !== 'undefined') {
PokiSDK.gameplayStart();
}
};
window.restartGame = function() {
document.getElementById('gameOver').style.display = 'none';
startGame();
};
function gameOver() {
gameRunning = false;
document.getElementById('finalScore').textContent = score;
document.getElementById('gameOver').style.display = 'block';
if (pokiReady && typeof PokiSDK !== 'undefined') {
PokiSDK.gameplayStop();
PokiSDK.commercialBreak().catch(() => {});
}
}
function updateUI() {
document.getElementById('score').textContent = score;
document.getElementById('level').textContent = level;
document.getElementById('lines').textContent = lines;
document.getElementById('speed').textContent = (1.0 / dropInterval).toFixed(1) + 'x';
}
// ==================== 输入控制 ====================
window.moveLeft = function() {
if (gameRunning && currentPiece) {
if (currentPiece.move(-1, 0)) {
// 规则:锁定延迟期间移动成功,重置锁定计时
if (isLocking) {
lockTimer = 0;
// 移动后检查是否还能下落
if (currentPiece.isValidPosition(currentPiece.x, currentPiece.y + 1, currentPiece.shape)) {
isLocking = false;
}
}
}
}
};
window.moveRight = function() {
if (gameRunning && currentPiece) {
if (currentPiece.move(1, 0)) {
// 规则:锁定延迟期间移动成功,重置锁定计时
if (isLocking) {
lockTimer = 0;
// 移动后检查是否还能下落
if (currentPiece.isValidPosition(currentPiece.x, currentPiece.y + 1, currentPiece.shape)) {
isLocking = false;
}
}
}
}
};
window.moveDown = function() {
if (gameRunning && currentPiece) {
if (currentPiece.move(0, 1)) {
// 规则:软降每格+1分
score += 1;
updateUI();
// 移动成功,重置锁定状态
isLocking = false;
lockTimer = 0;
} else if (!isLocking) {
// 无法下落,立即锁定(软降时)
lockAndSpawn();
}
}
};
window.rotatePiece = function() {
if (gameRunning && currentPiece) {
if (currentPiece.rotate()) {
// 规则:锁定延迟期间旋转成功,重置锁定计时
if (isLocking) {
lockTimer = 0;
// 旋转后检查是否还能下落
if (currentPiece.isValidPosition(currentPiece.x, currentPiece.y + 1, currentPiece.shape)) {
isLocking = false;
}
}
}
}
};
function lockAndSpawn() {
if (currentPiece) {
currentPiece.lock();
checkLines();
spawnPiece();
}
}
function setupInput() {
app.keyboard.on('keydown', (event) => {
if (!gameRunning) return;
switch(event.key) {
case pc.KEY_LEFT:
moveLeft();
break;
case pc.KEY_RIGHT:
moveRight();
break;
case pc.KEY_DOWN:
moveDown();
break;
case pc.KEY_UP:
case pc.KEY_SPACE:
rotatePiece();
break;
case pc.KEY_P:
gamePaused = !gamePaused;
break;
}
});
}
// 启动Poki
initPoki().then(() => {
console.log('🎮 俄罗斯方块 PlayCanvas版已加载');
});
</script>
<!-- 登录认证 -->
<script src="../auth.js?v=1"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
GAME_AUTH.init('tetris', '俄罗斯方块');
});
</script>
</body>
</html>