105 lines
6.4 KiB
TypeScript
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);
|
|
});
|
|
}
|