Put a real world board behind the RANKINGS button

Ranked on KILLS IN A SINGLE RUN. Games survived is the same information at a
tenth the resolution — most players land between 5 and 20, so that board is a
wall of ties broken by who got there first. Games ride along as the tiebreak
and as a column.

The image is python:3.12-alpine running server.py now, not nginx. The board
needs one process that can write, and everything the nginx conf did here — the
no-cache headers and the manifest MIME type — is a few lines of stdlib. /data
must be a persistent volume or every deploy resets the ladder to empty.

A name belongs to whoever took it first: the client mints a random id once and
the row remembers it, so a stranger cannot overwrite your score. The id never
leaves the server. That is the whole security model and it is enough.

The prompt to sign the board appears on exactly one occasion — a run that beat
your own best kill count, on the tap that leaves the death screen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5 committed 2026-08-04 17:03:17 +02:00
1 parent 1fccc11b1d
commit 3291246425
8 files changed
+374 -48

No files matched your search

+47 -3
View File
@@ -131,7 +131,46 @@ let stPeriod = 'all';
const openStats = () => { stats.drawStats(stPeriod, SPRV); go('stats'); };
document.getElementById('mStart').addEventListener('click', () => go('setup'));
document.getElementById('mStats').addEventListener('click', openStats);
document.getElementById('mRanks').addEventListener('click', () => { stats.drawRanks(); go('ranks'); });
document.getElementById('mRanks').addEventListener('click', () => { go('ranks'); stats.drawRanks(); });
// --- THE NAME PROMPT. It appears on exactly one occasion: a run that beat your own best kill
// count, on the tap that takes you off the death screen. Anything more often is a form standing
// between you and the next match.
const nbName = document.getElementById('nbName'), nbMsg = document.getElementById('nbMsg');
let nbRun = null;
function askName(run) {
nbRun = run;
document.getElementById('nbScore').innerHTML =
`<b>${run.kills.toLocaleString('en-GB')}</b> KILLS · <b>${run.games}</b> GAME${run.games === 1 ? '' : 'S'}`;
nbName.value = stats.savedName();
nbMsg.textContent = 'PUT IT ON THE WORLD BOARD.';
document.getElementById('nbSend').disabled = false;
go('newbest');
// focus only where a keyboard is already there. On a phone this would throw the soft keyboard up
// over the score you just earned, before you have read it.
if (!matchMedia('(pointer: coarse)').matches) nbName.focus();
}
document.getElementById('nbSkip').addEventListener('click', () => { nbRun = null; go('splash'); });
document.getElementById('nbSend').addEventListener('click', async () => {
const name = stats.tidyName(nbName.value);
if (!name) { nbMsg.textContent = 'A NAME FIRST.'; nbName.focus(); return; }
nbName.value = name; // show what is actually going on the board
if (!nbRun) return go('splash');
const btn = document.getElementById('nbSend');
btn.disabled = true; nbMsg.textContent = 'SENDING…'; // the double-tap posts twice otherwise
try {
const r = await stats.submit(name, nbRun.kills, nbRun.games);
nbRun = null;
go('ranks');
stats.drawRanks(r.scores); // the board came back with the reply — no second round trip
} catch (e) {
btn.disabled = false;
nbMsg.textContent = e.taken ? 'THAT NAME IS SPOKEN FOR. PICK ANOTHER.'
: 'THE BOARD DID NOT ANSWER. TRY AGAIN?';
if (e.taken) { nbName.select(); nbName.focus(); }
}
});
nbName.addEventListener('keydown', e => { if (e.key === 'Enter') document.getElementById('nbSend').click(); });
document.getElementById('cStart').addEventListener('click', start);
for (const id of ['sBack', 'stBack', 'rkBack'])
document.getElementById(id).addEventListener('click', () => go('splash'));
@@ -190,8 +229,13 @@ addEventListener('pagehide', () => {
const maybeRestart = () => {
if (G.modal || G.mode !== 'eaten' || G.eatenT <= EATEN_RESTART) return;
restartRun();
refreshSplash(); // the run just ended: both splash figures have moved
intro.hidden = false; go('splash');
refreshSplash(); // the run just ended: the splash count has moved
intro.hidden = false;
// stats.lastRun() survives restartRun on purpose — it lives in the stats module, not on G, which
// has just been restored from PRISTINE. Consumed on read, so the prompt appears once.
const run = stats.lastRun();
if (run && run.newKillBest && run.kills > 0) askName(run);
else go('splash');
};
cv.addEventListener('pointerdown', maybeRestart);
addEventListener('keydown', maybeRestart);