Discard valuation knows whose hand it is; the shed spares the playbook
The end-of-turn shed was eating cards the brain itself casts: BIG MAN, MIST BODY, SHRINK, LIFESAVER, and STONE DEAD all sat at or below the shed threshold. But worth is contextual — LIFESAVER is genuinely dead at a two-seat table, BIG MAN outside the berserker's book — so discardValue now takes the view and style: STONE DEAD joins the attacks tier outright, the rest are kept exactly by the wizard who can play them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0138A8CjeQRpvzKxuMfz1Bqc
This commit is contained in:
co-authored by
Claude Fable 5
parent
78085e26fb
commit
4e14926c0c
@@ -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. */
|
||||
|
||||
Reference in New Issue
Block a user