Boobytrap decoys die their own deaths
Stepping on a face-down token flips it: a blank leaves the board the moment it is discovered, instead of all four indicators lingering until the real one detonates. The trap itself still waits, the caster still strolls over everything unharmed, and the chronicle narrates the flip so the table learns which squares stopped being scary. 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
e388691858
commit
5f1eda2731
@@ -378,3 +378,49 @@ describe("a wave's force is spent as it travels", () => {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("boobytrap decoys die their own deaths", () => {
|
||||
it("a stepped-on blank token vanishes alone; the real one still waits", () => {
|
||||
let { state } = newGame();
|
||||
const me = activePlayer(state);
|
||||
const enemy = state.players.find((p) => p.id !== me.id)!;
|
||||
const view = boardView(state);
|
||||
const open: Cell[] = [];
|
||||
for (const key of Object.keys(view.cells)) {
|
||||
const [x, y] = key.split(",").map(Number) as [number, number];
|
||||
if (state.squareContents[key]) continue;
|
||||
open.push({ x, y });
|
||||
if (open.length === 4) break;
|
||||
}
|
||||
const bt = giveCard(state, me.id, "boobytrap");
|
||||
state = must(state, me.id, {
|
||||
type: "cast", instanceId: bt.instanceId, params: { cells: open },
|
||||
});
|
||||
state = must(state, me.id, { type: "endTurn", draw: 0 });
|
||||
// The enemy steps onto a BLANK (open[1] — the real trap is open[0]).
|
||||
const e = state.players.find((p) => p.id === enemy.id)!;
|
||||
const blank = open[1]!;
|
||||
for (const side of SIDES) {
|
||||
const from = { x: blank.x + (side === "E" ? -1 : side === "W" ? 1 : 0),
|
||||
y: blank.y + (side === "S" ? -1 : side === "N" ? 1 : 0) };
|
||||
if (!view.cells[cellKey(from)]) continue;
|
||||
const t = stepTarget(view, from, side);
|
||||
if (t.kind === "step" && cellKey(t.to) === cellKey(blank)) {
|
||||
e.position = from;
|
||||
const before = e.life;
|
||||
const r = applyCommand(state, enemy.id, { type: "move", direction: side });
|
||||
if (!r.ok) throw new Error(r.error);
|
||||
state = r.state;
|
||||
expect(r.events.some((ev) => ev.type === "boobytrapBlank")).toBe(true);
|
||||
const after = state.players.find((p) => p.id === enemy.id)!;
|
||||
expect(after.life).toBe(before);
|
||||
const trap = state.boobytraps[0]!;
|
||||
expect(trap.cells.length).toBe(3);
|
||||
expect(trap.cells.some((c) => cellKey(c) === cellKey(blank))).toBe(false);
|
||||
expect(trap.realKey).toBe(cellKey(open[0]!));
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new Error("setup: no approach to the blank token");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user