Automatons wield Pass Through Wall and deny roads with terrain
Pass Through Wall joins the march: the bot banks a crossing when stepping through one wall beats the walk by 4+ steps (or no road exists), walks to the chosen wall, and spends the charge stepping through. passWallCharges is now in PlayerPublicView — the cast is public at a physical table. Path denial for guardGold tiers: when an enemy carries the bot's treasure toward home, or closes on its gold on the floor, the bot reconstructs the threat's shortest path and prices every corridor line and empty square on it; CREATE WALL, FILL SQUARE WITH STONE, or THORNBUSH lands wherever the detour costs the enemy 3+ steps, legality mirrored from the engine (sighted, empty, off homes and warp tokens). All five cards leave the bottom discard tier (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
2d53ba4bc0
commit
78085e26fb
@@ -5,7 +5,7 @@ import {
|
||||
type GameState,
|
||||
type PlayerId,
|
||||
} from "../src/game";
|
||||
import { edgeKey } from "../src/board";
|
||||
import { cellKey, edgeKey } from "../src/board";
|
||||
import { viewFor } from "../src/view";
|
||||
import { automatonCommand, automatonFallback, type AutomatonStyle, type AutomatonTier } from "../src/automaton";
|
||||
|
||||
@@ -260,3 +260,73 @@ describe("the clockwork wields destroy wall", () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("the clockwork wields pass through wall", () => {
|
||||
it("banks a crossing when bricked in, then steps through the wall", () => {
|
||||
let { state } = createGame({ playerIds: ["bot", "other"], seed: 42, sets: ["basic"], deckRev: 24 });
|
||||
while (actingSeat(state) !== "bot") {
|
||||
const r = applyCommand(state, actingSeat(state), { type: "endTurn", draw: 0 });
|
||||
if (!r.ok) throw new Error(r.error);
|
||||
state = r.state;
|
||||
}
|
||||
const bot = state.players.find((p) => p.id === "bot")!;
|
||||
bot.position = { x: 4, y: 4 };
|
||||
for (const side of ["N", "S", "E", "W"] as const) {
|
||||
state.edgeOverrides[edgeKey(bot.position, side)] = "wall";
|
||||
}
|
||||
state.players.find((p) => p.id === "other")!.position = { x: 0, y: 0 };
|
||||
bot.hand[0] = { instanceId: "pass-through-wall#T", cardId: "pass-through-wall" };
|
||||
const cast = automatonCommand(viewFor(state, "bot"), "hunter", "archmage");
|
||||
expect(cast).toMatchObject({ type: "cast", instanceId: "pass-through-wall#T" });
|
||||
let r = applyCommand(state, "bot", cast!);
|
||||
if (!r.ok) throw new Error(r.error);
|
||||
state = r.state;
|
||||
expect(state.players.find((p) => p.id === "bot")!.passWallCharges).toBe(1);
|
||||
// The charge is spent on a step through the bricks.
|
||||
const step = automatonCommand(viewFor(state, "bot"), "hunter", "archmage");
|
||||
expect(step?.type).toBe("move");
|
||||
r = applyCommand(state, "bot", step!);
|
||||
if (!r.ok) throw new Error(r.error);
|
||||
const after = r.state.players.find((p) => p.id === "bot")!;
|
||||
expect(cellKey(after.position)).not.toBe(cellKey({ x: 4, y: 4 }));
|
||||
expect(after.passWallCharges).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("the clockwork denies the road", () => {
|
||||
it("walls a thief's corridor when its treasure is being carried home", () => {
|
||||
let { state } = createGame({ playerIds: ["bot", "other"], seed: 42, sets: ["basic"], deckRev: 24 });
|
||||
while (actingSeat(state) !== "bot") {
|
||||
const r = applyCommand(state, actingSeat(state), { type: "endTurn", draw: 0 });
|
||||
if (!r.ok) throw new Error(r.error);
|
||||
state = r.state;
|
||||
}
|
||||
const bot = state.players.find((p) => p.id === "bot")!;
|
||||
const thief = state.players.find((p) => p.id === "other")!;
|
||||
// The thief carries the bot's treasure down a one-lane tube to its home.
|
||||
const t = state.treasures.find((t) => t.owner === "bot")!;
|
||||
t.position = null;
|
||||
t.carriedBy = "other";
|
||||
thief.carriedTreasureId = t.id;
|
||||
thief.position = { x: 4, y: 2 };
|
||||
thief.home = { x: 4, y: 6 };
|
||||
for (let y = 2; y <= 6; y++) {
|
||||
state.edgeOverrides[edgeKey({ x: 4, y }, "E")] = "wall";
|
||||
state.edgeOverrides[edgeKey({ x: 4, y }, "W")] = "wall";
|
||||
}
|
||||
state.edgeOverrides[edgeKey({ x: 4, y: 6 }, "S")] = "wall";
|
||||
for (let y = 2; y <= 5; y++) {
|
||||
state.edgeOverrides[edgeKey({ x: 4, y }, "S")] = "open";
|
||||
}
|
||||
bot.position = { x: 4, y: 4 };
|
||||
bot.hand = [{ instanceId: "create-wall#T", cardId: "create-wall" }];
|
||||
const cmd = automatonCommand(viewFor(state, "bot"), "hunter", "archmage");
|
||||
expect(cmd).toMatchObject({ type: "cast", instanceId: "create-wall#T" });
|
||||
const r = applyCommand(state, "bot", cmd!);
|
||||
if (!r.ok) throw new Error(r.error);
|
||||
// The blockade must actually sever the thief's road home.
|
||||
const target = (cmd as { target: { cell: { x: number; y: number }; side: string } }).target;
|
||||
expect(["N", "S"]).toContain(target.side);
|
||||
expect(target.cell.x).toBe(4);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user