1
0

AI playing better

This commit is contained in:
James Ketr 2025-10-13 13:43:26 -07:00
parent e092bd5d01
commit b541dd49e2

View File

@ -566,26 +566,72 @@ const processDiscard = async (_received?: any): Promise<any> => {
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;
}
const cards: string[] = [];
// Intelligent discard heuristic:
// - Compute counts for each resource type from our private hand.
// - Repeatedly discard from the type we have the most of (most-abundant first).
// - Tie-breaker: prefer to discard sheep first, then brick/wood, then wheat/stone
// (sheep is typically least critical; wheat/stone useful for cities/devs).
const discards: Record<string, number> = {};
types.forEach(type => {
for (let i = 0; i < game.private[type]; i++) {
cards.push(type);
}
});
if (cards.length === 0) {
const counts: Record<string, number> = {};
types.forEach(type => { counts[type] = Math.max(0, Number(game.private[type]) || 0); });
let totalCards = types.reduce((s, t) => s + (counts[t] || 0), 0);
if (totalCards === 0) {
// nothing to discard (defensive)
return;
}
while (mustDiscard--) {
const type = cards[Math.floor(Math.random() * cards.length)] as string;
if (!(type in discards)) {
discards[type] = (discards[type] || 0) + 1;
const tieOrder = [ 'sheep', 'brick', 'wood', 'wheat', 'stone' ];
while (mustDiscard-- > 0 && totalCards > 0) {
// pick the resource with the highest count; tie-break using tieOrder
let bestType: string | null = null;
let bestCount = -1;
for (const t of types) {
const c = counts[t] || 0;
if (c > bestCount) {
bestCount = c;
bestType = t;
} else if (c === bestCount && c > 0 && bestType) {
// tie-breaker by tieOrder index (lower index == prefer to discard)
const curTie = tieOrder.indexOf(t);
const bestTie = tieOrder.indexOf(bestType);
if (curTie !== -1 && bestTie !== -1 && curTie < bestTie) {
bestType = t;
}
}
}
if (!bestType || (counts[bestType] || 0) <= 0) {
// no selectable resource left
break;
}
discards[bestType] = (discards[bestType] || 0) + 1;
counts[bestType] = (counts[bestType] || 0) - 1;
totalCards--;
}
console.log(`discarding - `, discards);
send({
@ -775,6 +821,31 @@ const processNormal = async (received?: any): Promise<any> => {
};
}
// 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.
if (game.turn && game.turn.robberInAction) {
if (!game.players) {
console.log(`${name} - robber in action and players missing; requesting players`);
return { players: anyValue };
}
const playerObj = game.players[game.color];
const playerResources = (playerObj && typeof playerObj.resources !== 'undefined') ? Number(playerObj.resources) : undefined;
const privateTotal = types.reduce((s, t) => s + (Number(game.private && game.private[t]) || 0), 0);
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`);
}
}
}
console.log(`${name}'s turn. Processing...`);
if (!game.dice) {
@ -895,10 +966,19 @@ const message = async (data: WebSocket.Data): Promise<void> => {
switch (msg.type) {
case 'warning':
if (game.turn && game.turn.color === game.color && game.state !== 'lobby') {
console.log(`WARNING: ${msg.warning}. Passing.`);
send({ type: 'pass' });
waitingFor = { turn: { color: game.color } };
processWaitingFor(waitingFor);
console.log(`WARNING: ${msg.warning}.`);
// If robber is in action or we may need to discard, do not blindly pass.
if (game.turn.robberInAction) {
console.log(`${name} - WARNING received while robber in action; ensuring players data`);
// Request players so we learn mustDiscard fields and can act accordingly.
waitingFor = { players: anyValue };
processWaitingFor(waitingFor);
} else {
console.log(`${name} - not in robber action; passing.`);
send({ type: 'pass' });
waitingFor = { turn: { color: game.color } };
processWaitingFor(waitingFor);
}
}
break;