部分修复
This commit is contained in:
537
frontend/smallgame/2048/controls.js
vendored
Normal file
537
frontend/smallgame/2048/controls.js
vendored
Normal 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;
|
||||
424
frontend/smallgame/2048/game-logic.js
Normal file
424
frontend/smallgame/2048/game-logic.js
Normal 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;
|
||||
});
|
||||
165
frontend/smallgame/2048/index.html
Normal file
165
frontend/smallgame/2048/index.html
Normal 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">×</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>
|
||||
381
frontend/smallgame/2048/statistics.js
Normal file
381
frontend/smallgame/2048/statistics.js
Normal 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();
|
||||
}
|
||||
});
|
||||
611
frontend/smallgame/2048/styles.css
Normal file
611
frontend/smallgame/2048/styles.css
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user