Files
Dune/tests/fixture-homeworld-occupied-bidding.ts

90 lines
4.4 KiB
TypeScript

import assert from 'node:assert/strict';
import {
applyAction, createGame, initializeHomeworldOccupationGameForAudit,
joinGame, newPlayer, viewGame, type Game,
} from '../game/engine';
import { FACTIONS, type FactionId } from '../game/catalog';
import { botActions } from '../game/bots';
import { assertDefenseInventory } from './fixture-homeworld-occupied-defenses';
export const biddingPlayer = (game: Game, id: string) => game.players.find(player => player.id === id)!;
/** Original lobby, selected expansion decks and full original setup pipeline.
* All later conserved positions are labelled controlled fixtures, not gameplay. */
export function freshBiddingGame(native: FactionId, ally: FactionId = 'emperor', advanced = true): Game {
const roster: [string, FactionId][] = [
['native', native], ['occupier', 'guild'], ['ally', ally],
['observer', 'beneGesserit'], ['fremen', 'fremen'],
];
const expansions = [...new Set(roster.map(([, faction]) =>
FACTIONS.find(card => card.id === faction)!.expansion))].filter(expansion => expansion !== 'base');
let game = createGame('OCCUPIEDBIDDING', newPlayer('native', native, native), advanced, expansions);
for (const [id, faction] of roster.slice(1)) joinGame(game, newPlayer(id, id, faction));
game = applyAction(game, 'native', { type: 'homeworlds', enabled: true });
for (const player of game.players) game = applyAction(game, player.id, { type: 'ready' });
game = initializeHomeworldOccupationGameForAudit(game);
for (let step = 0; game.status === 'setup' && step < 100; step++) {
let progressed = false;
for (const player of game.players) {
const view = viewGame(game, player.id);
view.players.find(seat => seat.id === player.id)!.bot = 'Easy';
const action = botActions(view)[0];
if (!action) continue;
game = applyAction(game, player.id, action);
progressed = true;
break;
}
assert.ok(progressed, 'original Bidding fixture setup has a legal next choice');
}
assert.equal(game.status, 'playing');
assert.equal(game.homeworldOccupationPreview, true);
assert.deepEqual(game.homeworldOccupationHistory!.qualifications, []);
assertDefenseInventory(game);
assert.equal(new Set(biddingPhysicalIds(game)).size, biddingPhysicalIds(game).length);
return game;
}
/** Live physical ownership only: original knowledge aliases are not cards. */
export function biddingPhysicalIds(game: Game): string[] {
return [
...game.players.flatMap(player => player.hand), ...game.deck, ...game.discard,
...(game.richeseCache ?? []), ...(game.richeseRemoved ?? []),
...(game.ixSetupCards ?? []), ...(game.ixAuction?.cards ?? []),
...(game.ornithopter ? [game.ornithopter.card] : []),
...(game.auction?.cards.slice(game.auction.index + Number(game.currentAuctionSale?.origin === 'normal')) ?? []),
].map(card => card.id).sort();
}
/** Controlled original-card transfers to stage receiver capacity. No fake
* Treachery card, duplicate or destruction is used to manufacture a full hand. */
export function stageBiddingHandSize(game: Game, id: string, count: number) {
const original = biddingPhysicalIds(game);
const player = biddingPlayer(game, id);
while (player.hand.length > count) game.deck.push(player.hand.pop()!);
while (player.hand.length < count) {
const card = game.deck.shift() ?? game.discard.shift();
assert.ok(card, 'capacity fixture requires an original drawable card');
player.hand.push(card);
}
assert.equal(player.hand.length, count);
assert.deepEqual(biddingPhysicalIds(game), original, 'controlled hand transfer conserves all original physical IDs');
}
/** Labelled conserved transfer from original stock, including already-dealt
* cards. Random setup may deal every Karama; never fabricate a replacement. */
export function stageBiddingKarama(game: Game, id: string) {
const original = biddingPhysicalIds(game), receiver = biddingPlayer(game, id);
const held = receiver.hand.find(card => card.effect === 'karama');
if (held) return held;
const stock = [game.deck, game.discard, ...game.players.filter(player => player.id !== id).map(player => player.hand)];
for (const source of stock) {
const index = source.findIndex(card => card.effect === 'karama');
if (index < 0) continue;
const card = source.splice(index, 1)[0];
receiver.hand.push(card);
assert.deepEqual(biddingPhysicalIds(game), original);
return card;
}
throw new Error('Original unused fixture stock has no Karama.');
}