/** * 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" ? state.last ? `round over — ${state.last.winner} took it, ${state.last.why.charAt(0).toLowerCase()}${state.last.why.slice(1)}` : "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) => `${escape(p.name)}${p.bot ? " bot" : ""}${escape(p.agent)}${p.kills}${p.deaths}`, ) .join(""); return ` val — attack, defend, plant the spike

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)}` : ""}
click to aim · esc releases
waiting for the next round
Click the map to aim, then press H for how to play.
${["", "", "", "", ""] .map(() => `
—
`) .join("")}
100 hp Classic 12 800 cr ult 0/6

Buy phase. Spend your credits on a gun and armour for this round; a greyed price is more than you have, and the outlined one is what you hold. Two abilities are bought here too, and your agent can be swapped until you buy one.

agent${AGENTS.map( (a) => ``, ).join("")}
gun${weaponIds() .map( (id) => ``, ) .join("")}
armour
abilities
${state.events .slice(0, 3) .map((e) => `
${escape(e.text)}
`) .join("")}

attackers

${row("atk")}
playeragentkd

defenders

${row("def")}
playeragentkd
`; }