diff --git a/server/ai/app.ts b/server/ai/app.ts index 0f5587f..5c70acd 100644 --- a/server/ai/app.ts +++ b/server/ai/app.ts @@ -170,34 +170,60 @@ const tryProgress = (_received?: any): any => { if (!game.private || !game.private.development) { return undefined; } - let vps = 0; + let firstPlayableProgress: any = undefined; + // Count VP cards and remember the first non-VP progress card we can play for (let i = 0; i < game.private.development.length; i++) { const card = game.private.development[i]; - if (card.turn >= game.turns || card.played || card.type === 'vp') { + // card.turn >= game.turns => newly drawn this turn and can't be played + if (card.turn >= game.turns || card.played) { if (card.type === 'vp') vps++; continue; } + if (card.type === 'vp') { + vps++; + continue; + } + // remember the first playable non-VP card + if (!firstPlayableProgress) firstPlayableProgress = card; + } - console.log(`${name} - Playing -`, card); - send({ - type: 'play-card', - card - }); + // If we have a non-VP progress card to play, play it (existing behavior) + if (firstPlayableProgress) { + console.log(`${name} - Playing -`, firstPlayableProgress); + send({ type: 'play-card', card: firstPlayableProgress }); + return { turn: { actions: anyValue } }; + } - return { - turn: { - actions: anyValue + // If we have VP cards, consider playing them to win. Determine victory threshold. + if (vps > 0) { + const currentPoints = (game.players && game.players[game.color] && game.players[game.color].points) || 0; + let victoryThreshold = 10; + try { + if (game.rules && game.rules['victory-points'] && ('points' in game.rules['victory-points'])) { + victoryThreshold = Number(game.rules['victory-points'].points) || victoryThreshold; + } + } catch (e) { + // ignore and use default + } + + // If playing our VP cards would reach or exceed the threshold, play one now + if (currentPoints + vps >= victoryThreshold) { + // find first unplayed VP card object + for (let i = 0; i < game.private.development.length; i++) { + const card = game.private.development[i]; + if (card.type === 'vp' && !(card.played) && !(card.turn >= game.turns)) { + console.log(`${name} - Playing VP to attempt win -`, card); + send({ type: 'play-card', card }); + return { turn: { actions: anyValue } }; + } } } + + // Otherwise just announce how many VP cards we have + send({ type: 'chat', message: `I have ${vps} VP cards!` }); } - if (vps) { - send({ - type: 'chat', - message: `I have ${vps} VP cards!` - }); - } return undefined; }; @@ -365,7 +391,7 @@ const processLobby = (received: any): any => { /* AI selected a Player. Wait for game-order. * Only treat the bot as already selected if its color is not the 'unassigned' sentinel. - * Some servers may return an empty 'unselected' array even while the player's color + * Server may return an empty 'unselected' array even while the player's color * remains 'unassigned', so guard that case explicitly. */ if (received.unselected.indexOf(name) === -1 && game.color !== 'unassigned') { send({ @@ -566,25 +592,6 @@ const processDiscard = async (_received?: any): Promise => { let mustDiscard = game.players[game.color].mustDiscard; - // Some servers may not explicitly set `mustDiscard` in the players object; - // derive it from the player's resource count when possible. According to - // rules: if a player has >7 resources they must discard floor(resources/2). - if (!mustDiscard) { - const playerResources = Number(game.players[game.color].resources || 0); - const privateTotal = types.reduce((s, t) => s + (Number(game.private && game.private[t]) || 0), 0); - const totalResources = playerResources || privateTotal; - if (totalResources > 7) { - mustDiscard = Math.floor(totalResources / 2); - console.log(`${name} - computed mustDiscard=${mustDiscard} from totalResources=${totalResources} (playerResources=${playerResources}, privateTotal=${privateTotal})`); - // Store it so subsequent logic can observe it if desired - try { - game.players[game.color].mustDiscard = mustDiscard; - } catch (e) { - // ignore if assignment fails on read-only snapshot - } - } - } - if (!mustDiscard) { return; } @@ -822,8 +829,7 @@ const processNormal = async (received?: any): Promise => { } // If the robber is active on our turn, ensure we have player information - // only when a discard might be required. Some servers omit `mustDiscard`; - // in that case request `players` only if the known resource total > 7. + // only when a discard might be required. if (game.turn && game.turn.robberInAction) { if (!game.players) { console.log(`${name} - robber in action and players missing; requesting players`); @@ -836,13 +842,8 @@ const processNormal = async (received?: any): Promise => { const totalResources = (typeof playerResources !== 'undefined') ? playerResources : privateTotal; if (typeof game.players[game.color].mustDiscard === 'undefined') { - if (totalResources > 7) { - console.log(`${name} - robber in action but mustDiscard unknown and totalResources=${totalResources} > 7; requesting players`); - return { players: anyValue }; - } else { - // No discard required (totalResources <= 7); proceed with turn actions. - console.log(`${name} - robber in action but no discard required (totalResources=${totalResources}); proceeding`); - } + // No discard required (totalResources <= 7); proceed with turn actions. + console.log(`${name} - robber in action but no discard required (totalResources=${totalResources}); proceeding`); } }