// Script a complete 2p game on the local server: walk together, punch to the death. import { WebSocket } from "ws"; import { stepTarget, cellKey } from "./packages/engine/src/board.ts"; const url = "ws://localhost:8899/"; function client() { const ws = new WebSocket(url); const queue = []; const waiters = []; ws.on("message", (d) => { const m = JSON.parse(d.toString()); if (waiters.length) waiters.shift()(m); else queue.push(m); }); const next = () => new Promise((res) => (queue.length ? res(queue.shift()) : waiters.push(res))); const send = (m) => ws.send(JSON.stringify(m)); return { ws, next, send, queue }; } const until = async (c, pred) => { for (;;) { const m = await c.next(); if (pred(m)) return m; } }; const a = client(); await until(a, (m) => m.type === "welcome"); a.send({ type: "create", name: "alice" }); const seatA = await until(a, (m) => m.type === "seat"); const room = await until(a, (m) => m.type === "room"); const code = room.roomId; const b = client(); await until(b, (m) => m.type === "welcome"); b.send({ type: "join", roomId: code, name: "bob" }); await until(b, (m) => m.type === "seat"); a.send({ type: "start", expansion: false }); let viewA = (await until(a, (m) => m.type === "state")).view; let viewB; const me = (v, who) => v.players.find((p) => p.id === who); const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); const settle = async () => { await sleep(120); let err = null; for (const c of [a, b]) { while (c.queue.length) { const m = c.queue.shift(); if (m.type === "state" && c === a) viewA = m.view; if (m.type === "error") err = m.message; } } return err; }; const cmd = async (c, command) => { await settle(); c.send({ type: "command", command }); const err = await settle(); return err ? { error: err } : { view: viewA }; }; const step = async (c, dir) => cmd(c, { type: "move", direction: dir }); let safety = 600; for (;;) { if (--safety <= 0) throw new Error("script ran away"); await settle(); const who = viewA.activePlayerId; const active = who === "alice" ? a : b; const other = who === "alice" ? b : a; const av = viewA; if (av.phase === "finished") break; const meP = me(av, who); const foe = av.players.find((p) => p.id !== who); const sameCell = meP.position.x === foe.position.x && meP.position.y === foe.position.y; let acted = false; if (sameCell && av.turn.round > 1 && !av.turn.attackUsed) { const r = await cmd(active, { type: "punch", targetId: foe.id }); if (!r.error) { await cmd(other, { type: "pass" }); acted = true; } } if (!acted && who === "alice" && av.turn.movementAllowance > av.turn.movementUsed) { // BFS the maze toward bob; take the first step of the shortest path. const board = av.board; const start = meP.position, goal = foe.position; const prev = new Map([[cellKey(start), null]]); let frontier = [start]; let found = null; while (frontier.length && !found) { const nf = []; for (const c of frontier) { for (const d of ["N", "S", "E", "W"]) { const t = stepTarget(board, c, d); if (t.kind === "blocked") continue; const k = cellKey(t.to); if (prev.has(k)) continue; prev.set(k, { from: c, dir: d }); if (k === cellKey(goal)) { found = t.to; break; } nf.push(t.to); } if (found) break; } frontier = nf; } if (found) { let node = cellKey(found), hop = prev.get(node); while (hop && cellKey(hop.from) !== cellKey(start)) { node = cellKey(hop.from); hop = prev.get(node); } if (hop) { const r = await step(active, hop.dir); if (!r.error) continue; } } } const r = await cmd(active, { type: "endTurn", draw: 0 }); if (r.error && /game is over/.test(r.error)) break; if (r.error) throw new Error("endTurn failed: " + r.error); } console.log("FINISHED. winner:", viewA.winner, "room:", code); // Full replay request. a.send({ type: "catchUp", sinceSeq: 0, full: true }); const cu = await until(a, (m) => m.type === "catchUp" || m.type === "error"); if (cu.type === "error") { console.log("replay error:", cu.message); process.exit(1); } console.log("steps:", cu.steps.length); const s0 = cu.steps[0]; console.log("step0 keys:", Object.keys(s0).join(",")); console.log("step0.view.board present:", !!s0.view?.board, "cells:", s0.view ? Object.keys(s0.view.board.cells).length : 0); console.log("SEAT for browser:", JSON.stringify([{ roomId: code, name: "alice", token: seatA.token }])); process.exit(0);