const WebSocket = require('ws') const DRAW_VEL = 2.38 const ROOMS = 10 const BASE = 'ws://127.0.0.1:3000/ws?room=' function throwFor(ws, team, broom_x, broom_y, velocity = DRAW_VEL, curl = 0, friction = 1.0) { ws.send(JSON.stringify({ type: 'throw', team, broom_x, broom_y, velocity, curl, friction })) } function connect(room) { return new Promise((resolve, reject) => { const ws = new WebSocket(BASE + room) const messages = [] ws.on('open', () => resolve({ ws, messages })) ws.on('error', reject) ws.on('message', (data) => { try { messages.push(JSON.parse(data.toString())) } catch (_) {} }) }) } function waitFor(messages, pred, timeout = 20000) { const start = Date.now() return new Promise((resolve, reject) => { const check = () => { if (pred()) return resolve(undefined) if (Date.now() - start > timeout) { const errs = messages.filter(m => m.type === 'error') return reject(new Error( 'timeout types=' + messages.map(m => m.type).join(',') + ' errs=' + JSON.stringify(errs) )) } setTimeout(check, 50) } check() }) } function latestState(messages) { for (let i = messages.length - 1; i >= 0; i--) { if (messages[i].type === 'game_state') return messages[i] } return null } function remainingSum(st) { if (!st || !Array.isArray(st.stones_remaining)) return 16 return st.stones_remaining[0] + st.stones_remaining[1] } async function runRoom(i) { const room = `LOAD${i}_${process.hrtime.bigint()}` const p1 = await connect(room) const p2 = await connect(room) await waitFor(p1.messages, () => { const st = latestState(p1.messages) return st && st.phase === 'playing' }, 8000) let st = latestState(p1.messages) const gsBefore = p1.messages.filter(m => m.type === 'game_state').length throwFor(p1.ws, st.turn_team, 0.2, 38.5, DRAW_VEL, 0, 1.0) await waitFor(p1.messages, () => p1.messages.some(m => m.type === 'trajectories'), 20000) // Must wait for post-throw game_state (remaining decreased), not the pre-throw playing state. await waitFor(p1.messages, () => { const s = latestState(p1.messages) return s && s.phase === 'playing' && remainingSum(s) < 16 && p1.messages.filter(m => m.type === 'game_state').length > gsBefore }, 20000) st = latestState(p1.messages) const trajBefore = p1.messages.filter(m => m.type === 'trajectories').length throwFor(p2.ws, st.turn_team, -0.2, 38.5, DRAW_VEL, 0, 1.0) await waitFor(p1.messages, () => p1.messages.filter(m => m.type === 'trajectories').length > trajBefore, 20000) p1.ws.close() p2.ws.close() } ;(async () => { const start = Date.now() await Promise.all(Array.from({ length: ROOMS }, (_, i) => runRoom(i))) console.log(`10 rooms played start-to-throw-to-close in ${Date.now() - start}ms`) process.exit(0) })().catch((e) => { console.error(e) process.exit(1) })