1import * as _ from 'lodash-es' 6const fakeDrawing = [{ points: [{ x: 100, y: 100 }, { x: 200, y: 200 }], brushRadius: 3 }] 7const fakeDescriptions = ['cat on mat', 'dancing robot', 'happy cloud', 'confused penguin', 'wizard cooking'] 9type CreateAgentOpts = { sessId: string, playerAlias: string, seed?: string, type?: 'random' | 'chaotic' } 11/** Creates an agent that plays the game */ 12export const createAgent = ({ sessId, playerAlias, seed = 'agent', type = 'random' }: CreateAgentOpts): TestAgent => { 16 const ps = _.get(gameState, ['playerStates', sessId]) 17 if (!ps) return { sessId, playerAction: 'setPlayerAlias', playerAlias } 18 if (ps.hasBeenKicked) return null 20 const { assignedStackName } = ps 21 const task = ps.publicState?.myTaskName 23 if (task === 'InitialPrompt' || task === 'DescribeDrawing') { 24 return { sessId, playerAction: 'submitDescription', descriptionText: seededLo.sample(fakeDescriptions), assignedStackName } 26 if (task === 'DrawDescription') { 27 return { sessId, playerAction: 'submitDrawing', finishedTrails: fakeDrawing, assignedStackName } 33 const action = chooseAction(gameState) 34 if (!action) return null 35 const roll = seededLo.random(0, 100) 36 if (roll < 10 && action.assignedStackName) { 37 const stacks = _.keys(_.get(gameState, ['hiddenState', 'paperStacks'], {})) 38 const wrongStack = seededLo.sample(stacks.filter(s => s !== action.assignedStackName)) 39 if (wrongStack) return { ...action, assignedStackName: wrongStack } 41 if (roll >= 10 && roll < 15) return { sessId, playerAction: 'leaveGame' } 45 return { sessId, playerAlias, chooseAction: type === 'chaotic' ? chooseChaoticAction : chooseAction } 48export { testPlayerPrefix } 50type CreateTeamOpts = { count?: number, seed?: string, type?: 'random' | 'chaotic' } 52export const createAgentTeam = ({ count = 4, seed = 'team', type = 'random' }: CreateTeamOpts = {}): TestAgent[] => { 53 return _.times(count, i => createAgent({ 63 let noActionRounds = 0 64 let initialGameId: string | null = null 66 while (round++ < maxRounds) { 67 const gameState = harness.getState() 68 if (gameState?.isGameFin) break 70 // Track game ID - if it changes, the game finished and reset 71 // This is the primary way to detect game completion in live tests since 72 // the server resets state immediately after saving finished games 73 if (!initialGameId && gameState?.id) { 74 initialGameId = gameState.id 75 } else if (initialGameId && gameState?.id && gameState.id !== initialGameId) { 76 // Game ID changed = game finished and a new one started 77 return { rounds: round, isGameFin: true, gameIdChanged: true, reachedMaxRounds: false } 80 let actionsThisRound = 0 81 for (const agent of agents) { 82 const action = agent.chooseAction(gameState) 84 await harness.applyAction(action) 89 onRound?.({ round, actionsThisRound, isGameFin: gameState?.isGameFin }) 91 // Detect stuck state - if no agent can act for several rounds, break 92 if (actionsThisRound === 0) { 94 if (noActionRounds >= 5) { 95 break // All agents are waiting or blocked 101 const finalState = harness.getState() 102 return { rounds: round, isGameFin: finalState?.isGameFin, reachedMaxRounds: round >= maxRounds }