/**
* The page. The first paint is the match as it stands right now — score,
* round, phase, scoreboard — server-rendered HTML, so somebody arriving with
* a cold cache or no JavaScript still sees a live match. The client script
* then takes the same state over a socket and draws it.
*/
import { AGENTS } from "../abilities.ts";
import type { Result } from "../db.ts";
import type { MatchState } from "../state.ts";
import { WEAPONS, weaponIds } from "../weapons.ts";
export const escape = (s: string): string =>
s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c] ?? c);
const phaseWord = (state: MatchState): string =>
state.phase === "buy"
? "buy phase"
: state.phase === "live"
? state.spike.planted
? `spike down · ${Math.ceil(state.spike.fuse)}s`
: "live"
: state.phase === "end"
? "round over"
: "match over";
export function gamePage(opts: {
state: MatchState;
script: string;
name: string;
env: string;
commit: string;
ssh: string | undefined;
results: Result[];
}): string {
const { state } = opts;
const board = [...state.players].sort((a, b) => b.kills - a.kills);
const row = (side: "atk" | "def"): string =>
board
.filter((p) => p.side === side)
.map(
(p) =>
`
val
${escape(opts.env)}${opts.commit ? " @ " + escape(opts.commit) : ""}
connecting…
red ${state.score.red}
${Math.max(0, Math.ceil(state.clock))}
${state.score.blue} blue
round ${state.round} · ${escape(phaseWord(state))} · ${escape(state.attackers)} attacking${state.result ? ` · ${escape(state.result)}` : ""}
waiting for the next round
100 hp
Classic 12
800 cr
ult 0/6
Buy phase — credits are refused server-side if you cannot afford them.
${AGENTS.map(
(a) => ``,
).join("")}
${weaponIds()
.map((id) => ``)
.join("")}
${state.events
.slice(0, 3)
.map((e) => `
${escape(e.text)}
`)
.join("")}
attackers
| player | agent | k | d |
${row("atk")}
defenders
| player | agent | k | d |
${row("def")}
`;
}