Two phones, one match: the shot is the message, the receiver is the judge
VS A FRIEND on the mode screen (host a code, join a code), a ~110-line RFC6455 relay in server.py that holds no game state, and src/net.js: each phone plays itself near-side and the opponent's court is this one rotated half a turn about the net. The phone a ball flies toward makes every call on it; the striker's copy is picture. Six tests pin the rule from both seats. No reconnect yet — a dropped radio is a card and a reload. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
108ed695eb
commit
f302354bb2
11 files changed
+493
-21
No files matched your search
+39
-3
@@ -6,7 +6,8 @@
|
||||
import { TUNE, COURTS, TOURS, MODES, MENU, DIFFS, EATEN_RESTART, DONE_RESTART, WEAPONS, bossArt } from './config.js';
|
||||
import { P, O, ball, score, G, setupPoint, selectCourt, selectTour } from './state.js';
|
||||
import { update, restartRun, keyPressed, solveShot, inServeBox, inCourt, forceZombies, splatPoint, deliverTin, selectWeapon, grantWeapon, showModal, dismissModal, flickTarget, dragServe, toggleTestMode } from './gameplay.js';
|
||||
import { startDuel, resolveInterlude, startBossFight, endBossFight, startDrill, endDrill, startMatch, endMatch, startBench, endBench, FALL, benchFall } from './beats.js';
|
||||
import { startDuel, resolveInterlude, startBossFight, endBossFight, startDrill, endDrill, startMatch, endMatch, startBench, endBench, FALL, benchFall, startNetVs } from './beats.js';
|
||||
import { netConnect } from './net.js';
|
||||
import { fitViewport, render, proj, RES, SPRV, LOAD, SPR } from './render.js';
|
||||
import { voice, ambient, menuMusic, menuDuck } from './audio.js';
|
||||
import { zombies, spawnZombie, spawnRacketZombie, spawnMachine, spawnChapterBoss, piles, gore, parts, pellets, extras, shots, pops, explode, hurtPlayer, killPlayer , spawnBarrel } from './zombies.js';
|
||||
@@ -99,10 +100,35 @@ const banner = (card, cls, last) => {
|
||||
const pickMode = id => { pickedMode = id; try { localStorage.setItem('mode', id); } catch {} };
|
||||
// TAP ADVANCES. Each screen's tap both answers and moves on — the wizard's whole promise is one
|
||||
// decision per screen, and a NEXT button under a single row of choices is a second tap for nothing.
|
||||
function buildSub() {
|
||||
// NETPLAY's second step: two chips, and either one IS the start — a room needs a socket, not a
|
||||
// setup screen (court and tour ride whatever was last picked). HOST shows the code to share in a
|
||||
// modal over the frozen court; JOIN asks for a friend's code.
|
||||
// ponytail: prompt() is the join UI — replace with a drawn code entry if the mode ships.
|
||||
function netChip(label, join) {
|
||||
const b = document.createElement('button');
|
||||
b.className = 'chip';
|
||||
b.textContent = label;
|
||||
b.addEventListener('click', () => {
|
||||
let code = null;
|
||||
if (join) {
|
||||
code = (prompt('ROOM CODE FROM YOUR OPPONENT') || '').trim().toUpperCase();
|
||||
if (!code) return;
|
||||
}
|
||||
start('classic');
|
||||
if (startNetVs()) netConnect(code);
|
||||
});
|
||||
return b;
|
||||
}
|
||||
|
||||
function buildSub(top) {
|
||||
modeSub.innerHTML = '';
|
||||
const t = topOf(pickedMode);
|
||||
const t = top || topOf(pickedMode);
|
||||
document.getElementById('subTitle').textContent = t.diffs ? 'DIFFICULTY' : (t.name || '');
|
||||
if (t.net) {
|
||||
modeSub.appendChild(netChip('HOST — GET A ROOM CODE', false));
|
||||
modeSub.appendChild(netChip('JOIN WITH A CODE', true));
|
||||
return;
|
||||
}
|
||||
if (t.diffs) for (const d of DIFFS) {
|
||||
const b = document.createElement('button');
|
||||
b.className = 'chip';
|
||||
@@ -131,6 +157,9 @@ function buildTops() {
|
||||
for (const t of MENU) {
|
||||
const b = banner(t.mode ? modeOf(t.mode) : t, 'mode', topOf(pickedMode) === t);
|
||||
b.addEventListener('click', () => {
|
||||
// the netplay poster deals HOST/JOIN and never touches pickedMode — it is not a run mode
|
||||
// the stats screen or the summary could name, and either chip starts the game itself
|
||||
if (t.net) { buildSub(t); go('modesub'); return; }
|
||||
// a group remembers which child you were on; entering it fresh lands on its first card
|
||||
pickMode(t.mode || (t.kids.includes(pickedMode) ? pickedMode : t.kids[0]));
|
||||
buildSub();
|
||||
@@ -349,6 +378,8 @@ document.getElementById('scMatch').addEventListener('click', () => {
|
||||
start(null); intoSplatter();
|
||||
startMatch();
|
||||
});
|
||||
// (netplay has no back-room button: the VS A FRIEND poster on the mode screen IS the door, and
|
||||
// #net / #net-CODE below covers sending a phone straight in from a URL)
|
||||
// THE SPRITE BENCH — the art test. Same "drop into a real run first" trick as its neighbours, so the
|
||||
// body is drawn by the real renderer at the real size, on the real court, under the real palette.
|
||||
// Judging a walk cycle anywhere else is judging a different picture.
|
||||
@@ -552,6 +583,11 @@ if (location.hash === '#drill') setTimeout(() => { start(null); intoSplatter();
|
||||
if (location.hash === '#match') setTimeout(() => { start(null); intoSplatter(); startMatch(); }, 700);
|
||||
if (location.hash === '#cup') setTimeout(() => { start(null); intoSplatter(); startMatch(true); }, 700);
|
||||
if (location.hash === '#bench') setTimeout(() => { start(null); intoSplatter(); startBench(); }, 700);
|
||||
// #net hosts a room, #net-ABCD joins one — the netplay door from a URL (slice(5) skips '#net-')
|
||||
if (location.hash.startsWith('#net')) setTimeout(() => {
|
||||
start('classic');
|
||||
if (startNetVs()) netConnect(location.hash.slice(5).toUpperCase() || null);
|
||||
}, 700);
|
||||
|
||||
requestAnimationFrame(frame);
|
||||
|
||||
|
||||
Reference in new issue
Block a user