86 lines
5.6 KiB
TypeScript
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)));
|
|
});
|