The clockwork throws more spells and fewer fists
Punching was mostly deck arithmetic — nineteen castable damage cards in a 196-card deck — but three leaks made it worse. The march was burning the biggest NUMBER card on long walks, starving waterbolts and lightning; the biggest number is now reserved whenever an attack in hand wants it. Heave-Ho, priced for defense, was being cast without a treasure to throw (refused, turn stumbled) — now never cast. Illusionary Attack was cast without naming a spell to fake — now it fakes a fireball, the best-selling lie. And STONE DEAD joins the arsenal against anyone flashing displayed stones: number times the visible count, cast when the arithmetic beats the alternatives. Measured across thirty berserker mirrors: attack casts up a quarter, the tier handicap sharper (25-5), no stalls. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
b01e27c644
commit
13ce8c442c
@@ -383,6 +383,9 @@ function bestAttack(view: GameView, targetId: PlayerId, tier: TierTraits): Comma
|
|||||||
for (const c of view.yourHand) {
|
for (const c of view.yourHand) {
|
||||||
const atk = ATTACKS[c.cardId];
|
const atk = ATTACKS[c.cardId];
|
||||||
if (!atk) continue;
|
if (!atk) continue;
|
||||||
|
// Heave-Ho throws the treasure we are carrying: priced for defense,
|
||||||
|
// never worth casting.
|
||||||
|
if (c.cardId === "heave-ho") continue;
|
||||||
if (atk.sameSquare && !together) continue;
|
if (atk.sameSquare && !together) continue;
|
||||||
// A wand needs its charges set by a number on first use.
|
// A wand needs its charges set by a number on first use.
|
||||||
const isWand = c.cardId === "blaster-wand";
|
const isWand = c.cardId === "blaster-wand";
|
||||||
@@ -401,12 +404,31 @@ function bestAttack(view: GameView, targetId: PlayerId, tier: TierTraits): Comma
|
|||||||
cmd: {
|
cmd: {
|
||||||
type: "cast", instanceId: c.instanceId,
|
type: "cast", instanceId: c.instanceId,
|
||||||
target: { kind: "player", playerId: targetId },
|
target: { kind: "player", playerId: targetId },
|
||||||
|
// An illusion needs a spell to imitate; a fireball sells best.
|
||||||
|
...(c.cardId === "illusionary-attack" ? { params: { cardId: "fireball" } } : {}),
|
||||||
...(withNumber ? { numberInstanceIds: [biggest.instanceId] } : {}),
|
...(withNumber ? { numberInstanceIds: [biggest.instanceId] } : {}),
|
||||||
...(amplified ? { amplifyInstanceIds: [amplify.instanceId] } : {}),
|
...(amplified ? { amplifyInstanceIds: [amplify.instanceId] } : {}),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// STONE DEAD: number times the stones the victim carries — the displayed
|
||||||
|
// ones are the visible floor of that count.
|
||||||
|
const sd = view.yourHand.find((c) => c.cardId === "stone-dead");
|
||||||
|
if (sd && biggest) {
|
||||||
|
const shown = target.displayed.filter((c) => STONES.has(c.cardId)).length;
|
||||||
|
const dmg = biggestValue * shown;
|
||||||
|
if (dmg >= 4 && dmg > (best?.damage ?? 0)) {
|
||||||
|
best = {
|
||||||
|
damage: dmg,
|
||||||
|
cmd: {
|
||||||
|
type: "cast", instanceId: sd.instanceId,
|
||||||
|
target: { kind: "player", playerId: targetId },
|
||||||
|
numberInstanceIds: [biggest.instanceId],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
return best?.cmd ?? null;
|
return best?.cmd ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -690,9 +712,19 @@ export function automatonCommand(
|
|||||||
const movesLeft = view.turn.movementAllowance - view.turn.movementUsed;
|
const movesLeft = view.turn.movementAllowance - view.turn.movementUsed;
|
||||||
if (!view.turn.numberPlayedForMovement && path.distance > movesLeft) {
|
if (!view.turn.numberPlayedForMovement && path.distance > movesLeft) {
|
||||||
const numbers = numbersInHand(view);
|
const numbers = numbersInHand(view);
|
||||||
const helper = numbers.find(
|
// The war chest: when an attack in hand wants a number, the
|
||||||
|
// biggest one is reserved — a waterbolt unfired outbids a longer
|
||||||
|
// march, and walking is free next turn.
|
||||||
|
const wantsNumber = view.yourHand.some((c) => {
|
||||||
|
const atk = ATTACKS[c.cardId];
|
||||||
|
if (!atk) return false;
|
||||||
|
return atk.perNumber || atk.needsNumber === true ||
|
||||||
|
(c.cardId === "blaster-wand" && view.wandCharges[c.instanceId] == null);
|
||||||
|
});
|
||||||
|
const spendable = wantsNumber ? numbers.slice(0, -1) : numbers;
|
||||||
|
const helper = spendable.find(
|
||||||
(n) => movesLeft + (cardDef(n.cardId).value ?? 0) >= path.distance,
|
(n) => movesLeft + (cardDef(n.cardId).value ?? 0) >= path.distance,
|
||||||
) ?? (path.distance > movesLeft + 2 ? numbers[numbers.length - 1] : undefined);
|
) ?? (path.distance > movesLeft + 2 ? spendable[spendable.length - 1] : undefined);
|
||||||
if (helper) {
|
if (helper) {
|
||||||
return { type: "playNumberForMovement", instanceId: helper.instanceId };
|
return { type: "playNumberForMovement", instanceId: helper.instanceId };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user