Rev 17: the wave carries monsters as it carries wizards
Stone to Water's wave (and every waterwall collapse) washed wizards back and crushed them against the maze — while a skeleton in the same water stood dry. The wave only knew creatures well enough to drown fire imps. From rev 17 it carries them all: washed back along the surge, one point of crush per space the maze refuses, fire imps still destroyed outright. Earlier ledgers keep their dry monsters and replay so. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
aef88d892a
commit
e57307efbe
@@ -2347,8 +2347,11 @@ function waveFromEdge(state: GameState, events: GameEvent[], cell: Cell, side: S
|
|||||||
if (p.alive && cellKey(p.position) === cellKey(probe)) washBackN(state, events, p, dir, range);
|
if (p.alive && cellKey(p.position) === cellKey(probe)) washBackN(state, events, p, dir, range);
|
||||||
}
|
}
|
||||||
for (const c of [...state.creatures]) {
|
for (const c of [...state.creatures]) {
|
||||||
if (c.kind === "fire-imp" && cellKey(c.position) === cellKey(probe)) {
|
if (cellKey(c.position) !== cellKey(probe)) continue;
|
||||||
|
if (c.kind === "fire-imp") {
|
||||||
destroyCreature(state, events, c, reason);
|
destroyCreature(state, events, c, reason);
|
||||||
|
} else if ((state.config.deckRev ?? 1) >= 17) {
|
||||||
|
washBackCreature(state, events, c, dir, range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (state.squareContents[cellKey(probe)]?.kind === "slime") {
|
if (state.squareContents[cellKey(probe)]?.kind === "slime") {
|
||||||
@@ -2371,8 +2374,11 @@ function waveFromCell(state: GameState, events: GameEvent[], center: Cell, range
|
|||||||
if (p.alive && cellKey(p.position) === cellKey(probe)) washBackN(state, events, p, dir, range);
|
if (p.alive && cellKey(p.position) === cellKey(probe)) washBackN(state, events, p, dir, range);
|
||||||
}
|
}
|
||||||
for (const c of [...state.creatures]) {
|
for (const c of [...state.creatures]) {
|
||||||
if (c.kind === "fire-imp" && cellKey(c.position) === cellKey(probe)) {
|
if (cellKey(c.position) !== cellKey(probe)) continue;
|
||||||
|
if (c.kind === "fire-imp") {
|
||||||
destroyCreature(state, events, c, "rushing water");
|
destroyCreature(state, events, c, "rushing water");
|
||||||
|
} else if ((state.config.deckRev ?? 1) >= 17) {
|
||||||
|
washBackCreature(state, events, c, dir, range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2473,6 +2479,25 @@ function washBackN(state: GameState, events: GameEvent[], p: PlayerState, dir: S
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The wave carries monsters as it carries wizards (rules rev 17): washed
|
||||||
|
* back, and crushed a point per space the maze refuses them. Fire imps
|
||||||
|
* never reach this — water destroys them outright. */
|
||||||
|
function washBackCreature(state: GameState, events: GameEvent[], c: CreatureState, dir: Side, range: number): void {
|
||||||
|
const view = boardView(state);
|
||||||
|
let moved = 0;
|
||||||
|
for (let i = 0; i < range; i++) {
|
||||||
|
const step = stepTarget(view, c.position, dir);
|
||||||
|
if (step.kind === "blocked") break;
|
||||||
|
if (state.squareContents[cellKey(step.to)]?.kind === "stone") break;
|
||||||
|
c.position = step.to;
|
||||||
|
moved++;
|
||||||
|
}
|
||||||
|
const blockedSpaces = range - moved;
|
||||||
|
if (blockedSpaces > 0) {
|
||||||
|
damageCreature(state, events, c, blockedSpaces, "waterwall crush");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Creatures
|
// Creatures
|
||||||
|
|
||||||
|
|||||||
@@ -550,3 +550,42 @@ describe("creatures walk open doorways", () => {
|
|||||||
throw new Error("setup: seed 42 grew a maze with no doors");
|
throw new Error("setup: seed 42 grew a maze with no doors");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("the wave carries monsters (rules rev 17)", () => {
|
||||||
|
it("a cornered skeleton takes the waterwall crush", () => {
|
||||||
|
let { state } = createGame({ playerIds: ["a", "b"], seed: 42, sets: ["basic", "expansion1"], deckRev: 17 });
|
||||||
|
state = toRound2(state);
|
||||||
|
const caster = activePlayer(state);
|
||||||
|
// A skeleton pinned against solid stone, one square from the wave's edge.
|
||||||
|
const view = boardView(state);
|
||||||
|
for (const [key, edge] of Object.entries(view.edges)) {
|
||||||
|
if (edge !== "wall") continue;
|
||||||
|
const [kind, coords] = key.split(":") as [string, string];
|
||||||
|
if (kind !== "H") continue;
|
||||||
|
const [x, y] = coords.split(",").map(Number) as [number, number];
|
||||||
|
// Wave from this wall southward washes the square below.
|
||||||
|
const below = { x, y: y + 1 };
|
||||||
|
if (!view.cells[cellKey(below)]) continue;
|
||||||
|
state.creatures.push({
|
||||||
|
id: "sk1", kind: "skeleton", controllerId: caster.id,
|
||||||
|
position: { ...below }, damage: 0, maxDamage: 4, movesPerTurn: 3,
|
||||||
|
movementUsed: 0, attackUsed: false, justCreated: false,
|
||||||
|
wallPassesPerTurn: 0, wallPassUsed: 0, scorchedThisTurn: [],
|
||||||
|
});
|
||||||
|
// Pin it: stone directly south of it.
|
||||||
|
state.squareContents[cellKey({ x, y: y + 2 })] = { kind: "stone", damage: 0, createdBy: caster.id };
|
||||||
|
caster.position = { x, y };
|
||||||
|
const stw = giveCard(state, caster.id, "stone-to-water");
|
||||||
|
const r = applyCommand(state, caster.id, {
|
||||||
|
type: "cast", instanceId: stw.instanceId,
|
||||||
|
target: { kind: "edge", cell: { x, y }, side: "S" },
|
||||||
|
});
|
||||||
|
if (!r.ok) throw new Error(r.error);
|
||||||
|
const sk = r.state.creatures.find((c) => c.id === "sk1");
|
||||||
|
// Washed against the stone: it took crush damage (or died of it).
|
||||||
|
if (sk) expect(sk.damage).toBeGreaterThan(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new Error("setup: no horizontal wall with a floor below");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export interface Room {
|
|||||||
const rooms = new Map<string, Room>();
|
const rooms = new Map<string, Room>();
|
||||||
|
|
||||||
/** Rules revision new games are dealt under (stored games keep their own). */
|
/** Rules revision new games are dealt under (stored games keep their own). */
|
||||||
const RULES_REV = 16;
|
const RULES_REV = 17;
|
||||||
|
|
||||||
const ROOM_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
const ROOM_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
||||||
|
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ class LocalGame {
|
|||||||
seed,
|
seed,
|
||||||
sets: expansion ? ["basic", "expansion1"] : ["basic"],
|
sets: expansion ? ["basic", "expansion1"] : ["basic"],
|
||||||
...(colors ? { colors } : {}),
|
...(colors ? { colors } : {}),
|
||||||
deckRev: 16,
|
deckRev: 17,
|
||||||
};
|
};
|
||||||
const { state, events } = createGame(config);
|
const { state, events } = createGame(config);
|
||||||
for (const e of events) {
|
for (const e of events) {
|
||||||
|
|||||||
Reference in New Issue
Block a user