forked from eros/curltastic
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
73 lines
3.1 KiB
JavaScript
73 lines
3.1 KiB
JavaScript
const WebSocket = require('ws')
|
|
const room = 'COLQA' + Math.floor(Math.random() * 1000)
|
|
const url = 'ws://127.0.0.1:3000/ws?room=' + room
|
|
|
|
function connect() {
|
|
return new Promise((resolve, reject) => {
|
|
const ws = new WebSocket(url)
|
|
const messages = []
|
|
ws.on('open', () => resolve({ ws, messages }))
|
|
ws.on('message', (data) => messages.push(JSON.parse(data.toString())))
|
|
ws.on('error', reject)
|
|
})
|
|
}
|
|
|
|
function waitFor(messages, pred, timeout = 10000) {
|
|
const start = Date.now()
|
|
return new Promise((resolve, reject) => {
|
|
const check = () => {
|
|
if (pred()) return resolve(undefined)
|
|
if (Date.now() - start > timeout) return reject(new Error('timeout'))
|
|
setTimeout(check, 50)
|
|
}
|
|
check()
|
|
})
|
|
}
|
|
|
|
;(async () => {
|
|
const c = await connect()
|
|
await waitFor(c.messages, () => {
|
|
const last = c.messages[c.messages.length - 1]
|
|
return last && last.type === 'game_state' && last.phase === 'playing'
|
|
})
|
|
|
|
// First throw: weight 7 so it stays in house.
|
|
let state = c.messages[c.messages.length - 1]
|
|
c.ws.send(JSON.stringify({ type: 'throw', team: state.turn_team, broom_x: 0.0, broom_y: 38.5, weight: 7, curl: 0, friction: 1.0 }))
|
|
await waitFor(c.messages, () => c.messages.filter(m => m.type === 'game_state').length > 1)
|
|
state = c.messages[c.messages.length - 1]
|
|
console.log('After first throw stones:', state.stones.map(s => ({ id: s.id, x: s.x, y: s.y })))
|
|
if (state.stones.length !== 1) throw new Error('expected first stone in play')
|
|
|
|
const trajCountBefore = c.messages.filter(m => m.type === 'trajectory').length
|
|
console.log('trajectory count before second throw:', trajCountBefore)
|
|
|
|
// Second throw aimed slightly off-center so it hits the first stone.
|
|
c.ws.send(JSON.stringify({ type: 'throw', team: state.turn_team, broom_x: 0.25, broom_y: 38.5, weight: 7, curl: 0, friction: 1.0 }))
|
|
await waitFor(c.messages, () => c.messages.filter(m => m.type === 'trajectory').length > trajCountBefore)
|
|
|
|
const traj = c.messages.filter(m => m.type === 'trajectory').pop()
|
|
console.log('Trajectory paths count:', traj.paths.length)
|
|
for (const p of traj.paths) {
|
|
console.log('stone_id', p.stone_id, 'path length', p.path.length, 'first', p.path[0], 'last', p.path[p.path.length - 1])
|
|
}
|
|
|
|
const ids = traj.paths.map(p => p.stone_id).sort((a, b) => a - b)
|
|
if (ids.length !== 2 || ids[0] !== 1 || ids[1] !== 2) throw new Error('expected both stone ids in trajectory, got ' + JSON.stringify(ids))
|
|
for (const p of traj.paths) {
|
|
if (p.path.length < 5) throw new Error('path too short for stone ' + p.stone_id)
|
|
}
|
|
|
|
// Verify the first stone actually moved because of collision.
|
|
const stone1Path = traj.paths.find(p => p.stone_id === 1).path
|
|
const first = stone1Path[0]
|
|
const last = stone1Path[stone1Path.length - 1]
|
|
const dist = Math.sqrt((last[0]-first[0])**2 + (last[1]-first[1])**2)
|
|
console.log('stone1 moved', dist, 'm')
|
|
if (dist < 0.05) throw new Error('expected first stone to move after collision')
|
|
|
|
console.log('COLLISION TRAJECTORY QA PASSED')
|
|
c.ws.close()
|
|
process.exit(0)
|
|
})().catch(e => { console.error(e); process.exit(1) })
|