Let the player choose a court before the match
Three courts, picked on a second start-screen stage: CENTRE COURT (grass), TERRE ROUGE (clay) and THE SLAB (hard). A court is a row in config.COURTS and four PNGs — render() never learns their names. Each row carries two palettes, because every court still rots: what "drained" means on orange clay is not what it means on green lawn. Mow stripes became a flag rather than a colour — recolouring the lawn and leaving the bands on still read as grass. Bounce is two numbers copied into TUNE on selection, so the three bounce sites in gameplay.js and the T panel stay untouched: grass skids low and fast, clay sits up and drags, hard is the old baseline. Per-court crowd sheets and ad strips. Each court's clap frame is generated from its OWN seated sheet, not from x_crowd2 — pairing one court's crowd with another's applause cross-fades between two different crowds. The picker is a second panel in the existing #intro overlay, rows built from the table. Last pick is remembered and comes up pre-highlighted; 1..N or Enter on a keyboard. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6378ff2a1b
commit
fa30fb94c4
15 files changed
+166
-36
No files matched your search
@@ -3,8 +3,8 @@
|
||||
// Module map: config (tuning/constants) · state (mutable game state) · gameplay (logic/update)
|
||||
// render (camera/drawing) · input (keys/touch) · audio (sfx/umpire) · panel (T tuning UI)
|
||||
|
||||
import { TUNE, COURT, UMP_H, UMP_X, EATEN_RESTART } from './config.js';
|
||||
import { P, O, ball, score, G, setupPoint } from './state.js';
|
||||
import { TUNE, COURT, COURTS, UMP_H, UMP_X, EATEN_RESTART } from './config.js';
|
||||
import { P, O, ball, score, G, setupPoint, selectCourt } from './state.js';
|
||||
import { update, keyPressed, solveShot, inServeBox, inCourt, forceZombies, splatPoint, deliverTin, selectWeapon, showModal, dismissModal, startDuel, resolveInterlude, flickTarget, startBossFight, endBossFight } from './gameplay.js';
|
||||
import { fitViewport, render, proj, RES } from './render.js';
|
||||
import { voice, ambient } from './audio.js';
|
||||
@@ -39,13 +39,28 @@ setupPoint();
|
||||
// warm the pixel HUD font so the canvas never paints a system-serif fallback frame
|
||||
if (document.fonts) { document.fonts.load('700 8px Silkscreen'); document.fonts.load('400 8px Silkscreen'); }
|
||||
|
||||
// intro overlay: first tap dismisses it, unlocks audio (iOS needs the gesture) and starts play
|
||||
// intro overlay, two stages: the first tap opens court select, picking a court starts play and
|
||||
// unlocks audio (iOS needs the gesture, and the SECOND tap is still a gesture — the pick is what
|
||||
// has to unlock it, not the splash).
|
||||
const intro = document.getElementById('intro');
|
||||
function dismissIntro() {
|
||||
const courts = document.getElementById('courts');
|
||||
const lastCourt = (() => { try { return localStorage.getItem('court'); } catch { return null; } })();
|
||||
for (const c of COURTS) {
|
||||
const b = document.createElement('button');
|
||||
b.className = c.id === (lastCourt || COURTS[0].id) ? 'on' : ''; // your last court comes up pre-picked
|
||||
b.innerHTML = `<img src="assets/${c.tile}.png?v=81" alt=""><span><b>${c.name}</b><i>${c.sub}</i></span>`;
|
||||
b.addEventListener('pointerdown', e => { e.preventDefault(); e.stopPropagation(); start(c.id); });
|
||||
courts.appendChild(b);
|
||||
}
|
||||
function openPicker() { if (intro.parentNode) intro.classList.add('picking'); }
|
||||
function start(id) {
|
||||
if (!intro.parentNode) return;
|
||||
selectCourt(id);
|
||||
intro.remove(); ambient(); voice('quiet_please'); // low atmosphere bed starts with the first gesture
|
||||
}
|
||||
intro.addEventListener('pointerdown', e => { e.preventDefault(); dismissIntro(); });
|
||||
// a tap anywhere on the splash opens the picker; on the picker itself only the buttons act, so a
|
||||
// stray tap beside a tile can't start a match on a court you didn't choose.
|
||||
intro.addEventListener('pointerdown', e => { e.preventDefault(); openPicker(); });
|
||||
// which build am I looking at? Push = deploy and the browser cannot see git, so the served file's
|
||||
// own timestamp IS the version marker — it changes on every deploy, with nothing to remember to bump.
|
||||
// Match it against `git log -1 --format=%cd` when a phone looks suspiciously out of date.
|
||||
@@ -53,7 +68,15 @@ fetch('main.js', { method: 'HEAD' })
|
||||
.then(r => { const t = r.headers.get('last-modified'); if (!t) return;
|
||||
document.getElementById('build').textContent = 'BUILD ' + new Date(t).toLocaleString('en-GB').replace(',', ''); })
|
||||
.catch(() => {}); // no header, no line — never a broken start screen
|
||||
addEventListener('keydown', () => dismissIntro(), { once: true });
|
||||
// keyboard: any key opens the picker, then 1..N picks a court and Enter/Space takes the
|
||||
// highlighted one. Not `once` any more — it has two stages to get through.
|
||||
addEventListener('keydown', e => {
|
||||
if (!intro.parentNode) return;
|
||||
if (!intro.classList.contains('picking')) { openPicker(); return; }
|
||||
const n = parseInt(e.key, 10);
|
||||
if (n >= 1 && n <= COURTS.length) start(COURTS[n - 1].id);
|
||||
else if (e.key === 'Enter' || e.key === ' ') start(lastCourt || COURTS[0].id);
|
||||
});
|
||||
|
||||
// eaten: any tap or key restarts — but only once the death animation has played out, so a
|
||||
// panic tap during the gore burst can't skip it. A modal owns its own gesture (see input.js).
|
||||
|
||||
Reference in new issue
Block a user