Files
kaltaquise-gamification/kidney_lab/js/highscore.js
2026-04-16 08:14:20 +02:00

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 &amp; 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 };
})();