84 lines
4.0 KiB
TypeScript
84 lines
4.0 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import { viewGame, type Action, type Game } from '../game/engine';
|
|
import { semutaEmperorPlayer } from './fixture-semuta-emperor-share';
|
|
import { applySemutaHomeworldChoamStep, holdSemutaHomeworldChoamCard } from './fixture-semuta-homeworld-choam';
|
|
import {
|
|
createSemutaHomeworldChoamMovementFixture,
|
|
type SemutaHomeworldChoamMovementFixture,
|
|
} from './fixture-semuta-homeworld-choam-movement';
|
|
import {
|
|
createSemutaHomeworldChoamReturnsFixture,
|
|
type SemutaHomeworldChoamReturnsFixture,
|
|
} from './fixture-semuta-homeworld-choam-returns';
|
|
import type { RicheseSkillsTechStep } from './fixture-richese-skills-tech';
|
|
|
|
export type SemutaHomeworldChoamKaramaPower = 'kulon' | 'baliset' | 'laLaLa' | 'gamont';
|
|
export type SemutaHomeworldChoamKaramaFixture = {
|
|
setup: Game;
|
|
beforeDeclaration: Game;
|
|
/** Exact original declaration response, before Emperor spends its Karama. */
|
|
game: Game;
|
|
owner: string;
|
|
holder: string;
|
|
target: string;
|
|
canceller: string;
|
|
card: string;
|
|
karama: string;
|
|
power: SemutaHomeworldChoamKaramaPower;
|
|
action: Action;
|
|
cancelAction: Action;
|
|
actions: RicheseSkillsTechStep[];
|
|
movement?: SemutaHomeworldChoamMovementFixture;
|
|
returns?: SemutaHomeworldChoamReturnsFixture;
|
|
};
|
|
|
|
/** Genuine native prefix and printed declaration. Only conserved original
|
|
* physical cards are positioned; the movement fixture already holds Karama. */
|
|
export function createSemutaHomeworldChoamKaramaFixture(
|
|
power: SemutaHomeworldChoamKaramaPower, advanced = false, fullHolder = false,
|
|
): SemutaHomeworldChoamKaramaFixture {
|
|
const movement = power === 'kulon' || power === 'baliset'
|
|
? createSemutaHomeworldChoamMovementFixture(power, advanced, fullHolder) : undefined;
|
|
const returns = power === 'laLaLa' || power === 'gamont'
|
|
? createSemutaHomeworldChoamReturnsFixture(power, advanced) : undefined;
|
|
const native = movement ?? returns;
|
|
assert.ok(native);
|
|
const beforeDeclaration = structuredClone(native.game);
|
|
const canceller = movement?.canceller
|
|
?? beforeDeclaration.players.find(player => player.faction === 'emperor')!.id;
|
|
assert.equal(semutaEmperorPlayer(beforeDeclaration, canceller).faction, 'emperor');
|
|
const held = semutaEmperorPlayer(beforeDeclaration, canceller).hand.find(card => card.effect === 'karama');
|
|
const karama = held?.id ?? holdSemutaHomeworldChoamCard(beforeDeclaration, canceller, 'Karama');
|
|
const actions = structuredClone(native.actions);
|
|
const game = applySemutaHomeworldChoamStep(beforeDeclaration, native.owner, native.action, actions);
|
|
assert.equal(game.response?.kind, 'choamWorthless');
|
|
assert.equal(game.response!.owner, native.owner);
|
|
assert.ok(viewGame(game, canceller).responseControls?.cancelCards.includes(karama));
|
|
return { setup: native.setup, beforeDeclaration, game,
|
|
owner: native.owner, holder: native.holder, target: native.target, canceller,
|
|
card: native.card, karama, power, action: native.action,
|
|
cancelAction: { type: 'card', mode: 'cancel', card: karama }, actions, movement, returns };
|
|
}
|
|
|
|
export function cancelSemutaHomeworldChoamKarama(
|
|
fixture: SemutaHomeworldChoamKaramaFixture, state = fixture.game, actions?: RicheseSkillsTechStep[],
|
|
): Game {
|
|
return applySemutaHomeworldChoamStep(state, fixture.canceller, fixture.cancelAction, actions);
|
|
}
|
|
|
|
/** Singleton printed Karama only; pass every still-entitled neutral participant. */
|
|
export function reactSemutaHomeworldChoamKarama(
|
|
state: Game, holder: string, claim: boolean, actions?: RicheseSkillsTechStep[],
|
|
): Game {
|
|
const pending = state.pendingTreacheryDiscard;
|
|
assert.ok(pending);
|
|
const reaction = pending.reaction;
|
|
if (reaction?.stage !== 'offer') throw Error('Expected the fresh native Karama discard offer');
|
|
const event = pending.batch.event;
|
|
if (claim) return applySemutaHomeworldChoamStep(state, holder, { type: 'semutaCommit', event }, actions);
|
|
let game = state;
|
|
for (const player of state.players.filter(player => !reaction.passed.includes(player.id)))
|
|
game = applySemutaHomeworldChoamStep(game, player.id, { type: 'semutaPass', event }, actions);
|
|
return game;
|
|
}
|