Files
Dune/tests/fixture-bureaucrat-qualifying-auction.ts

146 lines
6.9 KiB
TypeScript

import assert from 'node:assert/strict';
import { applyAction, handLimit, viewGame, type Action, type Game } from '../game/engine';
import { createBureaucratQualifyingSplitSetup, advanceBureaucratQualifyingSplit, type BureaucratQualifyingSplitStep } from './fixture-bureaucrat-qualifying-split';
export interface BureaucratQualifyingAuctionFixture {
game: Game;
setup: Game;
beforeBribe: Game;
bribe: Game;
afterFirstMentat: Game;
worm: Game;
alliance: Game;
sourceSetup: Game;
beforePledge: Game;
originalPledge: BureaucratQualifyingSplitStep;
pledged: Game;
beforeAction: Game;
action: Action;
payer: string;
donor: string;
trainer: string;
guild: string;
emperor: string;
payerShare: number;
donorShare: number;
amount: number;
counterCard?: string;
physical: string[];
reservedCard: string;
bonusCard?: string;
actions: BureaucratQualifyingSplitStep[];
staging: string[];
}
export const qualifyingAuctionPlayer = (game: Game, id: string) => game.players.find(player => player.id === id)!;
export function qualifyingAuctionPhysicalCards(game: Game): string[] {
return [...game.deck, ...game.discard, ...game.players.flatMap(player => player.hand),
...(game.auction?.cards.slice(game.auction.index + (game.currentAuctionSale?.origin === 'normal' ? 1 : 0)) ?? [])]
.map(card => card.id).sort();
}
/** Swap conserved physical cards, retaining the original hand size and auction
* quantity. Harkonnen's Karama makes the native winning payment a real choice;
* no purchase bonus is dealt until the original income continuation finishes. */
function positionKarama(game: Game, actor: string, staging: string[]): string {
const player = qualifyingAuctionPlayer(game, actor);
const held = player.hand.find(card => card.effect === 'karama');
if (held) return held.id;
const index = game.deck.findIndex(card => card.effect === 'karama');
assert.ok(index >= 0, 'An original undealt physical Karama must be available');
const outgoing = player.hand[0];
assert.ok(outgoing);
const incoming = game.deck[index];
game.deck[index] = outgoing;
player.hand[0] = incoming;
staging.push(`Swap original held ${outgoing.id} with original undealt ${incoming.id} for ${actor}; retain hand size and committed lots.`);
return incoming.id;
}
export function createBureaucratQualifyingAuctionFixture({
advanced = false, payerShare = 5, donorShare = 3, donor = 'f', counter = false, lastHandSpace = false,
}: { advanced?: boolean; payerShare?: number; donorShare?: number; donor?: 'f' | 'b' | 'e'; counter?: boolean; lastHandSpace?: boolean } = {}): BureaucratQualifyingAuctionFixture {
const native = createBureaucratQualifyingSplitSetup(advanced);
let game = native.game, alliance = native.alliance;
let actions = native.actions;
const staging = native.staging;
if (donor !== native.donor) {
// Branch at the actual worm, before either mutual alliance proposal.
game = structuredClone(native.worm);
actions = actions.slice(0, actions.findIndex(step => step.action.type === 'alliance'));
for (const [actor, target] of [[native.payer, donor], [donor, native.payer]]) {
const action: Action = { type: 'alliance', target };
actions.push({ actor, action });
game = applyAction(game, actor, action);
}
alliance = structuredClone(game);
game = advanceBureaucratQualifyingSplit(game, state => state.phase === 3 && !!state.auction &&
!state.phaseOpening && !state.response && !state.decision, actions);
}
const sourceSetup = structuredClone(game);
const physical = qualifyingAuctionPhysicalCards(game);
positionKarama(game, native.payer, staging);
const counterCard = counter ? positionKarama(game, native.trainer, staging) : undefined;
if (lastHandSpace) {
const payer = qualifyingAuctionPlayer(game, native.payer);
while (payer.hand.length < handLimit(payer) - 1) {
const incoming = game.deck.shift();
assert.ok(incoming);
payer.hand.push(incoming);
staging.push(`Conserve original undealt ${incoming.id} into ${payer.id} after original auction quantity committed.`);
}
}
assert.deepEqual(qualifyingAuctionPhysicalCards(game), physical);
const originalPledge: BureaucratQualifyingSplitStep = { actor: donor, action: { type: 'pledgeAid', amount: donorShare } };
const beforePledge = structuredClone(game);
actions.push(originalPledge);
game = applyAction(game, originalPledge.actor, originalPledge.action);
const pledged = structuredClone(game);
for (let n = 0; game.auction!.active !== native.payer && n < game.players.length; n++) {
const step: BureaucratQualifyingSplitStep = { actor: game.auction!.active, action: { type: 'passBid' } };
actions.push(step);
game = applyAction(game, step.actor, step.action);
}
assert.equal(game.auction!.active, native.payer);
const beforeAction = structuredClone(game), amount = payerShare + donorShare;
const action: Action = { type: 'bid', amount, allyPayment: donorShare };
return { ...native, donor, alliance, game, sourceSetup, beforePledge, originalPledge, pledged, beforeAction, action,
payerShare, donorShare, amount, counterCard, physical, reservedCard: game.auction!.cards[game.auction!.index].id,
bonusCard: game.deck[0]?.id, actions, staging };
}
export function payBureaucratQualifyingAuction(fixture: BureaucratQualifyingAuctionFixture, state = fixture.game): Game {
let game = applyAction(state, fixture.payer, fixture.action);
fixture.actions.push({ actor: fixture.payer, action: fixture.action });
for (let n = 0; game.decision?.kind !== 'auctionPayment' && n < game.players.length + 2; n++) {
assert.ok(game.auction && !game.currentAuctionSale);
const step: BureaucratQualifyingSplitStep = { actor: game.auction.active, action: { type: 'passBid' } };
fixture.actions.push(step);
game = applyAction(game, step.actor, step.action);
}
assert.equal(game.decision?.kind, 'auctionPayment');
assert.equal(game.decision!.player, fixture.payer);
const action: Action = { type: 'decision', karama: false };
fixture.actions.push({ actor: fixture.payer, action });
return applyAction(game, fixture.payer, action);
}
export function allowQualifyingAuctionResponses(state: Game): Game {
let game = state;
for (let n = 0; game.response && n < 40; n++) {
const actor = game.players.find(player => {
const controls = viewGame(game, player.id).responseControls;
return controls && !controls.hasPassed && controls.cancelCards.length > 0;
});
assert.ok(actor, 'The original response must have a legal unpassed counter holder');
game = applyAction(game, actor.id, { type: 'passResponse' });
}
assert.equal(game.response, null);
return game;
}
export function chooseQualifyingAuction(game: Game, redirect = true): Game {
assert.equal(game.decision?.kind, 'bureaucratPayment');
return applyAction(game, game.decision!.player, { type: 'decision', event: game.bureaucratPaymentEvent, redirect });
}