Waves spend their force as they travel (rev 24); bots wield Destroy Wall
Rules rev 24: a waterwall wave that finds a victim dist cells from its source has only range-dist spaces of push left — a range-2 wave throws its adjacent victim two spaces but a victim at its far edge only one, and spent force never converts into crush damage. Applies to WATERWALL and both STONE TO WATER waves; older revisions keep the flat full-range wash so stored games replay unchanged. Automatons now use DESTROY WALL on the march: two BFS distance maps (from the bot, from its objectives) price every visible wall by the shortcut its removal opens; the bot blasts when it saves 4+ steps of walking — or when no road exists at all — never standing beside the blast unless trapped and healthy. The card also leaves the shed pile: discardValue 2 -> 6. 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
dfd7a2ff56
commit
2d53ba4bc0
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { applyCommand, activePlayer, boardView, gameLos } from "../src/game";
|
||||
import { cellKey, SIDES, stepTarget, type Cell } from "../src/board";
|
||||
import { applyCommand, activePlayer, boardView, createGame, gameLos } from "../src/game";
|
||||
import { cellKey, edgeKey, SIDES, stepTarget, type Cell } from "../src/board";
|
||||
import type { CardInstance } from "../src/cards";
|
||||
import { eligibleCellsFor, sightedCellsFor, viewFor } from "../src/view";
|
||||
import { newExpansionGame as newGame, must, giveCard, emptyNeighborCell } from "./helpers";
|
||||
@@ -212,3 +212,44 @@ describe("eligibility dimming mirrors the engine", () => {
|
||||
for (const k of lit) expect(seen.has(k)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("a wave's force is spent as it travels (rules rev 24)", () => {
|
||||
/** Caster at B, one cell above A; the target wall is A's south edge, so
|
||||
* the range-2 wave covers A (dist 0) and B (dist 1). */
|
||||
function rig(deckRev: number, behind: "open" | "wall") {
|
||||
const { state } = createGame({ playerIds: ["a", "b"], seed: 42, sets: ["basic", "expansion1"], deckRev });
|
||||
const me = activePlayer(state);
|
||||
const A = { x: 4, y: 5 }, B = { x: 4, y: 4 }, Bn = { x: 4, y: 3 }, Bnn = { x: 4, y: 2 };
|
||||
for (const c of [A, B, Bn, Bnn]) expect(boardView(state).cells[cellKey(c)]).toBeTruthy();
|
||||
state.edgeOverrides[edgeKey(A, "S")] = "wall";
|
||||
state.edgeOverrides[edgeKey(A, "N")] = "open";
|
||||
state.edgeOverrides[edgeKey(B, "N")] = behind;
|
||||
state.edgeOverrides[edgeKey(Bn, "N")] = "open";
|
||||
me.position = { ...B };
|
||||
// The other wizard waits far outside the wave.
|
||||
state.players.find((p) => p.id !== me.id)!.position = { x: 0, y: 9 };
|
||||
const stw = giveCard(state, me.id, "stone-to-water", "SW", 0);
|
||||
const after = must(state, me.id, {
|
||||
type: "cast", instanceId: stw.instanceId, target: { kind: "edge", cell: A, side: "S" },
|
||||
});
|
||||
return { me: after.players.find((p) => p.id === me.id)!, B, Bn, Bnn };
|
||||
}
|
||||
|
||||
it("a victim at the wave's far edge is carried one space, unhurt", () => {
|
||||
const { me, Bn } = rig(24, "open");
|
||||
expect(cellKey(me.position)).toBe(cellKey(Bn));
|
||||
expect(me.life).toBe(15);
|
||||
});
|
||||
|
||||
it("only unspent force crushes: one space of push blocked is one damage", () => {
|
||||
const { me, B } = rig(24, "wall");
|
||||
expect(cellKey(me.position)).toBe(cellKey(B));
|
||||
expect(me.life).toBe(14);
|
||||
});
|
||||
|
||||
it("older revisions keep the flat full-range wash for replay fidelity", () => {
|
||||
const { me, Bnn } = rig(23, "open");
|
||||
expect(cellKey(me.position)).toBe(cellKey(Bnn));
|
||||
expect(me.life).toBe(15);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user