Chaos aims at no one
The client always offered CHAOS from the hint bar without a target, and the engine refused it: "attack spells target a player". Not this one — cast bare, it sweeps every other living wizard into the pile, each offered their FULL SHIELD sit-out in turn order from the caster. No stack forms; full shields only ever excluded a player from the storm, so the queue is the whole defense. The older player-targeted form stays accepted, so stored games replay unchanged. The scramble joins the chronicle's notable events — a turn that redeals every hand earns its eye. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015RCWSTnb1KYTPyL4GmhGnF
This commit is contained in:
co-authored by
Claude Fable 5
parent
168e57ab91
commit
805fb09782
@@ -4933,6 +4933,28 @@ function doCast(prev: GameState, cmd: Extract<Command, { type: "cast" }>): Comma
|
||||
}
|
||||
return { ok: true, state, events: events2 };
|
||||
}
|
||||
// CHAOS aims at no one: cast bare, it sweeps every other living
|
||||
// wizard into the pile, each offered their FULL SHIELD sit-out in
|
||||
// turn order. (Full shields only exclude; nothing in the set cancels
|
||||
// a zero-point storm, so the queue is the whole defense.) The older
|
||||
// player-targeted form is still accepted below, so stored games
|
||||
// replay unchanged.
|
||||
if (inHand.cardId === "chaos" && (!cmd.target || cmd.target.kind !== "player")) {
|
||||
consumeCast(state, caster, inHand, mods, false);
|
||||
if (state.turn.attackUsed) state.turn.secondAttackUsed = true;
|
||||
state.turn.attackUsed = true;
|
||||
state.lastSpellUsed[caster.id] = inHand.cardId;
|
||||
const events: GameEvent[] = [{
|
||||
type: "spellCast", caster: caster.id, card: inHand, cardId: inHand.cardId,
|
||||
numberCards: mods.numbers, numberValue: mods.magnitude.numberValue,
|
||||
from: caster.position, target: null, targetCell: null,
|
||||
}];
|
||||
const queue = turnOrderFrom(state, caster.id).filter((id) =>
|
||||
id !== caster.id && state.players.find((p) => p.id === id)!.alive);
|
||||
state.chaosPending = { casterId: caster.id, excluded: [], queue };
|
||||
finishChaosIfReady(state, events);
|
||||
return { ok: true, state, events };
|
||||
}
|
||||
if (!cmd.target || cmd.target.kind !== "player") return err("attack spells target a player");
|
||||
if (cmd.target.playerId === caster.id) return err("you cannot attack yourself");
|
||||
const target = state.players.find((p) => p.id === (cmd.target as { playerId: PlayerId }).playerId);
|
||||
|
||||
@@ -402,6 +402,35 @@ describe("the ward window and chaos shields", () => {
|
||||
expect(state.players.find((p) => p.id === owner.id)!.hand.some((c) => c.cardId === "ward")).toBe(false);
|
||||
});
|
||||
|
||||
it("chaos casts bare — no target, every other wizard queued for a shield", () => {
|
||||
let { state } = threeGame();
|
||||
state = toRound2(state);
|
||||
state = must(state, activePlayer(state).id, { type: "endTurn", draw: 0 });
|
||||
const caster = activePlayer(state);
|
||||
const others = state.players.filter((p) => p.id !== caster.id).map((p) => p.id);
|
||||
const chaos = giveCard(state, caster.id, "chaos", "C", 0);
|
||||
const shielder = others[0]!;
|
||||
giveCard(state, shielder, "full-shield", "S", 0);
|
||||
const shielderHand = () => state.players.find((p) => p.id === shielder)!.hand.map((c) => c.instanceId).sort();
|
||||
const kept = shielderHand().filter((id) => id !== "full-shield#S").sort();
|
||||
|
||||
state = must(state, caster.id, { type: "cast", instanceId: chaos.instanceId });
|
||||
// No stack: the queue opens at once, in turn order from the caster.
|
||||
expect(state.stack).toBeNull();
|
||||
expect(state.chaosPending?.queue.length).toBe(2);
|
||||
const first = state.chaosPending!.queue[0]!;
|
||||
state = first === shielder
|
||||
? must(state, first, { type: "counteract", instanceId: "full-shield#S" })
|
||||
: must(state, first, { type: "pass" });
|
||||
const second = state.chaosPending!.queue[0]!;
|
||||
state = second === shielder
|
||||
? must(state, second, { type: "counteract", instanceId: "full-shield#S" })
|
||||
: must(state, second, { type: "pass" });
|
||||
expect(state.chaosPending).toBeNull();
|
||||
// The shielded hand rode out the storm untouched.
|
||||
expect(shielderHand()).toEqual(kept);
|
||||
});
|
||||
|
||||
it("chaos: bystanders may shield out, reflections are refused", () => {
|
||||
let { state } = threeGame();
|
||||
state = toRound2(state);
|
||||
|
||||
@@ -226,6 +226,7 @@ const NOTABLE_EVENTS = new Set([
|
||||
"firewallBurned", "objectThrown", "gameWon", "positionsSwapped",
|
||||
"teleported", "stonesDestroyed", "thumbOfGod", "stoneTurnedToWater",
|
||||
"waterwallCrashes", "sectorRotated", "sectorRelocated", "homeBasesSwapped",
|
||||
"handsScrambled",
|
||||
]);
|
||||
|
||||
const SEAT_KEY = "wizwar-seat";
|
||||
|
||||
Reference in New Issue
Block a user