Files
Dune/tests/sapho-truth-richese-runtime.test.ts

105 lines
6.4 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { viewGame, type Game } from '../game/engine';
import { createSaphoTruthRicheseFixture as fixture, assertRicheseParent, act, inventory, player } from './fixture-sapho-truth-richese';
import { truthOrderFrame as frame, passTruthOrder as pass, playSaphoTruthOrder as play,
finishTruthOrder as finish, finishOriginalTruthQuestions as answer, passFreshDiscard,
rejectSaphoTruthOrder as reject, ownOrderChoices as choices } from './fixture-sapho-truth-order';
function assertSealedPrivacy(game: Game, bidder: string, observer: string) {
const own = viewGame(game,bidder).richeseAuction!, other = viewGame(game,observer).richeseAuction!;
assert.equal(own.ownBid,1); assert.equal(other.ownBid,null);
assert.equal(other.revealedBids,null);
assert.equal('sealed' in other,false);
}
function continueBidding(game: Game, method: 'onceAround'|'silent', lot: string) {
const actor = method === 'onceAround' ? game.richeseAuction!.active! :
game.richeseAuction!.order.find(id => !Object.hasOwn(game.richeseAuction!.sealed,id))!;
assert.ok(actor);
return act(game,actor,{type:'richeseBid',event:lot,amount:method === 'silent' ? 0 : null,allyPayment:0});
}
for (const advanced of [false,true]) for (const method of ['onceAround','silent'] as const) {
void test(`${advanced ? 'Advanced' : 'Basic'} ${method} Sapho preserves the funded native cache lot while ordering Truth`, () => {
const f = fixture({advanced,method,standingBid:true}), state = f.game, current = frame(state);
assertRicheseParent(state,f.parent);
if (method === 'silent') assertSealedPrivacy(state,f.semutaHolder,f.owner);
assert.ok(choices(state,f.owner).some(row => row.card === f.card && row.mode === f.mode));
const noOp = f.mode === 'first' ? 'last' : 'first';
reject(state,f.owner,{type:'saphoTruthOrder',event:current.event,revision:current.revision,card:f.card,mode:noOp});
reject(state,f.owner,{type:'saphoTruthOrder',event:'stale',revision:current.revision,card:f.card,mode:f.mode});
reject(state,f.owner,{type:'saphoTruthOrder',event:current.event,revision:current.revision + 1,card:f.card,mode:f.mode});
let game = play(state,f.owner,f.card,f.mode);
assertRicheseParent(game,f.parent);
if (method === 'silent') assertSealedPrivacy(game,f.semutaHolder,f.owner);
game = passFreshDiscard(game);
assertRicheseParent(game,f.parent);
reject(game,f.owner,{type:'saphoTruthOrder',event:current.event,revision:current.revision,card:f.card,mode:f.mode});
game = finish(game);
assert.equal(f.mode === 'first' ? game.truthtrance!.queue[0].player : game.truthtrance!.queue.at(-1)!.player,f.owner);
assertRicheseParent(game,f.parent);
game = answer(game);
assert.equal(game.truthtrance,null); assertRicheseParent(game,f.parent);
if (method === 'silent') assertSealedPrivacy(game,f.semutaHolder,f.owner);
assert.deepEqual(inventory(game),f.originalCards);
game = continueBidding(game,method,f.lot);
assert.equal(game.richeseAuction?.event,f.lot);
assert.deepEqual(inventory(game),f.originalCards);
});
}
for (const method of ['onceAround','silent'] as const) for (const claim of [false,true]) {
void test(`Basic ${method} Sapho Semuta ${claim ? 'claim' : 'pass'} restores the original funded cache lot`, () => {
const f = fixture({method,standingBid:true,semuta:true});
assert.ok(f.semuta); assertRicheseParent(f.game,f.parent);
let game = pass(f.game,f.other);
game = play(game,f.owner,f.card,f.mode);
const pending = game.pendingTreacheryDiscard; assert.ok(pending?.reaction);
assert.equal(pending.continuation.kind,'saphoTruthOrderDiscard');
assert.equal(pending.batch.entries[0].card.id,f.card);
assert.equal(viewGame(game,f.semutaHolder).semutaReaction?.canCommit,true);
assertRicheseParent(game,f.parent);
if (method === 'silent') assertSealedPrivacy(game,f.semutaHolder,f.owner);
if (claim) game = act(game,f.semutaHolder,{type:'semutaCommit',event:pending.batch.event});
else game = passFreshDiscard(game);
assert.equal(game.pendingTreacheryDiscard,null); assert.equal(game.truthtrance?.stage,'ask');
assertRicheseParent(game,f.parent);
assert.equal(player(game,f.semutaHolder).hand.some(card => card.id === f.card),claim);
assert.equal(game.discard.some(card => card.id === f.semuta),claim);
assert.deepEqual(inventory(game),f.originalCards);
game = answer(game); assert.equal(game.truthtrance,null); assertRicheseParent(game,f.parent);
game = continueBidding(game,method,f.lot);
assert.equal(game.richeseAuction?.event,f.lot); assert.deepEqual(inventory(game),f.originalCards);
});
}
for (const method of ['onceAround','silent'] as const) {
void test(`Basic ${method} definite Truth cost offers a fresh Semuta reaction after Sapho was passed`, () => {
const f = fixture({method,standingBid:true,semuta:true});
let game = pass(f.game,f.other);
game = play(game,f.owner,f.card,f.mode);
assert.equal(game.pendingTreacheryDiscard?.continuation.kind,'saphoTruthOrderDiscard');
game = passFreshDiscard(game);
assert.equal(game.truthtrance?.stage,'ask'); assertRicheseParent(game,f.parent);
const asker = game.truthtrance!.queue[0].player;
const target = game.players.find(row => row.id !== asker)!.id;
const consumed = game.truthtrance!.queue[0].card;
game = act(game,asker,{type:'truthAsk',question:{kind:'fact',target,fact:{kind:'spice',compare:'gte',value:0}}});
game = act(game,target,{type:'truthAnswer',answer:'yes'});
const pending = game.pendingTreacheryDiscard; assert.ok(pending?.reaction);
assert.equal(pending.continuation.kind,'truthtranceDiscard');
assert.equal(pending.batch.entries[0].card.id,consumed);
assert.equal(viewGame(game,f.semutaHolder).semutaReaction?.canCommit,true);
assertRicheseParent(game,f.parent);
if (method === 'silent') assertSealedPrivacy(game,f.semutaHolder,f.owner);
game = act(game,f.semutaHolder,{type:'semutaCommit',event:pending.batch.event});
assert.equal(game.pendingTreacheryDiscard,null);
assert.ok(player(game,f.semutaHolder).hand.some(card => card.id === consumed));
assertRicheseParent(game,f.parent);
game = answer(game); assert.equal(game.truthtrance,null); assertRicheseParent(game,f.parent);
assert.deepEqual(inventory(game),f.originalCards);
game = continueBidding(game,method,f.lot);
assert.equal(game.richeseAuction?.event,f.lot); assert.deepEqual(inventory(game),f.originalCards);
});
}