Rev 20: a wave names its victims before it pushes (LK97)
Kestrel stood against a wall Mad Hywel turned to water, was washed one square back into stone, and was crushed twice. The sweep walked the way it pushed: having shoved him onto the next square, its next probe found him there and shoved him again with the force left. From rev 20 a sweep names every victim along its line first, then pushes each once; older games replay the double shove. A test washes a wizard one square into a wall under both readings. With it, a client fix: a tap on a bare square while holding a card no longer walks there. Kestrel tapped a slime with LIGHTNING BLAST in hand and strode into it. The tap now says the card is aimed at a wizard or creature and leaves the feet alone. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jm2auWk6RP71CjaAb4FMoG
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
e5d82a94af
commit
3f71b04c3b
@@ -636,3 +636,42 @@ describe("a pit on the board's rim (M4Q7)", () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("a wave names its victims before it pushes (LK97)", () => {
|
||||
/** A wall between two squares, a wizard on its near side with one open
|
||||
* square behind them and a wall beyond it, and a caster's square beside. */
|
||||
function site(state: GameState) {
|
||||
const view = boardView(state);
|
||||
for (const k of Object.keys(view.cells)) {
|
||||
const [x, y] = k.split(",").map(Number) as [number, number];
|
||||
const a = { x, y };
|
||||
if (state.squareContents[k] || view.homes.some((h) => cellKey(h) === k)) continue;
|
||||
if ((view.edges[edgeKey(a, "S")] ?? "open") !== "wall" || !view.cells[cellKey(neighbor(a, "S"))]) continue;
|
||||
const back = stepTarget(view, a, "N");
|
||||
if (back.kind !== "step" || state.squareContents[cellKey(back.to)] || view.homes.some((h) => cellKey(h) === cellKey(back.to))) continue;
|
||||
if (stepTarget(view, back.to, "N").kind !== "blocked") continue;
|
||||
const beside = stepTarget(view, a, "E");
|
||||
if (beside.kind !== "step" || state.squareContents[cellKey(beside.to)]) continue;
|
||||
return { a, back: back.to, beside: beside.to };
|
||||
}
|
||||
throw new Error("no such wall on this board");
|
||||
}
|
||||
for (const [deckRev, crush] of [[20, 1], [19, 2]] as const) {
|
||||
it(`washed one square into a wall: rev ${deckRev} costs ${crush}`, () => {
|
||||
let { state } = createGame({ playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"], deckRev });
|
||||
const caster = activePlayer(state);
|
||||
const victim = state.players.find((p) => p.id !== caster.id)!;
|
||||
const s = site(state);
|
||||
caster.position = { ...s.beside };
|
||||
victim.position = { ...s.a };
|
||||
const card = giveCard(state, caster.id, "stone-to-water");
|
||||
const r = applyCommand(state, caster.id, { type: "cast", instanceId: card.instanceId, target: { kind: "edge", cell: s.a, side: "S" } });
|
||||
expect(r.ok).toBe(true);
|
||||
if (!r.ok) return;
|
||||
const v = r.state.players.find((p) => p.id === victim.id)!;
|
||||
expect(cellKey(v.position)).toBe(cellKey(s.back));
|
||||
expect(v.life).toBe(15 - crush);
|
||||
expect(r.events.filter((e) => e.type === "washedBack" && e.player === victim.id).length).toBe(crush === 1 ? 1 : 2);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user