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)!; /** A quiet Basic Nexus, wallets and physical Truthtrances are staged; pair * formation, questions, unanimous admission, departure and bribe are actions. */ function fixture():{game:Game; cards:string[]} { let game=createGame('GROUPADMITLEAVE',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'); seat(game,'a').spice=8; game=act(game,'a',{type:'alliance',target:'b'}); game=act(game,'b',{type:'alliance',target:'a'}); 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, 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); } for (const proposer of ['a','b']) void test(`a ${proposer === 'a' ? 'proposer' : 'final voter'} can admit, leave and pay without releasing their answers`,()=>{ const {game:initial,cards}=fixture(); let game=ask(initial,cards[0],{kind:'alliance',target:'a',turn:initial.turn,partner:'c'},'no'); game=ask(game,cards[1],{kind:'bribe',target:'a',turn:game.turn,recipient:'e',minimum:2},'yes'); // Empty joint ledger invokes the whole accepted-order planner. game.turnActionPromises=[]; const pair=coalitionOf(game.players,game.coalitionState,'a'); assert.ok(pair); game=act(game,proposer,{type:'coalitionPropose',groupId:pair.id,entrant:'c', expectedRevision:game.coalitionState!.revision}); if (proposer==='a') game=answer(game,'b',true); game=answer(game,'c',true); if (proposer==='b') game=answer(game,'a',true); assert.deepEqual(coalitionOf(game.players,game.coalitionState,'a')!.members,['a','b','c']); 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); stillBinding(game); game=act(game,'a',{type:'ready'}); assert.ok(game.ready.includes('a')); });