Files
Dune/tests/ecaz-duke-plain-harkonnen-runtime.test.ts
T
Quintine 57e7611468
Verify and publish checkpoint container / container (push) Successful in 11m21s
Guard Duke grants using public capture uncertainty
2026-10-29 19:22:46 +11:00

86 lines
5.6 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { createGame, joinGame, newPlayer, viewGame, type Action, type Game } from '../game/engine';
import { DUKE_VIDAL_ID } from '../game/duke-vidal';
import { actDukeAmbassador, advanceDukeAmbassador, dukeAmbassadorClean } from './fixture-duke-ambassador';
/** Genuine Advanced setup, placement and shipment. Only unopened Spice cards
* and the original scalar lotteries are controlled to avoid a worm detour. */
function nativeArrival() {
let game = createGame('PLAINECAZHARKONNENDUKE', newPlayer('ecaz', 'Ecaz', 'ecaz'), true, ['ecaz']);
joinGame(game, newPlayer('harkonnen', 'Harkonnen', 'harkonnen'));
joinGame(game, newPlayer('guild', 'Guild', 'guild'));
const actions: { actor:string; action:Action }[] = [];
const act = (actor:string, action:Action) => { game = actDukeAmbassador(game, actor, action, actions); };
const reach = (until:(state:Game)=>boolean) => { game = advanceDukeAmbassador(game, until, actions); };
for (const player of game.players) act(player.id, {type:'ready'});
act(game.host, {type:'start', advancedPreview:true});
reach(state => state.status === 'playing');
assert.equal(game.advanced, true);
assert.deepEqual(game.expansions, ['ecaz']);
assert.equal(game.dukeCaptureUncertain, false);
assert.ok(!game.nexusCards && !game.homeworlds && !game.leaderSkills && !game.discoveryEnabled);
const token = game.ecazAmbassadors!.tokens.find(item => item.effect === 'ecaz');
assert.ok(token && token.zone === 'supply');
const originalDuke = structuredClone(game.dukeVidal!.leader);
for (const position of [0,1]) {
const index = game.spiceDeck.findIndex((card,i) => i >= position && 'territory' in card && !card.discovery);
assert.ok(index >= position);
[game.spiceDeck[position], game.spiceDeck[index]] = [game.spiceDeck[index], game.spiceDeck[position]];
}
reach(state => state.phase === 4 && !state.response && state.decision?.kind === 'ecazPlacement');
act('ecaz', {type:'decision', token:token.id, territory:'arrakeen'});
reach(state => state.phase === 4 && !state.response && state.decision?.kind === 'ecazPlacement');
act('ecaz', {type:'decision', decline:true});
reach(state => state.phase === 5 && state.active === 'harkonnen' && dukeAmbassadorClean(state));
const beforeSpice = game.players.find(player => player.id === 'harkonnen')!.spice;
act('harkonnen', {type:'ship', amount:1, territory:'arrakeen', sector:10});
reach(state => !state.response && state.pendingAmbassador?.stage === 'offer');
assert.equal(game.pendingAmbassador!.token, token.id);
return { game, token, originalDuke, beforeSpice, actions };
}
void test('plain Advanced Ecaz may acquire the original Duke on a real Harkonnen arrival before any capture', () => {
const source = nativeArrival();
let game = source.game;
const {token, originalDuke, beforeSpice, actions} = source;
const act = (actor:string, action:Action) => { game = actDukeAmbassador(game, actor, action, actions); };
const reach = (until:(state:Game)=>boolean) => { game = advanceDukeAmbassador(game, until, actions); };
const offer = viewGame(game, 'ecaz').ambassadorEntry;
assert.ok(offer);
assert.equal(offer.dukeAcquisition?.blocked, null);
const event = game.pendingAmbassador!.event;
act('ecaz', {type:'decision', event, trigger:true, beneficiary:'ecaz', choice:'duke'});
reach(state => dukeAmbassadorClean(state) && !state.pendingAmbassador);
assert.deepEqual(game.dukeVidal!.leader, originalDuke);
assert.deepEqual([game.dukeVidal!.controller, game.dukeVidal!.source], ['ecaz','ecaz']);
assert.equal(game.ecazAmbassadors!.tokens.find(item => item.id === token.id)!.zone, 'supply');
assert.equal(game.players.find(player => player.id === 'harkonnen')!.forces['arrakeen:10'], 1);
assert.ok(game.players.find(player => player.id === 'harkonnen')!.spice < beforeSpice);
assert.equal(game.active, 'harkonnen', 'The original shipment and movement turn resumes');
act('harkonnen', {type:'endMovement'});
assert.notEqual(game.active, 'harkonnen');
assert.ok(game.players.every(player => player.leaders.every(leader => leader.id !== DUKE_VIDAL_ID)));
});
void test('plain Advanced Ecaz alliance automatically lends the original Duke on Harkonnen acceptance', () => {
const source = nativeArrival();
let game = source.game;
const {token, originalDuke, actions} = source;
const act = (actor:string, action:Action) => { game = actDukeAmbassador(game, actor, action, actions); };
const reach = (until:(state:Game)=>boolean) => { game = advanceDukeAmbassador(game, until, actions); };
const event = game.pendingAmbassador!.event;
act('ecaz', {type:'decision', event, trigger:true, beneficiary:'ecaz', choice:'alliance'});
assert.equal(game.pendingAmbassador?.stage, 'allianceReply');
act('harkonnen', {type:'decision', event, accept:true});
reach(state => dukeAmbassadorClean(state) && !state.pendingAmbassador);
assert.equal(game.players.find(player => player.id === 'ecaz')!.ally, 'harkonnen');
assert.equal(game.players.find(player => player.id === 'harkonnen')!.ally, 'ecaz');
assert.deepEqual(game.dukeVidal!.leader, originalDuke);
assert.deepEqual([game.dukeVidal!.controller, game.dukeVidal!.source], ['harkonnen','ally']);
assert.equal(game.ecazAmbassadors!.tokens.find(item => item.id === token.id)!.zone, 'supply');
assert.equal(game.players.find(player => player.id === 'harkonnen')!.forces['arrakeen:10'], 1);
assert.equal(game.active, 'harkonnen', 'The original movement turn resumes without a loan acknowledgement');
assert.ok(game.players.every(player => player.leaders.every(leader => leader.id !== DUKE_VIDAL_ID)));
});