diff --git a/packages/engine/src/automaton.ts b/packages/engine/src/automaton.ts index 681abf5..ef21ba0 100644 --- a/packages/engine/src/automaton.ts +++ b/packages/engine/src/automaton.ts @@ -112,18 +112,25 @@ function numbersInHand(view: GameView): CardInstance[] { .sort((a, b) => (cardDef(a.cardId).value ?? 0) - (cardDef(b.cardId).value ?? 0)); } -/** Cards the automaton happily discards to churn its hand, worst first. */ -function discardValue(c: CardInstance): number { +/** Cards the automaton happily discards to churn its hand, worst first. + * A card is only worth keeping to the wizard who can actually play it — + * LIFESAVER is dead weight at a two-seat table, BIG MAN outside the + * berserker's book — so the valuation knows whose hand this is. */ +function discardValue(c: CardInstance, view: GameView, style?: AutomatonStyle): number { const def = cardDef(c.cardId); if (def.cardType === "counteraction" || def.cardType === "neutral/counteraction") return 9; if (SUMMONS.has(c.cardId) || UNLOCKS.includes(c.cardId) || STONES.has(c.cardId)) return 8; - if (ATTACKS[c.cardId] != null || AFFLICTIONS[c.cardId] != null) return 7; + if (ATTACKS[c.cardId] != null || AFFLICTIONS[c.cardId] != null || + c.cardId === "stone-dead") return 7; if (c.cardId === "speed" || c.cardId === "interrupt" || c.cardId === "opportunity-fire" || c.cardId === "ward" || c.cardId === "drop-object" || c.cardId === "gift-from-above" || c.cardId === "deja-vu" || c.cardId === "amplify" || c.cardId === "safe" || c.cardId === "glue" || c.cardId === "destroy-wall" || c.cardId === "pass-through-wall" || c.cardId === "create-wall" || c.cardId === "fill-square-with-stone" || c.cardId === "thornbush") return 6; + if (c.cardId === "lifesaver" && view.players.length > 2) return 6; + if (c.cardId === "big-man" && style === "berserker") return 6; + if ((c.cardId === "mist-body" || c.cardId === "shrink") && style === "worrier") return 6; if (def.cardType === "number") return 4 + (def.value ?? 0); if (def.cardType === "attack") return 3; return 2; // situational neutrals go first @@ -455,12 +462,12 @@ function overLimit(view: GameView): number { * too full to take the draw. doEndTurn caps the draw by the room left, so a * clogged hand never refreshes; only cards the brain has no play for are * shed (good counters and attacks are hoarded, as a human would). */ -function endTurnDrawing(view: GameView, tier: TierTraits): Command { +function endTurnDrawing(view: GameView, tier: TierTraits, style?: AutomatonStyle): Command { const deficit = tier.draw - (handLimitOf(view) - view.yourHand.length); if (tier.sheds && deficit > 0) { const shed = [...view.yourHand] - .filter((c) => discardValue(c) <= 3) - .sort((a, b) => discardValue(a) - discardValue(b)) + .filter((c) => discardValue(c, view, style) <= 3) + .sort((a, b) => discardValue(a, view, style) - discardValue(b, view, style)) .slice(0, deficit) .map((c) => c.instanceId); if (shed.length > 0) return { type: "discard", instanceIds: shed }; @@ -468,9 +475,9 @@ function endTurnDrawing(view: GameView, tier: TierTraits): Command { return { type: "endTurn", draw: tier.draw }; } -function worstCards(view: GameView, n: number): string[] { +function worstCards(view: GameView, n: number, style?: AutomatonStyle): string[] { return [...view.yourHand] - .sort((a, b) => discardValue(a) - discardValue(b)) + .sort((a, b) => discardValue(a, view, style) - discardValue(b, view, style)) .slice(0, n) .map((c) => c.instanceId); } @@ -807,7 +814,7 @@ export function automatonCommand( if (view.phase !== "playing") return null; if (view.pendingDiscard === you) { - return { type: "discard", instanceIds: worstCards(view, Math.max(1, overLimit(view))) }; + return { type: "discard", instanceIds: worstCards(view, Math.max(1, overLimit(view)), style) }; } if (view.chaosPending && view.chaosPending.queue[0] === you) { const shield = inHand(view, "full-shield"); @@ -824,7 +831,7 @@ export function automatonCommand( const self = me(view); const here = cellKey(self.position); - if (view.turn.actionsEnded) return endTurnDrawing(view, tier); + if (view.turn.actionsEnded) return endTurnDrawing(view, tier, style); // Deliver or grab treasure underfoot. if (self.carriedTreasureId && here === cellKey(self.home)) return { type: "dropTreasure" }; @@ -988,7 +995,7 @@ export function automatonCommand( } } - return endTurnDrawing(view, tier); + return endTurnDrawing(view, tier, style); } /** The safe fallback when the automaton's choice was refused. */