Compose Homeworld storm and native Worthless counter costs with Semuta
This commit is contained in:
1 parent
506bf25bc6
commit
7004977e75
12 files changed
+776
-13
No files matched your search
@@ -0,0 +1,97 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { handLimit, viewGame, type Action, type Game } from '../game/engine';
|
||||
import { choamPowerAction, choamPowerPlays } from '../game/choam-power-options';
|
||||
import { advanceSemutaHomeworldChoam, applySemutaHomeworldChoamStep,
|
||||
holdSemutaHomeworldChoamCard } from './fixture-semuta-homeworld-choam';
|
||||
import { createSemutaHomeworldChoamMovementFixture,
|
||||
allowSemutaHomeworldChoamMovement } from './fixture-semuta-homeworld-choam-movement';
|
||||
import { moveSemutaEmperorCard, semutaEmperorPlayer } from './fixture-semuta-emperor-share';
|
||||
import type { RicheseSkillsTechStep } from './fixture-richese-skills-tech';
|
||||
|
||||
export type SemutaHomeworldChoamStormFixture = {
|
||||
setup: Game; beforeShipment: Game; afterShipment: Game; beforeWeather: Game;
|
||||
afterWeather: Game; game: Game; owner: string; holder: string; target: string;
|
||||
canceller: string; weatherOwner: string; weatherCard: string; retainedKulon: string;
|
||||
card: string; source: 'printed'; territory: string; origin: string;
|
||||
action: Action; weatherAction: Action; weatherAmount: number; shipment: Action;
|
||||
participants: string[]; actions: RicheseSkillsTechStep[]; staging: string[];
|
||||
};
|
||||
const clean = (game: Game) => !game.phaseOpening && !game.response &&
|
||||
!game.decision && !game.pendingTreacheryDiscard;
|
||||
|
||||
/** Paid original reserves shipment, original first-turn suffix, then actual
|
||||
* Weather Control and native Storm readiness. No force or clock is assigned. */
|
||||
export function createSemutaHomeworldChoamStormFixture(
|
||||
advanced = false, fullHolder = false,
|
||||
): SemutaHomeworldChoamStormFixture {
|
||||
const prefix = createSemutaHomeworldChoamMovementFixture('kulon', advanced, false);
|
||||
let game = structuredClone(prefix.afterShipment);
|
||||
const actions = [...prefix.actions], staging = [...prefix.staging];
|
||||
const owner = prefix.owner, holder = prefix.holder;
|
||||
const weatherPlayer = game.players.find(player => player.faction === 'atreides');
|
||||
assert.ok(weatherPlayer, 'The original setup supplies the other Weather seat');
|
||||
const weatherOwner = weatherPlayer.id;
|
||||
assert.notEqual(weatherOwner, owner);
|
||||
const card = holdSemutaHomeworldChoamCard(game, owner, 'Jubba Cloak');
|
||||
const weatherCard = holdSemutaHomeworldChoamCard(game, weatherOwner, 'Weather Control');
|
||||
staging.push('Conserved original Jubba Cloak to CHOAM and Weather Control to Atreides; Kulon remains unplayed.');
|
||||
for (const player of game.players) assert.ok(player.hand.length <= handLimit(player));
|
||||
game = advanceSemutaHomeworldChoam(game,
|
||||
state => state.turn === 2 && state.phase === 0 && clean(state), actions);
|
||||
assert.equal(semutaEmperorPlayer(game, owner).forces[prefix.origin], 1);
|
||||
assert.ok(semutaEmperorPlayer(game, owner).hand.some(held => held.id === prefix.card));
|
||||
const beforeWeather = structuredClone(game);
|
||||
const amount = (7 - game.storm + 18) % 18;
|
||||
assert.ok(amount >= 0 && amount <= 10, 'Original Weather dial can reach the shipped counter');
|
||||
const weatherAction: Action = { type: 'card', card: weatherCard, amount };
|
||||
game = applySemutaHomeworldChoamStep(game, weatherOwner, weatherAction, actions);
|
||||
game = advanceSemutaHomeworldChoam(game, state => clean(state), actions);
|
||||
const afterWeather = structuredClone(game);
|
||||
assert.equal(game.stormPending, amount);
|
||||
assert.equal(game.stormMovementSource?.kind, 'weather');
|
||||
game = advanceSemutaHomeworldChoam(game,
|
||||
state => state.decision?.kind === 'choamStorm' && state.decision.player === owner, actions);
|
||||
assert.equal(game.turn, 2);
|
||||
assert.equal(game.phase, 0);
|
||||
assert.equal(game.storm, beforeWeather.storm);
|
||||
assert.equal(semutaEmperorPlayer(game, owner).forces[prefix.origin], 1);
|
||||
if (fullHolder) {
|
||||
const player = semutaEmperorPlayer(game, holder);
|
||||
while (player.hand.length < handLimit(player)) {
|
||||
assert.ok(game.deck[0]);
|
||||
moveSemutaEmperorCard(game, game.deck[0].id, holder);
|
||||
}
|
||||
staging.push('Conserved original undealt cards fill Richese to its native hand limit.');
|
||||
}
|
||||
const view = viewGame(game, owner);
|
||||
const printed = choamPowerPlays(view, 'jubba').find(play => play.source === 'printed' && play.card.id === card);
|
||||
assert.ok(printed);
|
||||
const territory = 'red_chasm';
|
||||
const action = choamPowerAction(view, printed, { territory });
|
||||
assert.ok(action);
|
||||
return { setup: prefix.setup, beforeShipment: prefix.beforeShipment,
|
||||
afterShipment: prefix.afterShipment, beforeWeather, afterWeather, game,
|
||||
owner, holder, target: owner, canceller: prefix.canceller, weatherOwner, weatherCard,
|
||||
retainedKulon: prefix.card, card, source: 'printed', territory, origin: prefix.origin,
|
||||
action, weatherAction, weatherAmount: amount, shipment: prefix.shipment,
|
||||
participants: game.players.map(player => player.id), actions, staging };
|
||||
}
|
||||
|
||||
/** Declare the canonical printed power and pass only real Karama controls. */
|
||||
export function disposeSemutaHomeworldChoamStorm(
|
||||
fixture: SemutaHomeworldChoamStormFixture, state = fixture.game,
|
||||
actions?: RicheseSkillsTechStep[],
|
||||
): Game {
|
||||
return allowSemutaHomeworldChoamMovement(
|
||||
applySemutaHomeworldChoamStep(state, fixture.owner, fixture.action, actions), actions);
|
||||
}
|
||||
|
||||
/** Finish the original Storm's CHOAM market and opening controls, not a new turn. */
|
||||
export function finishSemutaHomeworldChoamStorm(
|
||||
state: Game, actions?: RicheseSkillsTechStep[],
|
||||
): Game {
|
||||
const game = advanceSemutaHomeworldChoam(state,
|
||||
current => current.phase === 1 && clean(current), actions);
|
||||
assert.equal(game.turn, 2);
|
||||
return game;
|
||||
}
|
||||
Reference in new issue
Block a user