From 21dcd532a30d7a58f1a9ed863cb815299deea2f4 Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Sat, 15 Aug 2026 23:02:43 -0400 Subject: [PATCH] =?UTF-8?q?Add=20Swap=20Home=20Bases=20=E2=80=94=20the=20a?= =?UTF-8?q?udit's=20one=20escapee?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Neutral, LOS to the other player, legal only when both home bases hold an equal number of treasures. With this, every card in the 6th edition game is implemented except Thumb Of God, which awaits its digital redesign. 118 tests passing. Co-Authored-By: Claude Fable 5 --- packages/engine/src/game.ts | 23 +++++++++++++++++++ packages/engine/test/expansion-combat.test.ts | 17 ++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/packages/engine/src/game.ts b/packages/engine/src/game.ts index 843fb0f..b152b94 100644 --- a/packages/engine/src/game.ts +++ b/packages/engine/src/game.ts @@ -2062,6 +2062,29 @@ const CARD_EFFECTS: Record kind: "neutral", resolve: () => "played out of turn — use it during another player's turn", }, + "swap-home-bases": { + kind: "neutral", + // "Swap your home base with any other player, as long as you both have an + // equal number of treasures on your home bases. You must be within L.O.S." + resolve: (state, events, caster, cmd) => { + if (!cmd.target || cmd.target.kind !== "player") return "choose whose home to take"; + const other = state.players.find((p) => p.id === (cmd.target as { playerId: PlayerId }).playerId); + if (!other || !other.alive) return "no such living player"; + if (other.id === caster.id) return "that is already your home"; + if (!gameLos(state, caster.position, other.position)) return "no line of sight to them"; + const onHome = (home: Cell) => + state.treasures.filter((t) => t.position && cellKey(t.position) === cellKey(home)).length; + if (onHome(caster.home) !== onHome(other.home)) { + return "your home bases must hold an equal number of treasures"; + } + const mine = caster.home; + caster.home = other.home; + other.home = mine; + events.push({ type: "positionsSwapped", a: caster.id, b: other.id, aTo: caster.home, bTo: other.home }); + checkVictory(state, events); + return null; + }, + }, "reuse-spell": { kind: "neutral", // "You may retrieve any spell you use immediately after you use it (but diff --git a/packages/engine/test/expansion-combat.test.ts b/packages/engine/test/expansion-combat.test.ts index f75f7d4..6665a41 100644 --- a/packages/engine/test/expansion-combat.test.ts +++ b/packages/engine/test/expansion-combat.test.ts @@ -209,3 +209,20 @@ describe("expansion combat cards", () => { } }); }); + +describe("swap home bases", () => { + it("trades homes when both bases hold equal treasures", () => { + let { state } = newGame(); + const me = activePlayer(state); + const other = state.players.find((p) => p.id !== me.id)!; + other.position = { ...me.position }; // LOS guaranteed + const myHome = { ...me.home }; + const theirHome = { ...other.home }; + const shb = giveCard(state, me.id, "swap-home-bases"); + state = must(state, me.id, { + type: "cast", instanceId: shb.instanceId, target: { kind: "player", playerId: other.id }, + }); + expect(cellKey(state.players.find((p) => p.id === me.id)!.home)).toBe(cellKey(theirHome)); + expect(cellKey(state.players.find((p) => p.id === other.id)!.home)).toBe(cellKey(myHome)); + }); +});