Files
Dune/tests/fixture-homeworld-occupied-producers.ts
T

76 lines
4.3 KiB
TypeScript

import assert from 'node:assert/strict';
import { applyAction, createGame, initializeHomeworldOccupationGameForAudit, joinGame, newPlayer, viewGame, type Action, type Game } from '../game/engine';
import { botActions } from '../game/bots';
/** Drive original phase/control producers, never synthesize a paid receipt or
* advance the phase clock by assignment. Tests may stop before any choice. */
export function advanceOriginal(game: Game, stop: (game: Game) => boolean, limit = 600): Game {
for (let step = 0; (game.phaseOpening || !stop(game)) && step < limit; step++) {
let choice: { player: string; action: Action } | undefined;
if (game.phaseOpening) {
const player = game.players.find(player => !game.phaseOpening!.passed.includes(player.id));
if (player) choice = { player: player.id, action: { type: 'ready' } };
} else if (game.response) {
const player = game.players.find(player => !game.response!.passed.includes(player.id));
if (player) choice = { player: player.id, action: { type: 'passResponse' } };
}
if (!choice) 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;
if (action.type === 'stormDial') action.amount = 0;
choice = { player: player.id, action };
break;
}
assert.ok(choice, 'original source progression must have a legal next action');
game = applyAction(game, choice.player, choice.action);
}
assert.ok(!game.phaseOpening && stop(game), 'original source progression must reach its settled requested boundary');
return game;
}
export function buyOriginalNormalLot(game: Game, buyer: string, amount: number): Game {
assert.ok(game.auction && !game.phaseOpening && !game.response && !game.decision);
const card = game.auction.cards[game.auction.index].id;
for (let step = 0; game.auction!.active !== buyer && step < game.players.length; step++)
game = applyAction(game, game.auction!.active, { type: 'passBid' });
assert.equal(game.auction!.active, buyer);
game = applyAction(game, buyer, { type: 'bid', amount, allyPayment: 0 });
for (let step = 0; !game.currentAuctionSale && !game.players.find(player => player.id === buyer)!.hand.some(held => held.id === card) &&
step < game.players.length + 2; step++) {
if (game.decision?.kind === 'auctionPayment')
game = applyAction(game, buyer, { type: 'decision', karama: false });
else game = applyAction(game, game.auction!.active, { type: 'passBid' });
}
assert.ok(game.players.find(player => player.id === buyer)!.hand.some(held => held.id === card),
'the original purchase producer must deliver its actual physical card');
return game;
}
export function cash(game: Game): Record<string, number> {
return Object.fromEntries(game.players.map(player => [player.id, player.spice]));
}
/** Fresh original Collection roster includes Ecaz shared legs and a real
* Harkonnen occupier; the ordinary setup inventory is never replaced. */
export function freshSouthernProducerGame(): Game {
let game = createGame('SOUTHERNPRODUCER', newPlayer('native', 'Fremen', 'fremen'), true, ['ecaz']);
for (const [id, faction] of [['occupier', 'harkonnen'], ['ally', 'guild'], ['ecaz', 'ecaz'], ['atreides', 'atreides']] as const)
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);
return advanceOriginal(game, game => game.status === 'playing');
}
/** Original native Guild payment can coexist with a BG accompaniment choice. */
export function freshGuildAdvisorProducerGame(): Game {
let game = createGame('GUILDADVISORPRODUCER', newPlayer('native', 'Guild', 'guild'), true);
for (const [id, faction] of [['occupier', 'atreides'], ['observer', 'beneGesserit'], ['buyer', 'emperor']] as const)
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' });
return advanceOriginal(initializeHomeworldOccupationGameForAudit(game), game => game.status === 'playing');
}