chore: sync local changes (2026-03-12)

This commit is contained in:
2026-03-12 18:58:26 +08:00
parent 04a4cb962a
commit 939442e061
348 changed files with 91638 additions and 92091 deletions

View File

@@ -1,437 +1,437 @@
// 游戏控制模块 - 处理键盘和触摸输入
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() {
// 确保iframe能够获得焦点并接收键盘事件
const gameContainer = document.querySelector('.container');
if (gameContainer) {
gameContainer.setAttribute('tabindex', '0');
gameContainer.focus();
}
// 为document和window都添加键盘事件监听器确保在iframe中也能工作
const handleKeyDown = (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;
}
};
// 同时监听document和window的键盘事件
document.addEventListener('keydown', handleKeyDown);
window.addEventListener('keydown', handleKeyDown);
// 确保游戏容器在点击时获得焦点
if (gameContainer) {
gameContainer.addEventListener('click', () => {
gameContainer.focus();
});
}
}
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;
}
}
// 创建全局控制实例
let gameControls;
// 页面加载完成后初始化控制
document.addEventListener('DOMContentLoaded', () => {
// 等待游戏对象初始化完成
const initControls = () => {
if (window.game2048) {
gameControls = new GameControls();
console.log('Game controls initialized successfully');
} else {
console.log('Waiting for game2048 to initialize...');
setTimeout(initControls, 100);
}
};
initControls();
});
// 导出控制实例
// 游戏控制模块 - 处理键盘和触摸输入
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() {
// 确保iframe能够获得焦点并接收键盘事件
const gameContainer = document.querySelector('.container');
if (gameContainer) {
gameContainer.setAttribute('tabindex', '0');
gameContainer.focus();
}
// 为document和window都添加键盘事件监听器确保在iframe中也能工作
const handleKeyDown = (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;
}
};
// 同时监听document和window的键盘事件
document.addEventListener('keydown', handleKeyDown);
window.addEventListener('keydown', handleKeyDown);
// 确保游戏容器在点击时获得焦点
if (gameContainer) {
gameContainer.addEventListener('click', () => {
gameContainer.focus();
});
}
}
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;
}
}
// 创建全局控制实例
let gameControls;
// 页面加载完成后初始化控制
document.addEventListener('DOMContentLoaded', () => {
// 等待游戏对象初始化完成
const initControls = () => {
if (window.game2048) {
gameControls = new GameControls();
console.log('Game controls initialized successfully');
} else {
console.log('Waiting for game2048 to initialize...');
setTimeout(initControls, 100);
}
};
initControls();
});
// 导出控制实例
window.gameControls = gameControls;

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +1,20 @@
const playerdata = [
{
"名称":"树萌芽",
"账号":"3205788256@qq.com",
"分数":1232,
"时间":"2025-09-08"
},
{
"名称":"柚大青",
"账号":"2143323382@qq.com",
"分数":132,
"时间":"2025-09-21"
},
{
"名称":"牛马",
"账号":"2973419538@qq.com",
"分数":876,
"时间":"2025-09-25"
}
const playerdata = [
{
"名称":"树萌芽",
"账号":"3205788256@qq.com",
"分数":1232,
"时间":"2025-09-08"
},
{
"名称":"柚大青",
"账号":"2143323382@qq.com",
"分数":132,
"时间":"2025-09-21"
},
{
"名称":"牛马",
"账号":"2973419538@qq.com",
"分数":876,
"时间":"2025-09-25"
}
]

View File

@@ -1,287 +1,287 @@
// 游戏控制模块
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':
case 'a':
case 'A':
e.preventDefault();
this.game.moveLeft();
this.startKeyRepeat(key, () => this.game.moveLeft());
break;
case 'ArrowRight':
case 'd':
case 'D':
e.preventDefault();
this.game.moveRight();
this.startKeyRepeat(key, () => this.game.moveRight());
break;
case 'ArrowDown':
case 's':
case 'S':
e.preventDefault();
this.game.moveDown();
this.startKeyRepeat(key, () => this.game.moveDown());
break;
case 'ArrowUp':
case 'w':
case 'W':
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);
// 游戏控制模块
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':
case 'a':
case 'A':
e.preventDefault();
this.game.moveLeft();
this.startKeyRepeat(key, () => this.game.moveLeft());
break;
case 'ArrowRight':
case 'd':
case 'D':
e.preventDefault();
this.game.moveRight();
this.startKeyRepeat(key, () => this.game.moveRight());
break;
case 'ArrowDown':
case 's':
case 'S':
e.preventDefault();
this.game.moveDown();
this.startKeyRepeat(key, () => this.game.moveDown());
break;
case 'ArrowUp':
case 'w':
case 'W':
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

@@ -1,20 +1,20 @@
const playerdata = [
{
"名称":"树萌芽",
"账号":"3205788256@qq.com",
"分数":1232,
"时间":"2025-09-08"
},
{
"名称":"柚大青",
"账号":"2143323382@qq.com",
"分数":132,
"时间":"2025-09-21"
},
{
"名称":"牛马",
"账号":"2973419538@qq.com",
"分数":876,
"时间":"2025-09-25"
}
const playerdata = [
{
"名称":"树萌芽",
"账号":"3205788256@qq.com",
"分数":1232,
"时间":"2025-09-08"
},
{
"名称":"柚大青",
"账号":"2143323382@qq.com",
"分数":132,
"时间":"2025-09-21"
},
{
"名称":"牛马",
"账号":"2973419538@qq.com",
"分数":876,
"时间":"2025-09-25"
}
]

View File

@@ -1,92 +1,92 @@
<!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>
</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="leaderboard" id="leaderboard">
<div class="leaderboard-title">本局排行榜</div>
<div class="leaderboard-wrap">
<table class="leaderboard-table">
<thead>
<tr>
<th>排名</th>
<th>名称</th>
<th>分数</th>
<th>游戏时长</th>
</tr>
</thead>
<tbody id="leaderboardBody"></tbody>
</table>
</div>
<div class="leaderboard-tip">仅显示前10名“游戏时长”为模拟数据已与您的成绩合并</div>
</div>
<button class="game-btn" id="playAgainBtn">再玩一次</button>
</div>
</div>
<script src="tetris.js"></script>
<script src="game-controls.js"></script>
<script src="gamedata.js"></script>
<script src="game-stats.js"></script>
</body>
</html>
<!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>
</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="leaderboard" id="leaderboard">
<div class="leaderboard-title">本局排行榜</div>
<div class="leaderboard-wrap">
<table class="leaderboard-table">
<thead>
<tr>
<th>排名</th>
<th>名称</th>
<th>分数</th>
<th>游戏时长</th>
</tr>
</thead>
<tbody id="leaderboardBody"></tbody>
</table>
</div>
<div class="leaderboard-tip">仅显示前10名“游戏时长”为模拟数据已与您的成绩合并</div>
</div>
<button class="game-btn" id="playAgainBtn">再玩一次</button>
</div>
</div>
<script src="tetris.js"></script>
<script src="game-controls.js"></script>
<script src="gamedata.js"></script>
<script src="game-stats.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -1,486 +1,486 @@
// 俄罗斯方块主游戏逻辑
class TetrisGame {
constructor() {
this.canvas = document.getElementById('gameCanvas');
this.ctx = this.canvas.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;
}
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();
}
}
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();
// 俄罗斯方块主游戏逻辑
class TetrisGame {
constructor() {
this.canvas = document.getElementById('gameCanvas');
this.ctx = this.canvas.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;
}
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();
}
}
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();

View File

