Daggers, rocks, blades, and wands can be set down too

The rulebook's movable-object list is wider than the stones: "magic
stones, treasure chests, the dagger, the large rock, and the
wizardblade" — and the expansion expects wands to change hands, since
"its remaining charges go with it." A new isMovableObject helper in
the engine names that full set, doDropObject honors it, and the hint
bar's "Drop it here" button follows suit. Charges are keyed by card
instance, so a dropped wand carries them to whoever picks it up —
covered by new tests, along with the refusal to drop spell cards.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-16 09:59:09 -04:00
co-authored by Claude Fable 5
parent c04ed1e0ee
commit 789d927122
3 changed files with 68 additions and 3 deletions
+49
View File
@@ -198,3 +198,52 @@ describe("magic wands", () => {
expect(state.players.find((p) => p.id === me.id)!.hand.some((c) => c.cardId === "fireball")).toBe(true);
});
});
describe("dropping objects", () => {
it("a charged wand can be dropped and picked up, charges intact", () => {
let { state } = newGame();
state = toRound2(state);
const { attacker, defender } = faceOff(state);
const wand = giveCard(state, attacker, "blaster-wand");
giveCard(state, attacker, "number-3", "N", 1);
// Charge it with a 3 (one charge spent on the shot, two remain).
state = must(state, attacker, {
type: "cast", instanceId: wand.instanceId, numberInstanceIds: ["number-3#N"],
target: { kind: "player", playerId: defender },
});
state = must(state, defender, { type: "pass" });
expect(state.wandCharges[wand.instanceId]).toBe(2);
// Set it down: leaves the hand and the displayed list, lands on the floor.
state = must(state, attacker, { type: "dropObject", instanceId: wand.instanceId });
const owner = state.players.find((p) => p.id === attacker)!;
expect(owner.hand.some((c) => c.instanceId === wand.instanceId)).toBe(false);
expect(owner.displayed.includes(wand.instanceId)).toBe(false);
const key = cellKey(owner.position);
expect((state.groundObjects[key] ?? []).some((c) => c.instanceId === wand.instanceId)).toBe(true);
// The wizard standing there picks it up on their turn — charges travel.
state = must(state, attacker, { type: "endTurn", draw: 0 });
state = must(state, defender, { type: "pickUpObject", instanceId: wand.instanceId });
const thief = state.players.find((p) => p.id === defender)!;
expect(thief.hand.some((c) => c.instanceId === wand.instanceId)).toBe(true);
expect(state.wandCharges[wand.instanceId]).toBe(2);
expect(state.players.find((p) => p.id === defender)!.id).toBe(activePlayer(state).id);
});
it("the dagger and wizardblade can be set down; spell cards cannot", () => {
let { state } = newGame();
state = toRound2(state);
const who = activePlayer(state).id;
const dagger = giveCard(state, who, "dagger");
const fireball = giveCard(state, who, "fireball", "F", 1);
state = must(state, who, { type: "dropObject", instanceId: dagger.instanceId });
expect((state.groundObjects[cellKey(activePlayer(state).position)] ?? [])
.some((c) => c.cardId === "dagger")).toBe(true);
const refused = applyCommand(state, who, { type: "dropObject", instanceId: fireball.instanceId });
expect(refused.ok).toBe(false);
});
});