不知名提交
This commit is contained in:
@@ -1,338 +1,96 @@
|
||||
// 游戏统计和成就系统
|
||||
class GameStats {
|
||||
constructor() {
|
||||
this.achievements = [
|
||||
{
|
||||
id: 'first_game',
|
||||
name: '初次体验',
|
||||
description: '完成第一次游戏',
|
||||
condition: (stats) => true
|
||||
},
|
||||
{
|
||||
id: 'score_1000',
|
||||
name: '小试牛刀',
|
||||
description: '单局得分达到1000分',
|
||||
condition: (stats) => stats.score >= 1000
|
||||
},
|
||||
{
|
||||
id: 'score_5000',
|
||||
name: '游戏达人',
|
||||
description: '单局得分达到5000分',
|
||||
condition: (stats) => stats.score >= 5000
|
||||
},
|
||||
{
|
||||
id: 'score_10000',
|
||||
name: '方块大师',
|
||||
description: '单局得分达到10000分',
|
||||
condition: (stats) => stats.score >= 10000
|
||||
},
|
||||
{
|
||||
id: 'level_5',
|
||||
name: '步步高升',
|
||||
description: '达到第5级',
|
||||
condition: (stats) => stats.level >= 5
|
||||
},
|
||||
{
|
||||
id: 'level_10',
|
||||
name: '速度之王',
|
||||
description: '达到第10级',
|
||||
condition: (stats) => stats.level >= 10
|
||||
},
|
||||
{
|
||||
id: 'lines_50',
|
||||
name: '消除专家',
|
||||
description: '累计消除50行',
|
||||
condition: (stats) => stats.lines >= 50
|
||||
},
|
||||
{
|
||||
id: 'lines_100',
|
||||
name: '清理大师',
|
||||
description: '累计消除100行',
|
||||
condition: (stats) => stats.lines >= 100
|
||||
},
|
||||
{
|
||||
id: 'tetris',
|
||||
name: 'Tetris!',
|
||||
description: '一次消除4行',
|
||||
condition: (stats) => stats.maxCombo >= 4
|
||||
},
|
||||
{
|
||||
id: 'time_10min',
|
||||
name: '持久战士',
|
||||
description: '单局游戏时间超过10分钟',
|
||||
condition: (stats) => stats.playTime >= 600000
|
||||
},
|
||||
{
|
||||
id: 'efficiency',
|
||||
name: '效率专家',
|
||||
description: '平均每分钟得分超过500',
|
||||
condition: (stats) => stats.avgScore >= 500
|
||||
}
|
||||
];
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
const playAgainBtn = document.getElementById('playAgainBtn');
|
||||
playAgainBtn.addEventListener('click', () => {
|
||||
this.hideStats();
|
||||
game.restart();
|
||||
});
|
||||
}
|
||||
|
||||
showStats(gameData) {
|
||||
const playTimeMinutes = gameData.playTime / 60000;
|
||||
const avgScore = playTimeMinutes > 0 ? Math.round(gameData.score / playTimeMinutes) : 0;
|
||||
|
||||
const stats = {
|
||||
...gameData,
|
||||
avgScore: avgScore
|
||||
};
|
||||
|
||||
// 更新统计显示
|
||||
document.getElementById('finalScore').textContent = stats.score.toLocaleString();
|
||||
document.getElementById('finalLevel').textContent = stats.level;
|
||||
document.getElementById('finalLines').textContent = stats.lines;
|
||||
document.getElementById('playTime').textContent = this.formatTime(stats.playTime);
|
||||
document.getElementById('maxCombo').textContent = stats.maxCombo;
|
||||
document.getElementById('avgScore').textContent = stats.avgScore;
|
||||
|
||||
// 检查成就
|
||||
const achievement = this.checkAchievements(stats);
|
||||
this.displayAchievement(achievement);
|
||||
|
||||
// 显示统计界面
|
||||
document.getElementById('gameStats').style.display = 'flex';
|
||||
document.getElementById('gameStats').classList.add('fade-in');
|
||||
}
|
||||
|
||||
hideStats() {
|
||||
document.getElementById('gameStats').style.display = 'none';
|
||||
document.getElementById('gameStats').classList.remove('fade-in');
|
||||
}
|
||||
|
||||
formatTime(milliseconds) {
|
||||
const seconds = Math.floor(milliseconds / 1000);
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const remainingSeconds = seconds % 60;
|
||||
|
||||
return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
checkAchievements(stats) {
|
||||
// 获取已获得的成就
|
||||
const earnedAchievements = this.getEarnedAchievements();
|
||||
|
||||
// 检查新成就
|
||||
for (let achievement of this.achievements) {
|
||||
if (!earnedAchievements.includes(achievement.id) &&
|
||||
achievement.condition(stats)) {
|
||||
|
||||
// 保存新成就
|
||||
this.saveAchievement(achievement.id);
|
||||
return achievement;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
displayAchievement(achievement) {
|
||||
const achievementEl = document.getElementById('achievement');
|
||||
|
||||
if (achievement) {
|
||||
achievementEl.innerHTML = `
|
||||
🏆 <strong>成就解锁!</strong><br>
|
||||
<strong>${achievement.name}</strong><br>
|
||||
${achievement.description}
|
||||
`;
|
||||
achievementEl.classList.add('pulse');
|
||||
} else {
|
||||
// 显示随机鼓励话语
|
||||
const encouragements = [
|
||||
'继续努力,你会变得更强!',
|
||||
'每一次游戏都是进步的机会!',
|
||||
'方块世界需要你的智慧!',
|
||||
'熟能生巧,加油!',
|
||||
'下一局一定会更好!',
|
||||
'坚持就是胜利!',
|
||||
'你的反应速度在提升!',
|
||||
'策略思维正在增强!'
|
||||
];
|
||||
|
||||
const randomEncouragement = encouragements[Math.floor(Math.random() * encouragements.length)];
|
||||
achievementEl.innerHTML = `💪 ${randomEncouragement}`;
|
||||
achievementEl.classList.remove('pulse');
|
||||
}
|
||||
}
|
||||
|
||||
getEarnedAchievements() {
|
||||
const saved = localStorage.getItem('tetris_achievements');
|
||||
return saved ? JSON.parse(saved) : [];
|
||||
}
|
||||
|
||||
saveAchievement(achievementId) {
|
||||
const earned = this.getEarnedAchievements();
|
||||
if (!earned.includes(achievementId)) {
|
||||
earned.push(achievementId);
|
||||
localStorage.setItem('tetris_achievements', JSON.stringify(earned));
|
||||
}
|
||||
}
|
||||
|
||||
// 获取历史最佳记录
|
||||
getBestStats() {
|
||||
const saved = localStorage.getItem('tetris_best_stats');
|
||||
return saved ? JSON.parse(saved) : {
|
||||
score: 0,
|
||||
level: 0,
|
||||
lines: 0,
|
||||
maxCombo: 0
|
||||
};
|
||||
}
|
||||
|
||||
// 保存最佳记录
|
||||
saveBestStats(stats) {
|
||||
const best = this.getBestStats();
|
||||
let updated = false;
|
||||
|
||||
if (stats.score > best.score) {
|
||||
best.score = stats.score;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if (stats.level > best.level) {
|
||||
best.level = stats.level;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if (stats.lines > best.lines) {
|
||||
best.lines = stats.lines;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if (stats.maxCombo > best.maxCombo) {
|
||||
best.maxCombo = stats.maxCombo;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
localStorage.setItem('tetris_best_stats', JSON.stringify(best));
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
// 显示排行榜
|
||||
showLeaderboard() {
|
||||
const best = this.getBestStats();
|
||||
const earned = this.getEarnedAchievements();
|
||||
|
||||
console.log('最佳记录:', best);
|
||||
console.log('已获得成就:', earned.length + '/' + this.achievements.length);
|
||||
}
|
||||
}
|
||||
// 游戏结束排行榜展示
|
||||
const gameStats = {
|
||||
showStats({ score, playTime }) {
|
||||
// 将毫秒转为 mm:ss
|
||||
const formatDuration = (ms) => {
|
||||
const totalSec = Math.max(0, Math.floor(ms / 1000));
|
||||
const m = String(Math.floor(totalSec / 60)).padStart(2, '0');
|
||||
const s = String(totalSec % 60).padStart(2, '0');
|
||||
return `${m}:${s}`;
|
||||
};
|
||||
|
||||
// 高级特效系统
|
||||
class GameEffects {
|
||||
constructor(game) {
|
||||
this.game = game;
|
||||
this.particles = [];
|
||||
this.effects = [];
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
// 创建特效canvas
|
||||
this.effectsCanvas = document.createElement('canvas');
|
||||
this.effectsCanvas.width = this.game.canvas.width;
|
||||
this.effectsCanvas.height = this.game.canvas.height;
|
||||
this.effectsCanvas.style.position = 'absolute';
|
||||
this.effectsCanvas.style.top = '0';
|
||||
this.effectsCanvas.style.left = '0';
|
||||
this.effectsCanvas.style.pointerEvents = 'none';
|
||||
this.effectsCanvas.style.zIndex = '10';
|
||||
|
||||
this.effectsCtx = this.effectsCanvas.getContext('2d');
|
||||
|
||||
// 将特效canvas添加到游戏板容器中
|
||||
this.game.canvas.parentElement.style.position = 'relative';
|
||||
this.game.canvas.parentElement.appendChild(this.effectsCanvas);
|
||||
}
|
||||
|
||||
// 行消除特效
|
||||
lineCleared(row) {
|
||||
for (let i = 0; i < 20; i++) {
|
||||
this.particles.push({
|
||||
x: Math.random() * this.game.canvas.width,
|
||||
y: row * this.game.CELL_SIZE + this.game.CELL_SIZE / 2,
|
||||
vx: (Math.random() - 0.5) * 10,
|
||||
vy: (Math.random() - 0.5) * 10,
|
||||
life: 1,
|
||||
decay: 0.02,
|
||||
color: `hsl(${Math.random() * 360}, 100%, 50%)`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 方块锁定特效
|
||||
pieceLocked(piece) {
|
||||
const centerX = (piece.x + piece.matrix[0].length / 2) * this.game.CELL_SIZE;
|
||||
const centerY = (piece.y + piece.matrix.length / 2) * this.game.CELL_SIZE;
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
this.particles.push({
|
||||
x: centerX,
|
||||
y: centerY,
|
||||
vx: (Math.random() - 0.5) * 8,
|
||||
vy: (Math.random() - 0.5) * 8,
|
||||
life: 0.8,
|
||||
decay: 0.03,
|
||||
color: piece.color
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 更新特效
|
||||
update() {
|
||||
// 更新粒子
|
||||
for (let i = this.particles.length - 1; i >= 0; i--) {
|
||||
const particle = this.particles[i];
|
||||
|
||||
particle.x += particle.vx;
|
||||
particle.y += particle.vy;
|
||||
particle.life -= particle.decay;
|
||||
|
||||
if (particle.life <= 0) {
|
||||
this.particles.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制特效
|
||||
draw() {
|
||||
this.effectsCtx.clearRect(0, 0, this.effectsCanvas.width, this.effectsCanvas.height);
|
||||
|
||||
// 绘制粒子
|
||||
for (let particle of this.particles) {
|
||||
this.effectsCtx.save();
|
||||
this.effectsCtx.globalAlpha = particle.life;
|
||||
this.effectsCtx.fillStyle = particle.color;
|
||||
this.effectsCtx.beginPath();
|
||||
this.effectsCtx.arc(particle.x, particle.y, 3, 0, Math.PI * 2);
|
||||
this.effectsCtx.fill();
|
||||
this.effectsCtx.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
// 构造排行榜数据(模拟),将当前成绩与 gamedata.js 合并
|
||||
const todayStr = (() => {
|
||||
const d = new Date();
|
||||
const y = d.getFullYear();
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(d.getDate()).padStart(2, '0');
|
||||
return `${y}-${m}-${day}`;
|
||||
})();
|
||||
|
||||
// 创建统计系统实例
|
||||
const gameStats = new GameStats();
|
||||
// 当前玩家信息(可根据实际项目替换为真实用户)
|
||||
const currentEntry = {
|
||||
名称: localStorage.getItem('tetris_player_name') || '我',
|
||||
账号: localStorage.getItem('tetris_player_account') || 'guest@local',
|
||||
分数: score,
|
||||
时间: formatDuration(playTime), // 排行榜展示“游戏时长”
|
||||
isCurrent: true,
|
||||
};
|
||||
|
||||
// 在适当的地方创建特效系统
|
||||
// const gameEffects = new GameEffects(game);
|
||||
// 注意:在浏览器中,使用 const 声明的全局变量不会挂载到 window 上
|
||||
// 因此这里直接使用 playerdata,而不是 window.playerdata
|
||||
const baseData = (typeof playerdata !== 'undefined' && Array.isArray(playerdata)) ? playerdata : [];
|
||||
|
||||
// 为基础数据模拟“游戏时长”(mm:ss),以满足展示需求
|
||||
const simulateDuration = (scoreVal) => {
|
||||
const sec = Math.max(30, Math.min(30 * 60, Math.round((Number(scoreVal) || 0) * 1.2)));
|
||||
return formatDuration(sec * 1000);
|
||||
};
|
||||
|
||||
const merged = [...baseData.map((d) => ({
|
||||
...d,
|
||||
// 使用已有分数推导一个模拟时长
|
||||
时间: simulateDuration(d.分数),
|
||||
isCurrent: false,
|
||||
})), currentEntry]
|
||||
.sort((a, b) => (b.分数 || 0) - (a.分数 || 0));
|
||||
|
||||
// 3) 渲染排行榜(取前10)
|
||||
const tbody = document.getElementById('leaderboardBody');
|
||||
tbody.innerHTML = '';
|
||||
const topN = merged.slice(0, 10);
|
||||
topN.forEach((item, idx) => {
|
||||
const tr = document.createElement('tr');
|
||||
if (item.isCurrent) {
|
||||
tr.classList.add('current-row');
|
||||
}
|
||||
const rankCell = document.createElement('td');
|
||||
const nameCell = document.createElement('td');
|
||||
const scoreCell = document.createElement('td');
|
||||
const timeCell = document.createElement('td');
|
||||
|
||||
const rankBadge = document.createElement('span');
|
||||
rankBadge.className = 'rank-badge';
|
||||
rankBadge.textContent = String(idx + 1);
|
||||
rankCell.appendChild(rankBadge);
|
||||
|
||||
nameCell.textContent = item.名称 || '未知';
|
||||
scoreCell.textContent = item.分数 || 0;
|
||||
timeCell.textContent = item.时间 || formatDuration(playTime);
|
||||
|
||||
tr.appendChild(rankCell);
|
||||
tr.appendChild(nameCell);
|
||||
tr.appendChild(scoreCell);
|
||||
tr.appendChild(timeCell);
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
|
||||
// 4) 展示排行榜界面
|
||||
const statsEl = document.getElementById('gameStats');
|
||||
statsEl.style.display = 'flex';
|
||||
|
||||
// 5) 再玩一次按钮
|
||||
const playAgainBtn = document.getElementById('playAgainBtn');
|
||||
if (playAgainBtn) {
|
||||
playAgainBtn.onclick = () => {
|
||||
statsEl.style.display = 'none';
|
||||
if (window.game && typeof window.game.restart === 'function') {
|
||||
window.game.restart();
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// 暴露到全局
|
||||
window.gameStats = gameStats;
|
||||
@@ -61,40 +61,32 @@
|
||||
<!-- 游戏结束统计界面 -->
|
||||
<div class="game-stats" id="gameStats">
|
||||
<div class="stats-content">
|
||||
<h2>游戏结束</h2>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">最终分数</span>
|
||||
<span class="stat-value" id="finalScore">0</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">达到等级</span>
|
||||
<span class="stat-value" id="finalLevel">1</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">消除行数</span>
|
||||
<span class="stat-value" id="finalLines">0</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">游戏时长</span>
|
||||
<span class="stat-value" id="playTime">00:00</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">单次消除最大行数</span>
|
||||
<span class="stat-value" id="maxCombo">0</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">平均每分钟分数</span>
|
||||
<span class="stat-value" id="avgScore">0</span>
|
||||
<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>
|
||||
<div class="achievement" id="achievement"></div>
|
||||
<button class="game-btn" id="playAgainBtn">再玩一次</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="tetris.js"></script>
|
||||
<script src="game-controls.js"></script>
|
||||
<script src="gamedata.js"></script>
|
||||
<script src="game-stats.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -319,6 +319,84 @@ body {
|
||||
box-shadow: 0 4px 8px rgba(46, 125, 50, 0.3);
|
||||
}
|
||||
|
||||
/* 排行榜样式 */
|
||||
.leaderboard {
|
||||
background: linear-gradient(135deg, #e8f5e8 0%, #f1f8e9 100%);
|
||||
color: #2e7d32;
|
||||
border: 1px solid rgba(46, 125, 50, 0.3);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 6px 18px rgba(46, 125, 50, 0.25);
|
||||
padding: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.leaderboard-title {
|
||||
font-weight: 700;
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 12px;
|
||||
background: linear-gradient(135deg, #4caf50 0%, #8bc34a 50%, #cddc39 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.leaderboard-wrap {
|
||||
max-height: 260px;
|
||||
overflow: auto;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.leaderboard-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.leaderboard-table thead tr {
|
||||
background: linear-gradient(135deg, #66bb6a 0%, #8bc34a 100%);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.leaderboard-table th,
|
||||
.leaderboard-table td {
|
||||
text-align: left;
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid rgba(46, 125, 50, 0.15);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.leaderboard-table tbody tr {
|
||||
background: linear-gradient(135deg, rgba(46,125,50,0.08) 0%, rgba(46,125,50,0.03) 100%);
|
||||
transition: background 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.leaderboard-table tbody tr:hover {
|
||||
background: linear-gradient(135deg, rgba(46,125,50,0.12) 0%, rgba(46,125,50,0.06) 100%);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.rank-badge {
|
||||
display: inline-block;
|
||||
min-width: 32px;
|
||||
text-align: center;
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(45deg, #66bb6a, #8bc34a);
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.current-row {
|
||||
outline: 2px solid rgba(76, 175, 80, 0.7);
|
||||
box-shadow: 0 0 0 4px rgba(76, 175, 80, 0.15) inset;
|
||||
}
|
||||
|
||||
.leaderboard-tip {
|
||||
margin-top: 10px;
|
||||
font-size: 0.85rem;
|
||||
color: #388e3c;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.game-container {
|
||||
@@ -378,6 +456,15 @@ body {
|
||||
padding: 20px;
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
.leaderboard-wrap {
|
||||
max-height: 200px;
|
||||
}
|
||||
.leaderboard-table th,
|
||||
.leaderboard-table td {
|
||||
padding: 8px 10px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
@@ -449,3 +536,39 @@ body {
|
||||
.pulse {
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
/* 摘要卡片 */
|
||||
.leaderboard-summary {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.summary-item {
|
||||
background: linear-gradient(135deg, #2e7d32 0%, #388e3c 100%);
|
||||
color: #fff;
|
||||
padding: 12px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(46, 125, 50, 0.3);
|
||||
box-shadow: 0 4px 8px rgba(46, 125, 50, 0.2);
|
||||
}
|
||||
|
||||
.summary-label {
|
||||
display: block;
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.summary-value {
|
||||
display: block;
|
||||
font-size: 1.3rem;
|
||||
font-weight: 700;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.leaderboard-summary {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user