Announce leaderless native Basic battles before plans
Verify and publish checkpoint container / container (push) Successful in 10m14s
Verify and publish checkpoint container / container (push) Successful in 10m14s
This commit is contained in:
1 parent
00e01a7cd2
commit
f73a6e0a9c
9 files changed
+481
-21
No files matched your search
@@ -0,0 +1,97 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { applyAction, viewGame, type Game } from '../game/engine';
|
||||
import { acquireDuke } from '../game/duke-vidal';
|
||||
import { basicEcazNativeFixture, nextBasicEcazNativeStep } from './fixture-basic-ecaz-native';
|
||||
import { basicEcazOccupyBattleAction, basicEcazOccupyFixture } from './fixture-basic-ecaz-occupy';
|
||||
import { leaderlessSeat, stageLeaderlessCard } from './fixture-basic-leaderless-harkonnen';
|
||||
|
||||
export type OccupyLeaderlessOptions = {
|
||||
lead: 'ecaz' | 'ally';
|
||||
dead: 'selected' | 'opponent' | 'unselected' | 'none';
|
||||
duke?: boolean;
|
||||
cancel?: boolean;
|
||||
};
|
||||
|
||||
/** Public Basic selected-deck setup and original alliance/coalition admission.
|
||||
* The shared fixture stages conserved board pieces and wallets. This fixture
|
||||
* stages native public leader history and conserves the physical hands. */
|
||||
export function basicNativeLeaderlessOccupy(options: OccupyLeaderlessOptions) {
|
||||
const fixture = basicEcazNativeFixture({ allyFaction: 'choam', opponentFaction: 'guild' });
|
||||
let game = structuredClone(fixture.beforeBattle);
|
||||
const selected = options.lead === 'ecaz' ? fixture.ecaz : fixture.ally;
|
||||
const unselected = options.lead === 'ecaz' ? fixture.ally : fixture.ecaz;
|
||||
const dead = options.dead === 'selected' ? selected : options.dead === 'opponent' ? fixture.opponent :
|
||||
options.dead === 'unselected' ? unselected : null;
|
||||
for (const actor of [fixture.ecaz, fixture.ally, fixture.opponent]) {
|
||||
stageLeaderlessCard(game, actor, 'none');
|
||||
for (const leader of leaderlessSeat(game, actor).leaders) {
|
||||
leader.dead = actor === dead;
|
||||
leader.deaths = actor === dead ? Math.max(1, leader.deaths) : leader.deaths;
|
||||
if (actor !== dead) leader.usedAt = 'arrakeen';
|
||||
else delete leader.usedAt;
|
||||
}
|
||||
}
|
||||
if (options.duke) {
|
||||
assert.ok(game.dukeVidal);
|
||||
game.dukeVidal = acquireDuke(game.dukeVidal, fixture.ecaz, game.turn, 'ecaz');
|
||||
}
|
||||
const choice = viewGame(game, game.active!).battleChoices.find(row => row.territory === fixture.territory);
|
||||
assert.ok(choice);
|
||||
game = applyAction(game, choice.chooser, { type: 'chooseBattle', territory: fixture.territory,
|
||||
target: choice.attacker === choice.chooser ? choice.defender : choice.attacker });
|
||||
assert.equal(game.decision?.kind, 'ecazBattleLead');
|
||||
const event = game.decision.event;
|
||||
game = applyAction(game, game.decision.player, { type: 'decision', event, lead: selected });
|
||||
if (options.cancel) {
|
||||
const card = leaderlessSeat(game, fixture.opponent).hand.find(c => c.effect === 'karama');
|
||||
assert.ok(card);
|
||||
game = applyAction(game, fixture.opponent, { type: 'card', mode: 'cancel', card: card.id });
|
||||
}
|
||||
for (let attempts = 0; attempts < 100 && (game.pendingTreacheryDiscard || game.response || game.phaseOpening || game.decision); attempts++) {
|
||||
const next = nextBasicEcazNativeStep(game); assert.ok(next);
|
||||
game = applyAction(game, next.actor, next.action);
|
||||
}
|
||||
assert.ok(game.battle && !game.pendingTreacheryDiscard && !game.response && !game.phaseOpening && !game.decision);
|
||||
return { fixture, game, selected, unselected, dead, event };
|
||||
}
|
||||
|
||||
/** Resolve existing Richese/shared preparation without submitting battle plans. */
|
||||
export function finishOccupyLeaderlessPreparation(state: Game): Game {
|
||||
let game = state;
|
||||
for (let attempts = 0; attempts < 100; attempts++) {
|
||||
if (game.battle && !game.battle.preparation && (!game.battle.preLeader || game.battle.preLeader.closed) &&
|
||||
!game.pendingTreacheryDiscard && !game.response && !game.phaseOpening && !game.decision) return game;
|
||||
const next = nextBasicEcazNativeStep(game); assert.ok(next);
|
||||
game = applyAction(game, next.actor, next.action);
|
||||
}
|
||||
throw new Error('Native Occupy leader preparation did not finish');
|
||||
}
|
||||
|
||||
/** Supplemental audit-entry fixture without Richese: original setup, worm
|
||||
* alliance and shipments, plus labelled conserved rival and leader staging. */
|
||||
export function basicOccupyNoRicheseLeaderless(duke = false) {
|
||||
const fixture = basicEcazOccupyFixture({ chooseBattle: false });
|
||||
let game = structuredClone(fixture.game);
|
||||
assert.ok(!game.players.some(player => player.faction === 'richese'));
|
||||
stageLeaderlessCard(game, fixture.ecaz, 'none');
|
||||
for (const leader of leaderlessSeat(game, fixture.ecaz).leaders) {
|
||||
leader.dead = true;
|
||||
leader.deaths = Math.max(1, leader.deaths);
|
||||
delete leader.usedAt;
|
||||
}
|
||||
if (duke) {
|
||||
assert.ok(game.dukeVidal);
|
||||
game.dukeVidal = acquireDuke(game.dukeVidal, fixture.ecaz, game.turn, 'ecaz');
|
||||
}
|
||||
const chosen = basicEcazOccupyBattleAction({ ...fixture, game });
|
||||
game = applyAction(game, chosen.actor, chosen.action);
|
||||
assert.equal(game.decision?.kind, 'ecazBattleLead');
|
||||
game = applyAction(game, game.decision.player,
|
||||
{ type: 'decision', event: game.decision.event, lead: fixture.ecaz });
|
||||
for (let attempts = 0; attempts < 100 && (game.pendingTreacheryDiscard || game.response || game.decision); attempts++) {
|
||||
const next = nextBasicEcazNativeStep(game); assert.ok(next);
|
||||
game = applyAction(game, next.actor, next.action);
|
||||
}
|
||||
assert.ok(game.battle && !game.pendingTreacheryDiscard && !game.response && !game.decision);
|
||||
return { fixture, game };
|
||||
}
|
||||
Reference in new issue
Block a user