85 lines
4.5 KiB
TypeScript
85 lines
4.5 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { baseDeck } from '../game/cards';
|
|
import { coalitionOf, emptyCoalitionState } from '../game/coalition-alliance';
|
|
import { applyAction, createGame, newPlayer, viewGame, type Action, type Game } from '../game/engine';
|
|
|
|
const act = (game:Game, actor:string, action:Action) => applyAction(game,actor,action);
|
|
const seat = (game:Game, id:string) => game.players.find(player=>player.id===id)!;
|
|
|
|
/** The quiet Basic Nexus, wallets and physical Truthtrances are staged. Group
|
|
* formation, both answers, the invitation, departure and bribes are actions. */
|
|
function fixture():{game:Game; cards:string[]} {
|
|
let game=createGame('GROUPPENDINGLEAVE',newPlayer('a','Atreides','atreides'),false);
|
|
game.players.push(newPlayer('b','Emperor','emperor'),newPlayer('c','Fremen','fremen'),
|
|
newPlayer('d','Harkonnen','harkonnen'),newPlayer('e','Guild','guild'),
|
|
newPlayer('f','Bene Gesserit','beneGesserit'));
|
|
Object.assign(game,{status:'playing',phase:1,turn:2,nexus:true,
|
|
order:['a','b','c','d','e','f'],ready:[],deck:baseDeck()});
|
|
game.coalitionState=emptyCoalitionState('unlimited');
|
|
for (const player of game.players) player.spice=8;
|
|
game=act(game,'a',{type:'alliance',target:'b'});
|
|
game=act(game,'b',{type:'alliance',target:'a'});
|
|
const pair=coalitionOf(game.players,game.coalitionState,'a'); assert.ok(pair);
|
|
game=act(game,'a',{type:'coalitionPropose',groupId:pair.id,entrant:'c',
|
|
expectedRevision:game.coalitionState!.revision});
|
|
game=answer(game,'b',true); game=answer(game,'c',true);
|
|
const cards:string[]=[];
|
|
for (let count=0;count<2;count++) {
|
|
const index=game.deck.findIndex(card=>card.effect==='truthtrance'); assert.ok(index>=0);
|
|
const [card]=game.deck.splice(index,1); cards.push(card.id); seat(game,'f').hand.push(card);
|
|
}
|
|
return {game,cards};
|
|
}
|
|
function ask(game:Game, card:string, question:Record<string,unknown>, value:'yes'|'no'):Game {
|
|
game=act(game,'f',{type:'card',card});
|
|
while (game.truthtrance?.stage==='priority') {
|
|
const next=game.players.find(player=>!game.truthtrance!.passed.includes(player.id)); assert.ok(next);
|
|
game=act(game,next.id,{type:'truthPass'});
|
|
}
|
|
game=act(game,'f',{type:'truthAsk',question});
|
|
return act(game,'a',{type:'truthAnswer',answer:value});
|
|
}
|
|
function answer(game:Game, actor:string, accept:boolean):Game {
|
|
const offer=game.coalitionState!.proposal; assert.ok(offer);
|
|
return act(game,actor,{type:'coalitionAnswer',event:offer.event,nexus:offer.nexus,
|
|
turn:offer.turn,accept,expectedRevision:game.coalitionState!.revision});
|
|
}
|
|
function stillBinding(game:Game) {
|
|
assert.equal(game.alliancePromises!.find(row=>row.player==='a')!.released ?? false,false);
|
|
assert.equal(game.bribePromises!.find(row=>row.player==='a')!.released ?? false,false);
|
|
}
|
|
|
|
void test('a pending group invitation preserves an owned leave and joint bribe through unrelated actions',()=>{
|
|
const {game:initial,cards}=fixture();
|
|
let game=ask(initial,cards[0],{kind:'alliance',target:'a',turn:initial.turn,partner:'b'},'no');
|
|
game=ask(game,cards[1],{kind:'bribe',target:'a',turn:game.turn,recipient:'e',minimum:2},'yes');
|
|
// An empty ledger invokes the joint accepted-order reconciler.
|
|
game.turnActionPromises=[];
|
|
assert.equal(viewGame(game,'a').alliancePromiseGuide?.action.type,'coalitionLeave');
|
|
const group=coalitionOf(game.players,game.coalitionState,'a'); assert.ok(group);
|
|
game=act(game,'c',{type:'coalitionPropose',groupId:group.id,entrant:'d',
|
|
expectedRevision:game.coalitionState!.revision});
|
|
assert.ok(game.coalitionState!.proposal);
|
|
stillBinding(game);
|
|
game=answer(game,'d',true);
|
|
stillBinding(game);
|
|
game=answer(game,'a',false);
|
|
assert.equal(game.coalitionState!.proposal,null);
|
|
stillBinding(game);
|
|
game=act(game,'d',{type:'bribe',target:'f',amount:1});
|
|
stillBinding(game);
|
|
const leave=viewGame(game,'a').alliancePromiseGuide; assert.ok(leave);
|
|
assert.equal(leave.action.type,'coalitionLeave');
|
|
game=act(game,'a',leave.action);
|
|
assert.equal(coalitionOf(game.players,game.coalitionState,'a'),null);
|
|
assert.deepEqual(coalitionOf(game.players,game.coalitionState,'b')!.members,['b','c']);
|
|
stillBinding(game);
|
|
const payment=viewGame(game,'a').bribePromiseGuide; assert.ok(payment);
|
|
assert.equal(payment.action.type,'bribe'); assert.equal(payment.action.target,'e');
|
|
game=act(game,'a',payment.action);
|
|
assert.equal(seat(game,'e').bribes,2);
|
|
assert.ok(game.bribePromises!.find(row=>row.player==='a')!.fulfilled);
|
|
assert.equal(game.alliancePromises!.find(row=>row.player==='a')!.released ?? false,false);
|
|
});
|