@@ -1,413 +1,413 @@
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);
// 渲染排行榜
renderLeaderboard();
// 显示游戏结束弹窗
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 formatDateYYYYMMDD() {
var d = new Date();
var y = d.getFullYear();
var m = String(d.getMonth() + 1).padStart(2, '0');
var day = String(d.getDate()).padStart(2, '0');
return y + '-' + m + '-' + day;
}
function escapeHtml(str) {
if (typeof str !== 'string') return str;
return str
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function renderLeaderboard(){
var nowStr = formatDateYYYYMMDD();
// 当前玩家数据(模拟)
var me = {
"名称": "我",
"账号": "guest",
"分数": myScore,
"时间": nowStr,
"__isMe": true
};
// 合并现有数据与当前玩家
var data = (typeof playerdata !== 'undefined' && Array.isArray(playerdata))
? playerdata.slice() : [];
data.push(me);
// 按分数降序排序
data.sort(function(a, b){
return (b["分数"] || 0) - (a["分数"] || 0);
});
var tbody = document.getElementById('leaderboard-body');
if (!tbody) return;
tbody.innerHTML = '';
var myRank = -1;
for (var i = 0; i < data.length; i++){
var row = data[i];
var tr = document.createElement('tr');
if (row.__isMe){
myRank = i + 1;
tr.className = 'leaderboard-row-me';
}
tr.innerHTML =
'<td>' + (i + 1) + '</td>' +
'<td>' + escapeHtml(row["名称"] || '') + '</td>' +
'<td>' + (row["分数"] || 0) + '</td>' +
'<td>' + escapeHtml(row["时间"] || '') + '</td>';
// 只展示前10名
if (i < 10) tbody.appendChild(tr);
}
// 更新我的数据摘要
var rankEl = document.getElementById('my-rank');
var scoreEl = document.getElementById('my-score');
var timeEl = document.getElementById('my-time');
if (rankEl) rankEl.textContent = myRank > 0 ? myRank : '-';
if (scoreEl) scoreEl.textContent = myScore;
if (timeEl) timeEl.textContent = nowStr;
}
// 处理触摸事件
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
}
}
}
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);
// 渲染排行榜
renderLeaderboard();
// 显示游戏结束弹窗
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 formatDateYYYYMMDD() {
var d = new Date();
var y = d.getFullYear();
var m = String(d.getMonth() + 1).padStart(2, '0');
var day = String(d.getDate()).padStart(2, '0');
return y + '-' + m + '-' + day;
}
function escapeHtml(str) {
if (typeof str !== 'string') return str;
return str
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function renderLeaderboard(){
var nowStr = formatDateYYYYMMDD();
// 当前玩家数据(模拟)
var me = {
"名称": "我",
"账号": "guest",
"分数": myScore,
"时间": nowStr,
"__isMe": true
};
// 合并现有数据与当前玩家
var data = (typeof playerdata !== 'undefined' && Array.isArray(playerdata))
? playerdata.slice() : [];
data.push(me);
// 按分数降序排序
data.sort(function(a, b){
return (b["分数"] || 0) - (a["分数"] || 0);
});
var tbody = document.getElementById('leaderboard-body');
if (!tbody) return;
tbody.innerHTML = '';
var myRank = -1;
for (var i = 0; i < data.length; i++){
var row = data[i];
var tr = document.createElement('tr');
if (row.__isMe){
myRank = i + 1;
tr.className = 'leaderboard-row-me';
}
tr.innerHTML =
'<td>' + (i + 1) + '</td>' +
'<td>' + escapeHtml(row["名称"] || '') + '</td>' +
'<td>' + (row["分数"] || 0) + '</td>' +
'<td>' + escapeHtml(row["时间"] || '') + '</td>';
// 只展示前10名
if (i < 10) tbody.appendChild(tr);
}
// 更新我的数据摘要
var rankEl = document.getElementById('my-rank');
var scoreEl = document.getElementById('my-score');
var timeEl = document.getElementById('my-time');
if (rankEl) rankEl.textContent = myRank > 0 ? myRank : '-';
if (scoreEl) scoreEl.textContent = myScore;
if (timeEl) timeEl.textContent = nowStr;
}
// 处理触摸事件
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

@@ -1,20 +1,20 @@
const playerdata = [
{
"名称":"树萌芽",
"账号":"3205788256@qq.com",
"分数":1232,
"时间":"2025-09-08"
},
{
"名称":"柚大青",
"账号":"2143323382@qq.com",
"分数":132,
"时间":"2025-09-21"
},
{
"名称":"牛马",
"账号":"2973419538@qq.com",
"分数":876,
"时间":"2025-09-25"
}
const playerdata = [
{
"名称":"树萌芽",
"账号":"3205788256@qq.com",
"分数":1232,
"时间":"2025-09-08"
},
{
"名称":"柚大青",
"账号":"2143323382@qq.com",
"分数":132,
"时间":"2025-09-21"
},
{
"名称":"牛马",
"账号":"2973419538@qq.com",
"分数":876,
"时间":"2025-09-25"
}
]

View File

@@ -1,334 +1,334 @@
<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: linear-gradient(135deg, #a8e6cf 0%, #dcedc8 50%, #f1f8e9 100%);
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: linear-gradient(135deg, #e8f5e8 0%, #f1f8e9 100%);
border-radius: 15px;
box-shadow: 0 10px 30px rgba(46,125,50,0.3);
padding: 20px;
position: relative;
border: 1px solid rgba(129,199,132,0.2);
}
.game-title {
font-size: 24px;
font-weight: bold;
color: #1b5e20;
margin-bottom: 10px;
text-align: center;
text-shadow: 1px 1px 2px rgba(255,255,255,0.5);
}
.score-display {
position: relative;
width: 300px;
height: 60px;
background: linear-gradient(135deg, #2e7d32 0%, #388e3c 100%);
border-radius: 8px 8px 0 0;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 8px rgba(46,125,50,0.3);
}
.game-container {
position: relative;
width: 300px;
height: 600px;
border: 3px solid #2e7d32;
border-top: none;
border-radius: 0 0 8px 8px;
overflow: hidden;
background: white;
box-shadow: 0 4px 12px rgba(46,125,50,0.2);
}
.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: linear-gradient(45deg, #66bb6a, #4caf50);
color: white;
box-shadow: 0 4px 12px rgba(76,175,80,0.3);
}
.start-btn:hover {
background: linear-gradient(45deg, #4caf50, #388e3c);
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(76,175,80,0.4);
}
.pause-btn {
background: linear-gradient(45deg, #81c784, #66bb6a);
color: white;
box-shadow: 0 4px 12px rgba(129,199,132,0.3);
}
.pause-btn:hover {
background: linear-gradient(45deg, #66bb6a, #4caf50);
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(129,199,132,0.4);
}
.instructions {
text-align: center;
color: #2e7d32;
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: linear-gradient(135deg, #e8f5e8 0%, #f1f8e9 100%);
padding: 30px;
border-radius: 15px;
text-align: center;
box-shadow: 0 20px 40px rgba(46,125,50,0.3);
max-width: 300px;
width: 90%;
border: 1px solid rgba(129,199,132,0.3);
}
.modal-title {
font-size: 24px;
font-weight: bold;
color: #c62828;
margin-bottom: 15px;
text-shadow: 1px 1px 2px rgba(255,255,255,0.5);
}
.final-score, .final-speed {
font-size: 18px;
margin: 15px 0;
color: #1b5e20;
}
.final-speed {
color: #2e7d32;
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: linear-gradient(45deg, #66bb6a, #4caf50);
color: white;
box-shadow: 0 4px 12px rgba(76,175,80,0.3);
}
.restart-btn:hover {
background: linear-gradient(45deg, #4caf50, #388e3c);
box-shadow: 0 6px 16px rgba(76,175,80,0.4);
}
/* 排行榜样式 */
.leaderboard {
margin-top: 15px;
background: rgba(255,255,255,0.6);
border: 1px solid rgba(129,199,132,0.3);
border-radius: 10px;
overflow: hidden;
}
.leaderboard-title {
background: linear-gradient(45deg, #66bb6a, #4caf50);
color: white;
font-weight: bold;
font-size: 16px;
padding: 8px 12px;
text-align: left;
box-shadow: inset 0 -1px 0 rgba(255,255,255,0.2);
}
.leaderboard-meta {
color: #2e7d32;
font-size: 13px;
padding: 8px 12px;
border-bottom: 1px solid rgba(129,199,132,0.2);
display: flex;
justify-content: space-between;
gap: 8px;
flex-wrap: wrap;
}
.leaderboard-table {
width: 100%;
border-collapse: collapse;
}
.leaderboard-table th, .leaderboard-table td {
padding: 8px 6px;
font-size: 13px;
border-bottom: 1px solid rgba(129,199,132,0.2);
color: #1b5e20;
text-align: center;
}
.leaderboard-table th {
background: rgba(129,199,132,0.2);
font-weight: bold;
color: #1b5e20;
}
.leaderboard-row-me {
background: rgba(198,40,40,0.08);
border-left: 3px solid #c62828;
}
.leaderboard-table tr:nth-child(even) {
background: rgba(129,199,132,0.1);
}
/* 移动端适配 */
@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>
<!-- 排行榜区域 -->
<div class="leaderboard">
<div class="leaderboard-title">排行榜</div>
<div class="leaderboard-meta">
<span>我的排名:第 <span id="my-rank">-</span></span>
<span>我的分数:<span id="my-score">0</span></span>
<span>时间:<span id="my-time">--</span></span>
</div>
<table class="leaderboard-table">
<thead>
<tr>
<th>排名</th>
<th>名称</th>
<th>分数</th>
<th>时间</th>
</tr>
</thead>
<tbody id="leaderboard-body"></tbody>
</table>
</div>
<button id="restart-btn" class="modal-btn restart-btn">重新开始</button>
</div>
</div>
<audio id="music" src="MUSIC.mp3" loop></audio>
<script src="gamedata.js"></script>
<script src="game.js"></script>
</body>
<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: linear-gradient(135deg, #a8e6cf 0%, #dcedc8 50%, #f1f8e9 100%);
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: linear-gradient(135deg, #e8f5e8 0%, #f1f8e9 100%);
border-radius: 15px;
box-shadow: 0 10px 30px rgba(46,125,50,0.3);
padding: 20px;
position: relative;
border: 1px solid rgba(129,199,132,0.2);
}
.game-title {
font-size: 24px;
font-weight: bold;
color: #1b5e20;
margin-bottom: 10px;
text-align: center;
text-shadow: 1px 1px 2px rgba(255,255,255,0.5);
}
.score-display {
position: relative;
width: 300px;
height: 60px;
background: linear-gradient(135deg, #2e7d32 0%, #388e3c 100%);
border-radius: 8px 8px 0 0;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 8px rgba(46,125,50,0.3);
}
.game-container {
position: relative;
width: 300px;
height: 600px;
border: 3px solid #2e7d32;
border-top: none;
border-radius: 0 0 8px 8px;
overflow: hidden;
background: white;
box-shadow: 0 4px 12px rgba(46,125,50,0.2);
}
.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: linear-gradient(45deg, #66bb6a, #4caf50);
color: white;
box-shadow: 0 4px 12px rgba(76,175,80,0.3);
}
.start-btn:hover {
background: linear-gradient(45deg, #4caf50, #388e3c);
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(76,175,80,0.4);
}
.pause-btn {
background: linear-gradient(45deg, #81c784, #66bb6a);
color: white;
box-shadow: 0 4px 12px rgba(129,199,132,0.3);
}
.pause-btn:hover {
background: linear-gradient(45deg, #66bb6a, #4caf50);
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(129,199,132,0.4);
}
.instructions {
text-align: center;
color: #2e7d32;
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: linear-gradient(135deg, #e8f5e8 0%, #f1f8e9 100%);
padding: 30px;
border-radius: 15px;
text-align: center;
box-shadow: 0 20px 40px rgba(46,125,50,0.3);
max-width: 300px;
width: 90%;
border: 1px solid rgba(129,199,132,0.3);
}
.modal-title {
font-size: 24px;
font-weight: bold;
color: #c62828;
margin-bottom: 15px;
text-shadow: 1px 1px 2px rgba(255,255,255,0.5);
}
.final-score, .final-speed {
font-size: 18px;
margin: 15px 0;
color: #1b5e20;
}
.final-speed {
color: #2e7d32;
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: linear-gradient(45deg, #66bb6a, #4caf50);
color: white;
box-shadow: 0 4px 12px rgba(76,175,80,0.3);
}
.restart-btn:hover {
background: linear-gradient(45deg, #4caf50, #388e3c);
box-shadow: 0 6px 16px rgba(76,175,80,0.4);
}
/* 排行榜样式 */
.leaderboard {
margin-top: 15px;
background: rgba(255,255,255,0.6);
border: 1px solid rgba(129,199,132,0.3);
border-radius: 10px;
overflow: hidden;
}
.leaderboard-title {
background: linear-gradient(45deg, #66bb6a, #4caf50);
color: white;
font-weight: bold;
font-size: 16px;
padding: 8px 12px;
text-align: left;
box-shadow: inset 0 -1px 0 rgba(255,255,255,0.2);
}
.leaderboard-meta {
color: #2e7d32;
font-size: 13px;
padding: 8px 12px;
border-bottom: 1px solid rgba(129,199,132,0.2);
display: flex;
justify-content: space-between;
gap: 8px;
flex-wrap: wrap;
}
.leaderboard-table {
width: 100%;
border-collapse: collapse;
}
.leaderboard-table th, .leaderboard-table td {
padding: 8px 6px;
font-size: 13px;
border-bottom: 1px solid rgba(129,199,132,0.2);
color: #1b5e20;
text-align: center;
}
.leaderboard-table th {
background: rgba(129,199,132,0.2);
font-weight: bold;
color: #1b5e20;
}
.leaderboard-row-me {
background: rgba(198,40,40,0.08);
border-left: 3px solid #c62828;
}
.leaderboard-table tr:nth-child(even) {
background: rgba(129,199,132,0.1);
}
/* 移动端适配 */
@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>
<!-- 排行榜区域 -->
<div class="leaderboard">
<div class="leaderboard-title">排行榜</div>
<div class="leaderboard-meta">
<span>我的排名:第 <span id="my-rank">-</span></span>
<span>我的分数:<span id="my-score">0</span></span>
<span>时间:<span id="my-time">--</span></span>
</div>
<table class="leaderboard-table">
<thead>
<tr>
<th>排名</th>
<th>名称</th>
<th>分数</th>
<th>时间</th>
</tr>
</thead>
<tbody id="leaderboard-body"></tbody>
</table>
</div>
<button id="restart-btn" class="modal-btn restart-btn">重新开始</button>
</div>
</div>
<audio id="music" src="MUSIC.mp3" loop></audio>
<script src="gamedata.js"></script>
<script src="game.js"></script>
</body>
</html>

View File

@@ -1,79 +1,79 @@
/* 经典扫雷 - 手机竖屏优先 + 电脑端适配 */
:root{
--bg:#e8f5e8;
--panel:#c8e6c9;
--accent:#2e7d32;
--accent-2:#388e3c;
--text:#1b5e20;
--muted:#4caf50;
--danger:#d32f2f;
--success:#22c55e;
--warn:#f57c00;
--cell:#f1f8e9;
--cell-hover:#dcedc8;
--flag:#4caf50;
}
*{box-sizing:border-box}
html,body{height:100%;}
body{margin:0;background:linear-gradient(135deg, #a8e6cf 0%, #dcedc8 50%, #f1f8e9 100%);color:var(--text);font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,"PingFang SC","Microsoft Yahei",sans-serif;-webkit-tap-highlight-color:transparent}
.app{min-height:100dvh;display:flex;flex-direction:column;gap:12px;padding:12px;}
.header{display:flex;flex-direction:column;gap:10px;background:linear-gradient(135deg, #c8e6c9 0%, #e8f5e8 100%);border:1px solid rgba(46, 125, 50, 0.2);border-radius:14px;padding:12px 12px 10px;backdrop-filter:blur(6px);box-shadow:0 4px 8px rgba(46, 125, 50, 0.1)}
.title{margin:0;font-size:20px;letter-spacing:1px;color:#1b5e20;text-shadow:1px 1px 2px rgba(255,255,255,0.5)}
.hud{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;align-items:center}
.hud-item{display:flex;flex-direction:column;align-items:center;justify-content:center;background:linear-gradient(135deg, #a5d6a7 0%, #c8e6c9 100%);border:1px solid rgba(46, 125, 50, 0.2);border-radius:10px;padding:8px 6px;box-shadow:0 2px 4px rgba(46, 125, 50, 0.1)}
.hud-item .label{font-size:12px;color:#2e7d32}
.hud-item .value{font-size:18px;font-weight:700;color:#1b5e20}
.btn{appearance:none;border:none;background:linear-gradient(135deg, #81c784 0%, #a5d6a7 100%);color:#fff;padding:10px 12px;border-radius:10px;cursor:pointer;outline:none;transition:.15s transform,.15s background;display:inline-flex;align-items:center;justify-content:center;box-shadow:0 2px 4px rgba(46, 125, 50, 0.2)}
.btn:active{transform:scale(.98)}
.btn.primary{background:linear-gradient(135deg,var(--accent),var(--accent-2));color:#fff}
.btn.primary:active{filter:brightness(.95)}
.main{display:flex;flex-direction:column;gap:12px}
.board-wrapper{display:flex;justify-content:center;align-items:center}
.board{display:grid;gap:4px;touch-action:manipulation;user-select:none;background:linear-gradient(135deg, #c8e6c9 0%, #e8f5e8 100%);border:1px solid rgba(46, 125, 50, 0.2);border-radius:12px;padding:6px;width:100%;max-width:92vw;box-shadow:0 4px 8px rgba(46, 125, 50, 0.1)}
.cell{display:grid;place-items:center;background:var(--cell);border-radius:8px;border:1px solid rgba(46, 125, 50, 0.15);font-weight:700;color:#4caf50;box-shadow:inset 0 -1px 0 rgba(255,255,255,0.3);aspect-ratio:1/1;font-size:clamp(12px, 2.2vw, 18px)}
.cell.revealed{background:#e8f5e8;color:#2e7d32}
.cell:hover{background:var(--cell-hover)}
.cell.flag::after{content:"🚩"}
.cell.mine.revealed{background:#ffcdd2;color:#d32f2f}
.cell.mine.revealed::after{content:"💣"}
.cell[data-n="1"].revealed{color:#1976d2}
.cell[data-n="2"].revealed{color:#388e3c}
.cell[data-n="3"].revealed{color:#d32f2f}
.cell[data-n="4"].revealed{color:#7b1fa2}
.cell[data-n="5"].revealed{color:#f57c00}
.cell[data-n="6"].revealed{color:#00796b}
.cell[data-n="7"].revealed{color:#c2185b}
.cell[data-n="8"].revealed{color:#455a64}
.tips{font-size:12px;color:var(--muted);text-align:center}
.toast{position:fixed;left:50%;bottom:18px;transform:translateX(-50%);background:linear-gradient(135deg, #c8e6c9 0%, #e8f5e8 100%);border:1px solid rgba(46, 125, 50, 0.2);padding:10px 14px;border-radius:10px;color:#1b5e20;box-shadow:0 4px 8px rgba(46, 125, 50, 0.2)}
.modal-overlay{position:fixed;inset:0;background:rgba(46, 125, 50, 0.3);display:grid;place-items:center;padding:14px}
.modal{width:min(520px,92vw);background:linear-gradient(135deg, #e8f5e8 0%, #f1f8e9 100%);border:1px solid rgba(46, 125, 50, 0.2);border-radius:14px;padding:16px 14px;box-shadow:0 8px 16px rgba(46, 125, 50, 0.2)}
.modal h2{margin:4px 0 8px;font-size:20px;color:#1b5e20}
.stats{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin:8px 0 14px}
.stats .card{background:linear-gradient(135deg, #c8e6c9 0%, #e8f5e8 100%);border:1px solid rgba(46, 125, 50, 0.2);border-radius:10px;padding:10px;box-shadow:0 2px 4px rgba(46, 125, 50, 0.1)}
.stats .card .k{font-size:12px;color:#2e7d32}
.stats .card .v{font-size:18px;font-weight:700;color:#1b5e20}
.modal-actions{display:flex;gap:10px;justify-content:flex-end}
/* 响应式:手机竖屏优先 */
@media (min-width: 480px){
.title{font-size:22px}
}
@media (min-width: 640px){
.app{padding:18px}
.hud{grid-template-columns:repeat(5,minmax(0,1fr))}
}
@media (min-width: 1024px){
.board{ max-width: 420px; }
.header{ padding:10px 10px 8px; }
.hud-item{ padding:6px 4px; }
.hud-item .value{ font-size:16px; }
.title{ font-size:18px; }
}
@media (orientation: landscape) and (max-width: 900px){
.board{transform:scale(.9)}
/* 经典扫雷 - 手机竖屏优先 + 电脑端适配 */
:root{
--bg:#e8f5e8;
--panel:#c8e6c9;
--accent:#2e7d32;
--accent-2:#388e3c;
--text:#1b5e20;
--muted:#4caf50;
--danger:#d32f2f;
--success:#22c55e;
--warn:#f57c00;
--cell:#f1f8e9;
--cell-hover:#dcedc8;
--flag:#4caf50;
}
*{box-sizing:border-box}
html,body{height:100%;}
body{margin:0;background:linear-gradient(135deg, #a8e6cf 0%, #dcedc8 50%, #f1f8e9 100%);color:var(--text);font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,"PingFang SC","Microsoft Yahei",sans-serif;-webkit-tap-highlight-color:transparent}
.app{min-height:100dvh;display:flex;flex-direction:column;gap:12px;padding:12px;}
.header{display:flex;flex-direction:column;gap:10px;background:linear-gradient(135deg, #c8e6c9 0%, #e8f5e8 100%);border:1px solid rgba(46, 125, 50, 0.2);border-radius:14px;padding:12px 12px 10px;backdrop-filter:blur(6px);box-shadow:0 4px 8px rgba(46, 125, 50, 0.1)}
.title{margin:0;font-size:20px;letter-spacing:1px;color:#1b5e20;text-shadow:1px 1px 2px rgba(255,255,255,0.5)}
.hud{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;align-items:center}
.hud-item{display:flex;flex-direction:column;align-items:center;justify-content:center;background:linear-gradient(135deg, #a5d6a7 0%, #c8e6c9 100%);border:1px solid rgba(46, 125, 50, 0.2);border-radius:10px;padding:8px 6px;box-shadow:0 2px 4px rgba(46, 125, 50, 0.1)}
.hud-item .label{font-size:12px;color:#2e7d32}
.hud-item .value{font-size:18px;font-weight:700;color:#1b5e20}
.btn{appearance:none;border:none;background:linear-gradient(135deg, #81c784 0%, #a5d6a7 100%);color:#fff;padding:10px 12px;border-radius:10px;cursor:pointer;outline:none;transition:.15s transform,.15s background;display:inline-flex;align-items:center;justify-content:center;box-shadow:0 2px 4px rgba(46, 125, 50, 0.2)}
.btn:active{transform:scale(.98)}
.btn.primary{background:linear-gradient(135deg,var(--accent),var(--accent-2));color:#fff}
.btn.primary:active{filter:brightness(.95)}
.main{display:flex;flex-direction:column;gap:12px}
.board-wrapper{display:flex;justify-content:center;align-items:center}
.board{display:grid;gap:4px;touch-action:manipulation;user-select:none;background:linear-gradient(135deg, #c8e6c9 0%, #e8f5e8 100%);border:1px solid rgba(46, 125, 50, 0.2);border-radius:12px;padding:6px;width:100%;max-width:92vw;box-shadow:0 4px 8px rgba(46, 125, 50, 0.1)}
.cell{display:grid;place-items:center;background:var(--cell);border-radius:8px;border:1px solid rgba(46, 125, 50, 0.15);font-weight:700;color:#4caf50;box-shadow:inset 0 -1px 0 rgba(255,255,255,0.3);aspect-ratio:1/1;font-size:clamp(12px, 2.2vw, 18px)}
.cell.revealed{background:#e8f5e8;color:#2e7d32}
.cell:hover{background:var(--cell-hover)}
.cell.flag::after{content:"🚩"}
.cell.mine.revealed{background:#ffcdd2;color:#d32f2f}
.cell.mine.revealed::after{content:"💣"}
.cell[data-n="1"].revealed{color:#1976d2}
.cell[data-n="2"].revealed{color:#388e3c}
.cell[data-n="3"].revealed{color:#d32f2f}
.cell[data-n="4"].revealed{color:#7b1fa2}
.cell[data-n="5"].revealed{color:#f57c00}
.cell[data-n="6"].revealed{color:#00796b}
.cell[data-n="7"].revealed{color:#c2185b}
.cell[data-n="8"].revealed{color:#455a64}
.tips{font-size:12px;color:var(--muted);text-align:center}
.toast{position:fixed;left:50%;bottom:18px;transform:translateX(-50%);background:linear-gradient(135deg, #c8e6c9 0%, #e8f5e8 100%);border:1px solid rgba(46, 125, 50, 0.2);padding:10px 14px;border-radius:10px;color:#1b5e20;box-shadow:0 4px 8px rgba(46, 125, 50, 0.2)}
.modal-overlay{position:fixed;inset:0;background:rgba(46, 125, 50, 0.3);display:grid;place-items:center;padding:14px}
.modal{width:min(520px,92vw);background:linear-gradient(135deg, #e8f5e8 0%, #f1f8e9 100%);border:1px solid rgba(46, 125, 50, 0.2);border-radius:14px;padding:16px 14px;box-shadow:0 8px 16px rgba(46, 125, 50, 0.2)}
.modal h2{margin:4px 0 8px;font-size:20px;color:#1b5e20}
.stats{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin:8px 0 14px}
.stats .card{background:linear-gradient(135deg, #c8e6c9 0%, #e8f5e8 100%);border:1px solid rgba(46, 125, 50, 0.2);border-radius:10px;padding:10px;box-shadow:0 2px 4px rgba(46, 125, 50, 0.1)}
.stats .card .k{font-size:12px;color:#2e7d32}
.stats .card .v{font-size:18px;font-weight:700;color:#1b5e20}
.modal-actions{display:flex;gap:10px;justify-content:flex-end}
/* 响应式:手机竖屏优先 */
@media (min-width: 480px){
.title{font-size:22px}
}
@media (min-width: 640px){
.app{padding:18px}
.hud{grid-template-columns:repeat(5,minmax(0,1fr))}
}
@media (min-width: 1024px){
.board{ max-width: 420px; }
.header{ padding:10px 10px 8px; }
.hud-item{ padding:6px 4px; }
.hud-item .value{ font-size:16px; }
.title{ font-size:18px; }
}
@media (orientation: landscape) and (max-width: 900px){
.board{transform:scale(.9)}
}

View File

@@ -1,20 +1,20 @@
const playerdata = [
{
"名称":"树萌芽",
"账号":"3205788256@qq.com",
"分数":1232,
"时间":"2025-09-08"
},
{
"名称":"柚大青",
"账号":"2143323382@qq.com",
"分数":132,
"时间":"2025-09-21"
},
{
"名称":"牛马",
"账号":"2973419538@qq.com",
"分数":876,
"时间":"2025-09-25"
}
const playerdata = [
{
"名称":"树萌芽",
"账号":"3205788256@qq.com",
"分数":1232,
"时间":"2025-09-08"
},
{
"名称":"柚大青",
"账号":"2143323382@qq.com",
"分数":132,
"时间":"2025-09-21"
},
{
"名称":"牛马",
"账号":"2973419538@qq.com",
"分数":876,
"时间":"2025-09-25"
}
]

View File

@@ -1,56 +1,56 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="theme-color" content="#2c3e50" />
<title>经典扫雷(手机竖屏适配)</title>
<meta name="description" content="经典Windows扫雷手机竖屏与电脑端自适应支持触摸与键盘操作闯关无尽模式难度逐步增加。" />
<link rel="preload" href="./css/style.css" as="style" />
<link rel="stylesheet" href="./css/style.css" />
</head>
<body>
<div class="app">
<header class="header">
<h1 class="title">经典扫雷</h1>
<div class="hud">
<div class="hud-item"><span class="label">关卡</span><span id="level" class="value">1</span></div>
<div class="hud-item"><span class="label">雷数</span><span id="mines" class="value">0</span></div>
<div class="hud-item"><span class="label">计时</span><span id="timer" class="value">00:00</span></div>
<button id="btn-restart" class="btn primary" aria-label="重新开始">重开</button>
</div>
</header>
<main class="main">
<section class="board-wrapper">
<div id="board" class="board" role="grid" aria-label="扫雷棋盘"></div>
</section>
<section class="tips">
<p>
手机:点开格子,长按插旗;电脑:左键开格,右键插旗;键盘:方向键移动,空格/回车开格F 插旗。
</p>
</section>
</main>
<!-- 关卡完成提示 -->
<div id="toast-level" class="toast" aria-live="polite" style="display:none;"></div>
<!-- 结束统计弹窗 -->
<div id="modal-overlay" class="modal-overlay" style="display:none;">
<div class="modal" role="dialog" aria-labelledby="gameover-title" aria-modal="true">
<h2 id="gameover-title">游戏结束</h2>
<div id="stats" class="stats"></div>
<div class="modal-actions">
<button id="btn-retry" class="btn primary">重新开始</button>
<button id="btn-close" class="btn">关闭</button>
</div>
</div>
</div>
</div>
<script type="module" src="./js/main.js"></script>
</body>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="theme-color" content="#2c3e50" />
<title>经典扫雷(手机竖屏适配)</title>
<meta name="description" content="经典Windows扫雷手机竖屏与电脑端自适应支持触摸与键盘操作闯关无尽模式难度逐步增加。" />
<link rel="preload" href="./css/style.css" as="style" />
<link rel="stylesheet" href="./css/style.css" />
</head>
<body>
<div class="app">
<header class="header">
<h1 class="title">经典扫雷</h1>
<div class="hud">
<div class="hud-item"><span class="label">关卡</span><span id="level" class="value">1</span></div>
<div class="hud-item"><span class="label">雷数</span><span id="mines" class="value">0</span></div>
<div class="hud-item"><span class="label">计时</span><span id="timer" class="value">00:00</span></div>
<button id="btn-restart" class="btn primary" aria-label="重新开始">重开</button>
</div>
</header>
<main class="main">
<section class="board-wrapper">
<div id="board" class="board" role="grid" aria-label="扫雷棋盘"></div>
</section>
<section class="tips">
<p>
手机:点开格子,长按插旗;电脑:左键开格,右键插旗;键盘:方向键移动,空格/回车开格F 插旗。
</p>
</section>
</main>
<!-- 关卡完成提示 -->
<div id="toast-level" class="toast" aria-live="polite" style="display:none;"></div>
<!-- 结束统计弹窗 -->
<div id="modal-overlay" class="modal-overlay" style="display:none;">
<div class="modal" role="dialog" aria-labelledby="gameover-title" aria-modal="true">
<h2 id="gameover-title">游戏结束</h2>
<div id="stats" class="stats"></div>
<div class="modal-actions">
<button id="btn-retry" class="btn primary">重新开始</button>
<button id="btn-close" class="btn">关闭</button>
</div>
</div>
</div>
</div>
<script type="module" src="./js/main.js"></script>
</body>
</html>

View File

@@ -1,273 +1,273 @@
// 经典扫雷(手机竖屏适配 + 无尽模式 + 键盘操作)
// 模块:状态、生成、交互、键盘、统计
class RNG {
constructor(seed = Date.now()) { this.seed = seed >>> 0; }
next() { // xorshift32
let x = this.seed;
x ^= x << 13; x ^= x >>> 17; x ^= x << 5;
this.seed = x >>> 0; return this.seed / 0xffffffff;
}
range(min, max){ return Math.floor(this.next() * (max - min + 1)) + min; }
}
const GameConfig = {
start: { rows: 10, cols: 8, mineRatio: 0.12 }, // 竖屏优先:更多行
levelStep(cfg){
// 难度递增:逐步增加行列与雷密度,控制在移动端也能点击
const next = { ...cfg };
if (next.rows < 16) next.rows++;
if (next.cols < 12) next.cols += (next.rows % 2 === 0 ? 1 : 0);
next.mineRatio = Math.min(0.24, +(next.mineRatio + 0.02).toFixed(2));
return next;
}
}
const State = {
level: 1,
rows: 0,
cols: 0,
mineCount: 0,
revealed: 0,
flags: 0,
grid: [], // { mine, r, c, around, revealed, flag }
timer: 0,
timerId: null,
startTs: 0,
rng: new RNG(),
stats: { opened:0, flagged:0, mistakes:0, time:0 }
};
function el(sel){ return document.querySelector(sel); }
function make(tag, cls){ const e = document.createElement(tag); if(cls) e.className = cls; return e; }
function updateMinesHud(){ el('#mines').textContent = String(Math.max(0, State.mineCount - State.flags)); }
function startTimer(){
State.startTs = Date.now();
const timerEl = el('#timer');
clearInterval(State.timerId);
State.timerId = setInterval(() => {
State.timer = Math.floor((Date.now() - State.startTs)/1000);
const m = String(Math.floor(State.timer/60)).padStart(2,'0');
const s = String(State.timer%60).padStart(2,'0');
timerEl.textContent = `${m}:${s}`;
}, 250);
}
function stopTimer(){ clearInterval(State.timerId); State.timerId = null; }
function setupBoard(cfg){
State.rows = cfg.rows; State.cols = cfg.cols;
const total = cfg.rows * cfg.cols;
State.mineCount = Math.max(1, Math.floor(total * cfg.mineRatio));
State.revealed = 0; State.flags = 0; State.grid = [];
State.stats = { opened:0, flagged:0, mistakes:0, time:0 };
// 更新HUD
el('#level').textContent = String(State.level);
updateMinesHud();
const board = el('#board');
board.innerHTML = '';
board.style.gridTemplateColumns = `repeat(${State.cols}, 1fr)`;
// 生成空格子
for(let r=0;r<State.rows;r++){
State.grid[r] = [];
for(let c=0;c<State.cols;c++){
const cell = { mine:false, r, c, around:0, revealed:false, flag:false, el: null };
const div = make('button','cell');
div.type = 'button';
div.setAttribute('role','gridcell');
div.setAttribute('aria-label', `r${r} c${c}`);
div.addEventListener('contextmenu', e=> e.preventDefault());
// 触摸长按
let pressTimer = null; let longPressed=false;
div.addEventListener('touchstart', e => {
longPressed=false;
pressTimer = setTimeout(()=>{ longPressed=true; toggleFlag(cell); }, 420);
}, {passive:true});
div.addEventListener('touchend', e => { if(pressTimer){ clearTimeout(pressTimer); if(!longPressed) openCell(cell); } });
// 鼠标
div.addEventListener('mousedown', e => {
if(e.button===2){ toggleFlag(cell); }
else if(e.button===0){ if(cell.revealed && cell.around>0) chord(cell); else openCell(cell); }
});
cell.el = div;
State.grid[r][c] = cell;
board.appendChild(div);
}
}
// 随机埋雷
let placed=0;
const setMine = (r,c)=>{ if(!State.grid[r][c].mine){ State.grid[r][c].mine=true; placed++; } };
while(placed < State.mineCount){ setMine(State.rng.range(0,State.rows-1), State.rng.range(0,State.cols-1)); }
recomputeArounds();
// 启动计时
startTimer();
}
function visitNeighbors(r,c, cb){
for(let dr=-1; dr<=1; dr++){
for(let dc=-1; dc<=1; dc++){
if(dr===0 && dc===0) continue;
const nr=r+dr, nc=c+dc;
if(nr>=0 && nr<State.rows && nc>=0 && nc<State.cols) cb(nr,nc);
}
}
}
function countFlagsAround(r,c){ let n=0; visitNeighbors(r,c,(nr,nc)=>{ if(State.grid[nr][nc].flag) n++; }); return n; }
function chord(cell){
if(!cell.revealed || cell.around<=0) return;
const flagged = countFlagsAround(cell.r, cell.c);
if(flagged === cell.around){
visitNeighbors(cell.r, cell.c, (nr,nc)=>{
const ncell = State.grid[nr][nc];
if(!ncell.revealed && !ncell.flag){ openCell(ncell); }
});
}
}
function recomputeArounds(){
for(let r=0;r<State.rows;r++){
for(let c=0;c<State.cols;c++){
if(State.grid[r][c].mine){ State.grid[r][c].around = 0; continue; }
let n=0; visitNeighbors(r,c,(nr,nc)=>{ if(State.grid[nr][nc].mine) n++; });
State.grid[r][c].around = n;
}
}
}
function safeFirstClick(badCell){
// 移除当前雷并将其放到其他非雷位置
badCell.mine = false;
while(true){
const r = State.rng.range(0, State.rows-1);
const c = State.rng.range(0, State.cols-1);
const target = State.grid[r][c];
if(target!==badCell && !target.mine){ target.mine = true; break; }
}
recomputeArounds();
}
function openCell(cell){
if(cell.revealed || cell.flag) return;
// 首次点击必定安全:若第一次就点到雷,则移动该雷并重算数字
if(State.revealed===0 && cell.mine){
safeFirstClick(cell);
}
cell.revealed = true; State.revealed++; State.stats.opened++;
cell.el.classList.add('revealed');
if(cell.mine){
cell.el.classList.add('mine');
endGame(false);
return;
}
if(cell.around>0){ cell.el.dataset.n = cell.around; cell.el.textContent = cell.around; }
else{
// flood fill
visitNeighbors(cell.r, cell.c, (nr,nc)=>{
const ncell = State.grid[nr][nc];
if(!ncell.revealed && !ncell.mine) openCell(ncell);
});
}
checkWin();
}
function toggleFlag(cell){
if(cell.revealed) return;
cell.flag = !cell.flag;
State.flags += cell.flag ? 1 : -1;
State.stats.flagged += cell.flag ? 1 : 0;
cell.el.classList.toggle('flag', cell.flag);
updateMinesHud();
}
function checkWin(){
const totalSafe = State.rows*State.cols - State.mineCount;
if(State.revealed >= totalSafe){
// 通关 -> 进入下一关
showToast(`${State.level} 关完成!`);
stopTimer();
setTimeout(()=>{
State.level++;
const nextCfg = GameConfig.levelStep({ rows: State.rows, cols: State.cols, mineRatio: State.mineCount/(State.rows*State.cols) });
setupBoard(nextCfg);
}, 600);
}
}
function showToast(msg){
const t = el('#toast-level');
t.textContent = msg; t.style.display='block';
t.animate([
{ transform:'translate(-50%, 20px)', opacity:0 },
{ transform:'translate(-50%, 0)', opacity:1, offset:.2 },
{ transform:'translate(-50%, 0)', opacity:1, offset:.8 },
{ transform:'translate(-50%, 10px)', opacity:0 }
], { duration:1200, easing:'ease' }).onfinish = ()=> t.style.display='none';
}
function endGame(win){
stopTimer();
// 展示所有雷
for(let r=0;r<State.rows;r++){
for(let c=0;c<State.cols;c++){
const cell = State.grid[r][c];
if(cell.mine){ cell.el.classList.add('revealed','mine'); }
}
}
State.stats.time = State.timer;
const statsHtml = `
<div class="stats">
<div class="card"><div class="k">关卡</div><div class="v">${State.level}</div></div>
<div class="card"><div class="k">总用时</div><div class="v">${formatTime(State.timer)}</div></div>
<div class="card"><div class="k">开格</div><div class="v">${State.stats.opened}</div></div>
<div class="card"><div class="k">插旗</div><div class="v">${State.stats.flagged}</div></div>
</div>
<p style="color:#94a3b8;margin:6px 0 10px">再接再厉,挑战更高难度!</p>
`;
el('#stats').innerHTML = statsHtml;
el('#modal-overlay').style.display = 'grid';
}
function formatTime(sec){ const m=Math.floor(sec/60), s=sec%60; return `${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')}`; }
function bindUI(){
el('#btn-restart').addEventListener('click', ()=> restart());
el('#btn-retry').addEventListener('click', ()=> restart());
el('#btn-close').addEventListener('click', ()=>{ el('#modal-overlay').style.display='none'; });
}
function restart(){
el('#modal-overlay').style.display='none';
State.level = 1; setupBoard(GameConfig.start);
}
// 键盘操作(电脑端)
let kb = { r:0, c:0 };
function bindKeyboard(){
document.addEventListener('keydown', (e)=>{
const key = e.key.toLowerCase();
if(['arrowup','w'].includes(key)) move(-1,0);
else if(['arrowdown','s'].includes(key)) move(1,0);
else if(['arrowleft','a'].includes(key)) move(0,-1);
else if(['arrowright','d'].includes(key)) move(0,1);
else if(key==='f'){ toggleFlag(State.grid[kb.r][kb.c]); highlightFocus(); }
else if(key===' ' || key==='enter'){ openCell(State.grid[kb.r][kb.c]); highlightFocus(); }
});
}
function move(dr,dc){ kb.r = clamp(kb.r+dr,0,State.rows-1); kb.c = clamp(kb.c+dc,0,State.cols-1); highlightFocus(); }
function clamp(v,min,max){ return Math.max(min, Math.min(max, v)); }
function highlightFocus(){
// 简单高亮当前聚焦格
for(let r=0;r<State.rows;r++){
for(let c=0;c<State.cols;c++){
const el = State.grid[r][c].el;
el.style.outline = (r===kb.r && c===kb.c) ? '2px solid rgba(96,165,250,.9)' : 'none';
}
}
}
// 初始化
bindUI(); bindKeyboard();
setupBoard(GameConfig.start);
// 经典扫雷(手机竖屏适配 + 无尽模式 + 键盘操作)
// 模块:状态、生成、交互、键盘、统计
class RNG {
constructor(seed = Date.now()) { this.seed = seed >>> 0; }
next() { // xorshift32
let x = this.seed;
x ^= x << 13; x ^= x >>> 17; x ^= x << 5;
this.seed = x >>> 0; return this.seed / 0xffffffff;
}
range(min, max){ return Math.floor(this.next() * (max - min + 1)) + min; }
}
const GameConfig = {
start: { rows: 10, cols: 8, mineRatio: 0.12 }, // 竖屏优先:更多行
levelStep(cfg){
// 难度递增:逐步增加行列与雷密度,控制在移动端也能点击
const next = { ...cfg };
if (next.rows < 16) next.rows++;
if (next.cols < 12) next.cols += (next.rows % 2 === 0 ? 1 : 0);
next.mineRatio = Math.min(0.24, +(next.mineRatio + 0.02).toFixed(2));
return next;
}
}
const State = {
level: 1,
rows: 0,
cols: 0,
mineCount: 0,
revealed: 0,
flags: 0,
grid: [], // { mine, r, c, around, revealed, flag }
timer: 0,
timerId: null,
startTs: 0,
rng: new RNG(),
stats: { opened:0, flagged:0, mistakes:0, time:0 }
};
function el(sel){ return document.querySelector(sel); }
function make(tag, cls){ const e = document.createElement(tag); if(cls) e.className = cls; return e; }
function updateMinesHud(){ el('#mines').textContent = String(Math.max(0, State.mineCount - State.flags)); }
function startTimer(){
State.startTs = Date.now();
const timerEl = el('#timer');
clearInterval(State.timerId);
State.timerId = setInterval(() => {
State.timer = Math.floor((Date.now() - State.startTs)/1000);
const m = String(Math.floor(State.timer/60)).padStart(2,'0');
const s = String(State.timer%60).padStart(2,'0');
timerEl.textContent = `${m}:${s}`;
}, 250);
}
function stopTimer(){ clearInterval(State.timerId); State.timerId = null; }
function setupBoard(cfg){
State.rows = cfg.rows; State.cols = cfg.cols;
const total = cfg.rows * cfg.cols;
State.mineCount = Math.max(1, Math.floor(total * cfg.mineRatio));
State.revealed = 0; State.flags = 0; State.grid = [];
State.stats = { opened:0, flagged:0, mistakes:0, time:0 };
// 更新HUD
el('#level').textContent = String(State.level);
updateMinesHud();
const board = el('#board');
board.innerHTML = '';
board.style.gridTemplateColumns = `repeat(${State.cols}, 1fr)`;
// 生成空格子
for(let r=0;r<State.rows;r++){
State.grid[r] = [];
for(let c=0;c<State.cols;c++){
const cell = { mine:false, r, c, around:0, revealed:false, flag:false, el: null };
const div = make('button','cell');
div.type = 'button';
div.setAttribute('role','gridcell');
div.setAttribute('aria-label', `r${r} c${c}`);
div.addEventListener('contextmenu', e=> e.preventDefault());
// 触摸长按
let pressTimer = null; let longPressed=false;
div.addEventListener('touchstart', e => {
longPressed=false;
pressTimer = setTimeout(()=>{ longPressed=true; toggleFlag(cell); }, 420);
}, {passive:true});
div.addEventListener('touchend', e => { if(pressTimer){ clearTimeout(pressTimer); if(!longPressed) openCell(cell); } });
// 鼠标
div.addEventListener('mousedown', e => {
if(e.button===2){ toggleFlag(cell); }
else if(e.button===0){ if(cell.revealed && cell.around>0) chord(cell); else openCell(cell); }
});
cell.el = div;
State.grid[r][c] = cell;
board.appendChild(div);
}
}
// 随机埋雷
let placed=0;
const setMine = (r,c)=>{ if(!State.grid[r][c].mine){ State.grid[r][c].mine=true; placed++; } };
while(placed < State.mineCount){ setMine(State.rng.range(0,State.rows-1), State.rng.range(0,State.cols-1)); }
recomputeArounds();
// 启动计时
startTimer();
}
function visitNeighbors(r,c, cb){
for(let dr=-1; dr<=1; dr++){
for(let dc=-1; dc<=1; dc++){
if(dr===0 && dc===0) continue;
const nr=r+dr, nc=c+dc;
if(nr>=0 && nr<State.rows && nc>=0 && nc<State.cols) cb(nr,nc);
}
}
}
function countFlagsAround(r,c){ let n=0; visitNeighbors(r,c,(nr,nc)=>{ if(State.grid[nr][nc].flag) n++; }); return n; }
function chord(cell){
if(!cell.revealed || cell.around<=0) return;
const flagged = countFlagsAround(cell.r, cell.c);
if(flagged === cell.around){
visitNeighbors(cell.r, cell.c, (nr,nc)=>{
const ncell = State.grid[nr][nc];
if(!ncell.revealed && !ncell.flag){ openCell(ncell); }
});
}
}
function recomputeArounds(){
for(let r=0;r<State.rows;r++){
for(let c=0;c<State.cols;c++){
if(State.grid[r][c].mine){ State.grid[r][c].around = 0; continue; }
let n=0; visitNeighbors(r,c,(nr,nc)=>{ if(State.grid[nr][nc].mine) n++; });
State.grid[r][c].around = n;
}
}
}
function safeFirstClick(badCell){
// 移除当前雷并将其放到其他非雷位置
badCell.mine = false;
while(true){
const r = State.rng.range(0, State.rows-1);
const c = State.rng.range(0, State.cols-1);
const target = State.grid[r][c];
if(target!==badCell && !target.mine){ target.mine = true; break; }
}
recomputeArounds();
}
function openCell(cell){
if(cell.revealed || cell.flag) return;
// 首次点击必定安全:若第一次就点到雷,则移动该雷并重算数字
if(State.revealed===0 && cell.mine){
safeFirstClick(cell);
}
cell.revealed = true; State.revealed++; State.stats.opened++;
cell.el.classList.add('revealed');
if(cell.mine){
cell.el.classList.add('mine');
endGame(false);
return;
}
if(cell.around>0){ cell.el.dataset.n = cell.around; cell.el.textContent = cell.around; }
else{
// flood fill
visitNeighbors(cell.r, cell.c, (nr,nc)=>{
const ncell = State.grid[nr][nc];
if(!ncell.revealed && !ncell.mine) openCell(ncell);
});
}
checkWin();
}
function toggleFlag(cell){
if(cell.revealed) return;
cell.flag = !cell.flag;
State.flags += cell.flag ? 1 : -1;
State.stats.flagged += cell.flag ? 1 : 0;
cell.el.classList.toggle('flag', cell.flag);
updateMinesHud();
}
function checkWin(){
const totalSafe = State.rows*State.cols - State.mineCount;
if(State.revealed >= totalSafe){
// 通关 -> 进入下一关
showToast(`${State.level} 关完成!`);
stopTimer();
setTimeout(()=>{
State.level++;
const nextCfg = GameConfig.levelStep({ rows: State.rows, cols: State.cols, mineRatio: State.mineCount/(State.rows*State.cols) });
setupBoard(nextCfg);
}, 600);
}
}
function showToast(msg){
const t = el('#toast-level');
t.textContent = msg; t.style.display='block';
t.animate([
{ transform:'translate(-50%, 20px)', opacity:0 },
{ transform:'translate(-50%, 0)', opacity:1, offset:.2 },
{ transform:'translate(-50%, 0)', opacity:1, offset:.8 },
{ transform:'translate(-50%, 10px)', opacity:0 }
], { duration:1200, easing:'ease' }).onfinish = ()=> t.style.display='none';
}
function endGame(win){
stopTimer();
// 展示所有雷
for(let r=0;r<State.rows;r++){
for(let c=0;c<State.cols;c++){
const cell = State.grid[r][c];
if(cell.mine){ cell.el.classList.add('revealed','mine'); }
}
}
State.stats.time = State.timer;
const statsHtml = `
<div class="stats">
<div class="card"><div class="k">关卡</div><div class="v">${State.level}</div></div>
<div class="card"><div class="k">总用时</div><div class="v">${formatTime(State.timer)}</div></div>
<div class="card"><div class="k">开格</div><div class="v">${State.stats.opened}</div></div>
<div class="card"><div class="k">插旗</div><div class="v">${State.stats.flagged}</div></div>
</div>
<p style="color:#94a3b8;margin:6px 0 10px">再接再厉,挑战更高难度!</p>
`;
el('#stats').innerHTML = statsHtml;
el('#modal-overlay').style.display = 'grid';
}
function formatTime(sec){ const m=Math.floor(sec/60), s=sec%60; return `${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')}`; }
function bindUI(){
el('#btn-restart').addEventListener('click', ()=> restart());
el('#btn-retry').addEventListener('click', ()=> restart());
el('#btn-close').addEventListener('click', ()=>{ el('#modal-overlay').style.display='none'; });
}
function restart(){
el('#modal-overlay').style.display='none';
State.level = 1; setupBoard(GameConfig.start);
}
// 键盘操作(电脑端)
let kb = { r:0, c:0 };
function bindKeyboard(){
document.addEventListener('keydown', (e)=>{
const key = e.key.toLowerCase();
if(['arrowup','w'].includes(key)) move(-1,0);
else if(['arrowdown','s'].includes(key)) move(1,0);
else if(['arrowleft','a'].includes(key)) move(0,-1);
else if(['arrowright','d'].includes(key)) move(0,1);
else if(key==='f'){ toggleFlag(State.grid[kb.r][kb.c]); highlightFocus(); }
else if(key===' ' || key==='enter'){ openCell(State.grid[kb.r][kb.c]); highlightFocus(); }
});
}
function move(dr,dc){ kb.r = clamp(kb.r+dr,0,State.rows-1); kb.c = clamp(kb.c+dc,0,State.cols-1); highlightFocus(); }
function clamp(v,min,max){ return Math.max(min, Math.min(max, v)); }
function highlightFocus(){
// 简单高亮当前聚焦格
for(let r=0;r<State.rows;r++){
for(let c=0;c<State.cols;c++){
const el = State.grid[r][c].el;
el.style.outline = (r===kb.r && c===kb.c) ? '2px solid rgba(96,165,250,.9)' : 'none';
}
}
}
// 初始化
bindUI(); bindKeyboard();
setupBoard(GameConfig.start);
highlightFocus();

View File

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

View File

@@ -1,191 +1,191 @@
class GameControls {
constructor(game) {
this.game = game;
this.initControls();
this.initTouchControls();
}
initControls() {
// 重新开始按钮
document.getElementById('restartBtn').addEventListener('click', () => {
this.game.restart();
});
// 键盘快捷键
this.initKeyboardShortcuts();
}
initKeyboardShortcuts() {
document.addEventListener('keydown', (e) => {
switch(e.key) {
case 'r':
case 'R':
if (this.game.gameOver) {
this.game.restart();
}
break;
}
});
}
initTouchControls() {
const canvas = document.getElementById('gameCanvas');
let isDragging = false;
let lastTouchX = 0;
let lastTouchY = 0;
let lastDirectionChange = 0;
const directionChangeDelay = 200; // 防止方向变化过快
// 触摸开始
canvas.addEventListener('touchstart', (e) => {
isDragging = true;
lastTouchX = e.touches[0].clientX;
lastTouchY = e.touches[0].clientY;
e.preventDefault();
}, { passive: false });
// 拖动过程中实时检测方向
canvas.addEventListener('touchmove', (e) => {
if (!isDragging) return;
const currentTime = Date.now();
if (currentTime - lastDirectionChange < directionChangeDelay) {
e.preventDefault();
return;
}
const currentTouchX = e.touches[0].clientX;
const currentTouchY = e.touches[0].clientY;
const deltaX = currentTouchX - lastTouchX;
const deltaY = currentTouchY - lastTouchY;
const minDragDistance = 20; // 最小拖动距离
// 检查是否达到最小拖动距离
if (Math.abs(deltaX) > minDragDistance || Math.abs(deltaY) > minDragDistance) {
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// 水平拖动
if (deltaX > 0) {
this.game.changeDirection(1, 0); // 向右拖动
} else {
this.game.changeDirection(-1, 0); // 向左拖动
}
} else {
// 垂直拖动
if (deltaY > 0) {
this.game.changeDirection(0, 1); // 向下拖动
} else {
this.game.changeDirection(0, -1); // 向上拖动
}
}
// 更新最后触摸位置和方向变化时间
lastTouchX = currentTouchX;
lastTouchY = currentTouchY;
lastDirectionChange = currentTime;
// 添加触觉反馈
this.vibrate(30);
}
e.preventDefault();
}, { passive: false });
// 触摸结束
canvas.addEventListener('touchend', (e) => {
isDragging = false;
e.preventDefault();
}, { passive: false });
// 触摸取消
canvas.addEventListener('touchcancel', (e) => {
isDragging = false;
e.preventDefault();
}, { passive: false });
// 防止移动端页面滚动
document.addEventListener('touchmove', (e) => {
if (e.target.closest('.game-container')) {
e.preventDefault();
}
}, { passive: false });
// 添加鼠标拖动支持(用于桌面测试)
this.initMouseDragControls(canvas);
}
// 鼠标拖动控制(用于桌面测试)
initMouseDragControls(canvas) {
let isDragging = false;
let lastMouseX = 0;
let lastMouseY = 0;
let lastDirectionChange = 0;
const directionChangeDelay = 200;
canvas.addEventListener('mousedown', (e) => {
isDragging = true;
lastMouseX = e.clientX;
lastMouseY = e.clientY;
e.preventDefault();
});
canvas.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const currentTime = Date.now();
if (currentTime - lastDirectionChange < directionChangeDelay) {
return;
}
const currentMouseX = e.clientX;
const currentMouseY = e.clientY;
const deltaX = currentMouseX - lastMouseX;
const deltaY = currentMouseY - lastMouseY;
const minDragDistance = 20;
if (Math.abs(deltaX) > minDragDistance || Math.abs(deltaY) > minDragDistance) {
if (Math.abs(deltaX) > Math.abs(deltaY)) {
if (deltaX > 0) {
this.game.changeDirection(1, 0); // 向右拖动
} else {
this.game.changeDirection(-1, 0); // 向左拖动
}
} else {
if (deltaY > 0) {
this.game.changeDirection(0, 1); // 向下拖动
} else {
this.game.changeDirection(0, -1); // 向上拖动
}
}
lastMouseX = currentMouseX;
lastMouseY = currentMouseY;
lastDirectionChange = currentTime;
}
});
canvas.addEventListener('mouseup', () => {
isDragging = false;
});
canvas.addEventListener('mouseleave', () => {
isDragging = false;
});
}
// 震动反馈(移动端)
vibrate(duration = 50) {
if ('vibrate' in navigator) {
navigator.vibrate(duration);
}
}
}
// 初始化控制模块
document.addEventListener('DOMContentLoaded', () => {
// 等待游戏实例创建后初始化控制
setTimeout(() => {
new GameControls(game);
}, 100);
class GameControls {
constructor(game) {
this.game = game;
this.initControls();
this.initTouchControls();
}
initControls() {
// 重新开始按钮
document.getElementById('restartBtn').addEventListener('click', () => {
this.game.restart();
});
// 键盘快捷键
this.initKeyboardShortcuts();
}
initKeyboardShortcuts() {
document.addEventListener('keydown', (e) => {
switch(e.key) {
case 'r':
case 'R':
if (this.game.gameOver) {
this.game.restart();
}
break;
}
});
}
initTouchControls() {
const canvas = document.getElementById('gameCanvas');
let isDragging = false;
let lastTouchX = 0;
let lastTouchY = 0;
let lastDirectionChange = 0;
const directionChangeDelay = 200; // 防止方向变化过快
// 触摸开始
canvas.addEventListener('touchstart', (e) => {
isDragging = true;
lastTouchX = e.touches[0].clientX;
lastTouchY = e.touches[0].clientY;
e.preventDefault();
}, { passive: false });
// 拖动过程中实时检测方向
canvas.addEventListener('touchmove', (e) => {
if (!isDragging) return;
const currentTime = Date.now();
if (currentTime - lastDirectionChange < directionChangeDelay) {
e.preventDefault();
return;
}
const currentTouchX = e.touches[0].clientX;
const currentTouchY = e.touches[0].clientY;
const deltaX = currentTouchX - lastTouchX;
const deltaY = currentTouchY - lastTouchY;
const minDragDistance = 20; // 最小拖动距离
// 检查是否达到最小拖动距离
if (Math.abs(deltaX) > minDragDistance || Math.abs(deltaY) > minDragDistance) {
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// 水平拖动
if (deltaX > 0) {
this.game.changeDirection(1, 0); // 向右拖动
} else {
this.game.changeDirection(-1, 0); // 向左拖动
}
} else {
// 垂直拖动
if (deltaY > 0) {
this.game.changeDirection(0, 1); // 向下拖动
} else {
this.game.changeDirection(0, -1); // 向上拖动
}
}
// 更新最后触摸位置和方向变化时间
lastTouchX = currentTouchX;
lastTouchY = currentTouchY;
lastDirectionChange = currentTime;
// 添加触觉反馈
this.vibrate(30);
}
e.preventDefault();
}, { passive: false });
// 触摸结束
canvas.addEventListener('touchend', (e) => {
isDragging = false;
e.preventDefault();
}, { passive: false });
// 触摸取消
canvas.addEventListener('touchcancel', (e) => {
isDragging = false;
e.preventDefault();
}, { passive: false });
// 防止移动端页面滚动
document.addEventListener('touchmove', (e) => {
if (e.target.closest('.game-container')) {
e.preventDefault();
}
}, { passive: false });
// 添加鼠标拖动支持(用于桌面测试)
this.initMouseDragControls(canvas);
}
// 鼠标拖动控制(用于桌面测试)
initMouseDragControls(canvas) {
let isDragging = false;
let lastMouseX = 0;
let lastMouseY = 0;
let lastDirectionChange = 0;
const directionChangeDelay = 200;
canvas.addEventListener('mousedown', (e) => {
isDragging = true;
lastMouseX = e.clientX;
lastMouseY = e.clientY;
e.preventDefault();
});
canvas.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const currentTime = Date.now();
if (currentTime - lastDirectionChange < directionChangeDelay) {
return;
}
const currentMouseX = e.clientX;
const currentMouseY = e.clientY;
const deltaX = currentMouseX - lastMouseX;
const deltaY = currentMouseY - lastMouseY;
const minDragDistance = 20;
if (Math.abs(deltaX) > minDragDistance || Math.abs(deltaY) > minDragDistance) {
if (Math.abs(deltaX) > Math.abs(deltaY)) {
if (deltaX > 0) {
this.game.changeDirection(1, 0); // 向右拖动
} else {
this.game.changeDirection(-1, 0); // 向左拖动
}
} else {
if (deltaY > 0) {
this.game.changeDirection(0, 1); // 向下拖动
} else {
this.game.changeDirection(0, -1); // 向上拖动
}
}
lastMouseX = currentMouseX;
lastMouseY = currentMouseY;
lastDirectionChange = currentTime;
}
});
canvas.addEventListener('mouseup', () => {
isDragging = false;
});
canvas.addEventListener('mouseleave', () => {
isDragging = false;
});
}
// 震动反馈(移动端)
vibrate(duration = 50) {
if ('vibrate' in navigator) {
navigator.vibrate(duration);
}
}
}
// 初始化控制模块
document.addEventListener('DOMContentLoaded', () => {
// 等待游戏实例创建后初始化控制
setTimeout(() => {
new GameControls(game);
}, 100);
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,268 +1,268 @@
class GameStatistics {
constructor() {
this.highScores = JSON.parse(localStorage.getItem('snakeHighScores')) || [];
this.sessionStats = {
gamesPlayed: 0,
totalScore: 0,
maxLength: 0,
maxLevel: 0,
totalTime: 0
};
this.init();
}
init() {
// 恢复会话统计(如果存在)
const savedSession = localStorage.getItem('snakeSessionStats');
if (savedSession) {
this.sessionStats = JSON.parse(savedSession);
}
// 监听游戏事件
this.setupEventListeners();
}
setupEventListeners() {
// 监听自定义游戏事件
document.addEventListener('gameOver', (e) => {
this.handleGameOver(e.detail);
});
document.addEventListener('foodEaten', (e) => {
this.handleFoodEaten(e.detail);
});
document.addEventListener('levelUp', (e) => {
this.handleLevelUp(e.detail);
});
}
handleGameOver(gameData) {
this.sessionStats.gamesPlayed++;
this.sessionStats.totalScore += gameData.score;
this.sessionStats.maxLength = Math.max(this.sessionStats.maxLength, gameData.length);
this.sessionStats.maxLevel = Math.max(this.sessionStats.maxLevel, gameData.level);
this.sessionStats.totalTime += gameData.gameTime;
// 保存会话统计
localStorage.setItem('snakeSessionStats', JSON.stringify(this.sessionStats));
// 检查是否进入高分榜
this.checkHighScore(gameData);
// 显示统计信息
this.displaySessionStats();
}
handleFoodEaten(foodData) {
// 可以记录特殊食物统计等
console.log('食物被吃掉:', foodData);
}
handleLevelUp(levelData) {
// 等级提升统计
console.log('等级提升到:', levelData.level);
}
checkHighScore(gameData) {
const highScoreEntry = {
score: gameData.score,
length: gameData.length,
level: gameData.level,
time: gameData.gameTime,
date: new Date().toLocaleDateString(),
timestamp: Date.now()
};
// 添加到高分榜
this.highScores.push(highScoreEntry);
// 按分数排序(降序)
this.highScores.sort((a, b) => b.score - a.score);
// 只保留前10名
this.highScores = this.highScores.slice(0, 10);
// 保存到本地存储
localStorage.setItem('snakeHighScores', JSON.stringify(this.highScores));
}
displaySessionStats() {
const statsElement = document.createElement('div');
statsElement.className = 'session-stats';
statsElement.innerHTML = `
<h3>本次会话统计</h3>
<p>游戏次数: ${this.sessionStats.gamesPlayed}</p>
<p>总得分: ${this.sessionStats.totalScore}</p>
<p>最高长度: ${this.sessionStats.maxLength}</p>
<p>最高等级: ${this.sessionStats.maxLevel}</p>
<p>总游戏时间: ${Math.floor(this.sessionStats.totalTime / 60)}分钟</p>
<p>平均得分: ${Math.round(this.sessionStats.totalScore / this.sessionStats.gamesPlayed)}</p>
`;
// 添加到游戏结束模态框
const statsContainer = document.querySelector('.stats');
if (statsContainer && !document.querySelector('.session-stats')) {
statsContainer.appendChild(statsElement);
}
}
displayHighScores() {
const highScoresElement = document.createElement('div');
highScoresElement.className = 'high-scores';
if (this.highScores.length > 0) {
highScoresElement.innerHTML = `
<h3>🏆 高分榜</h3>
${this.highScores.map((score, index) => `
<div class="score-item ${index === 0 ? 'first-place' : ''}">
<span class="rank">${index + 1}.</span>
<span class="score">${score.score}分</span>
<span class="length">长度:${score.length}</span>
<span class="date">${score.date}</span>
</div>
`).join('')}
`;
} else {
highScoresElement.innerHTML = '<p>暂无高分记录</p>';
}
// 添加到游戏结束模态框
const modalContent = document.querySelector('.modal-content');
if (modalContent && !document.querySelector('.high-scores')) {
modalContent.appendChild(highScoresElement);
}
}
getAchievements(gameData) {
const achievements = [];
if (gameData.score >= 100) achievements.push('百分达人');
if (gameData.length >= 20) achievements.push('长蛇之王');
if (gameData.level >= 5) achievements.push('等级大师');
if (gameData.gameTime >= 300) achievements.push('持久战将');
if (gameData.score >= 50 && gameData.gameTime <= 60) achievements.push('速通高手');
return achievements;
}
// 工具方法:格式化时间
formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins}${secs}`;
}
// 清除统计
clearStatistics() {
this.highScores = [];
this.sessionStats = {
gamesPlayed: 0,
totalScore: 0,
maxLength: 0,
maxLevel: 0,
totalTime: 0
};
localStorage.removeItem('snakeHighScores');
localStorage.removeItem('snakeSessionStats');
console.log('统计信息已清除');
}
}
// 原游戏结束界面已移除,保留统计模块以便响应 'gameOver' 事件
// 初始化统计模块
let gameStats;
document.addEventListener('DOMContentLoaded', () => {
gameStats = new GameStatistics();
});
// 添加CSS样式
const statsStyles = `
.session-stats {
margin-top: 20px;
padding: 15px;
background: linear-gradient(135deg, #fdfcfb 0%, #e2d1c3 100%);
border-radius: 10px;
border: 2px solid #d4a76a;
}
.session-stats h3 {
color: #8b4513;
margin-bottom: 10px;
text-align: center;
}
.session-stats p {
margin: 5px 0;
color: #654321;
font-size: 0.9rem;
}
.high-scores {
margin-top: 20px;
padding: 15px;
background: linear-gradient(135deg, #fff1eb 0%, #ace0f9 100%);
border-radius: 10px;
border: 2px solid #4682b4;
}
.high-scores h3 {
color: #2c5282;
margin-bottom: 10px;
text-align: center;
}
.score-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #cbd5e0;
}
.score-item:last-child {
border-bottom: none;
}
.score-item.first-place {
background: linear-gradient(135deg, #fceabb 0%, #f8b500 100%);
border-radius: 5px;
padding: 8px;
margin: -8px -8px 8px -8px;
}
.rank {
font-weight: bold;
color: #2d3748;
min-width: 30px;
}
.score {
font-weight: bold;
color: #e53e3e;
min-width: 60px;
}
.length {
color: #4a5568;
font-size: 0.8rem;
min-width: 60px;
}
.date {
color: #718096;
font-size: 0.7rem;
min-width: 60px;
text-align: right;
}
`;
// 注入样式
const styleSheet = document.createElement('style');
styleSheet.textContent = statsStyles;
class GameStatistics {
constructor() {
this.highScores = JSON.parse(localStorage.getItem('snakeHighScores')) || [];
this.sessionStats = {
gamesPlayed: 0,
totalScore: 0,
maxLength: 0,
maxLevel: 0,
totalTime: 0
};
this.init();
}
init() {
// 恢复会话统计(如果存在)
const savedSession = localStorage.getItem('snakeSessionStats');
if (savedSession) {
this.sessionStats = JSON.parse(savedSession);
}
// 监听游戏事件
this.setupEventListeners();
}
setupEventListeners() {
// 监听自定义游戏事件
document.addEventListener('gameOver', (e) => {
this.handleGameOver(e.detail);
});
document.addEventListener('foodEaten', (e) => {
this.handleFoodEaten(e.detail);
});
document.addEventListener('levelUp', (e) => {
this.handleLevelUp(e.detail);
});
}
handleGameOver(gameData) {
this.sessionStats.gamesPlayed++;
this.sessionStats.totalScore += gameData.score;
this.sessionStats.maxLength = Math.max(this.sessionStats.maxLength, gameData.length);
this.sessionStats.maxLevel = Math.max(this.sessionStats.maxLevel, gameData.level);
this.sessionStats.totalTime += gameData.gameTime;
// 保存会话统计
localStorage.setItem('snakeSessionStats', JSON.stringify(this.sessionStats));
// 检查是否进入高分榜
this.checkHighScore(gameData);
// 显示统计信息
this.displaySessionStats();
}
handleFoodEaten(foodData) {
// 可以记录特殊食物统计等
console.log('食物被吃掉:', foodData);
}
handleLevelUp(levelData) {
// 等级提升统计
console.log('等级提升到:', levelData.level);
}
checkHighScore(gameData) {
const highScoreEntry = {
score: gameData.score,
length: gameData.length,
level: gameData.level,
time: gameData.gameTime,
date: new Date().toLocaleDateString(),
timestamp: Date.now()
};
// 添加到高分榜
this.highScores.push(highScoreEntry);
// 按分数排序(降序)
this.highScores.sort((a, b) => b.score - a.score);
// 只保留前10名
this.highScores = this.highScores.slice(0, 10);
// 保存到本地存储
localStorage.setItem('snakeHighScores', JSON.stringify(this.highScores));
}
displaySessionStats() {
const statsElement = document.createElement('div');
statsElement.className = 'session-stats';
statsElement.innerHTML = `
<h3>本次会话统计</h3>
<p>游戏次数: ${this.sessionStats.gamesPlayed}</p>
<p>总得分: ${this.sessionStats.totalScore}</p>
<p>最高长度: ${this.sessionStats.maxLength}</p>
<p>最高等级: ${this.sessionStats.maxLevel}</p>
<p>总游戏时间: ${Math.floor(this.sessionStats.totalTime / 60)}分钟</p>
<p>平均得分: ${Math.round(this.sessionStats.totalScore / this.sessionStats.gamesPlayed)}</p>
`;
// 添加到游戏结束模态框
const statsContainer = document.querySelector('.stats');
if (statsContainer && !document.querySelector('.session-stats')) {
statsContainer.appendChild(statsElement);
}
}
displayHighScores() {
const highScoresElement = document.createElement('div');
highScoresElement.className = 'high-scores';
if (this.highScores.length > 0) {
highScoresElement.innerHTML = `
<h3>🏆 高分榜</h3>
${this.highScores.map((score, index) => `
<div class="score-item ${index === 0 ? 'first-place' : ''}">
<span class="rank">${index + 1}.</span>
<span class="score">${score.score}分</span>
<span class="length">长度:${score.length}</span>
<span class="date">${score.date}</span>
</div>
`).join('')}
`;
} else {
highScoresElement.innerHTML = '<p>暂无高分记录</p>';
}
// 添加到游戏结束模态框
const modalContent = document.querySelector('.modal-content');
if (modalContent && !document.querySelector('.high-scores')) {
modalContent.appendChild(highScoresElement);
}
}
getAchievements(gameData) {
const achievements = [];
if (gameData.score >= 100) achievements.push('百分达人');
if (gameData.length >= 20) achievements.push('长蛇之王');
if (gameData.level >= 5) achievements.push('等级大师');
if (gameData.gameTime >= 300) achievements.push('持久战将');
if (gameData.score >= 50 && gameData.gameTime <= 60) achievements.push('速通高手');
return achievements;
}
// 工具方法:格式化时间
formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins}${secs}`;
}
// 清除统计
clearStatistics() {
this.highScores = [];
this.sessionStats = {
gamesPlayed: 0,
totalScore: 0,
maxLength: 0,
maxLevel: 0,
totalTime: 0
};
localStorage.removeItem('snakeHighScores');
localStorage.removeItem('snakeSessionStats');
console.log('统计信息已清除');
}
}
// 原游戏结束界面已移除,保留统计模块以便响应 'gameOver' 事件
// 初始化统计模块
let gameStats;
document.addEventListener('DOMContentLoaded', () => {
gameStats = new GameStatistics();
});
// 添加CSS样式
const statsStyles = `
.session-stats {
margin-top: 20px;
padding: 15px;
background: linear-gradient(135deg, #fdfcfb 0%, #e2d1c3 100%);
border-radius: 10px;
border: 2px solid #d4a76a;
}
.session-stats h3 {
color: #8b4513;
margin-bottom: 10px;
text-align: center;
}
.session-stats p {
margin: 5px 0;
color: #654321;
font-size: 0.9rem;
}
.high-scores {
margin-top: 20px;
padding: 15px;
background: linear-gradient(135deg, #fff1eb 0%, #ace0f9 100%);
border-radius: 10px;
border: 2px solid #4682b4;
}
.high-scores h3 {
color: #2c5282;
margin-bottom: 10px;
text-align: center;
}
.score-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #cbd5e0;
}
.score-item:last-child {
border-bottom: none;
}
.score-item.first-place {
background: linear-gradient(135deg, #fceabb 0%, #f8b500 100%);
border-radius: 5px;
padding: 8px;
margin: -8px -8px 8px -8px;
}
.rank {
font-weight: bold;
color: #2d3748;
min-width: 30px;
}
.score {
font-weight: bold;
color: #e53e3e;
min-width: 60px;
}
.length {
color: #4a5568;
font-size: 0.8rem;
min-width: 60px;
}
.date {
color: #718096;
font-size: 0.7rem;
min-width: 60px;
text-align: right;
}
`;
// 注入样式
const styleSheet = document.createElement('style');
styleSheet.textContent = statsStyles;
document.head.appendChild(styleSheet);

View File

@@ -1,50 +1,50 @@
const playerdata = [
{
"名称":"树萌芽",
"账号":"3205788256@qq.com",
"分数":1568,
"时间":"2025-09-08"
},
{
"名称":"风行者",
"账号":"4456723190@qq.com",
"分数":1987,
"时间":"2025-09-30"
},
{
"名称":"月光骑士",
"账号":"5832197462@qq.com",
"分数":876,
"时间":"2025-10-02"
},
{
"名称":"星河",
"账号":"6724981532@qq.com",
"分数":1345,
"时间":"2025-10-05"
},
{
"名称":"雷霆",
"账号":"7891234567@qq.com",
"分数":2105,
"时间":"2025-10-08"
},
{
"名称":"火焰猫",
"账号":"8912345678@qq.com",
"分数":654,
"时间":"2025-10-10"
},
{
"名称":"冰雪女王",
"账号":"9123456789@qq.com",
"分数":1789,
"时间":"2025-10-12"
},
{
"名称":"😊",
"账号":"1125234890@qq.com",
"分数":1432,
"时间":"2025-10-15"
}
const playerdata = [
{
"名称":"树萌芽",
"账号":"3205788256@qq.com",
"分数":1568,
"时间":"2025-09-08"
},
{
"名称":"风行者",
"账号":"4456723190@qq.com",
"分数":1987,
"时间":"2025-09-30"
},
{
"名称":"月光骑士",
"账号":"5832197462@qq.com",
"分数":876,
"时间":"2025-10-02"
},
{
"名称":"星河",
"账号":"6724981532@qq.com",
"分数":1345,
"时间":"2025-10-05"
},
{
"名称":"雷霆",
"账号":"7891234567@qq.com",
"分数":2105,
"时间":"2025-10-08"
},
{
"名称":"火焰猫",
"账号":"8912345678@qq.com",
"分数":654,
"时间":"2025-10-10"
},
{
"名称":"冰雪女王",
"账号":"9123456789@qq.com",
"分数":1789,
"时间":"2025-10-12"
},
{
"名称":"😊",
"账号":"1125234890@qq.com",
"分数":1432,
"时间":"2025-10-15"
}
]

View File

@@ -1,332 +1,332 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Arial', sans-serif;
background: linear-gradient(135deg, #a8e6cf 0%, #dcedc8 50%, #f1f8e9 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
}
.game-container {
background: linear-gradient(135deg, #e8f5e8 0%, #f1f8e9 100%);
border-radius: 20px;
padding: 20px;
box-shadow: 0 20px 40px rgba(46, 125, 50, 0.3);
max-width: 400px;
width: 95%;
margin: 20px auto;
border: 1px solid rgba(46, 125, 50, 0.2);
}
.game-header {
text-align: center;
margin-bottom: 20px;
}
.game-header h1 {
color: #1b5e20;
font-size: 2.5rem;
margin-bottom: 15px;
text-shadow: 2px 2px 4px rgba(255, 255, 255, 0.5);
}
.score-board {
display: flex;
justify-content: space-around;
background: linear-gradient(135deg, #2e7d32 0%, #388e3c 100%);
padding: 12px;
border-radius: 15px;
color: white;
font-weight: bold;
font-size: 1.1rem;
box-shadow: 0 4px 8px rgba(46, 125, 50, 0.2);
}
.score-board span {
display: flex;
flex-direction: column;
align-items: center;
}
.game-canvas-container {
display: flex;
justify-content: center;
margin: 20px 0;
}
#gameCanvas {
border: 3px solid #2e7d32;
border-radius: 10px;
background: #1b5e20;
box-shadow: 0 8px 16px rgba(46, 125, 50, 0.3);
}
.game-controls {
margin: 20px 0;
}
.control-row {
display: flex;
justify-content: center;
margin: 8px 0;
}
.control-btn {
width: 70px;
height: 70px;
margin: 0 10px;
border: none;
border-radius: 50%;
background: linear-gradient(135deg, #4caf50 0%, #66bb6a 100%);
color: white;
font-size: 1.5rem;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 8px rgba(46, 125, 50, 0.3);
}
.control-btn:hover {
box-shadow: 0 6px 12px rgba(46, 125, 50, 0.4);
}
.control-btn:active {
box-shadow: 0 2px 4px rgba(46, 125, 50, 0.2);
}
#pauseBtn {
background: linear-gradient(135deg, #81c784 0%, #a5d6a7 100%);
color: #1b5e20;
font-size: 1.2rem;
}
.game-instructions {
text-align: center;
color: #2e7d32;
font-size: 0.9rem;
margin-top: 15px;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(46, 125, 50, 0.6);
z-index: 1000;
justify-content: center;
align-items: center;
}
.modal-content {
background: linear-gradient(135deg, #e8f5e8 0%, #f1f8e9 100%);
padding: 30px;
border-radius: 20px;
text-align: center;
max-width: 400px;
width: 90%;
box-shadow: 0 20px 40px rgba(46, 125, 50, 0.4);
border: 1px solid rgba(46, 125, 50, 0.2);
}
.modal-content h2 {
color: #1b5e20;
margin-bottom: 20px;
font-size: 2rem;
text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.5);
}
.stats p {
margin: 10px 0;
font-size: 1.1rem;
color: #2e7d32;
}
.stats span {
font-weight: bold;
color: #1b5e20;
}
.restart-btn {
background: linear-gradient(135deg, #2e7d32 0%, #388e3c 100%);
color: white;
border: none;
padding: 15px 30px;
border-radius: 25px;
font-size: 1.2rem;
font-weight: bold;
cursor: pointer;
margin-top: 20px;
transition: all 0.3s ease;
box-shadow: 0 4px 8px rgba(46, 125, 50, 0.2);
}
.restart-btn:hover {
box-shadow: 0 8px 16px rgba(46, 125, 50, 0.3);
}
/* 排行榜样式 */
.leaderboard {
margin: 20px 0;
padding: 15px;
background: rgba(255, 255, 255, 0.3);
border-radius: 15px;
border: 1px solid rgba(46, 125, 50, 0.2);
}
.leaderboard-summary {
margin: 10px 0 15px;
padding: 10px 12px;
background: rgba(255, 255, 255, 0.5);
border-radius: 12px;
color: #1b5e20;
text-align: center;
border: 1px solid rgba(46, 125, 50, 0.2);
}
.leaderboard-summary p {
margin: 6px 0;
}
.leaderboard h3 {
color: #1b5e20;
margin-bottom: 15px;
font-size: 1.3rem;
text-align: center;
}
.leaderboard-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.leaderboard-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
background: rgba(255, 255, 255, 0.5);
border-radius: 10px;
font-size: 0.9rem;
transition: all 0.2s ease;
}
.leaderboard-item.current-player {
background: linear-gradient(135deg, #ffeb3b 0%, #fff176 100%);
font-weight: bold;
border: 2px solid #f57f17;
}
.leaderboard-item .rank {
font-weight: bold;
min-width: 30px;
text-align: left;
}
.leaderboard-item .player-name {
flex: 1;
text-align: left;
margin-left: 10px;
color: #2e7d32;
}
.leaderboard-item .player-score {
font-weight: bold;
color: #1b5e20;
min-width: 50px;
text-align: right;
}
.leaderboard-item .player-time {
color: #4a5568;
font-size: 0.8rem;
min-width: 80px;
text-align: right;
}
/* 手机端优化 */
@media (max-width: 768px) {
.game-container {
padding: 15px;
margin: 10px;
border-radius: 15px;
}
.game-header h1 {
font-size: 2rem;
}
.score-board {
font-size: 0.9rem;
padding: 8px;
}
.control-btn {
width: 60px;
height: 60px;
font-size: 1.3rem;
margin: 0 8px;
}
#pauseBtn {
font-size: 1rem;
}
#gameCanvas {
width: 280px;
height: 400px;
}
}
/* 超小屏幕优化 */
@media (max-width: 480px) {
.game-header h1 {
font-size: 1.8rem;
}
.score-board {
flex-direction: column;
gap: 5px;
}
.control-btn {
width: 50px;
height: 50px;
font-size: 1.1rem;
margin: 0 5px;
}
#gameCanvas {
width: 250px;
height: 350px;
}
.modal-content {
padding: 20px;
}
.modal-content h2 {
font-size: 1.5rem;
}
}
/* 防止文本选择 */
.control-btn, .restart-btn {
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Arial', sans-serif;
background: linear-gradient(135deg, #a8e6cf 0%, #dcedc8 50%, #f1f8e9 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
}
.game-container {
background: linear-gradient(135deg, #e8f5e8 0%, #f1f8e9 100%);
border-radius: 20px;
padding: 20px;
box-shadow: 0 20px 40px rgba(46, 125, 50, 0.3);
max-width: 400px;
width: 95%;
margin: 20px auto;
border: 1px solid rgba(46, 125, 50, 0.2);
}
.game-header {
text-align: center;
margin-bottom: 20px;
}
.game-header h1 {
color: #1b5e20;
font-size: 2.5rem;
margin-bottom: 15px;
text-shadow: 2px 2px 4px rgba(255, 255, 255, 0.5);
}
.score-board {
display: flex;
justify-content: space-around;
background: linear-gradient(135deg, #2e7d32 0%, #388e3c 100%);
padding: 12px;
border-radius: 15px;
color: white;
font-weight: bold;
font-size: 1.1rem;
box-shadow: 0 4px 8px rgba(46, 125, 50, 0.2);
}
.score-board span {
display: flex;
flex-direction: column;
align-items: center;
}
.game-canvas-container {
display: flex;
justify-content: center;
margin: 20px 0;
}
#gameCanvas {
border: 3px solid #2e7d32;
border-radius: 10px;
background: #1b5e20;
box-shadow: 0 8px 16px rgba(46, 125, 50, 0.3);
}
.game-controls {
margin: 20px 0;
}
.control-row {
display: flex;
justify-content: center;
margin: 8px 0;
}
.control-btn {
width: 70px;
height: 70px;
margin: 0 10px;
border: none;
border-radius: 50%;
background: linear-gradient(135deg, #4caf50 0%, #66bb6a 100%);
color: white;
font-size: 1.5rem;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 8px rgba(46, 125, 50, 0.3);
}
.control-btn:hover {
box-shadow: 0 6px 12px rgba(46, 125, 50, 0.4);
}
.control-btn:active {
box-shadow: 0 2px 4px rgba(46, 125, 50, 0.2);
}
#pauseBtn {
background: linear-gradient(135deg, #81c784 0%, #a5d6a7 100%);
color: #1b5e20;
font-size: 1.2rem;
}
.game-instructions {
text-align: center;
color: #2e7d32;
font-size: 0.9rem;
margin-top: 15px;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(46, 125, 50, 0.6);
z-index: 1000;
justify-content: center;
align-items: center;
}
.modal-content {
background: linear-gradient(135deg, #e8f5e8 0%, #f1f8e9 100%);
padding: 30px;
border-radius: 20px;
text-align: center;
max-width: 400px;
width: 90%;
box-shadow: 0 20px 40px rgba(46, 125, 50, 0.4);
border: 1px solid rgba(46, 125, 50, 0.2);
}
.modal-content h2 {
color: #1b5e20;
margin-bottom: 20px;
font-size: 2rem;
text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.5);
}
.stats p {
margin: 10px 0;
font-size: 1.1rem;
color: #2e7d32;
}
.stats span {
font-weight: bold;
color: #1b5e20;
}
.restart-btn {
background: linear-gradient(135deg, #2e7d32 0%, #388e3c 100%);
color: white;
border: none;
padding: 15px 30px;
border-radius: 25px;
font-size: 1.2rem;
font-weight: bold;
cursor: pointer;
margin-top: 20px;
transition: all 0.3s ease;
box-shadow: 0 4px 8px rgba(46, 125, 50, 0.2);
}
.restart-btn:hover {
box-shadow: 0 8px 16px rgba(46, 125, 50, 0.3);
}
/* 排行榜样式 */
.leaderboard {
margin: 20px 0;
padding: 15px;
background: rgba(255, 255, 255, 0.3);
border-radius: 15px;
border: 1px solid rgba(46, 125, 50, 0.2);
}
.leaderboard-summary {
margin: 10px 0 15px;
padding: 10px 12px;
background: rgba(255, 255, 255, 0.5);
border-radius: 12px;
color: #1b5e20;
text-align: center;
border: 1px solid rgba(46, 125, 50, 0.2);
}
.leaderboard-summary p {
margin: 6px 0;
}
.leaderboard h3 {
color: #1b5e20;
margin-bottom: 15px;
font-size: 1.3rem;
text-align: center;
}
.leaderboard-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.leaderboard-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
background: rgba(255, 255, 255, 0.5);
border-radius: 10px;
font-size: 0.9rem;
transition: all 0.2s ease;
}
.leaderboard-item.current-player {
background: linear-gradient(135deg, #ffeb3b 0%, #fff176 100%);
font-weight: bold;
border: 2px solid #f57f17;
}
.leaderboard-item .rank {
font-weight: bold;
min-width: 30px;
text-align: left;
}
.leaderboard-item .player-name {
flex: 1;
text-align: left;
margin-left: 10px;
color: #2e7d32;
}
.leaderboard-item .player-score {
font-weight: bold;
color: #1b5e20;
min-width: 50px;
text-align: right;
}
.leaderboard-item .player-time {
color: #4a5568;
font-size: 0.8rem;
min-width: 80px;
text-align: right;
}
/* 手机端优化 */
@media (max-width: 768px) {
.game-container {
padding: 15px;
margin: 10px;
border-radius: 15px;
}
.game-header h1 {
font-size: 2rem;
}
.score-board {
font-size: 0.9rem;
padding: 8px;
}
.control-btn {
width: 60px;
height: 60px;
font-size: 1.3rem;
margin: 0 8px;
}
#pauseBtn {
font-size: 1rem;
}
#gameCanvas {
width: 280px;
height: 400px;
}
}
/* 超小屏幕优化 */
@media (max-width: 480px) {
.game-header h1 {
font-size: 1.8rem;
}
.score-board {
flex-direction: column;
gap: 5px;
}
.control-btn {
width: 50px;
height: 50px;
font-size: 1.1rem;
margin: 0 5px;
}
#gameCanvas {
width: 250px;
height: 350px;
}
.modal-content {
padding: 20px;
}
.modal-content h2 {
font-size: 1.5rem;
}
}
/* 防止文本选择 */
.control-btn, .restart-btn {
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}
/* 动画效果已删除 */