145 lines
4.8 KiB
JavaScript
145 lines
4.8 KiB
JavaScript
/**
|
|
* highscore.js
|
|
* High-score screen: displays final score(s), persists top-10 to localStorage,
|
|
* shows leaderboard, offers back-to-start button.
|
|
*
|
|
* show(names, scores, playerCount)
|
|
* names — string (1P) or array of strings (2P)
|
|
* scores — number (1P) or array of numbers (2P)
|
|
* playerCount — 1 or 2
|
|
*/
|
|
|
|
const HighScoreScreen = (() => {
|
|
|
|
const STORAGE_KEY = 'kidneylab_scores';
|
|
|
|
/* ── Persistence ─────────────────────────────────────────────── */
|
|
|
|
function loadScores() {
|
|
try {
|
|
return JSON.parse(localStorage.getItem(STORAGE_KEY)) || [];
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function saveScore(name, score) {
|
|
const scores = loadScores();
|
|
scores.push({ name: name.toUpperCase().slice(0, 12), score, date: new Date().toLocaleDateString() });
|
|
scores.sort((a, b) => b.score - a.score);
|
|
const top10 = scores.slice(0, 10);
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(top10));
|
|
return top10;
|
|
}
|
|
|
|
/* ── Helpers ─────────────────────────────────────────────────── */
|
|
|
|
function rankLabel(rank) {
|
|
if (rank === 1) return '🏆 NEW HIGH SCORE!';
|
|
if (rank === 2) return '🥈 2nd place!';
|
|
if (rank === 3) return '🥉 3rd place!';
|
|
return `Rank #${rank}`;
|
|
}
|
|
|
|
function resultCardHTML(playerName, finalScore, scores, myRank) {
|
|
const isTop = myRank === 1;
|
|
return `
|
|
<div class="hs-result ${isTop ? 'hs-gold' : ''}">
|
|
<div class="hs-rank-label">${rankLabel(myRank)}</div>
|
|
<div class="hs-player">${playerName.toUpperCase()}</div>
|
|
<div class="hs-score-display">${finalScore >= 0 ? finalScore : 0}</div>
|
|
<div class="hs-score-label">POINTS</div>
|
|
</div>`;
|
|
}
|
|
|
|
function tableHTML(scores, highlightNames, highlightScores) {
|
|
const rowsHTML = scores.map((s, i) => {
|
|
// highlight any entry that matches one of the just-played players
|
|
const isMe = highlightNames.some((n, ni) =>
|
|
s.name === n.toUpperCase().slice(0, 12) && s.score === highlightScores[ni]
|
|
);
|
|
return `<tr class="${isMe ? 'my-row' : ''}">
|
|
<td class="rank-col">${i + 1}</td>
|
|
<td class="name-col">${s.name}</td>
|
|
<td class="score-col">${s.score}</td>
|
|
<td class="date-col">${s.date}</td>
|
|
</tr>`;
|
|
}).join('');
|
|
|
|
return `
|
|
<div class="hs-table-wrap">
|
|
<h2 class="hs-heading">HALL OF FAME</h2>
|
|
<table class="hs-table">
|
|
<thead><tr><th>#</th><th>NAME</th><th>SCORE</th><th>DATE</th></tr></thead>
|
|
<tbody>${rowsHTML}</tbody>
|
|
</table>
|
|
</div>`;
|
|
}
|
|
|
|
/* ── Public show ─────────────────────────────────────────────── */
|
|
|
|
function show(names, scores, playerCount) {
|
|
// Normalise to arrays
|
|
const nameArr = Array.isArray(names) ? names : [names];
|
|
const scoreArr = Array.isArray(scores) ? scores : [scores];
|
|
const count = playerCount || nameArr.length;
|
|
|
|
// Switch screen
|
|
document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
|
|
document.getElementById('highscore-screen').classList.add('active');
|
|
|
|
// Save all players' scores; last save wins for the leaderboard table
|
|
let leaderboard;
|
|
nameArr.forEach((n, i) => {
|
|
leaderboard = saveScore(n, scoreArr[i]);
|
|
});
|
|
|
|
// Build rank info for each player
|
|
const rankInfo = nameArr.map((n, i) => {
|
|
const rank = leaderboard.findIndex(
|
|
s => s.name === n.toUpperCase().slice(0, 12) && s.score === scoreArr[i]
|
|
) + 1;
|
|
return { name: n, score: scoreArr[i], rank };
|
|
});
|
|
|
|
render(rankInfo, leaderboard, nameArr, scoreArr);
|
|
}
|
|
|
|
function render(rankInfo, leaderboard, nameArr, scoreArr) {
|
|
const el = document.getElementById('highscore-screen');
|
|
|
|
// One result card per player
|
|
const cardsHTML = rankInfo.map(r =>
|
|
resultCardHTML(r.name, r.score, leaderboard, r.rank)
|
|
).join('');
|
|
|
|
// Cards side-by-side for 2P, centred for 1P
|
|
const cardsWrap = rankInfo.length > 1
|
|
? `<div style="display:flex;gap:16px;justify-content:center;flex-wrap:wrap;">${cardsHTML}</div>`
|
|
: cardsHTML;
|
|
|
|
el.innerHTML = `
|
|
<div class="hs-inner">
|
|
<div class="hs-logo">
|
|
<span class="logo-gw">GAME & WATCH</span>
|
|
<span class="logo-title">KIDNEY LAB</span>
|
|
</div>
|
|
|
|
${cardsWrap}
|
|
|
|
${tableHTML(leaderboard, nameArr, scoreArr)}
|
|
|
|
<div class="hs-actions">
|
|
<button id="back-btn" class="btn-primary">PLAY AGAIN</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
el.querySelector('#back-btn').addEventListener('click', () => {
|
|
IntroScreen.show();
|
|
});
|
|
}
|
|
|
|
return { show };
|
|
})();
|