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 send = (game:Game, actor:string, action:Action) => applyAction(game, actor, action); const seat = (game:Game, id:string) => game.players.find(player => player.id === id)!; /** Stage the Nexus, respondent's wallet and the two physical Truthtrance * cards. Alliance formation, both questions and all guided actions are real. */ function initial(): {game:Game; cards:string[]} { let game = createGame('GROUPBOTH', newPlayer('a','Atreides','atreides'), false); game.players.push(newPlayer('b','Emperor','emperor'), newPlayer('c','Fremen','fremen'), newPlayer('d','Harkonnen','harkonnen'), newPlayer('e','Guild','guild')); Object.assign(game,{status:'playing',phase:1,turn:2,nexus:true,ready:[], order:['a','b','c','d','e'],deck:baseDeck()}); game.coalitionState = emptyCoalitionState('unlimited'); seat(game,'a').spice = 8; game = send(game,'a',{type:'alliance',target:'b'}); game = send(game,'b',{type:'alliance',target:'a'}); const pair = coalitionOf(game.players,game.coalitionState,'a'); assert.ok(pair); game = send(game,'a',{type:'coalitionPropose',groupId:pair.id,entrant:'c', expectedRevision:game.coalitionState!.revision}); for (const actor of ['b','c']) { const offer = game.coalitionState!.proposal!; game = send(game,actor,{type:'coalitionAnswer',event:offer.event,nexus:offer.nexus, turn:offer.turn,accept:true,expectedRevision:game.coalitionState!.revision}); } 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,'e').hand.push(card); } return {game,cards}; } function ask(game:Game, card:string, question:unknown, answer:'yes'|'no'):Game { game = send(game,'e',{type:'card',card}); while(game.truthtrance?.stage === 'priority') { const next = game.players.find(player => !game.truthtrance!.passed.includes(player.id)); assert.ok(next); game = send(game,next.id,{type:'truthPass'}); } game = send(game,'e',{type:'truthAsk',question}); const view = viewGame(game,'a'); const answers = game.truthtrance?.question?.kind === 'bribe' ? view.truthBribeAnswers : view.truthAllianceAnswers; assert.ok(answers?.includes(answer), `The ${answer} answer must be legal with both commitments.`); return send(game,'a',{type:'truthAnswer',answer}); } void test('a group alliance No and positive bribe answer retain both legal guides',()=>{ const fixture = initial(); let game = ask(fixture.game,fixture.cards[0], {kind:'bribe',target:'a',turn:fixture.game.turn,recipient:'d',minimum:2},'yes'); game = ask(game,fixture.cards[1], {kind:'alliance',target:'a',turn:game.turn,partner:'b'},'no'); const bribe = game.bribePromises!.find(row=>row.player === 'a')!; const alliance = game.alliancePromises!.find(row=>row.player === 'a')!; assert.ok(!bribe.released && !bribe.fulfilled && !alliance.released && !alliance.fulfilled); const beforeReady = structuredClone(game); assert.throws(()=>send(game,'a',{type:'ready'})); assert.deepEqual(game,beforeReady); const guide = viewGame(game,'a').alliancePromiseGuide; assert.ok(guide); assert.equal(guide.action.type,'coalitionLeave'); game = send(game,'a',guide.action); assert.equal(coalitionOf(game.players,game.coalitionState,'a'),null); assert.deepEqual(coalitionOf(game.players,game.coalitionState,'b')!.members,['b','c']); assert.ok(!game.alliancePromises!.find(row=>row.event === alliance.event)!.released); assert.ok(!game.bribePromises!.find(row=>row.event === bribe.event)!.released); const payment = viewGame(game,'a').bribePromiseGuide; assert.ok(payment); assert.equal(payment.action.type,'bribe'); assert.equal(payment.action.target,'d'); game = send(game,'a',payment.action); assert.equal(game.bribePromises!.find(row=>row.event === bribe.event)!.fulfilled,true); assert.equal(seat(game,'d').bribes,2); game = send(game,'a',{type:'ready'}); assert.ok(game.ready.includes('a')); for(const card of fixture.cards) assert.equal(game.discard.filter(row=>row.id === card).length,1); });