// Try a room locally: node src/try-room.mjs [seed]. Starts referee/room.mjs with a scripted person at seat 0 and a // scripted agent at seat 1 (both play like the starter bot), the field and auto at the others, and prints every // @shoal line, shortened. On the way it hands seat 0 to an agent and sends one move with the old version (the room // drops it), lets seat 1 miss its time limit once (auto plays), then hands seat 1 back to auto. import { spawn } from 'node:child_process'; import { createInterface } from 'node:readline'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import starter from '../routes/bot.mjs'; const GAME = path.dirname(path.dirname(fileURLToPath(import.meta.url))); const seats = { 0: { kind: 'human', name: 'ada' }, 1: { kind: 'agent', name: 'bob-codex' }, 2: { kind: 'bot', field: 'upstream' }, 3: { kind: 'bot', field: 'random' }, }; const room = spawn(process.execPath, ['referee/room.mjs'], { cwd: process.env.ROOM_DIR ?? GAME, env: { ...process.env, SS_ROOM: JSON.stringify({ seats, seed: Number(process.argv[2] ?? 3), limit: 2 }) }, stdio: ['pipe', 'pipe', 'inherit'], }); const send = (m) => { console.log(`>> ${JSON.stringify(m)}`); room.stdin.write(`${JSON.stringify(m)}\n`); }; const short = (s) => (s.length > 300 ? `${s.slice(0, 300)}… (${s.length} bytes)` : s); let asked = { 0: 0, 1: 0 }; let handedOver = false; let stale = null; let missed = false; const lines = { event: 0, private: 0, request: 0 }; for await (const line of createInterface({ input: room.stdout })) { if (!line.startsWith('@shoal ')) { console.log(` log: ${line}`); continue; } const m = JSON.parse(line.slice(7)); if (m.event) lines.event++; else if (m.to !== undefined && m.version === undefined) lines.private++; else if (m.version !== undefined) lines.request++; console.log(`<< ${short(JSON.stringify(m))}`); if (m.end) break; if (m.version === undefined) continue; const seat = Number(m.to); if (!(seat in asked)) continue; asked[seat]++; // Seat 1 misses its limit once: no answer, auto plays for it after 2 s. if (seat === 1 && asked[1] === 3 && !missed) { missed = true; console.log(' (seat 1 lets its time run out)'); continue; } // After seat 0's fifth ask, a person hands it to their agent; the old controller's late move must be dropped. if (seat === 0 && asked[0] === 5 && !handedOver) { handedOver = true; stale = m.version; send({ control: { seat: '0', kind: 'agent', name: 'ada-codex' } }); continue; } if (seat === 0 && stale !== null && m.version !== stale) { send({ act: { seat: '0', version: stale, action: starter(m.obs, m.legal) } }); stale = null; } send({ act: { seat: String(seat), version: m.version, action: starter(m.obs, m.legal) } }); if (seat === 1 && asked[1] === 6) send({ control: { seat: '1', kind: 'auto' } }); } console.log(`\n${lines.event} public events, ${lines.private} private updates, ${lines.request} requests`); room.stdin.end();