forked from eros/curltastic
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
86 lines
2.9 KiB
JavaScript
86 lines
2.9 KiB
JavaScript
const WebSocket = require('ws')
|
|
|
|
const room = 'PERSIST' + Math.floor(Math.random() * 1000)
|
|
const base = 'ws://127.0.0.1:3000/ws?room=' + room
|
|
|
|
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, msg.type === 'game_state' ? ` turn=${msg.turn_team} stones=${msg.stones.length}` : '')
|
|
if (msg.type === 'joined') {
|
|
resolve({ ws, messages, team: msg.team })
|
|
}
|
|
})
|
|
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))
|
|
})
|
|
}
|
|
|
|
async function waitFor(condFn, timeout = 5000) {
|
|
const start = Date.now()
|
|
while (!condFn()) {
|
|
if (Date.now() - start > timeout) throw new Error('Timeout waiting')
|
|
await new Promise(r => setTimeout(r, 50))
|
|
}
|
|
}
|
|
|
|
function latestState(messages) {
|
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
if (messages[i].type === 'game_state') return messages[i]
|
|
}
|
|
return null
|
|
}
|
|
|
|
;(async () => {
|
|
const p1 = await connect('p1')
|
|
await waitFor(() => p1.messages.some(m => m.type === 'waiting'))
|
|
const p2 = await connect('p2')
|
|
await waitFor(() => p2.messages.some(m => m.type === 'game_state'))
|
|
|
|
// Determine the current player and throw two stones in the same end.
|
|
let state = latestState(p1.messages)
|
|
console.log('Initial state', state)
|
|
|
|
// First throw: yellow aims slightly left of center.
|
|
let turn = state.turn_team
|
|
let turnPlayer = turn === p1.team ? p1 : p2
|
|
turnPlayer.ws.send(JSON.stringify({ type: 'throw', broom_x: -0.3, broom_y: 39, weight: 7, curl: 1, friction: 1.0 }))
|
|
await waitFor(() => latestState(p1.messages)?.stones?.length === 1, 15000)
|
|
|
|
state = latestState(p1.messages)
|
|
console.log('After first throw:', state)
|
|
|
|
// Second throw: red aims slightly right of center.
|
|
turn = state.turn_team
|
|
turnPlayer = turn === p1.team ? p1 : p2
|
|
turnPlayer.ws.send(JSON.stringify({ type: 'throw', broom_x: 0.3, broom_y: 39, weight: 7, curl: -1, friction: 1.0 }))
|
|
await waitFor(() => latestState(p1.messages)?.stones?.length === 2, 15000)
|
|
|
|
state = latestState(p1.messages)
|
|
console.log('After second throw:', state)
|
|
|
|
if (state.stones.length !== 2) throw new Error(`Expected 2 stones after second throw, got ${state.stones.length}`)
|
|
|
|
// Check that ids are monotonic.
|
|
const ids = state.stones.map(s => s.id).sort((a, b) => a - b)
|
|
if (ids[0] !== 1 || ids[1] !== 2) throw new Error(`Unexpected stone ids: ${ids}`)
|
|
|
|
// Both stones should be in play (near house).
|
|
for (const s of state.stones) {
|
|
if (s.y < 35 || s.y > 42) throw new Error(`Stone ${s.id} is out of house: y=${s.y}`)
|
|
}
|
|
|
|
console.log('PERSISTENCE E2E PASSED')
|
|
p1.ws.close()
|
|
p2.ws.close()
|
|
process.exit(0)
|
|
})().catch(err => {
|
|
console.error(err)
|
|
process.exit(1)
|
|
})
|