部分修复

This commit is contained in:
2025-09-04 13:38:14 +08:00
parent cca1a969c5
commit eb13979d4d
137 changed files with 6848 additions and 26965 deletions

537
frontend/smallgame/2048/controls.js vendored Normal file
View File

@@ -0,0 +1,537 @@
// 游戏控制模块 - 处理键盘和触摸输入
class GameControls {
constructor() {
this.touchStartX = 0;
this.touchStartY = 0;
this.touchEndX = 0;
this.touchEndY = 0;
this.minSwipeDistance = 30; // 最小滑动距离
this.isGameActive = true;
this.initializeControls();
}
initializeControls() {
// 键盘控制
this.initKeyboardControls();
// 触摸控制
this.initTouchControls();
// 鼠标控制(用于电脑端测试)
this.initMouseControls();
// 防止页面滚动
this.preventScrolling();
}
initKeyboardControls() {
document.addEventListener('keydown', (e) => {
if (!this.isGameActive || !window.game2048) {
console.log('Game not ready:', { isGameActive: this.isGameActive, game2048: !!window.game2048 });
return;
}
// 防止默认行为
const preventKeys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'w', 'a', 's', 'd', 'W', 'A', 'S', 'D'];
if (preventKeys.includes(e.key)) {
e.preventDefault();
}
switch (e.key) {
case 'ArrowUp':
case 'w':
case 'W':
window.game2048.move('up');
break;
case 'ArrowDown':
case 's':
case 'S':
window.game2048.move('down');
break;
case 'ArrowLeft':
case 'a':
case 'A':
window.game2048.move('left');
break;
case 'ArrowRight':
case 'd':
case 'D':
window.game2048.move('right');
break;
case 'r':
case 'R':
// R键重新开始游戏
window.game2048.restart();
break;
case 'Escape':
// ESC键暂停/继续游戏
this.togglePause();
break;
}
});
}
initTouchControls() {
const gameContainer = document.querySelector('.game-container');
// 触摸开始
gameContainer.addEventListener('touchstart', (e) => {
if (!this.isGameActive) return;
e.preventDefault();
const touch = e.touches[0];
this.touchStartX = touch.clientX;
this.touchStartY = touch.clientY;
}, { passive: false });
// 触摸移动(可选:显示滑动方向提示)
gameContainer.addEventListener('touchmove', (e) => {
e.preventDefault();
}, { passive: false });
// 触摸结束
gameContainer.addEventListener('touchend', (e) => {
if (!this.isGameActive || !window.game2048) return;
e.preventDefault();
const touch = e.changedTouches[0];
this.touchEndX = touch.clientX;
this.touchEndY = touch.clientY;
this.handleSwipe();
}, { passive: false });
// 触摸取消
gameContainer.addEventListener('touchcancel', (e) => {
e.preventDefault();
this.resetTouch();
}, { passive: false });
}
initMouseControls() {
const gameContainer = document.querySelector('.game-container');
let isMouseDown = false;
let mouseStartX = 0;
let mouseStartY = 0;
// 鼠标按下
gameContainer.addEventListener('mousedown', (e) => {
if (!this.isGameActive) return;
isMouseDown = true;
mouseStartX = e.clientX;
mouseStartY = e.clientY;
e.preventDefault();
});
// 鼠标释放
gameContainer.addEventListener('mouseup', (e) => {
if (!this.isGameActive || !isMouseDown || !window.game2048) return;
isMouseDown = false;
const mouseEndX = e.clientX;
const mouseEndY = e.clientY;
// 使用鼠标坐标模拟触摸
this.touchStartX = mouseStartX;
this.touchStartY = mouseStartY;
this.touchEndX = mouseEndX;
this.touchEndY = mouseEndY;
this.handleSwipe();
e.preventDefault();
});
// 鼠标离开游戏区域
gameContainer.addEventListener('mouseleave', () => {
isMouseDown = false;
});
// 防止右键菜单
gameContainer.addEventListener('contextmenu', (e) => {
e.preventDefault();
});
}
handleSwipe() {
const deltaX = this.touchEndX - this.touchStartX;
const deltaY = this.touchEndY - this.touchStartY;
const absDeltaX = Math.abs(deltaX);
const absDeltaY = Math.abs(deltaY);
// 检查是否达到最小滑动距离
if (Math.max(absDeltaX, absDeltaY) < this.minSwipeDistance) {
this.resetTouch();
return;
}
// 确定滑动方向
let direction = null;
if (absDeltaX > absDeltaY) {
// 水平滑动
direction = deltaX > 0 ? 'right' : 'left';
} else {
// 垂直滑动
direction = deltaY > 0 ? 'down' : 'up';
}
// 执行移动
if (direction && window.game2048) {
window.game2048.move(direction);
// 添加触觉反馈(如果支持)
this.addHapticFeedback();
// 添加视觉反馈
this.addVisualFeedback(direction);
}
this.resetTouch();
}
resetTouch() {
this.touchStartX = 0;
this.touchStartY = 0;
this.touchEndX = 0;
this.touchEndY = 0;
}
addHapticFeedback() {
// 添加触觉反馈(仅在支持的设备上)
if (navigator.vibrate) {
navigator.vibrate(50); // 50ms的轻微震动
}
}
addVisualFeedback(direction) {
// 添加方向指示的视觉反馈
const gameContainer = document.querySelector('.game-container');
const feedback = document.createElement('div');
feedback.className = 'swipe-feedback';
feedback.textContent = this.getDirectionArrow(direction);
// 设置样式
feedback.style.cssText = `
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 48px;
color: rgba(255, 255, 255, 0.8);
pointer-events: none;
z-index: 1000;
animation: swipeFeedback 0.3s ease-out;
`;
gameContainer.appendChild(feedback);
// 添加动画样式(如果不存在)
if (!document.getElementById('swipe-feedback-styles')) {
const style = document.createElement('style');
style.id = 'swipe-feedback-styles';
style.textContent = `
@keyframes swipeFeedback {
0% {
opacity: 0;
transform: translate(-50%, -50%) scale(0.5);
}
50% {
opacity: 1;
transform: translate(-50%, -50%) scale(1.2);
}
100% {
opacity: 0;
transform: translate(-50%, -50%) scale(1);
}
}
`;
document.head.appendChild(style);
}
// 移除反馈元素
setTimeout(() => {
if (feedback.parentNode) {
feedback.remove();
}
}, 300);
}
getDirectionArrow(direction) {
const arrows = {
'up': '↑',
'down': '↓',
'left': '←',
'right': '→'
};
return arrows[direction] || '';
}
preventScrolling() {
// 防止页面滚动,特别是在移动设备上
document.addEventListener('touchmove', (e) => {
// 只在游戏容器内防止滚动
const gameContainer = document.querySelector('.game-container');
if (gameContainer && gameContainer.contains(e.target)) {
e.preventDefault();
}
}, { passive: false });
// 防止双击缩放
document.addEventListener('touchstart', (e) => {
if (e.touches.length > 1) {
e.preventDefault();
}
}, { passive: false });
// 防止长按选择文本
document.addEventListener('selectstart', (e) => {
const gameContainer = document.querySelector('.game-container');
if (gameContainer && gameContainer.contains(e.target)) {
e.preventDefault();
}
});
}
togglePause() {
this.isGameActive = !this.isGameActive;
const pauseOverlay = document.getElementById('pause-overlay') || this.createPauseOverlay();
if (this.isGameActive) {
pauseOverlay.style.display = 'none';
// 恢复计时器
if (window.game2048 && window.game2048.stats.startTime) {
const pausedTime = Date.now() - window.game2048.pauseStartTime;
window.game2048.stats.startTime += pausedTime;
}
} else {
pauseOverlay.style.display = 'flex';
// 记录暂停时间
if (window.game2048) {
window.game2048.pauseStartTime = Date.now();
}
}
}
createPauseOverlay() {
const overlay = document.createElement('div');
overlay.id = 'pause-overlay';
overlay.innerHTML = `
<div class="pause-content">
<h2>游戏暂停</h2>
<p>按ESC键或点击继续游戏</p>
<button class="resume-btn">继续游戏</button>
</div>
`;
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: none;
justify-content: center;
align-items: center;
z-index: 10000;
backdrop-filter: blur(5px);
`;
const pauseContent = overlay.querySelector('.pause-content');
pauseContent.style.cssText = `
background: white;
padding: 40px;
border-radius: 15px;
text-align: center;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
`;
const resumeBtn = overlay.querySelector('.resume-btn');
resumeBtn.style.cssText = `
background: linear-gradient(45deg, #4ecdc4, #44a08d);
color: white;
border: none;
padding: 12px 24px;
border-radius: 25px;
font-size: 16px;
font-weight: bold;
cursor: pointer;
margin-top: 20px;
transition: all 0.3s ease;
`;
// 继续游戏按钮事件
resumeBtn.addEventListener('click', () => {
this.togglePause();
});
// 点击背景继续游戏
overlay.addEventListener('click', (e) => {
if (e.target === overlay) {
this.togglePause();
}
});
document.body.appendChild(overlay);
return overlay;
}
// 禁用控制(游戏结束时调用)
disable() {
this.isGameActive = false;
}
// 启用控制(游戏开始时调用)
enable() {
this.isGameActive = true;
}
// 显示控制提示
showControlHints() {
const hints = document.createElement('div');
hints.className = 'control-hints';
hints.innerHTML = `
<div class="hint-content">
<h3>操作说明</h3>
<div class="hint-section">
<h4>📱 手机操作</h4>
<p>在游戏区域滑动手指移动方块</p>
<div class="gesture-demo">
<span>👆 上滑</span>
<span>👇 下滑</span>
<span>👈 左滑</span>
<span>👉 右滑</span>
</div>
</div>
<div class="hint-section">
<h4>⌨️ 键盘操作</h4>
<div class="key-demo">
<div class="key-row">
<span class="key">↑</span>
<span class="key">W</span>
<span>上移</span>
</div>
<div class="key-row">
<span class="key">↓</span>
<span class="key">S</span>
<span>下移</span>
</div>
<div class="key-row">
<span class="key">←</span>
<span class="key">A</span>
<span>左移</span>
</div>
<div class="key-row">
<span class="key">→</span>
<span class="key">D</span>
<span>右移</span>
</div>
<div class="key-row">
<span class="key">R</span>
<span>重新开始</span>
</div>
<div class="key-row">
<span class="key">ESC</span>
<span>暂停/继续</span>
</div>
</div>
</div>
<button class="close-hints">知道了</button>
</div>
`;
// 添加样式
hints.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 10000;
backdrop-filter: blur(5px);
`;
document.body.appendChild(hints);
// 关闭按钮事件
hints.querySelector('.close-hints').addEventListener('click', () => {
hints.remove();
});
// 点击背景关闭
hints.addEventListener('click', (e) => {
if (e.target === hints) {
hints.remove();
}
});
}
}
// 创建全局控制实例
let gameControls;
// 页面加载完成后初始化控制
document.addEventListener('DOMContentLoaded', () => {
// 等待游戏对象初始化完成
const initControls = () => {
if (window.game2048) {
gameControls = new GameControls();
console.log('Game controls initialized successfully');
// 创建帮助按钮
createHelpButton();
} else {
console.log('Waiting for game2048 to initialize...');
setTimeout(initControls, 100);
}
};
initControls();
});
// 创建帮助按钮函数
function createHelpButton() {
const helpBtn = document.createElement('button');
helpBtn.textContent = '❓';
helpBtn.title = '操作说明';
helpBtn.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
width: 50px;
height: 50px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.9);
border: none;
font-size: 20px;
cursor: pointer;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
z-index: 1000;
transition: all 0.3s ease;
`;
helpBtn.addEventListener('click', () => {
gameControls.showControlHints();
});
helpBtn.addEventListener('mouseenter', () => {
helpBtn.style.transform = 'scale(1.1)';
});
helpBtn.addEventListener('mouseleave', () => {
helpBtn.style.transform = 'scale(1)';
});
document.body.appendChild(helpBtn);
}
// 导出控制实例
window.gameControls = gameControls;

View File

@@ -0,0 +1,424 @@
// 2048游戏核心逻辑
class Game2048 {
constructor() {
this.size = 4;
this.grid = [];
this.score = 0;
this.bestScore = parseInt(localStorage.getItem('2048-best-score')) || 0;
this.gameWon = false;
this.gameOver = false;
this.moved = false;
// 游戏统计数据
this.stats = {
moves: 0,
startTime: null,
gameTime: 0,
maxTile: 2,
mergeCount: 0
};
this.initializeGrid();
this.updateDisplay();
this.addRandomTile();
this.addRandomTile();
this.updateDisplay();
// 绑定事件
this.bindEvents();
// 开始计时
this.startTimer();
}
initializeGrid() {
this.grid = [];
for (let i = 0; i < this.size; i++) {
this.grid[i] = [];
for (let j = 0; j < this.size; j++) {
this.grid[i][j] = 0;
}
}
}
addRandomTile() {
const emptyCells = [];
for (let i = 0; i < this.size; i++) {
for (let j = 0; j < this.size; j++) {
if (this.grid[i][j] === 0) {
emptyCells.push({x: i, y: j});
}
}
}
if (emptyCells.length > 0) {
const randomCell = emptyCells[Math.floor(Math.random() * emptyCells.length)];
const value = Math.random() < 0.9 ? 2 : 4;
this.grid[randomCell.x][randomCell.y] = value;
// 创建新方块动画
this.createTileElement(randomCell.x, randomCell.y, value, true);
}
}
createTileElement(x, y, value, isNew = false) {
const container = document.getElementById('tile-container');
const tile = document.createElement('div');
tile.className = `tile tile-${value}`;
if (isNew) tile.classList.add('tile-new');
tile.textContent = value;
tile.style.left = `${y * (100/4)}%`;
tile.style.top = `${x * (100/4)}%`;
tile.dataset.x = x;
tile.dataset.y = y;
tile.dataset.value = value;
container.appendChild(tile);
// 移除动画类
setTimeout(() => {
tile.classList.remove('tile-new');
}, 200);
}
updateDisplay() {
// 清除所有方块
const container = document.getElementById('tile-container');
container.innerHTML = '';
// 重新创建所有方块
for (let i = 0; i < this.size; i++) {
for (let j = 0; j < this.size; j++) {
if (this.grid[i][j] !== 0) {
this.createTileElement(i, j, this.grid[i][j]);
}
}
}
// 更新分数
document.getElementById('score').textContent = this.score;
document.getElementById('best-score').textContent = this.bestScore;
// 更新统计数据显示
if (window.gameStats) {
window.gameStats.updateDisplay();
}
}
move(direction) {
if (this.gameOver) return;
this.moved = false;
const previousGrid = this.grid.map(row => [...row]);
switch (direction) {
case 'up':
this.moveUp();
break;
case 'down':
this.moveDown();
break;
case 'left':
this.moveLeft();
break;
case 'right':
this.moveRight();
break;
}
if (this.moved) {
this.stats.moves++;
this.addRandomTile();
this.updateDisplay();
if (this.isGameWon() && !this.gameWon) {
this.gameWon = true;
this.showGameWon();
} else if (this.isGameOver()) {
this.gameOver = true;
this.showGameOver();
}
}
}
moveLeft() {
for (let i = 0; i < this.size; i++) {
const row = this.grid[i].filter(val => val !== 0);
const merged = [];
for (let j = 0; j < row.length - 1; j++) {
if (row[j] === row[j + 1] && !merged[j] && !merged[j + 1]) {
row[j] *= 2;
this.score += row[j];
this.stats.mergeCount++;
this.stats.maxTile = Math.max(this.stats.maxTile, row[j]);
row[j + 1] = 0;
merged[j] = true;
}
}
const newRow = row.filter(val => val !== 0);
while (newRow.length < this.size) {
newRow.push(0);
}
for (let j = 0; j < this.size; j++) {
if (this.grid[i][j] !== newRow[j]) {
this.moved = true;
}
this.grid[i][j] = newRow[j];
}
}
}
moveRight() {
for (let i = 0; i < this.size; i++) {
const row = this.grid[i].filter(val => val !== 0);
const merged = [];
for (let j = row.length - 1; j > 0; j--) {
if (row[j] === row[j - 1] && !merged[j] && !merged[j - 1]) {
row[j] *= 2;
this.score += row[j];
this.stats.mergeCount++;
this.stats.maxTile = Math.max(this.stats.maxTile, row[j]);
row[j - 1] = 0;
merged[j] = true;
}
}
const newRow = row.filter(val => val !== 0);
while (newRow.length < this.size) {
newRow.unshift(0);
}
for (let j = 0; j < this.size; j++) {
if (this.grid[i][j] !== newRow[j]) {
this.moved = true;
}
this.grid[i][j] = newRow[j];
}
}
}
moveUp() {
for (let j = 0; j < this.size; j++) {
const column = [];
for (let i = 0; i < this.size; i++) {
if (this.grid[i][j] !== 0) {
column.push(this.grid[i][j]);
}
}
const merged = [];
for (let i = 0; i < column.length - 1; i++) {
if (column[i] === column[i + 1] && !merged[i] && !merged[i + 1]) {
column[i] *= 2;
this.score += column[i];
this.stats.mergeCount++;
this.stats.maxTile = Math.max(this.stats.maxTile, column[i]);
column[i + 1] = 0;
merged[i] = true;
}
}
const newColumn = column.filter(val => val !== 0);
while (newColumn.length < this.size) {
newColumn.push(0);
}
for (let i = 0; i < this.size; i++) {
if (this.grid[i][j] !== newColumn[i]) {
this.moved = true;
}
this.grid[i][j] = newColumn[i];
}
}
}
moveDown() {
for (let j = 0; j < this.size; j++) {
const column = [];
for (let i = 0; i < this.size; i++) {
if (this.grid[i][j] !== 0) {
column.push(this.grid[i][j]);
}
}
const merged = [];
for (let i = column.length - 1; i > 0; i--) {
if (column[i] === column[i - 1] && !merged[i] && !merged[i - 1]) {
column[i] *= 2;
this.score += column[i];
this.stats.mergeCount++;
this.stats.maxTile = Math.max(this.stats.maxTile, column[i]);
column[i - 1] = 0;
merged[i] = true;
}
}
const newColumn = column.filter(val => val !== 0);
while (newColumn.length < this.size) {
newColumn.unshift(0);
}
for (let i = 0; i < this.size; i++) {
if (this.grid[i][j] !== newColumn[i]) {
this.moved = true;
}
this.grid[i][j] = newColumn[i];
}
}
}
isGameWon() {
for (let i = 0; i < this.size; i++) {
for (let j = 0; j < this.size; j++) {
if (this.grid[i][j] === 2048) {
return true;
}
}
}
return false;
}
isGameOver() {
// 检查是否有空格
for (let i = 0; i < this.size; i++) {
for (let j = 0; j < this.size; j++) {
if (this.grid[i][j] === 0) {
return false;
}
}
}
// 检查是否可以合并
for (let i = 0; i < this.size; i++) {
for (let j = 0; j < this.size; j++) {
const current = this.grid[i][j];
if (
(i > 0 && this.grid[i - 1][j] === current) ||
(i < this.size - 1 && this.grid[i + 1][j] === current) ||
(j > 0 && this.grid[i][j - 1] === current) ||
(j < this.size - 1 && this.grid[i][j + 1] === current)
) {
return false;
}
}
}
return true;
}
showGameWon() {
const message = document.getElementById('game-message');
message.className = 'game-message game-won';
message.style.display = 'flex';
message.querySelector('p').textContent = '你赢了!';
}
showGameOver() {
const message = document.getElementById('game-message');
message.className = 'game-message game-over';
message.style.display = 'flex';
message.querySelector('p').textContent = '游戏结束!';
// 显示最终统计
setTimeout(() => {
if (window.gameStats) {
window.gameStats.showFinalStats();
}
}, 1000);
}
restart() {
this.score = 0;
this.gameWon = false;
this.gameOver = false;
this.moved = false;
// 重置统计数据
this.stats = {
moves: 0,
startTime: null,
gameTime: 0,
maxTile: 2,
mergeCount: 0
};
this.initializeGrid();
this.addRandomTile();
this.addRandomTile();
this.updateDisplay();
// 隐藏游戏消息
document.getElementById('game-message').style.display = 'none';
// 重新开始计时
this.startTimer();
}
keepPlaying() {
document.getElementById('game-message').style.display = 'none';
}
startTimer() {
this.stats.startTime = Date.now();
if (this.timerInterval) {
clearInterval(this.timerInterval);
}
this.timerInterval = setInterval(() => {
if (!this.gameOver && this.stats.startTime) {
this.stats.gameTime = Math.floor((Date.now() - this.stats.startTime) / 1000);
if (window.gameStats) {
window.gameStats.updateDisplay();
}
}
}, 1000);
}
bindEvents() {
// 重新开始按钮
document.getElementById('restart-btn').addEventListener('click', () => {
this.restart();
});
// 继续游戏按钮
document.getElementById('keep-playing').addEventListener('click', () => {
this.keepPlaying();
});
// 重试按钮
document.getElementById('retry-btn').addEventListener('click', () => {
this.restart();
});
}
updateBestScore() {
if (this.score > this.bestScore) {
this.bestScore = this.score;
localStorage.setItem('2048-best-score', this.bestScore.toString());
}
}
}
// 游戏实例
let game;
// 页面加载完成后初始化游戏
document.addEventListener('DOMContentLoaded', () => {
game = new Game2048();
// 监听分数变化以更新最高分
const originalUpdateDisplay = game.updateDisplay.bind(game);
game.updateDisplay = function() {
originalUpdateDisplay();
this.updateBestScore();
};
// 导出游戏实例供其他模块使用
window.game2048 = game;
});

View File

@@ -0,0 +1,165 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>2048游戏</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<header class="header">
<h1 class="title">2048</h1>
<div class="score-container">
<div class="score-box">
<div class="score-label">分数</div>
<div class="score" id="score">0</div>
</div>
<div class="score-box">
<div class="score-label">最高分</div>
<div class="score" id="best-score">0</div>
</div>
</div>
</header>
<div class="game-intro">
<p class="game-explanation">
合并相同数字,达到<strong>2048</strong>
</p>
<div class="restart-button" id="restart-btn">新游戏</div>
</div>
<div class="game-container">
<div class="game-message" id="game-message">
<p></p>
<div class="lower">
<a class="keep-playing-button" id="keep-playing">继续游戏</a>
<a class="retry-button" id="retry-btn">重新开始</a>
</div>
</div>
<div class="grid-container">
<div class="grid-row">
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
</div>
<div class="grid-row">
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
</div>
<div class="grid-row">
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
</div>
<div class="grid-row">
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
<div class="grid-cell"></div>
</div>
</div>
<div class="tile-container" id="tile-container">
<!-- 动态生成的数字方块 -->
</div>
</div>
<div class="game-stats" id="game-stats">
<h3>游戏统计</h3>
<div class="stats-grid">
<div class="stat-item">
<span class="stat-label">移动次数:</span>
<span class="stat-value" id="moves-count">0</span>
</div>
<div class="stat-item">
<span class="stat-label">游戏时间:</span>
<span class="stat-value" id="game-time">00:00</span>
</div>
<div class="stat-item">
<span class="stat-label">最大数字:</span>
<span class="stat-value" id="max-tile">2</span>
</div>
<div class="stat-item">
<span class="stat-label">合并次数:</span>
<span class="stat-value" id="merge-count">0</span>
</div>
</div>
</div>
<div class="controls-hint">
<p><strong>操作说明:</strong></p>
<p>手机: 滑动屏幕移动方块</p>
<p>电脑: 使用方向键 ↑↓←→ 或 WASD 键</p>
</div>
</div>
<!-- 游戏结束统计弹窗 -->
<div class="modal" id="stats-modal">
<div class="modal-content">
<div class="modal-header">
<h2>游戏结束</h2>
<span class="close" id="close-modal">&times;</span>
</div>
<div class="modal-body">
<div class="final-score">
<h3>最终得分: <span id="final-score">0</span></h3>
</div>
<div class="achievement-section">
<h4>成就统计</h4>
<div class="achievement-grid">
<div class="achievement-item">
<span class="achievement-icon">🎯</span>
<div class="achievement-info">
<div class="achievement-title">总移动次数</div>
<div class="achievement-value" id="final-moves">0</div>
</div>
</div>
<div class="achievement-item">
<span class="achievement-icon">⏱️</span>
<div class="achievement-info">
<div class="achievement-title">游戏时长</div>
<div class="achievement-value" id="final-time">00:00</div>
</div>
</div>
<div class="achievement-item">
<span class="achievement-icon">🏆</span>
<div class="achievement-info">
<div class="achievement-title">最大数字</div>
<div class="achievement-value" id="final-max-tile">2</div>
</div>
</div>
<div class="achievement-item">
<span class="achievement-icon">🔥</span>
<div class="achievement-info">
<div class="achievement-title">合并次数</div>
<div class="achievement-value" id="final-merges">0</div>
</div>
</div>
<div class="achievement-item">
<span class="achievement-icon">📊</span>
<div class="achievement-info">
<div class="achievement-title">平均每步得分</div>
<div class="achievement-value" id="avg-score">0</div>
</div>
</div>
</div>
</div>
<div class="modal-actions">
<button class="btn btn-primary" id="new-game-btn">开始新游戏</button>
<button class="btn btn-secondary" id="share-btn">分享成绩</button>
</div>
</div>
</div>
</div>
<script src="game-logic.js"></script>
<script src="controls.js"></script>
<script src="statistics.js"></script>
</body>
</html>

View File

@@ -0,0 +1,381 @@
// 游戏统计模块
class GameStatistics {
constructor() {
this.achievements = {
firstWin: false,
speedRunner: false, // 5分钟内达到2048
efficient: false, // 少于500步达到2048
persistent: false, // 游戏时间超过30分钟
merger: false, // 单局合并超过100次
highScorer: false // 分数超过50000
};
this.loadAchievements();
this.initializeModal();
}
updateDisplay() {
if (!window.game2048) return;
const game = window.game2048;
// 更新实时统计显示
document.getElementById('moves-count').textContent = game.stats.moves;
document.getElementById('game-time').textContent = this.formatTime(game.stats.gameTime);
document.getElementById('max-tile').textContent = game.stats.maxTile;
document.getElementById('merge-count').textContent = game.stats.mergeCount;
}
formatTime(seconds) {
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
}
showFinalStats() {
if (!window.game2048) return;
const game = window.game2048;
const modal = document.getElementById('stats-modal');
// 更新最终统计数据
document.getElementById('final-score').textContent = game.score;
document.getElementById('final-moves').textContent = game.stats.moves;
document.getElementById('final-time').textContent = this.formatTime(game.stats.gameTime);
document.getElementById('final-max-tile').textContent = game.stats.maxTile;
document.getElementById('final-merges').textContent = game.stats.mergeCount;
// 计算平均每步得分
const avgScore = game.stats.moves > 0 ? Math.round(game.score / game.stats.moves) : 0;
document.getElementById('avg-score').textContent = avgScore;
// 检查成就
this.checkAchievements(game);
// 显示模态框
modal.style.display = 'block';
// 添加动画效果
setTimeout(() => {
modal.querySelector('.modal-content').style.transform = 'scale(1)';
}, 10);
}
checkAchievements(game) {
let newAchievements = [];
// 首次胜利
if (game.gameWon && !this.achievements.firstWin) {
this.achievements.firstWin = true;
newAchievements.push('🏆 首次胜利达到了2048');
}
// 速度跑者 - 5分钟内达到2048
if (game.gameWon && game.stats.gameTime <= 300 && !this.achievements.speedRunner) {
this.achievements.speedRunner = true;
newAchievements.push('⚡ 速度跑者5分钟内达到2048');
}
// 高效玩家 - 少于500步达到2048
if (game.gameWon && game.stats.moves < 500 && !this.achievements.efficient) {
this.achievements.efficient = true;
newAchievements.push('🎯 高效玩家少于500步达到2048');
}
// 坚持不懈 - 游戏时间超过30分钟
if (game.stats.gameTime >= 1800 && !this.achievements.persistent) {
this.achievements.persistent = true;
newAchievements.push('⏰ 坚持不懈游戏时间超过30分钟');
}
// 合并大师 - 单局合并超过100次
if (game.stats.mergeCount >= 100 && !this.achievements.merger) {
this.achievements.merger = true;
newAchievements.push('🔥 合并大师单局合并超过100次');
}
// 高分玩家 - 分数超过50000
if (game.score >= 50000 && !this.achievements.highScorer) {
this.achievements.highScorer = true;
newAchievements.push('💎 高分玩家分数超过50000');
}
// 保存成就
if (newAchievements.length > 0) {
this.saveAchievements();
this.showAchievementNotifications(newAchievements);
}
}
showAchievementNotifications(achievements) {
// 在成就区域显示新获得的成就
const achievementSection = document.querySelector('.achievement-section');
achievements.forEach((achievement, index) => {
setTimeout(() => {
const notification = document.createElement('div');
notification.className = 'achievement-notification';
notification.innerHTML = `
<div class="achievement-popup">
<span class="achievement-text">${achievement}</span>
</div>
`;
achievementSection.appendChild(notification);
// 添加样式
const popup = notification.querySelector('.achievement-popup');
popup.style.cssText = `
background: linear-gradient(45deg, #ff6b6b, #feca57);
color: white;
padding: 10px 15px;
border-radius: 20px;
margin: 5px 0;
font-weight: bold;
text-align: center;
animation: achievementSlide 0.5s ease-out;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
`;
// 添加动画样式
if (!document.getElementById('achievement-styles')) {
const style = document.createElement('style');
style.id = 'achievement-styles';
style.textContent = `
@keyframes achievementSlide {
from {
opacity: 0;
transform: translateX(-100%);
}
to {
opacity: 1;
transform: translateX(0);
}
}
`;
document.head.appendChild(style);
}
// 3秒后移除通知
setTimeout(() => {
if (notification.parentNode) {
notification.remove();
}
}, 3000);
}, index * 500);
});
}
saveAchievements() {
localStorage.setItem('2048-achievements', JSON.stringify(this.achievements));
}
loadAchievements() {
const saved = localStorage.getItem('2048-achievements');
if (saved) {
this.achievements = { ...this.achievements, ...JSON.parse(saved) };
}
}
initializeModal() {
const modal = document.getElementById('stats-modal');
const closeBtn = document.getElementById('close-modal');
const newGameBtn = document.getElementById('new-game-btn');
const shareBtn = document.getElementById('share-btn');
// 关闭模态框
closeBtn.addEventListener('click', () => {
modal.style.display = 'none';
});
// 点击模态框外部关闭
modal.addEventListener('click', (e) => {
if (e.target === modal) {
modal.style.display = 'none';
}
});
// 新游戏按钮
newGameBtn.addEventListener('click', () => {
modal.style.display = 'none';
if (window.game2048) {
window.game2048.restart();
}
});
// 分享按钮
shareBtn.addEventListener('click', () => {
this.shareScore();
});
// ESC键关闭模态框
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && modal.style.display === 'block') {
modal.style.display = 'none';
}
});
}
shareScore() {
if (!window.game2048) return;
const game = window.game2048;
const shareText = `我在2048游戏中获得了${game.score}分!\n` +
`最大数字: ${game.stats.maxTile}\n` +
`移动次数: ${game.stats.moves}\n` +
`游戏时间: ${this.formatTime(game.stats.gameTime)}\n` +
`来挑战一下吧!`;
// 尝试使用Web Share API
if (navigator.share) {
navigator.share({
title: '2048游戏成绩',
text: shareText,
url: window.location.href
}).catch(err => {
console.log('分享失败:', err);
this.fallbackShare(shareText);
});
} else {
this.fallbackShare(shareText);
}
}
fallbackShare(text) {
// 复制到剪贴板
if (navigator.clipboard) {
navigator.clipboard.writeText(text).then(() => {
this.showToast('成绩已复制到剪贴板!');
}).catch(() => {
this.showShareModal(text);
});
} else {
this.showShareModal(text);
}
}
showShareModal(text) {
// 创建分享文本显示框
const shareModal = document.createElement('div');
shareModal.innerHTML = `
<div style="
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
z-index: 10000;
max-width: 90%;
text-align: center;
">
<h3>分享你的成绩</h3>
<textarea readonly style="
width: 100%;
height: 120px;
margin: 10px 0;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
resize: none;
">${text}</textarea>
<div>
<button onclick="this.parentElement.parentElement.parentElement.remove()" style="
background: #4ecdc4;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
margin: 0 5px;
">关闭</button>
<button onclick="
this.parentElement.previousElementSibling.select();
document.execCommand('copy');
alert('已复制到剪贴板!');
" style="
background: #ff6b6b;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
margin: 0 5px;
">复制</button>
</div>
</div>
`;
document.body.appendChild(shareModal);
}
showToast(message) {
const toast = document.createElement('div');
toast.textContent = message;
toast.style.cssText = `
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 12px 24px;
border-radius: 25px;
z-index: 10000;
font-weight: bold;
animation: toastSlide 0.3s ease-out;
`;
document.body.appendChild(toast);
setTimeout(() => {
toast.remove();
}, 3000);
}
// 获取游戏统计摘要
getStatsSummary() {
if (!window.game2048) return null;
const game = window.game2048;
return {
score: game.score,
bestScore: game.bestScore,
moves: game.stats.moves,
gameTime: game.stats.gameTime,
maxTile: game.stats.maxTile,
mergeCount: game.stats.mergeCount,
achievements: this.achievements
};
}
// 重置所有统计数据
resetAllStats() {
this.achievements = {
firstWin: false,
speedRunner: false,
efficient: false,
persistent: false,
merger: false,
highScorer: false
};
localStorage.removeItem('2048-achievements');
localStorage.removeItem('2048-best-score');
this.showToast('所有统计数据已重置!');
}
}
// 创建全局统计实例
window.gameStats = new GameStatistics();
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', () => {
// 确保统计模块正确初始化
if (!window.gameStats) {
window.gameStats = new GameStatistics();
}
});

View File

@@ -0,0 +1,611 @@
/* 基础样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Arial', 'Microsoft YaHei', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #776e65;
font-size: 18px;
margin: 0;
padding: 0;
min-height: 100vh;
overflow-x: hidden;
}
.container {
max-width: 500px;
margin: 0 auto;
padding: 20px;
position: relative;
}
/* 头部样式 */
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
flex-wrap: wrap;
}
.title {
font-size: 48px;
font-weight: bold;
color: #ffffff;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
margin: 0;
}
.score-container {
display: flex;
gap: 10px;
}
.score-box {
background: rgba(255, 255, 255, 0.9);
padding: 10px 15px;
border-radius: 8px;
text-align: center;
min-width: 80px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.score-label {
font-size: 12px;
color: #776e65;
text-transform: uppercase;
font-weight: bold;
}
.score {
font-size: 20px;
font-weight: bold;
color: #ffffff;
background: linear-gradient(45deg, #ff6b6b, #ee5a24);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* 游戏介绍区域 */
.game-intro {
background: rgba(255, 255, 255, 0.9);
padding: 15px;
border-radius: 12px;
margin-bottom: 20px;
text-align: center;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.game-explanation {
margin-bottom: 15px;
font-size: 16px;
line-height: 1.5;
}
.restart-button {
background: linear-gradient(45deg, #4ecdc4, #44a08d);
color: white;
padding: 12px 24px;
border-radius: 25px;
cursor: pointer;
font-weight: bold;
transition: all 0.3s ease;
display: inline-block;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.restart-button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(0,0,0,0.3);
}
/* 游戏容器 */
.game-container {
position: relative;
background: rgba(255, 255, 255, 0.9);
border-radius: 12px;
padding: 15px;
margin-bottom: 20px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
/* 网格样式 */
.grid-container {
position: relative;
z-index: 1;
}
.grid-row {
display: flex;
margin-bottom: 8px;
}
.grid-row:last-child {
margin-bottom: 0;
}
.grid-cell {
width: calc(25% - 6px);
height: 80px;
background: rgba(238, 228, 218, 0.35);
border-radius: 8px;
margin-right: 8px;
position: relative;
}
.grid-cell:last-child {
margin-right: 0;
}
/* 数字方块容器 */
.tile-container {
position: absolute;
top: 15px;
left: 15px;
right: 15px;
bottom: 15px;
z-index: 2;
}
/* 数字方块样式 */
.tile {
position: absolute;
width: calc(25% - 6px);
height: 80px;
border-radius: 8px;
font-weight: bold;
display: flex;
justify-content: center;
align-items: center;
font-size: 32px;
transition: all 0.15s ease-in-out;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
/* 不同数字的颜色 */
.tile-2 { background: #eee4da; color: #776e65; }
.tile-4 { background: #ede0c8; color: #776e65; }
.tile-8 { background: #f2b179; color: #f9f6f2; }
.tile-16 { background: #f59563; color: #f9f6f2; }
.tile-32 { background: #f67c5f; color: #f9f6f2; }
.tile-64 { background: #f65e3b; color: #f9f6f2; }
.tile-128 { background: #edcf72; color: #f9f6f2; font-size: 28px; }
.tile-256 { background: #edcc61; color: #f9f6f2; font-size: 28px; }
.tile-512 { background: #edc850; color: #f9f6f2; font-size: 28px; }
.tile-1024 { background: #edc53f; color: #f9f6f2; font-size: 24px; }
.tile-2048 { background: #edc22e; color: #f9f6f2; font-size: 24px; box-shadow: 0 0 20px rgba(237, 194, 46, 0.5); }
.tile-super { background: #3c3a32; color: #f9f6f2; font-size: 20px; }
/* 动画效果 */
.tile-new {
animation: appear 0.2s ease-in-out;
}
.tile-merged {
animation: pop 0.2s ease-in-out;
}
@keyframes appear {
0% {
opacity: 0;
transform: scale(0);
}
100% {
opacity: 1;
transform: scale(1);
}
}
@keyframes pop {
0% {
transform: scale(1);
}
50% {
transform: scale(1.2);
}
100% {
transform: scale(1);
}
}
/* 游戏消息 */
.game-message {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.95);
z-index: 100;
text-align: center;
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 12px;
}
.game-message.game-won {
background: rgba(237, 194, 46, 0.9);
color: #f9f6f2;
}
.game-message.game-over {
background: rgba(238, 228, 218, 0.9);
color: #776e65;
}
.game-message p {
font-size: 36px;
font-weight: bold;
margin-bottom: 20px;
}
.lower {
display: flex;
gap: 15px;
}
.keep-playing-button,
.retry-button {
background: #8f7a66;
color: #f9f6f2;
padding: 12px 24px;
border-radius: 8px;
text-decoration: none;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
}
.keep-playing-button:hover,
.retry-button:hover {
background: #9f8a76;
transform: translateY(-2px);
}
/* 游戏统计 */
.game-stats {
background: rgba(255, 255, 255, 0.9);
padding: 15px;
border-radius: 12px;
margin-bottom: 20px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.game-stats h3 {
text-align: center;
margin-bottom: 15px;
color: #776e65;
font-size: 20px;
}
.stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.stat-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
background: rgba(238, 228, 218, 0.3);
border-radius: 6px;
}
.stat-label {
font-size: 14px;
color: #776e65;
}
.stat-value {
font-weight: bold;
color: #f67c5f;
font-size: 16px;
}
/* 操作提示 */
.controls-hint {
background: rgba(255, 255, 255, 0.9);
padding: 15px;
border-radius: 12px;
text-align: center;
font-size: 14px;
line-height: 1.6;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.controls-hint p {
margin-bottom: 5px;
}
.controls-hint p:last-child {
margin-bottom: 0;
}
/* 模态框样式 */
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(5px);
}
.modal-content {
background-color: #fefefe;
margin: 5% auto;
border-radius: 15px;
width: 90%;
max-width: 500px;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
animation: modalSlideIn 0.3s ease-out;
}
@keyframes modalSlideIn {
from {
opacity: 0;
transform: translateY(-50px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.modal-header {
padding: 20px;
border-bottom: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 15px 15px 0 0;
}
.modal-header h2 {
margin: 0;
font-size: 24px;
}
.close {
color: white;
font-size: 28px;
font-weight: bold;
cursor: pointer;
transition: color 0.3s ease;
}
.close:hover {
color: #ddd;
}
.modal-body {
padding: 20px;
}
.final-score {
text-align: center;
margin-bottom: 25px;
padding: 20px;
background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%);
border-radius: 10px;
color: white;
}
.final-score h3 {
margin: 0;
font-size: 28px;
text-shadow: 1px 1px 2px rgba(0,0,0,0.2);
}
.achievement-section h4 {
text-align: center;
margin-bottom: 20px;
color: #776e65;
font-size: 20px;
}
.achievement-grid {
display: grid;
grid-template-columns: 1fr;
gap: 15px;
margin-bottom: 25px;
}
.achievement-item {
display: flex;
align-items: center;
padding: 15px;
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
border-radius: 10px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
transition: transform 0.3s ease;
}
.achievement-item:hover {
transform: translateY(-2px);
}
.achievement-icon {
font-size: 32px;
margin-right: 15px;
}
.achievement-info {
flex: 1;
}
.achievement-title {
font-size: 14px;
color: #666;
margin-bottom: 5px;
}
.achievement-value {
font-size: 20px;
font-weight: bold;
color: #333;
}
.modal-actions {
display: flex;
gap: 15px;
justify-content: center;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 25px;
font-size: 16px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
display: inline-block;
}
.btn-primary {
background: linear-gradient(45deg, #4ecdc4, #44a08d);
color: white;
}
.btn-secondary {
background: linear-gradient(45deg, #ff9a9e, #fecfef);
color: white;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(0,0,0,0.2);
}
/* 手机端优化 */
@media (max-width: 480px) {
.container {
padding: 15px;
}
.title {
font-size: 36px;
}
.header {
flex-direction: column;
gap: 15px;
text-align: center;
}
.score-container {
justify-content: center;
}
.grid-cell,
.tile {
height: 70px;
font-size: 28px;
}
.tile-128,
.tile-256,
.tile-512 {
font-size: 24px;
}
.tile-1024,
.tile-2048 {
font-size: 20px;
}
.tile-super {
font-size: 16px;
}
.game-message p {
font-size: 28px;
}
.modal-content {
margin: 10% auto;
width: 95%;
}
.achievement-grid {
grid-template-columns: 1fr;
}
.modal-actions {
flex-direction: column;
}
}
/* 超小屏幕优化 */
@media (max-width: 360px) {
.grid-cell,
.tile {
height: 60px;
font-size: 24px;
}
.tile-128,
.tile-256,
.tile-512 {
font-size: 20px;
}
.tile-1024,
.tile-2048 {
font-size: 18px;
}
.tile-super {
font-size: 14px;
}
}
/* 电脑端优化 */
@media (min-width: 768px) {
.container {
padding: 30px;
}
.grid-cell,
.tile {
height: 90px;
font-size: 36px;
}
.achievement-grid {
grid-template-columns: 1fr 1fr;
}
.modal-actions {
flex-direction: row;
}
}
/* 触摸优化 */
@media (hover: none) and (pointer: coarse) {
.restart-button,
.keep-playing-button,
.retry-button,
.btn {
min-height: 44px;
min-width: 44px;
}
}

View File

@@ -0,0 +1,279 @@
// 游戏控制模块
class GameControls {
constructor(game) {
this.game = game;
this.keys = {};
this.keyRepeatDelay = 150;
this.keyRepeatInterval = 50;
this.keyTimers = {};
this.init();
}
init() {
this.setupKeyboardControls();
this.setupTouchControls();
this.setupButtonControls();
}
setupKeyboardControls() {
document.addEventListener('keydown', (e) => {
this.handleKeyDown(e);
});
document.addEventListener('keyup', (e) => {
this.handleKeyUp(e);
});
}
handleKeyDown(e) {
const key = e.key;
// 防止重复触发
if (this.keys[key]) return;
this.keys[key] = true;
switch(key) {
case 'ArrowLeft':
e.preventDefault();
this.game.moveLeft();
this.startKeyRepeat('ArrowLeft', () => this.game.moveLeft());
break;
case 'ArrowRight':
e.preventDefault();
this.game.moveRight();
this.startKeyRepeat('ArrowRight', () => this.game.moveRight());
break;
case 'ArrowDown':
e.preventDefault();
this.game.moveDown();
this.startKeyRepeat('ArrowDown', () => this.game.moveDown());
break;
case 'ArrowUp':
e.preventDefault();
this.game.rotatePiece();
break;
case ' ':
e.preventDefault();
if (this.game.gameRunning) {
this.game.pause();
}
break;
case 'Enter':
e.preventDefault();
if (!this.game.gameRunning) {
this.game.start();
}
break;
case 'r':
case 'R':
if (e.ctrlKey) {
e.preventDefault();
this.game.restart();
}
break;
case 'Escape':
e.preventDefault();
if (this.game.gameRunning) {
this.game.pause();
}
break;
}
}
handleKeyUp(e) {
const key = e.key;
this.keys[key] = false;
this.stopKeyRepeat(key);
}
startKeyRepeat(key, action) {
this.stopKeyRepeat(key);
this.keyTimers[key] = setTimeout(() => {
const intervalId = setInterval(() => {
if (this.keys[key]) {
action();
} else {
clearInterval(intervalId);
}
}, this.keyRepeatInterval);
this.keyTimers[key] = intervalId;
}, this.keyRepeatDelay);
}
stopKeyRepeat(key) {
if (this.keyTimers[key]) {
clearTimeout(this.keyTimers[key]);
clearInterval(this.keyTimers[key]);
delete this.keyTimers[key];
}
}
setupTouchControls() {
// 移动端触摸控制
const leftBtn = document.getElementById('leftBtn');
const rightBtn = document.getElementById('rightBtn');
const downBtn = document.getElementById('downBtn');
const rotateBtn = document.getElementById('rotateBtn');
const dropBtn = document.getElementById('dropBtn');
const pauseBtn = document.getElementById('pauseBtn');
// 左移
this.addTouchEvents(leftBtn, () => this.game.moveLeft());
// 右移
this.addTouchEvents(rightBtn, () => this.game.moveRight());
// 下移
this.addTouchEvents(downBtn, () => this.game.moveDown());
// 旋转
rotateBtn.addEventListener('touchstart', (e) => {
e.preventDefault();
this.game.rotatePiece();
});
rotateBtn.addEventListener('click', (e) => {
e.preventDefault();
this.game.rotatePiece();
});
// 硬降
dropBtn.addEventListener('touchstart', (e) => {
e.preventDefault();
this.game.hardDrop();
});
dropBtn.addEventListener('click', (e) => {
e.preventDefault();
this.game.hardDrop();
});
// 暂停
pauseBtn.addEventListener('touchstart', (e) => {
e.preventDefault();
this.game.pause();
});
pauseBtn.addEventListener('click', (e) => {
e.preventDefault();
this.game.pause();
});
}
addTouchEvents(element, action) {
let touchInterval;
let touchTimeout;
const startAction = (e) => {
e.preventDefault();
action();
touchTimeout = setTimeout(() => {
touchInterval = setInterval(action, this.keyRepeatInterval);
}, this.keyRepeatDelay);
};
const stopAction = (e) => {
e.preventDefault();
if (touchTimeout) {
clearTimeout(touchTimeout);
touchTimeout = null;
}
if (touchInterval) {
clearInterval(touchInterval);
touchInterval = null;
}
};
element.addEventListener('touchstart', startAction);
element.addEventListener('touchend', stopAction);
element.addEventListener('touchcancel', stopAction);
element.addEventListener('mousedown', startAction);
element.addEventListener('mouseup', stopAction);
element.addEventListener('mouseleave', stopAction);
}
setupButtonControls() {
const startBtn = document.getElementById('startBtn');
const restartBtn = document.getElementById('restartBtn');
startBtn.addEventListener('click', () => {
this.game.start();
});
restartBtn.addEventListener('click', () => {
this.game.restart();
});
}
// 游戏手势支持
setupSwipeControls() {
let startX = 0;
let startY = 0;
let threshold = 50;
this.game.canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
const touch = e.touches[0];
startX = touch.clientX;
startY = touch.clientY;
});
this.game.canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
});
this.game.canvas.addEventListener('touchend', (e) => {
e.preventDefault();
const touch = e.changedTouches[0];
const deltaX = touch.clientX - startX;
const deltaY = touch.clientY - startY;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// 水平滑动
if (Math.abs(deltaX) > threshold) {
if (deltaX > 0) {
this.game.moveRight();
} else {
this.game.moveLeft();
}
}
} else {
// 垂直滑动
if (Math.abs(deltaY) > threshold) {
if (deltaY > 0) {
this.game.moveDown();
} else {
this.game.rotatePiece();
}
}
}
});
// 双击旋转
let lastTap = 0;
this.game.canvas.addEventListener('touchend', (e) => {
const currentTime = new Date().getTime();
const tapLength = currentTime - lastTap;
if (tapLength < 500 && tapLength > 0) {
e.preventDefault();
this.game.rotatePiece();
}
lastTap = currentTime;
});
}
}
// 初始化控制系统
const gameControls = new GameControls(game);

View File

@@ -0,0 +1,338 @@
// 游戏统计和成就系统
class GameStats {
constructor() {
this.achievements = [
{
id: 'first_game',
name: '初次体验',
description: '完成第一次游戏',
condition: (stats) => true
},
{
id: 'score_1000',
name: '小试牛刀',
description: '单局得分达到1000分',
condition: (stats) => stats.score >= 1000
},
{
id: 'score_5000',
name: '游戏达人',
description: '单局得分达到5000分',
condition: (stats) => stats.score >= 5000
},
{
id: 'score_10000',
name: '方块大师',
description: '单局得分达到10000分',
condition: (stats) => stats.score >= 10000
},
{
id: 'level_5',
name: '步步高升',
description: '达到第5级',
condition: (stats) => stats.level >= 5
},
{
id: 'level_10',
name: '速度之王',
description: '达到第10级',
condition: (stats) => stats.level >= 10
},
{
id: 'lines_50',
name: '消除专家',
description: '累计消除50行',
condition: (stats) => stats.lines >= 50
},
{
id: 'lines_100',
name: '清理大师',
description: '累计消除100行',
condition: (stats) => stats.lines >= 100
},
{
id: 'tetris',
name: 'Tetris!',
description: '一次消除4行',
condition: (stats) => stats.maxCombo >= 4
},
{
id: 'time_10min',
name: '持久战士',
description: '单局游戏时间超过10分钟',
condition: (stats) => stats.playTime >= 600000
},
{
id: 'efficiency',
name: '效率专家',
description: '平均每分钟得分超过500',
condition: (stats) => stats.avgScore >= 500
}
];
this.init();
}
init() {
this.setupEventListeners();
}
setupEventListeners() {
const playAgainBtn = document.getElementById('playAgainBtn');
playAgainBtn.addEventListener('click', () => {
this.hideStats();
game.restart();
});
}
showStats(gameData) {
const playTimeMinutes = gameData.playTime / 60000;
const avgScore = playTimeMinutes > 0 ? Math.round(gameData.score / playTimeMinutes) : 0;
const stats = {
...gameData,
avgScore: avgScore
};
// 更新统计显示
document.getElementById('finalScore').textContent = stats.score.toLocaleString();
document.getElementById('finalLevel').textContent = stats.level;
document.getElementById('finalLines').textContent = stats.lines;
document.getElementById('playTime').textContent = this.formatTime(stats.playTime);
document.getElementById('maxCombo').textContent = stats.maxCombo;
document.getElementById('avgScore').textContent = stats.avgScore;
// 检查成就
const achievement = this.checkAchievements(stats);
this.displayAchievement(achievement);
// 显示统计界面
document.getElementById('gameStats').style.display = 'flex';
document.getElementById('gameStats').classList.add('fade-in');
}
hideStats() {
document.getElementById('gameStats').style.display = 'none';
document.getElementById('gameStats').classList.remove('fade-in');
}
formatTime(milliseconds) {
const seconds = Math.floor(milliseconds / 1000);
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
}
checkAchievements(stats) {
// 获取已获得的成就
const earnedAchievements = this.getEarnedAchievements();
// 检查新成就
for (let achievement of this.achievements) {
if (!earnedAchievements.includes(achievement.id) &&
achievement.condition(stats)) {
// 保存新成就
this.saveAchievement(achievement.id);
return achievement;
}
}
return null;
}
displayAchievement(achievement) {
const achievementEl = document.getElementById('achievement');
if (achievement) {
achievementEl.innerHTML = `
🏆 <strong>成就解锁!</strong><br>
<strong>${achievement.name}</strong><br>
${achievement.description}
`;
achievementEl.classList.add('pulse');
} else {
// 显示随机鼓励话语
const encouragements = [
'继续努力,你会变得更强!',
'每一次游戏都是进步的机会!',
'方块世界需要你的智慧!',
'熟能生巧,加油!',
'下一局一定会更好!',
'坚持就是胜利!',
'你的反应速度在提升!',
'策略思维正在增强!'
];
const randomEncouragement = encouragements[Math.floor(Math.random() * encouragements.length)];
achievementEl.innerHTML = `💪 ${randomEncouragement}`;
achievementEl.classList.remove('pulse');
}
}
getEarnedAchievements() {
const saved = localStorage.getItem('tetris_achievements');
return saved ? JSON.parse(saved) : [];
}
saveAchievement(achievementId) {
const earned = this.getEarnedAchievements();
if (!earned.includes(achievementId)) {
earned.push(achievementId);
localStorage.setItem('tetris_achievements', JSON.stringify(earned));
}
}
// 获取历史最佳记录
getBestStats() {
const saved = localStorage.getItem('tetris_best_stats');
return saved ? JSON.parse(saved) : {
score: 0,
level: 0,
lines: 0,
maxCombo: 0
};
}
// 保存最佳记录
saveBestStats(stats) {
const best = this.getBestStats();
let updated = false;
if (stats.score > best.score) {
best.score = stats.score;
updated = true;
}
if (stats.level > best.level) {
best.level = stats.level;
updated = true;
}
if (stats.lines > best.lines) {
best.lines = stats.lines;
updated = true;
}
if (stats.maxCombo > best.maxCombo) {
best.maxCombo = stats.maxCombo;
updated = true;
}
if (updated) {
localStorage.setItem('tetris_best_stats', JSON.stringify(best));
}
return updated;
}
// 显示排行榜
showLeaderboard() {
const best = this.getBestStats();
const earned = this.getEarnedAchievements();
console.log('最佳记录:', best);
console.log('已获得成就:', earned.length + '/' + this.achievements.length);
}
}
// 高级特效系统
class GameEffects {
constructor(game) {
this.game = game;
this.particles = [];
this.effects = [];
this.init();
}
init() {
// 创建特效canvas
this.effectsCanvas = document.createElement('canvas');
this.effectsCanvas.width = this.game.canvas.width;
this.effectsCanvas.height = this.game.canvas.height;
this.effectsCanvas.style.position = 'absolute';
this.effectsCanvas.style.top = '0';
this.effectsCanvas.style.left = '0';
this.effectsCanvas.style.pointerEvents = 'none';
this.effectsCanvas.style.zIndex = '10';
this.effectsCtx = this.effectsCanvas.getContext('2d');
// 将特效canvas添加到游戏板容器中
this.game.canvas.parentElement.style.position = 'relative';
this.game.canvas.parentElement.appendChild(this.effectsCanvas);
}
// 行消除特效
lineCleared(row) {
for (let i = 0; i < 20; i++) {
this.particles.push({
x: Math.random() * this.game.canvas.width,
y: row * this.game.CELL_SIZE + this.game.CELL_SIZE / 2,
vx: (Math.random() - 0.5) * 10,
vy: (Math.random() - 0.5) * 10,
life: 1,
decay: 0.02,
color: `hsl(${Math.random() * 360}, 100%, 50%)`
});
}
}
// 方块锁定特效
pieceLocked(piece) {
const centerX = (piece.x + piece.matrix[0].length / 2) * this.game.CELL_SIZE;
const centerY = (piece.y + piece.matrix.length / 2) * this.game.CELL_SIZE;
for (let i = 0; i < 10; i++) {
this.particles.push({
x: centerX,
y: centerY,
vx: (Math.random() - 0.5) * 8,
vy: (Math.random() - 0.5) * 8,
life: 0.8,
decay: 0.03,
color: piece.color
});
}
}
// 更新特效
update() {
// 更新粒子
for (let i = this.particles.length - 1; i >= 0; i--) {
const particle = this.particles[i];
particle.x += particle.vx;
particle.y += particle.vy;
particle.life -= particle.decay;
if (particle.life <= 0) {
this.particles.splice(i, 1);
}
}
}
// 绘制特效
draw() {
this.effectsCtx.clearRect(0, 0, this.effectsCanvas.width, this.effectsCanvas.height);
// 绘制粒子
for (let particle of this.particles) {
this.effectsCtx.save();
this.effectsCtx.globalAlpha = particle.life;
this.effectsCtx.fillStyle = particle.color;
this.effectsCtx.beginPath();
this.effectsCtx.arc(particle.x, particle.y, 3, 0, Math.PI * 2);
this.effectsCtx.fill();
this.effectsCtx.restore();
}
}
}
// 创建统计系统实例
const gameStats = new GameStats();
// 在适当的地方创建特效系统
// const gameEffects = new GameEffects(game);

View File

@@ -0,0 +1,124 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>俄罗斯方块</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="game-container">
<div class="game-header">
<h1>俄罗斯方块</h1>
<div class="score-board">
<div class="score-item">
<span class="label">分数:</span>
<span id="score">0</span>
</div>
<div class="score-item">
<span class="label">等级:</span>
<span id="level">1</span>
</div>
<div class="score-item">
<span class="label">消除行数:</span>
<span id="lines">0</span>
</div>
</div>
</div>
<div class="game-main">
<div class="game-board">
<canvas id="gameCanvas" width="300" height="600"></canvas>
<div class="game-overlay" id="gameOverlay">
<div class="overlay-content">
<h2 id="overlayTitle">游戏暂停</h2>
<p id="overlayMessage">按空格键继续游戏</p>
<button id="startBtn" class="game-btn">开始游戏</button>
<button id="restartBtn" class="game-btn">重新开始</button>
</div>
</div>
</div>
<div class="game-sidebar">
<div class="next-piece">
<h3>下一个</h3>
<canvas id="nextCanvas" width="120" height="120"></canvas>
</div>
<div class="controls-info">
<h3>操作说明</h3>
<div class="control-item">
<span class="key">←→</span>
<span class="desc">移动</span>
</div>
<div class="control-item">
<span class="key"></span>
<span class="desc">快速下降</span>
</div>
<div class="control-item">
<span class="key"></span>
<span class="desc">旋转</span>
</div>
<div class="control-item">
<span class="key">空格</span>
<span class="desc">暂停/继续</span>
</div>
</div>
</div>
</div>
<!-- 手机端触摸控制 -->
<div class="mobile-controls">
<div class="mobile-controls-left">
<button class="control-btn" id="rotateBtn"></button>
<button class="control-btn" id="leftBtn"></button>
<button class="control-btn" id="downBtn"></button>
</div>
<div class="mobile-controls-right">
<button class="control-btn" id="dropBtn"></button>
<button class="control-btn" id="rightBtn"></button>
<button class="control-btn" id="pauseBtn"></button>
</div>
</div>
</div>
<!-- 游戏结束统计界面 -->
<div class="game-stats" id="gameStats">
<div class="stats-content">
<h2>游戏结束</h2>
<div class="stats-grid">
<div class="stat-item">
<span class="stat-label">最终分数</span>
<span class="stat-value" id="finalScore">0</span>
</div>
<div class="stat-item">
<span class="stat-label">达到等级</span>
<span class="stat-value" id="finalLevel">1</span>
</div>
<div class="stat-item">
<span class="stat-label">消除行数</span>
<span class="stat-value" id="finalLines">0</span>
</div>
<div class="stat-item">
<span class="stat-label">游戏时长</span>
<span class="stat-value" id="playTime">00:00</span>
</div>
<div class="stat-item">
<span class="stat-label">单次消除最大行数</span>
<span class="stat-value" id="maxCombo">0</span>
</div>
<div class="stat-item">
<span class="stat-label">平均每分钟分数</span>
<span class="stat-value" id="avgScore">0</span>
</div>
</div>
<div class="achievement" id="achievement"></div>
<button class="game-btn" id="playAgainBtn">再玩一次</button>
</div>
</div>
<script src="tetris.js"></script>
<script src="game-controls.js"></script>
<script src="game-stats.js"></script>
</body>
</html>

View File

@@ -0,0 +1,459 @@
/* 基础样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
body {
font-family: 'Arial', sans-serif;
background: linear-gradient(135deg, #1e3c72, #2a5298);
color: white;
height: 100vh;
overflow: hidden;
user-select: none;
}
/* 游戏容器 */
.game-container {
height: 100vh;
display: flex;
flex-direction: column;
max-width: 800px;
margin: 0 auto;
padding: 10px;
}
/* 游戏头部 */
.game-header {
text-align: center;
margin-bottom: 15px;
}
.game-header h1 {
font-size: 2rem;
margin-bottom: 10px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
}
.score-board {
display: flex;
justify-content: center;
gap: 20px;
flex-wrap: wrap;
}
.score-item {
background: rgba(255,255,255,0.1);
padding: 8px 15px;
border-radius: 20px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255,255,255,0.2);
}
.score-item .label {
font-size: 0.9rem;
opacity: 0.8;
}
.score-item span:last-child {
font-weight: bold;
margin-left: 5px;
}
/* 游戏主体 */
.game-main {
display: flex;
flex: 1;
gap: 20px;
justify-content: center;
align-items: flex-start;
}
.game-board {
position: relative;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
}
#gameCanvas {
display: block;
background: #1a1a1a;
border: 2px solid #333;
}
/* 游戏覆盖层 */
.game-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.8);
display: flex;
align-items: center;
justify-content: center;
backdrop-filter: blur(5px);
}
.overlay-content {
text-align: center;
background: rgba(255,255,255,0.1);
padding: 30px;
border-radius: 15px;
border: 1px solid rgba(255,255,255,0.2);
}
.overlay-content h2 {
margin-bottom: 15px;
font-size: 1.8rem;
}
.overlay-content p {
margin-bottom: 20px;
opacity: 0.8;
}
/* 游戏按钮 */
.game-btn {
background: linear-gradient(45deg, #667eea, #764ba2);
border: none;
color: white;
padding: 12px 24px;
border-radius: 25px;
cursor: pointer;
font-size: 1rem;
margin: 5px;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
.game-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0,0,0,0.3);
}
.game-btn:active {
transform: translateY(0);
}
/* 侧边栏 */
.game-sidebar {
display: flex;
flex-direction: column;
gap: 20px;
min-width: 150px;
}
.next-piece {
background: rgba(255,255,255,0.1);
padding: 15px;
border-radius: 10px;
border: 1px solid rgba(255,255,255,0.2);
text-align: center;
}
.next-piece h3 {
margin-bottom: 10px;
font-size: 1.1rem;
}
#nextCanvas {
background: #1a1a1a;
border: 1px solid #333;
border-radius: 5px;
}
.controls-info {
background: rgba(255,255,255,0.1);
padding: 15px;
border-radius: 10px;
border: 1px solid rgba(255,255,255,0.2);
}
.controls-info h3 {
margin-bottom: 15px;
font-size: 1.1rem;
text-align: center;
}
.control-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
padding: 5px 0;
}
.control-item .key {
background: rgba(255,255,255,0.2);
padding: 4px 8px;
border-radius: 4px;
font-family: monospace;
font-size: 0.9rem;
}
.control-item .desc {
font-size: 0.9rem;
opacity: 0.8;
}
/* 手机端控制 */
.mobile-controls {
display: none;
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 100%;
pointer-events: none;
z-index: 20;
}
.mobile-controls-left {
position: absolute;
left: 10px;
display: flex;
flex-direction: column;
gap: 15px;
pointer-events: auto;
}
.mobile-controls-right {
position: absolute;
right: 10px;
display: flex;
flex-direction: column;
gap: 15px;
pointer-events: auto;
}
.control-btn {
width: 55px;
height: 55px;
border: none;
border-radius: 50%;
background: linear-gradient(45deg, #667eea, #764ba2);
color: white;
font-size: 1.4rem;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 4px 15px rgba(0,0,0,0.3);
user-select: none;
backdrop-filter: blur(10px);
border: 2px solid rgba(255,255,255,0.2);
}
.control-btn:active {
transform: scale(0.9);
box-shadow: 0 2px 8px rgba(0,0,0,0.4);
}
/* 游戏统计界面 */
.game-stats {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.9);
display: none;
align-items: center;
justify-content: center;
z-index: 1000;
backdrop-filter: blur(10px);
}
.stats-content {
background: linear-gradient(135deg, #667eea, #764ba2);
padding: 30px;
border-radius: 20px;
text-align: center;
max-width: 90%;
width: 400px;
box-shadow: 0 20px 40px rgba(0,0,0,0.3);
}
.stats-content h2 {
margin-bottom: 25px;
font-size: 2rem;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin-bottom: 25px;
}
.stat-item {
background: rgba(255,255,255,0.1);
padding: 15px;
border-radius: 10px;
border: 1px solid rgba(255,255,255,0.2);
}
.stat-label {
display: block;
font-size: 0.9rem;
opacity: 0.8;
margin-bottom: 5px;
}
.stat-value {
display: block;
font-size: 1.4rem;
font-weight: bold;
}
.achievement {
background: linear-gradient(45deg, #f093fb, #f5576c);
padding: 15px;
border-radius: 10px;
margin-bottom: 25px;
font-weight: bold;
min-height: 50px;
display: flex;
align-items: center;
justify-content: center;
}
/* 响应式设计 */
@media (max-width: 768px) {
.game-container {
padding: 5px;
position: relative;
}
.game-header h1 {
font-size: 1.5rem;
}
.score-board {
gap: 10px;
}
.score-item {
padding: 6px 12px;
font-size: 0.9rem;
}
.game-main {
flex-direction: column;
align-items: center;
gap: 15px;
position: relative;
}
.game-board {
position: relative;
z-index: 10;
}
.game-sidebar {
flex-direction: row;
justify-content: center;
min-width: unset;
width: 100%;
}
.next-piece,
.controls-info {
flex: 1;
max-width: 200px;
}
.mobile-controls {
display: block;
}
#gameCanvas {
width: 250px;
height: 500px;
}
.stats-grid {
grid-template-columns: 1fr;
gap: 10px;
}
.stats-content {
padding: 20px;
width: 95%;
}
}
@media (max-width: 480px) {
.game-header h1 {
font-size: 1.3rem;
}
.score-board {
gap: 8px;
}
.score-item {
padding: 4px 8px;
font-size: 0.8rem;
}
#gameCanvas {
width: 200px;
height: 400px;
}
.control-btn {
width: 45px;
height: 45px;
font-size: 1.2rem;
}
.mobile-controls-left,
.mobile-controls-right {
gap: 10px;
}
.mobile-controls-left {
left: 5px;
}
.mobile-controls-right {
right: 5px;
}
.game-sidebar {
flex-direction: column;
gap: 10px;
}
.next-piece,
.controls-info {
max-width: unset;
}
}
/* 隐藏类 */
.hidden {
display: none !important;
}
/* 动画效果 */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.fade-in {
animation: fadeIn 0.5s ease-out;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
.pulse {
animation: pulse 2s infinite;
}

View File

@@ -0,0 +1,521 @@
// 俄罗斯方块主游戏逻辑
class TetrisGame {
constructor() {
this.canvas = document.getElementById('gameCanvas');
this.ctx = this.canvas.getContext('2d');
this.nextCanvas = document.getElementById('nextCanvas');
this.nextCtx = this.nextCanvas.getContext('2d');
// 游戏配置
this.BOARD_WIDTH = 10;
this.BOARD_HEIGHT = 20;
this.CELL_SIZE = 30;
// 游戏状态
this.board = [];
this.currentPiece = null;
this.nextPiece = null;
this.score = 0;
this.level = 1;
this.lines = 0;
this.dropTime = 0;
this.dropInterval = 1000; // 初始下降间隔(毫秒)
this.gameRunning = false;
this.gamePaused = false;
this.lastTime = 0;
// 统计数据
this.gameStartTime = 0;
this.maxCombo = 0;
this.currentCombo = 0;
// 方块类型定义
this.pieces = {
I: {
color: '#00f5ff',
matrix: [
[0, 0, 0, 0],
[1, 1, 1, 1],
[0, 0, 0, 0],
[0, 0, 0, 0]
]
},
O: {
color: '#ffff00',
matrix: [
[1, 1],
[1, 1]
]
},
T: {
color: '#800080',
matrix: [
[0, 1, 0],
[1, 1, 1],
[0, 0, 0]
]
},
S: {
color: '#00ff00',
matrix: [
[0, 1, 1],
[1, 1, 0],
[0, 0, 0]
]
},
Z: {
color: '#ff0000',
matrix: [
[1, 1, 0],
[0, 1, 1],
[0, 0, 0]
]
},
J: {
color: '#0000ff',
matrix: [
[1, 0, 0],
[1, 1, 1],
[0, 0, 0]
]
},
L: {
color: '#ffa500',
matrix: [
[0, 0, 1],
[1, 1, 1],
[0, 0, 0]
]
}
};
this.pieceTypes = Object.keys(this.pieces);
this.init();
}
init() {
this.initBoard();
this.nextPiece = this.createPiece();
this.spawnPiece();
this.updateDisplay();
this.showOverlay('游戏准备', '点击开始游戏按钮开始');
}
initBoard() {
this.board = [];
for (let row = 0; row < this.BOARD_HEIGHT; row++) {
this.board[row] = [];
for (let col = 0; col < this.BOARD_WIDTH; col++) {
this.board[row][col] = 0;
}
}
}
createPiece() {
const type = this.pieceTypes[Math.floor(Math.random() * this.pieceTypes.length)];
const piece = this.pieces[type];
return {
type: type,
color: piece.color,
matrix: this.copyMatrix(piece.matrix),
x: Math.floor(this.BOARD_WIDTH / 2) - Math.floor(piece.matrix[0].length / 2),
y: 0
};
}
copyMatrix(matrix) {
return matrix.map(row => [...row]);
}
spawnPiece() {
this.currentPiece = this.nextPiece;
this.nextPiece = this.createPiece();
this.currentPiece.x = Math.floor(this.BOARD_WIDTH / 2) - Math.floor(this.currentPiece.matrix[0].length / 2);
this.currentPiece.y = 0;
// 检查游戏结束
if (this.collision(this.currentPiece)) {
this.gameOver();
return false;
}
this.drawNextPiece();
return true;
}
collision(piece, dx = 0, dy = 0) {
const matrix = piece.matrix;
const x = piece.x + dx;
const y = piece.y + dy;
for (let row = 0; row < matrix.length; row++) {
for (let col = 0; col < matrix[row].length; col++) {
if (matrix[row][col] !== 0) {
const newX = x + col;
const newY = y + row;
if (newX < 0 || newX >= this.BOARD_WIDTH ||
newY >= this.BOARD_HEIGHT ||
(newY >= 0 && this.board[newY][newX] !== 0)) {
return true;
}
}
}
}
return false;
}
rotate(piece) {
const matrix = piece.matrix;
const N = matrix.length;
const rotated = [];
// 创建旋转后的矩阵
for (let i = 0; i < N; i++) {
rotated[i] = [];
for (let j = 0; j < N; j++) {
rotated[i][j] = matrix[N - 1 - j][i];
}
}
return rotated;
}
hardDrop() {
while (!this.collision(this.currentPiece, 0, 1)) {
this.currentPiece.y++;
this.score += 2; // 硬降给额外分数
}
this.lockPiece();
}
lockPiece() {
const matrix = this.currentPiece.matrix;
const x = this.currentPiece.x;
const y = this.currentPiece.y;
// 将方块锁定到游戏板上
for (let row = 0; row < matrix.length; row++) {
for (let col = 0; col < matrix[row].length; col++) {
if (matrix[row][col] !== 0) {
if (y + row >= 0) {
this.board[y + row][x + col] = this.currentPiece.color;
}
}
}
}
// 检查并清除完整的行
const linesCleared = this.clearLines();
if (linesCleared > 0) {
this.lines += linesCleared;
this.currentCombo = linesCleared;
this.maxCombo = Math.max(this.maxCombo, this.currentCombo);
// 计算分数 (基于消除行数和等级)
const lineScores = [0, 40, 100, 300, 1200];
this.score += lineScores[linesCleared] * this.level;
// 升级逻辑
this.level = Math.floor(this.lines / 10) + 1;
this.dropInterval = Math.max(50, 1000 - (this.level - 1) * 50);
} else {
this.currentCombo = 0;
}
// 生成下一个方块
this.spawnPiece();
this.updateDisplay();
}
clearLines() {
let linesCleared = 0;
for (let row = this.BOARD_HEIGHT - 1; row >= 0; row--) {
if (this.board[row].every(cell => cell !== 0)) {
// 移除完整的行
this.board.splice(row, 1);
// 在顶部添加新的空行
this.board.unshift(new Array(this.BOARD_WIDTH).fill(0));
linesCleared++;
row++; // 重新检查当前行
}
}
return linesCleared;
}
update(deltaTime) {
if (!this.gameRunning || this.gamePaused) return;
this.dropTime += deltaTime;
if (this.dropTime >= this.dropInterval) {
if (!this.collision(this.currentPiece, 0, 1)) {
this.currentPiece.y++;
// 自然下降不加分
} else {
this.lockPiece();
}
this.dropTime = 0;
}
}
draw() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// 绘制游戏板
this.drawBoard();
// 绘制当前方块
if (this.currentPiece) {
this.drawPiece(this.currentPiece, this.ctx);
}
// 绘制网格
this.drawGrid();
}
drawBoard() {
for (let row = 0; row < this.BOARD_HEIGHT; row++) {
for (let col = 0; col < this.BOARD_WIDTH; col++) {
if (this.board[row][col] !== 0) {
this.ctx.fillStyle = this.board[row][col];
this.ctx.fillRect(
col * this.CELL_SIZE,
row * this.CELL_SIZE,
this.CELL_SIZE,
this.CELL_SIZE
);
this.ctx.strokeStyle = '#333';
this.ctx.lineWidth = 1;
this.ctx.strokeRect(
col * this.CELL_SIZE,
row * this.CELL_SIZE,
this.CELL_SIZE,
this.CELL_SIZE
);
}
}
}
}
drawPiece(piece, context) {
context.fillStyle = piece.color;
const matrix = piece.matrix;
for (let row = 0; row < matrix.length; row++) {
for (let col = 0; col < matrix[row].length; col++) {
if (matrix[row][col] !== 0) {
context.fillRect(
(piece.x + col) * this.CELL_SIZE,
(piece.y + row) * this.CELL_SIZE,
this.CELL_SIZE,
this.CELL_SIZE
);
context.strokeStyle = '#333';
context.lineWidth = 1;
context.strokeRect(
(piece.x + col) * this.CELL_SIZE,
(piece.y + row) * this.CELL_SIZE,
this.CELL_SIZE,
this.CELL_SIZE
);
}
}
}
}
drawGrid() {
this.ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
this.ctx.lineWidth = 1;
// 垂直线
for (let col = 0; col <= this.BOARD_WIDTH; col++) {
this.ctx.beginPath();
this.ctx.moveTo(col * this.CELL_SIZE, 0);
this.ctx.lineTo(col * this.CELL_SIZE, this.canvas.height);
this.ctx.stroke();
}
// 水平线
for (let row = 0; row <= this.BOARD_HEIGHT; row++) {
this.ctx.beginPath();
this.ctx.moveTo(0, row * this.CELL_SIZE);
this.ctx.lineTo(this.canvas.width, row * this.CELL_SIZE);
this.ctx.stroke();
}
}
drawNextPiece() {
this.nextCtx.clearRect(0, 0, this.nextCanvas.width, this.nextCanvas.height);
if (this.nextPiece) {
const size = 20;
const matrix = this.nextPiece.matrix;
const offsetX = (this.nextCanvas.width - matrix[0].length * size) / 2;
const offsetY = (this.nextCanvas.height - matrix.length * size) / 2;
this.nextCtx.fillStyle = this.nextPiece.color;
for (let row = 0; row < matrix.length; row++) {
for (let col = 0; col < matrix[row].length; col++) {
if (matrix[row][col] !== 0) {
this.nextCtx.fillRect(
offsetX + col * size,
offsetY + row * size,
size,
size
);
this.nextCtx.strokeStyle = '#333';
this.nextCtx.lineWidth = 1;
this.nextCtx.strokeRect(
offsetX + col * size,
offsetY + row * size,
size,
size
);
}
}
}
}
}
updateDisplay() {
document.getElementById('score').textContent = this.score;
document.getElementById('level').textContent = this.level;
document.getElementById('lines').textContent = this.lines;
}
showOverlay(title, message) {
document.getElementById('overlayTitle').textContent = title;
document.getElementById('overlayMessage').textContent = message;
document.getElementById('gameOverlay').style.display = 'flex';
}
hideOverlay() {
document.getElementById('gameOverlay').style.display = 'none';
}
start() {
this.gameRunning = true;
this.gamePaused = false;
this.gameStartTime = Date.now();
this.hideOverlay();
this.gameLoop();
}
pause() {
if (!this.gameRunning) return;
this.gamePaused = !this.gamePaused;
if (this.gamePaused) {
this.showOverlay('游戏暂停', '按空格键继续游戏');
} else {
this.hideOverlay();
this.gameLoop();
}
}
restart() {
this.gameRunning = false;
this.gamePaused = false;
this.score = 0;
this.level = 1;
this.lines = 0;
this.dropTime = 0;
this.dropInterval = 1000;
this.maxCombo = 0;
this.currentCombo = 0;
this.init();
this.start();
}
gameOver() {
this.gameRunning = false;
this.gamePaused = false;
// 显示游戏统计
gameStats.showStats({
score: this.score,
level: this.level,
lines: this.lines,
playTime: Date.now() - this.gameStartTime,
maxCombo: this.maxCombo
});
}
gameLoop(currentTime = 0) {
if (!this.gameRunning || this.gamePaused) return;
const deltaTime = currentTime - this.lastTime;
this.lastTime = currentTime;
this.update(deltaTime);
this.draw();
requestAnimationFrame((time) => this.gameLoop(time));
}
// 移动方法
moveLeft() {
if (this.gameRunning && !this.gamePaused && this.currentPiece) {
if (!this.collision(this.currentPiece, -1, 0)) {
this.currentPiece.x--;
}
}
}
moveRight() {
if (this.gameRunning && !this.gamePaused && this.currentPiece) {
if (!this.collision(this.currentPiece, 1, 0)) {
this.currentPiece.x++;
}
}
}
moveDown() {
if (this.gameRunning && !this.gamePaused && this.currentPiece) {
if (!this.collision(this.currentPiece, 0, 1)) {
this.currentPiece.y++;
this.score += 1; // 只有主动按下键才给软降分数
}
}
}
rotatePiece() {
if (this.gameRunning && !this.gamePaused && this.currentPiece) {
const rotated = this.rotate(this.currentPiece);
const originalMatrix = this.currentPiece.matrix;
this.currentPiece.matrix = rotated;
// 检查旋转后是否会碰撞
if (this.collision(this.currentPiece)) {
// 尝试wall kick
const kicks = [
[-1, 0], [1, 0], [0, -1], [-2, 0], [2, 0]
];
let canRotate = false;
for (let kick of kicks) {
if (!this.collision(this.currentPiece, kick[0], kick[1])) {
this.currentPiece.x += kick[0];
this.currentPiece.y += kick[1];
canRotate = true;
break;
}
}
if (!canRotate) {
this.currentPiece.matrix = originalMatrix;
}
}
}
}
}
// 创建游戏实例
const game = new TetrisGame();

Binary file not shown.

View File

@@ -0,0 +1,339 @@
var c = document.getElementById("piano");
var context = c.getContext("2d");
var b = document.getElementById("background");
var context_back = b.getContext("2d");
var a = document.getElementById("score_bar");
var context_score = a.getContext("2d");
var numOfTiles = 5;
var myScore = 0;
var eachState = [false,false,false,false,false];
var myTiles = [];
var intervalTmp;
var geneTmp;
var gameSpeed = 1; // 游戏速度倍数初始为1倍
var baseInterval = 5; // 基础更新间隔(毫秒)
var baseGenerateInterval = 600; // 基础生成间隔(毫秒)
paintWindow();
paintScoreBar();
// 添加全局鼠标和触摸事件监听
c.addEventListener('click', function(e) {
handleClick(e);
});
c.addEventListener('touchstart', function(e) {
e.preventDefault();
handleTouch(e);
});
document.getElementById('start_btn').addEventListener('click',function(e){
var content = document.getElementById('start_btn');
if(content.innerHTML == "开始游戏" || content.innerHTML == "继续游戏"){
startGame();
}
else{
pauseGame();
}
});
// 重新开始按钮事件
document.getElementById('restart-btn').addEventListener('click', function(){
restartGame();
});
function startGame(){
var content = document.getElementById('start_btn');
updateGameSpeed();
document.getElementById('music').play();
content.innerHTML = "暂停游戏";
content.className = "game-btn pause-btn";
}
// 更新游戏速度
function updateGameSpeed() {
// 清除现有定时器
if (intervalTmp) clearInterval(intervalTmp);
if (geneTmp) clearInterval(geneTmp);
// 保持正常1倍速度不加速
gameSpeed = 1;
// 设置新的定时器,优化性能
intervalTmp = setInterval(upDate, Math.max(baseInterval / gameSpeed, 3));
geneTmp = setInterval(geneBlock, Math.max(baseGenerateInterval / gameSpeed, 200));
}
function pauseGame(){
var content = document.getElementById('start_btn');
document.getElementById('music').pause();
window.clearInterval(intervalTmp);
window.clearInterval(geneTmp);
content.innerHTML = "继续游戏";
content.className = "game-btn start-btn";
}
function gameOver(){
document.getElementById('music').pause();
window.clearInterval(intervalTmp);
window.clearInterval(geneTmp);
// 显示最终得分和达到的最高速度
document.getElementById('final-score-value').innerHTML = myScore;
document.getElementById('final-speed-value').innerHTML = gameSpeed.toFixed(1);
// 显示游戏结束弹窗
document.getElementById('game-over-modal').style.display = 'flex';
}
function restartGame(){
// 重置游戏状态
myScore = 0;
gameSpeed = 1; // 重置游戏速度
eachState = [false,false,false,false,false];
myTiles = [];
// 清空画布
context.clearRect(0,0,300,600);
context_back.clearRect(0,0,300,600);
context_score.clearRect(0,0,300,60);
// 重新绘制背景
paintWindow();
paintScoreBar();
// 隐藏弹窗
document.getElementById('game-over-modal').style.display = 'none';
// 重置按钮状态
var content = document.getElementById('start_btn');
content.innerHTML = "开始游戏";
content.className = "game-btn start-btn";
}
function paintScoreBar(){
// 清空画布
context_score.clearRect(0,0,300,60);
// 绘制黑色背景
context_score.fillStyle = "#333";
context_score.fillRect(0,0,300,60);
// 更新HTML显示
document.getElementById('score-value').textContent = myScore;
document.getElementById('speed-value').textContent = gameSpeed.toFixed(1);
}
function geneBlock(){
var myRand = Math.floor(Math.random()*numOfTiles);
var i;
var flag = true;
for( i = 0; i < numOfTiles; ++i){
if(!eachState[i]){
flag = false;
}
}
if(flag)return;//if mytiles array didn't have false element, then return
while(eachState[myRand])
myRand = Math.floor(Math.random()*numOfTiles);
myTiles[myRand] = new Block(myRand);
}
function paintWindow(){
// 清空背景
context_back.clearRect(0,0,300,600);
// 绘制白色背景
context_back.fillStyle = "white";
context_back.fillRect(0,0,300,600);
// 绘制分隔线
context_back.strokeStyle = "#ddd";
context_back.lineWidth = 2;
// 垂直分隔线
context_back.beginPath();
context_back.moveTo(75,0);
context_back.lineTo(75,600);
context_back.stroke();
context_back.beginPath();
context_back.moveTo(150,0);
context_back.lineTo(150,600);
context_back.stroke();
context_back.beginPath();
context_back.moveTo(225,0);
context_back.lineTo(225,600);
context_back.stroke();
// 可点击区域警戒线
context_back.strokeStyle = "#ff4444";
context_back.lineWidth = 3;
context_back.beginPath();
context_back.moveTo(0,250);
context_back.lineTo(300,250);
context_back.stroke();
// 底部失败线
context_back.strokeStyle = "#ff4444";
context_back.lineWidth = 3;
context_back.beginPath();
context_back.moveTo(0,470);
context_back.lineTo(300,470);
context_back.stroke();
}
function Block(index){
if(!eachState[index])
eachState[index] = true;
this.index = index;
this.appearPos = Math.floor(Math.random()*4);
this.width = 75;
this.height = 120;
this.color = "black";
switch(this.appearPos){
case 0:
this.x = 0;
this.y = -120;
break;
case 1:
this.x = 75;
this.y = -120;
break;
case 2:
this.x = 150;
this.y = -120;
break;
case 3:
this.x = 225;
this.y = -120;
break;
}
context.fillStyle = this.color;
context.fillRect(this.x,this.y,this.width,this.height);
this.live = true;
window.addEventListener('keydown',function(e){
myTiles[index].keyCode = e.keyCode;
});
window.addEventListener('keyup',function(e){
myTiles[index].keyCode = false;
});
}
function move(index){
if(myTiles[index].live){
myTiles[index].y += Math.ceil(gameSpeed);
// 绘制逻辑已移到upDate函数中避免重复绘制
}
}
function afterRight(index){
myScore++;
// 清除方块在upDate中统一处理绘制
myTiles[index].live = false;
eachState[index] = false;
// 立即更新得分显示
paintScoreBar();
// 每次得分都更新游戏速度,实现平滑渐进加速
updateGameSpeed();
}
// 处理鼠标点击事件
function handleClick(e) {
var rect = c.getBoundingClientRect();
var x = e.clientX - rect.left;
var y = e.clientY - rect.top;
checkHit(x, y);
}
// 处理触摸事件
function handleTouch(e) {
var rect = c.getBoundingClientRect();
var touch = e.touches[0];
var x = touch.clientX - rect.left;
var y = touch.clientY - rect.top;
checkHit(x, y);
}
// 检查点击/触摸是否命中方块
function checkHit(x, y) {
// 检查是否点击到黑色方块
for (var i = 0; i < numOfTiles; i++) {
if (eachState[i] && myTiles[i].live) {
// 检查点击位置是否在方块范围内
if (x >= myTiles[i].x && x <= myTiles[i].x + 75 &&
y >= myTiles[i].y && y <= myTiles[i].y + 120) {
// 检查方块是否在可点击区域提高到130像素以上
if (myTiles[i].y + 120 > 130) {
afterRight(i);
return true;
}
}
}
}
// 如果没有点击到任何黑色方块,且点击位置在游戏区域内,则游戏结束
if (y > 130 && y < 600) {
gameOver();
return false;
}
return false;
}
function upDate(){//check keyCode whether correct
var i;
// 清空整个游戏区域,避免重叠
context.clearRect(0, 0, 300, 600);
// 移动并重绘所有活跃的方块
for(i = 0; i < numOfTiles; ++i){
if(eachState[i]){
myTiles[i].y += Math.ceil(gameSpeed); // 使用整数移动,避免模糊
context.fillStyle = "black";
context.fillRect(myTiles[i].x, myTiles[i].y, 75, 120);
}
}
for(i = 0; i < numOfTiles; ++i){
if( eachState[i] ){
if(myTiles[i].y < 470 && myTiles[i].y >350){
switch(myTiles[i].keyCode){
case 65: //A
if(myTiles[i].x == 0)
afterRight(i);
break;
case 83: //S
if(myTiles[i].x == 75)
afterRight(i);
break;
case 68: //D
if(myTiles[i].x == 150)
afterRight(i);
break;
case 70: //F
if(myTiles[i].x == 225)
afterRight(i);
break;
}
}
if(myTiles[i].y > 470){
// 方块到达底部,游戏结束
myTiles[i].live = false;
eachState[i] = false;
gameOver();
return; // 立即退出,避免继续处理
}
}
else{//not alive
}
}
}

View File

@@ -0,0 +1,251 @@
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>别踩白方块</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #f5f5f5;
font-family: 'Arial', 'Microsoft YaHei', sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
overflow: hidden;
touch-action: manipulation;
}
.game-wrapper {
display: flex;
flex-direction: column;
align-items: center;
background: white;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
padding: 20px;
position: relative;
}
.game-title {
font-size: 24px;
font-weight: bold;
color: #333;
margin-bottom: 10px;
text-align: center;
}
.score-display {
position: relative;
width: 300px;
height: 60px;
background: #333;
border-radius: 8px 8px 0 0;
display: flex;
align-items: center;
justify-content: center;
}
.game-container {
position: relative;
width: 300px;
height: 600px;
border: 3px solid #333;
border-top: none;
border-radius: 0 0 8px 8px;
overflow: hidden;
background: white;
}
.control-panel {
margin-top: 20px;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
}
.game-btn {
padding: 12px 30px;
font-size: 16px;
font-weight: bold;
border: none;
border-radius: 25px;
cursor: pointer;
transition: all 0.3s ease;
min-width: 120px;
}
.start-btn {
background: #4CAF50;
color: white;
}
.start-btn:hover {
background: #45a049;
transform: translateY(-2px);
}
.pause-btn {
background: #ff9800;
color: white;
}
.pause-btn:hover {
background: #e68900;
transform: translateY(-2px);
}
.instructions {
text-align: center;
color: #666;
font-size: 14px;
margin-top: 10px;
line-height: 1.4;
}
/* 游戏结束弹窗 */
.game-over-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.7);
display: none;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-content {
background: white;
padding: 30px;
border-radius: 15px;
text-align: center;
box-shadow: 0 20px 40px rgba(0,0,0,0.3);
max-width: 300px;
width: 90%;
}
.modal-title {
font-size: 24px;
font-weight: bold;
color: #e74c3c;
margin-bottom: 15px;
}
.final-score, .final-speed {
font-size: 18px;
margin: 15px 0;
color: #333;
}
.final-speed {
color: #666;
font-size: 16px;
}
.modal-btn {
padding: 10px 25px;
margin: 5px;
border: none;
border-radius: 20px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
transition: all 0.3s ease;
}
.restart-btn {
background: #4CAF50;
color: white;
}
.restart-btn:hover {
background: #45a049;
}
/* 移动端适配 */
@media (max-width: 768px) {
.game-wrapper {
padding: 15px;
margin: 10px;
}
.game-title {
font-size: 20px;
}
.game-container {
width: 280px;
height: 560px;
}
.score-display {
width: 280px;
}
.instructions {
font-size: 12px;
}
}
@media (max-width: 480px) {
.game-container {
width: 260px;
height: 520px;
}
.score-display {
width: 260px;
}
}
</style>
</head>
<body>
<div class="game-wrapper">
<h1 class="game-title">别踩白方块</h1>
<div class="score-display">
<canvas id="score_bar" width="300" height="60"></canvas>
<div style="position: absolute; color: white; font-size: 14px; display: flex; justify-content: space-between; width: 260px; padding: 0 20px;">
<span>得分: <span id="score-value">0</span></span>
<span>速度: <span id="speed-value">1.0</span>x</span>
</div>
</div>
<div class="game-container">
<canvas id="background" width="300" height="600"></canvas>
<canvas id="piano" width="300" height="600" style="position: absolute; top: 0; left: 0;"></canvas>
</div>
<div class="control-panel">
<button id="start_btn" class="game-btn start-btn">开始游戏</button>
<div class="instructions">
<div>电脑端:使用 A S D F 键</div>
<div>手机端:直接点击黑色方块</div>
</div>
</div>
</div>
<!-- 游戏结束弹窗 -->
<div id="game-over-modal" class="game-over-modal">
<div class="modal-content">
<h2 class="modal-title">游戏结束</h2>
<div class="final-score">最终得分: <span id="final-score-value">0</span></div>
<div class="final-speed">最高速度: <span id="final-speed-value">1.0</span>x</div>
<button id="restart-btn" class="modal-btn restart-btn">重新开始</button>
</div>
</div>
<audio id="music" src="MUSIC.mp3" loop></audio>
<script src="game.js"></script>
</body>
</html>

View File

@@ -0,0 +1,6 @@
1.把jscsshtml分开储存每个功能单独分成每个模块避免单个文件过大问题
2.网页适配手机端和电脑端,优先优化手机竖屏游玩体验,所有游戏都是手机竖屏游戏
3.游戏都是无尽模式,玩到后期越来越困难,游戏玩法尽可能丰富多样
4.电脑端可以有键盘快捷键操作
5.最后结束游戏要统计显示玩家获得的最终游戏数据,给玩家成就感
6.尽量不要在游戏网页中出现网页滚动条,会影响玩家的游玩体验