Mad Dash doubles everything it promises (rules rev 12)
'Including NUMBER cards and other add-ons' now means it: a number riding the cast fuels the dash — (base + number) × 2 — and numbers or POWER RUN points played under it double as well. The 5BM4 five that vanished into a borrowed 'burns 0 life for speed' line gets its own madDash event and chronicle voice. Older games doubled only the base and replay so, pinned at deckRev 11. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015RCWSTnb1KYTPyL4GmhGnF
This commit is contained in:
co-authored by
Claude Fable 5
parent
6189b4935c
commit
12ece6065a
@@ -176,6 +176,8 @@ export interface TurnState {
|
||||
/** A reflected LIGHTNING BLAST stuns mid-turn: no end-of-turn draw either
|
||||
* (FAQ: "you lose the rest of your turn and can't draw cards"). */
|
||||
drawForbidden?: boolean;
|
||||
/** MAD DASH: every movement gain this turn doubles — numbers, POWER RUN. */
|
||||
madDash?: boolean;
|
||||
}
|
||||
|
||||
export interface CastStack {
|
||||
@@ -236,7 +238,7 @@ export interface CastParams {
|
||||
}
|
||||
|
||||
/** The revision new games are dealt under; GameConfig.deckRev pins it per game. */
|
||||
export const CURRENT_RULES_REV = 11;
|
||||
export const CURRENT_RULES_REV = 12;
|
||||
|
||||
export interface GameConfig {
|
||||
playerIds: PlayerId[];
|
||||
@@ -278,6 +280,10 @@ export interface GameConfig {
|
||||
* walked, permanently — and a FULL REFLECTION returns a permanent
|
||||
* curse (SLOW DEATH, WALKING DEAD, IDIOT) onto its caster instead of
|
||||
* letting it evaporate.
|
||||
* Rev 12: MAD DASH doubles "NUMBER cards and other add-ons" too — a
|
||||
* number riding the cast fuels it, and numbers or POWER RUN points
|
||||
* played under the dash double; older games doubled only the base
|
||||
* allowance and consumed the rider without effect.
|
||||
*/
|
||||
deckRev?: number;
|
||||
}
|
||||
@@ -738,6 +744,7 @@ export type GameEvent =
|
||||
| { type: "cardDisplayed"; player: PlayerId; card: CardInstance }
|
||||
| { type: "extraTurnGranted"; player: PlayerId }
|
||||
| { type: "lifeTraded"; player: PlayerId; points: number; newAllowance: number }
|
||||
| { type: "madDash"; player: PlayerId; newAllowance: number }
|
||||
| { type: "trapSprung"; player: PlayerId; cardId?: string }
|
||||
| { type: "died"; player: PlayerId; killedBy: PlayerId | null }
|
||||
| { type: "handTaken"; from: PlayerId; to: PlayerId; count: number }
|
||||
@@ -1411,7 +1418,7 @@ const CARD_EFFECTS: Record<string, AttackEffect | NeutralEffect | CounterEffect>
|
||||
if (!Number.isInteger(points) || points < 1) return "choose how many life points to trade";
|
||||
if (points >= caster.life) return "that trade would kill you";
|
||||
caster.life -= points;
|
||||
state.turn.movementAllowance += points;
|
||||
state.turn.movementAllowance += state.turn.madDash ? points * 2 : points;
|
||||
events.push({ type: "lifeTraded", player: caster.id, points, newAllowance: state.turn.movementAllowance });
|
||||
return null;
|
||||
},
|
||||
@@ -1871,8 +1878,23 @@ const CARD_EFFECTS: Record<string, AttackEffect | NeutralEffect | CounterEffect>
|
||||
kind: "neutral",
|
||||
// "Doubles your movement (including NUMBER cards and other add-ons) for
|
||||
// one turn. You cannot carry treasures while exerting yourself."
|
||||
resolve: (state, events, caster) => {
|
||||
resolve: (state, events, caster, _cmd, magnitude) => {
|
||||
if (caster.carriedTreasureId) return "you cannot mad-dash while carrying a treasure";
|
||||
// Rev 12: "including NUMBER cards and other add-ons" — a number
|
||||
// riding the cast is the turn's movement number, and every movement
|
||||
// gain after the dash doubles too. Older games doubled only the base
|
||||
// allowance, ate the rider, and spoke in POWER RUN's voice.
|
||||
if ((state.config.deckRev ?? 1) >= 12) {
|
||||
const fuel = magnitude.numberValue ?? 0;
|
||||
if (fuel > 0) {
|
||||
if (state.turn.numberPlayedForMovement) return "a number already fuels this turn's movement";
|
||||
state.turn.numberPlayedForMovement = true;
|
||||
}
|
||||
state.turn.madDash = true;
|
||||
state.turn.movementAllowance = (state.turn.movementAllowance + fuel) * 2;
|
||||
events.push({ type: "madDash", player: caster.id, newAllowance: state.turn.movementAllowance });
|
||||
return null;
|
||||
}
|
||||
state.turn.movementAllowance *= 2;
|
||||
events.push({ type: "lifeTraded", player: caster.id, points: 0, newAllowance: state.turn.movementAllowance });
|
||||
return null;
|
||||
@@ -4494,7 +4516,7 @@ function doPlayNumberForMovement(prev: GameState, instanceId: string, addInstanc
|
||||
|
||||
const value = numberValue(card.cardId) + (displays(p, "powerstone") ? 1 : 0);
|
||||
state.discard.push(card);
|
||||
state.turn.movementAllowance += value;
|
||||
state.turn.movementAllowance += state.turn.madDash ? value * 2 : value;
|
||||
state.turn.numberPlayedForMovement = true;
|
||||
return {
|
||||
ok: true,
|
||||
|
||||
@@ -84,6 +84,49 @@ describe("expansion combat cards", () => {
|
||||
expect(state.players.find((p) => p.id === defender)!.life).toBe(16);
|
||||
});
|
||||
|
||||
it("mad dash doubles the base, the riding number, and later fuel (rev 12)", () => {
|
||||
let { state } = newGame();
|
||||
state = toRound2(state);
|
||||
const me = activePlayer(state).id;
|
||||
const md = giveCard(state, me, "mad-dash");
|
||||
giveCard(state, me, "exp1-number-5", "N5", 1);
|
||||
state = must(state, me, {
|
||||
type: "cast", instanceId: md.instanceId, numberInstanceIds: ["exp1-number-5#N5"],
|
||||
});
|
||||
expect(state.turn.movementAllowance).toBe(16); // (3 + 5) × 2
|
||||
// The rider was the turn's movement number: a bare second is refused.
|
||||
giveCard(state, me, "number-2", "N2", 0);
|
||||
expect(applyCommand(state, me, { type: "playNumberForMovement", instanceId: "number-2#N2" }).ok).toBe(false);
|
||||
// POWER RUN points double under the dash too.
|
||||
const pr = giveCard(state, me, "power-run", "PR", 2);
|
||||
state = must(state, me, { type: "cast", instanceId: pr.instanceId, params: { points: 2 } });
|
||||
expect(state.turn.movementAllowance).toBe(20); // 16 + 2×2
|
||||
});
|
||||
|
||||
it("a number played after a bare mad dash doubles as well (rev 12)", () => {
|
||||
let { state } = newGame();
|
||||
state = toRound2(state);
|
||||
const me = activePlayer(state).id;
|
||||
const md = giveCard(state, me, "mad-dash");
|
||||
state = must(state, me, { type: "cast", instanceId: md.instanceId });
|
||||
expect(state.turn.movementAllowance).toBe(6); // 3 × 2
|
||||
giveCard(state, me, "exp1-number-5", "N5", 0);
|
||||
state = must(state, me, { type: "playNumberForMovement", instanceId: "exp1-number-5#N5" });
|
||||
expect(state.turn.movementAllowance).toBe(16); // 6 + 5×2
|
||||
});
|
||||
|
||||
it("older decks double only the base and eat the rider (rev ≤11)", () => {
|
||||
let { state } = createGame({ playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"], deckRev: 11 });
|
||||
state = toRound2(state);
|
||||
const me = activePlayer(state).id;
|
||||
const md = giveCard(state, me, "mad-dash");
|
||||
giveCard(state, me, "exp1-number-5", "N5", 1);
|
||||
state = must(state, me, {
|
||||
type: "cast", instanceId: md.instanceId, numberInstanceIds: ["exp1-number-5#N5"],
|
||||
});
|
||||
expect(state.turn.movementAllowance).toBe(6); // 3 × 2, the five wasted
|
||||
});
|
||||
|
||||
it("mental swap trades entire hands", () => {
|
||||
let { state } = newGame();
|
||||
state = toRound2(state);
|
||||
|
||||
Reference in New Issue
Block a user