const WebSocket = require('ws') const DRAW_VEL = 2.38 const room = 'MULTI' + Math.floor(Math.random() * 1000) const base = 'ws://127.0.0.1:3000/ws?room=' + 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(name) { return new Promise((resolve, reject) => { const ws = new WebSocket(base) const messages = [] ws.on('message', (data) => { const msg = JSON.parse(data.toString()) messages.push(msg) console.log(`[${name}]`, msg.type) if (msg.type === 'joined') { resolve({ ws, messages }) } }) ws.on('open', () => console.log(`[${name}] open`)) ws.on('error', (e) => { console.error(`[${name}] error`, e.message); reject(e) }) ws.on('close', (code) => console.log(`[${name}] close`, code)) }) } function waitFor(client, pred, timeout = 10000) { const start = Date.now() return new Promise((resolve, reject) => { const check = () => { if (pred(client.messages)) return resolve(undefined) if (Date.now() - start > timeout) return reject(new Error('timeout')) setTimeout(check, 50) } check() }) } ;(async () => { // 3 clients join the same room const p1 = await connect('p1') const p2 = await connect('p2') const p3 = await connect('p3') // All 3 receive game_state after join await waitFor(p1, msgs => msgs.some(m => m.type === 'game_state'), 5000) await waitFor(p2, msgs => msgs.some(m => m.type === 'game_state'), 5000) await waitFor(p3, msgs => msgs.some(m => m.type === 'game_state'), 5000) console.log('All 3 clients received game_state') // p1 throws for the current turn_team const state = p1.messages.find(m => m.type === 'game_state') const turn = state.turn_team console.log(`p1 throwing for team ${turn}`) throwFor(p1.ws, turn, 0.0, 38.5, DRAW_VEL, 0, 1.0) // All 3 clients eventually see trajectories (plural) or updated game_state await waitFor(p1, msgs => msgs.some(m => m.type === 'trajectories'), 15000) await waitFor(p2, msgs => msgs.some(m => m.type === 'trajectories'), 15000) await waitFor(p3, msgs => msgs.some(m => m.type === 'trajectories'), 15000) console.log('All 3 clients received trajectories') // All 3 see an updated game_state after the throw await waitFor(p1, msgs => msgs.slice(-1)[0]?.type === 'game_state', 15000) await waitFor(p2, msgs => msgs.slice(-1)[0]?.type === 'game_state', 15000) await waitFor(p3, msgs => msgs.slice(-1)[0]?.type === 'game_state', 15000) console.log('All 3 clients received updated game_state after throw') console.log('Multi-client observer test passed') p1.ws.close() p2.ws.close() p3.ws.close() process.exit(0) })().catch(err => { console.error(err) process.exit(1) })