84 lines
5.1 KiB
TypeScript
84 lines
5.1 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { applyAction, viewGame, type Action, type Game } from '../game/engine';
|
|
import { isAdvisor } from '../game/advisors';
|
|
import { guildTransportQuote } from '../game/transport-quote';
|
|
import {
|
|
createSemutaAdvisorTransportMatchingFixture as fixture,
|
|
actSemutaAdvisorTransportMatching as act, allowSemutaAdvisorTransportMatching as allow,
|
|
finishSemutaAdvisorTransportMatching as finish, semutaAdvisorTransportMatchingPlayer as player,
|
|
semutaAdvisorTransportMatchingInventory as inventory,
|
|
} from './fixture-semuta-advisor-transport-matching';
|
|
|
|
const advisorEntry: Action = {type:'guildShip',from:'arrakeen:10',territory:'tueks_sietch',
|
|
sector:5,amount:1,elite:0,allyPayment:0};
|
|
function complete(game: Game) {
|
|
for (let count=0;game.response && ['guildTransportPermission','guildRate'].includes(game.response.kind) && count<3;count++)
|
|
game=allow(game);
|
|
return finish(game);
|
|
}
|
|
function reject(game: Game, actor: string, action: Action) {
|
|
const before=structuredClone(game);assert.throws(()=>applyAction(game,actor,action));assert.deepEqual(game,before);
|
|
}
|
|
function source() {
|
|
const f=fixture({kind:'guild'}),game=structuredClone(f.beforeDeclaration);
|
|
assert.equal(game.pendingAdvisorTransportMatching,undefined);
|
|
assert.ok((player(game,f.guild).forces['tueks_sietch:5']??0)>0);
|
|
assert.equal(player(game,f.owner).ally,f.guild);assert.equal(player(game,f.guild).ally,f.owner);
|
|
assert.equal(isAdvisor(player(game,f.owner),'arrakeen'),true);
|
|
assert.equal(player(game,f.owner).forces['arrakeen:10'],1);
|
|
return {f,game};
|
|
}
|
|
|
|
void test('fresh Guild board transport quotes and delivers the actual advisor into its allied foreign group',()=>{
|
|
const {f,game:before}=source();
|
|
assert.deepEqual(guildTransportQuote(viewGame(before,f.owner),advisorEntry).unavailableReasons,[]);
|
|
let game=act(before,f.owner,advisorEntry);
|
|
assert.equal(game.response?.kind,'guildTransportPermission');assert.equal(game.pendingAdvisorTransportMatching,undefined);
|
|
assert.equal(game.pendingGuildTransport?.advisorArrivalSource,'arrakeen');assert.equal(game.pendingGuildTransport?.advisors,true);
|
|
assert.deepEqual(player(game,f.owner).forces,player(before,f.owner).forces);
|
|
game=complete(game);
|
|
assert.equal(player(game,f.owner).forces['arrakeen:10']??0,0);
|
|
assert.equal(player(game,f.owner).forces['tueks_sietch:5'],1);assert.equal(isAdvisor(player(game,f.owner),'tueks_sietch'),true);
|
|
assert.deepEqual(player(game,f.guild).forces,player(before,f.guild).forces);
|
|
assert.equal(player(game,f.owner).spice,player(before,f.owner).spice-1);
|
|
assert.equal(player(game,f.guild).spice,player(before,f.guild).spice+1);
|
|
assert.equal(player(game,f.owner).shipped,true);assert.equal(player(game,f.owner).moved,0);
|
|
assert.equal(game.pendingAutomaticAdvisorFlips,undefined);assert.deepEqual(inventory(game),f.originalInventory);
|
|
reject(game,f.owner,advisorEntry);
|
|
});
|
|
|
|
void test('the source-stance cutover blocks fighter cross-entry and preserves the direct reserve default',()=>{
|
|
const {f,game}=source();
|
|
const fighter:Action={...advisorEntry,from:'imperial_basin:9'};
|
|
assert.ok(guildTransportQuote(viewGame(game,f.owner),fighter).unavailableReasons.length>0);
|
|
reject(game,f.owner,fighter);
|
|
const reserve=act(game,f.owner,{type:'ship',territory:'tueks_sietch',sector:5,amount:1,elite:0,allyPayment:0});
|
|
assert.ok(reserve.pendingShipment);assert.equal(reserve.pendingShipment.advisors,false);
|
|
assert.equal('advisorArrivalSource' in reserve.pendingShipment,false);
|
|
});
|
|
|
|
void test('a fresh Guild advisor declaration rejects a changed source before payment or transfer',()=>{
|
|
const {f,game:before}=source(),game=act(before,f.owner,advisorEntry);
|
|
assert.ok(game.pendingGuildTransport);game.pendingGuildTransport.advisorArrivalSource='polar_sink';
|
|
reject(game,f.holder,{type:'passResponse'});
|
|
});
|
|
|
|
void test('controlled legacy Guild permission bytes retain the original already-approved matching settlement',()=>{
|
|
const f=fixture({kind:'guild'});let game=allow(structuredClone(f.game));
|
|
assert.equal(game.response?.kind,'guildTransportPermission');const intent=game.pendingGuildTransport;assert.ok(intent);
|
|
// Explicit format supplement to a genuine approved prefix. This is not a
|
|
// historical saved-game capture and changes no physical source or approval.
|
|
delete intent.advisorArrivalSource;
|
|
const fields:unknown[]=[intent.event,intent.turn,intent.player,intent.fromReserves,intent.group,
|
|
intent.eliteGroup,intent.origin,intent.to,intent.sector,intent.amount,intent.elite,
|
|
intent.cost,intent.allyPayment,intent.advisors];
|
|
if(intent.permission!==undefined)fields.push(intent.permission);
|
|
intent.signature=JSON.stringify(fields);
|
|
game=complete(game);
|
|
assert.equal(player(game,f.owner).forces['imperial_basin:9'],1);
|
|
assert.equal(player(game,f.owner).forces['arrakeen:10'],2);assert.equal(isAdvisor(player(game,f.owner),'arrakeen'),true);
|
|
assert.equal(player(game,f.owner).shipped,true);assert.equal(player(game,f.owner).moved,0);
|
|
assert.deepEqual(inventory(game),f.originalInventory);assert.equal(game.pendingGuildTransport??null,null);
|
|
});
|