Support native Basic Ambassador and Terror arrivals
This commit is contained in:
1 parent
a07c6d8e50
commit
a45d81aa34
9 files changed
+258
-28
No files matched your search
@@ -0,0 +1,110 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { viewGame, type Game } from '../game/engine';
|
||||
import { botActions } from '../game/bots';
|
||||
import { DIFFICULTIES } from '../game/bot-profiles';
|
||||
import { act, player, createBasicNativeOverlapFixture, type First, type Native } from './fixture-basic-native-overlap';
|
||||
|
||||
function decline(game:Game, owner:First):Game {
|
||||
if (owner==='ambassador') {
|
||||
const pending=game.pendingAmbassador; assert.ok(pending);
|
||||
return act(game,'ecaz',{type:'decision',event:pending.event,decline:true});
|
||||
}
|
||||
assert.ok(game.pendingTerrorEntry);
|
||||
return act(game,'moritani',{type:'decision',decline:true});
|
||||
}
|
||||
|
||||
for (const native of ['ixians','tleilaxu'] as const satisfies readonly Native[])
|
||||
for (const first of ['ambassador','terror'] as const satisfies readonly First[]) {
|
||||
void test(`Basic ${native} native roster preserves the original shipment through ${first}-first overlapping arrivals`,()=>{
|
||||
const f=createBasicNativeOverlapFixture(native,first),initial=f.game;
|
||||
const spice=player(initial,f.entrant).spice,reserves=player(initial,f.entrant).reserves;
|
||||
const offered=act(initial,f.entrant,f.shipment);
|
||||
assert.equal(player(offered,f.entrant).spice,spice-1);
|
||||
assert.equal(player(offered,f.entrant).reserves,reserves-1);
|
||||
assert.equal(player(offered,f.entrant).forces[`${f.destination}:${f.sector}`],1);
|
||||
assert.equal(player(offered,f.entrant).shipped,true);
|
||||
assert.equal(offered.pendingArrivalOverlap?.first,first);
|
||||
assert.equal(offered.pendingArrivalOverlap?.stage,'first');
|
||||
assert.equal(offered.pendingArrivalOverlap?.entrant,f.entrant);
|
||||
assert.equal(viewGame(offered,f.entrant).arrivalOverlap?.first,first);
|
||||
const other:First=first==='ambassador'?'terror':'ambassador';
|
||||
const second=decline(offered,first);
|
||||
assert.equal(second.pendingArrivalOverlap?.first,first);
|
||||
assert.equal(second.pendingArrivalOverlap?.stage,'second');
|
||||
assert.equal(player(second,f.entrant).spice,spice-1);
|
||||
assert.equal(player(second,f.entrant).forces[`${f.destination}:${f.sector}`],1);
|
||||
const settled=decline(second,other);
|
||||
assert.equal(settled.pendingArrivalOverlap,null);
|
||||
assert.equal(settled.pendingAmbassador,null);
|
||||
assert.equal(settled.pendingTerrorEntry,null);
|
||||
assert.equal(player(settled,f.entrant).spice,spice-1);
|
||||
assert.equal(player(settled,f.entrant).reserves,reserves-1);
|
||||
assert.equal(player(settled,f.entrant).forces[`${f.destination}:${f.sector}`],1);
|
||||
assert.equal(settled.ecazAmbassadors?.tokens.find(t=>t.id===f.token)?.zone,'placed');
|
||||
assert.equal(settled.moritaniTerror?.tokens.find(t=>t.id===f.terror)?.status,'placed');
|
||||
});
|
||||
}
|
||||
|
||||
void test('all minimal bot profiles can legally handle the first mixed-native overlap decision',()=>{
|
||||
const f=createBasicNativeOverlapFixture('ixians','ambassador');
|
||||
const offered=act(f.game,f.entrant,f.shipment);
|
||||
for(const difficulty of DIFFICULTIES){
|
||||
const view=viewGame(offered,'ecaz');
|
||||
view.players.find(p=>p.id==='ecaz')!.bot=difficulty;
|
||||
const action=botActions(view)[0]; assert.ok(action,difficulty);
|
||||
const after=act(offered,'ecaz',action);
|
||||
assert.ok(after.pendingArrivalOverlap || after.pendingTerrorEntry || after.pendingAmbassador,
|
||||
'The bot choice enters the same real arrival continuation');
|
||||
}
|
||||
});
|
||||
|
||||
for (const first of ['ambassador','terror'] as const satisfies readonly First[]) {
|
||||
void test(`Basic mixed-native ${first}-first arrival settles Duke acquisition and Robbery once`,()=>{
|
||||
const f=createBasicNativeOverlapFixture('tleilaxu',first);
|
||||
const guildBefore=player(f.game,f.entrant).spice;
|
||||
const moritaniBefore=player(f.game,f.moritani).spice;
|
||||
let game=act(f.game,f.entrant,f.shipment);
|
||||
const afterInvoice=guildBefore-1, stolen=Math.ceil(afterInvoice/2);
|
||||
for(const owner of [first,first==='ambassador'?'terror':'ambassador'] as const){
|
||||
if(owner==='ambassador'){
|
||||
const entry=game.pendingAmbassador; assert.ok(entry);
|
||||
game=act(game,f.owner,{type:'decision',event:entry.event,trigger:true,
|
||||
beneficiary:f.owner,choice:'duke'});
|
||||
assert.equal(game.dukeVidal?.controller,f.owner);
|
||||
assert.equal(game.ecazAmbassadors?.tokens.find(t=>t.id===f.token)?.zone,'supply');
|
||||
}else{
|
||||
assert.equal(game.pendingTerrorEntry?.stage,'offer');
|
||||
game=act(game,f.moritani,{type:'decision',reveal:true});
|
||||
assert.equal(game.pendingTerrorEntry?.stage,'robbery');
|
||||
game=act(game,f.moritani,{type:'decision',choice:'spice'});
|
||||
assert.equal(player(game,f.entrant).spice,afterInvoice-stolen);
|
||||
assert.equal(player(game,f.moritani).spice,moritaniBefore+stolen);
|
||||
}
|
||||
}
|
||||
assert.equal(game.pendingArrivalOverlap,null);
|
||||
assert.equal(game.moritaniTerror?.tokens.find(t=>t.id===f.terror)?.status,'removed');
|
||||
assert.equal(player(game,f.entrant).forces[`${f.destination}:${f.sector}`],1);
|
||||
assert.equal(player(game,f.entrant).shipped,true);
|
||||
});
|
||||
}
|
||||
|
||||
void test('a real native Tleilaxu shipment triggers both reactions and resumes with physical forces',()=>{
|
||||
const f=createBasicNativeOverlapFixture('tleilaxu','terror','native');
|
||||
const initial=f.game, before=player(initial,f.entrant).spice;
|
||||
let game=act(initial,f.entrant,f.shipment);
|
||||
assert.equal(game.pendingArrivalOverlap?.entrant,'tleilaxu');
|
||||
assert.equal(game.pendingArrivalOverlap?.first,'terror');
|
||||
assert.equal(player(game,f.entrant).spice,before-1);
|
||||
assert.equal(player(game,f.entrant).forces[`${f.destination}:${f.sector}`],1);
|
||||
game=act(game,f.moritani,{type:'decision',reveal:true});
|
||||
assert.equal(game.pendingTerrorEntry?.stage,'robbery');
|
||||
game=act(game,f.moritani,{type:'decision',choice:'spice'});
|
||||
const pending=game.pendingAmbassador; assert.ok(pending);
|
||||
game=act(game,f.owner,{type:'decision',event:pending.event,trigger:true,
|
||||
beneficiary:f.owner,choice:'duke'});
|
||||
assert.equal(game.dukeVidal?.controller,f.owner);
|
||||
assert.equal(game.pendingArrivalOverlap,null);
|
||||
assert.equal(player(game,f.entrant).forces[`${f.destination}:${f.sector}`],1);
|
||||
assert.equal(player(game,f.entrant).shipped,true);
|
||||
});
|
||||
Reference in new issue
Block a user