AI playing better
This commit is contained in:
parent
e092bd5d01
commit
b541dd49e2
110
server/ai/app.ts
110
server/ai/app.ts
@ -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;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user