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,88 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { createGame, joinGame, newPlayer, viewGame, type Action, type Game } from '../game/engine';
|
||||
import type { FactionId } from '../game/catalog';
|
||||
import { actBasicSemutaAmbassador as act } from './fixture-basic-semuta-ambassador';
|
||||
import { nextRicheseStrongholdsNativeStep } from './fixture-richese-strongholds';
|
||||
import { withClassicDiscoveryNexusLottery } from './fixture-discovery-classic-nexus';
|
||||
|
||||
export type NativeLeaderlessFaction = Extract<FactionId, 'ixians' | 'tleilaxu' | 'choam' | 'richese' | 'ecaz' | 'moritani'>;
|
||||
export const nativeLeaderlessPlayer = (game: Game, id: string) => {
|
||||
const player = game.players.find(candidate => candidate.id === id); assert.ok(player); return player;
|
||||
};
|
||||
export const nativeLeaderlessClean = (game: Game) => !game.response && !game.phaseOpening && !game.decision &&
|
||||
!game.truthtrance && !game.pendingTreacheryDiscard;
|
||||
export function advanceNativeLeaderless(game: Game, until: (state: Game) => boolean): Game {
|
||||
for (let count = 0; count < 1600; count++) {
|
||||
if (until(game)) return game;
|
||||
assert.notEqual(game.status, 'finished');
|
||||
const step = nextRicheseStrongholdsNativeStep(game);
|
||||
assert.ok(step, `No native continuation at ${game.status}/${game.phase}/${game.decision?.kind}`);
|
||||
game = act(game, step.actor, step.action);
|
||||
}
|
||||
throw Error('The native public battle did not reach the requested boundary');
|
||||
}
|
||||
|
||||
/** Fresh public selected-deck setup, original phase progression and paid
|
||||
* shipments create the battle. Only private leader-use/death and conserved
|
||||
* hand/deck history are staged before the genuine chooseBattle action. */
|
||||
export function basicNativeLeaderlessFixture(faction: NativeLeaderlessFaction,
|
||||
options: { dead?: boolean; hero?: boolean; ghola?: boolean } = {}) {
|
||||
const expansions = faction === 'ixians' || faction === 'tleilaxu' ? ['ix'] :
|
||||
faction === 'choam' || faction === 'richese' ? ['choam'] : ['ecaz'];
|
||||
let game = withClassicDiscoveryNexusLottery(() => createGame(`NATIVELEADERLESS-${faction}`,
|
||||
newPlayer(faction, faction, faction), false, expansions));
|
||||
for (const other of ['guild', 'emperor'] as const) joinGame(game, newPlayer(other, other, other));
|
||||
for (const seat of game.players) game = act(game, seat.id, {type:'ready'});
|
||||
game = act(game, faction, {type:'start'});
|
||||
const setup = structuredClone(game);
|
||||
game = advanceNativeLeaderless(game, state => state.status === 'playing' && state.phase === 5 && nativeLeaderlessClean(state));
|
||||
const selected = ['pasty_mesa', 'false_wall_west', 'shield_wall'].map(id => {
|
||||
const sectors: Record<string, number[]> = { pasty_mesa:[5,6,7,8], false_wall_west:[16,17,18], shield_wall:[8,9] };
|
||||
return {territory:id, sector:sectors[id].find(value => value !== game.storm)};
|
||||
}).find(row => row.sector !== undefined && game.players.every(player =>
|
||||
Object.keys(player.forces).every(key => !key.startsWith(`${row.territory}:`))));
|
||||
assert.ok(selected && selected.sector !== undefined);
|
||||
const territory = selected.territory, sector = selected.sector;
|
||||
while (game.phase === 5) {
|
||||
if (!nativeLeaderlessClean(game)) {
|
||||
game = advanceNativeLeaderless(game, state => state.phase !== 5 || nativeLeaderlessClean(state));
|
||||
continue;
|
||||
}
|
||||
const actor = game.active!;
|
||||
if (actor === faction || actor === 'guild') {
|
||||
game = act(game, actor, {type:'ship', territory, sector, amount:1, elite:0, allyPayment:0});
|
||||
game = advanceNativeLeaderless(game, state => state.phase !== 5 || state.active === actor && nativeLeaderlessClean(state));
|
||||
}
|
||||
if (game.phase === 5) game = act(game, actor, {type:'endMovement'});
|
||||
}
|
||||
game = advanceNativeLeaderless(game, state => state.phase === 6 && nativeLeaderlessClean(state) && !state.battle && !!state.active);
|
||||
const player = nativeLeaderlessPlayer(game, faction);
|
||||
for (const leader of player.leaders) {
|
||||
assert.ok(!leader.dead && !leader.capturedBy);
|
||||
leader.usedAt = territory === 'carthag' ? 'arrakeen' : 'carthag';
|
||||
}
|
||||
if (options.dead) {
|
||||
player.leaders[0].dead = true; player.leaders[0].deaths = Math.max(1, player.leaders[0].deaths ?? 0);
|
||||
delete player.leaders[0].usedAt;
|
||||
}
|
||||
// Exchange exact original physical cards with the remaining deck; never
|
||||
// create an extra card or increase the player's hand size.
|
||||
const swap = (handIndex: number, predicate: (card: Game['deck'][number]) => boolean) => {
|
||||
const index = game.deck.findIndex(predicate); assert.ok(index >= 0);
|
||||
[player.hand[handIndex], game.deck[index]] = [game.deck[index], player.hand[handIndex]];
|
||||
};
|
||||
for (let i = 0; i < player.hand.length; i++) if (player.hand[i].kind === 'hero')
|
||||
swap(i, card => card.kind !== 'hero' && (!options.ghola || card.effect !== 'ghola'));
|
||||
if (options.hero || options.ghola) {
|
||||
assert.ok(player.hand.length > 0);
|
||||
swap(0, card => options.hero ? card.kind === 'hero' : card.effect === 'ghola');
|
||||
}
|
||||
const chooser = game.active!;
|
||||
const choice = viewGame(game, chooser).battleChoices.find(row => row.territory === territory &&
|
||||
[row.attacker, row.defender].includes(faction) && [row.attacker, row.defender].includes('guild'));
|
||||
assert.ok(choice);
|
||||
const choose: Action = {type:'chooseBattle', territory, target:choice.attacker === chooser ? choice.defender : choice.attacker};
|
||||
const beforeBattle = structuredClone(game);
|
||||
game = act(game, chooser, choose);
|
||||
return {game, setup, beforeBattle, faction, opponent:'guild', territory, sector, chooser, choose};
|
||||
}
|
||||
Reference in new issue
Block a user