79 lines
5.1 KiB
TypeScript
79 lines
5.1 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { viewGame } from '../game/engine';
|
|
import { createSaphoTruthAuctionFixture as fixture, assertAuctionParent, act, inventory, player } from './fixture-sapho-truth-auction';
|
|
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';
|
|
|
|
for (const advanced of [false,true]) for (const standingBid of [false,true]) for (const mode of ['first','last'] as const) {
|
|
const label = `${advanced ? 'Advanced' : 'Basic'} ${standingBid ? 'standing bid' : 'unbid'} ${mode}`;
|
|
void test(`${label} Sapho orders the original Truth questions without altering the ordinary auction`, () => {
|
|
const f = fixture({advanced,standingBid,mode}), state = f.game, current = frame(state);
|
|
assertAuctionParent(state,f.parent);
|
|
assert.ok(choices(state,f.owner).some(row => row.card === f.card && row.mode === mode));
|
|
const noOp = 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-auction-order',revision:current.revision,card:f.card,mode});
|
|
reject(state,f.owner,{type:'saphoTruthOrder',event:current.event,revision:current.revision+1,card:f.card,mode});
|
|
assertAuctionParent(state,f.parent);
|
|
let game = play(state,f.owner,f.card,mode);
|
|
assertAuctionParent(game,f.parent);
|
|
game = passFreshDiscard(game);
|
|
assertAuctionParent(game,f.parent);
|
|
assert.ok(frame(game).passed.includes(f.owner));
|
|
game = finish(game);
|
|
assert.equal(mode === 'first' ? game.truthtrance!.queue[0].player : game.truthtrance!.queue.at(-1)!.player,f.owner);
|
|
assertAuctionParent(game,f.parent);
|
|
game = answer(game);
|
|
assert.equal(game.truthtrance,null); assertAuctionParent(game,f.parent);
|
|
assert.deepEqual(inventory(game),f.originalCards);
|
|
// Continue the real original bidder: a normal pass cannot recreate the lot,
|
|
// discard its actual card, replay the standing bid or spend the invoice.
|
|
const actor = game.auction!.active, previous = structuredClone(game.auction!);
|
|
game = act(game,actor,{type:'passBid'});
|
|
assert.ok(game.auction); assert.equal(game.auction.index,previous.index);
|
|
assert.equal(game.auction.cards[previous.index].id,previous.cards[previous.index].id);
|
|
assert.equal(game.auction.bid,previous.bid); assert.equal(game.auction.bidder,previous.bidder);
|
|
assert.ok(game.auction.passed.includes(actor));
|
|
assert.deepEqual(inventory(game),f.originalCards);
|
|
});
|
|
}
|
|
|
|
for (const claim of [false,true]) void test(
|
|
`Basic ordinary standing bid survives a Sapho Truth-order Semuta ${claim ? 'claim' : 'pass'}`, () => {
|
|
const f = fixture({advanced:false,standingBid:true,mode:'first'}), state = structuredClone(f.game);
|
|
// Controlled conserved physical supplement: swap the actual cached Semuta
|
|
// with an existing Atreides hand card. The public game, funded standing
|
|
// invoice, original lot and previously queued Truths are not manufactured.
|
|
const claimant = player(state,'atreides'), cache = state.richeseCache;
|
|
assert.ok(cache); const semutaAt = cache.findIndex(card => card.effect === 'semutaDrug'); assert.ok(semutaAt >= 0);
|
|
assert.ok(claimant.hand.length > 0);
|
|
const [semuta] = cache.splice(semutaAt,1), [exchange] = claimant.hand.splice(0,1);
|
|
claimant.hand.push(semuta); cache.push(exchange);
|
|
assertAuctionParent(state,f.parent); assert.deepEqual(inventory(state),f.originalCards);
|
|
// The other holder passes first, so the Sapho use finishes the public
|
|
// ordering but its real fresh-discard Semuta window must settle first.
|
|
let game = pass(state,f.other);
|
|
assertAuctionParent(game,f.parent);
|
|
game = play(game,f.owner,f.card,'first');
|
|
assert.ok(game.pendingTreacheryDiscard?.reaction);
|
|
assert.equal(game.pendingTreacheryDiscard.batch.entries[0].card.id,f.card);
|
|
assert.equal(viewGame(game,'atreides').semutaReaction?.canCommit,true);
|
|
assertAuctionParent(game,f.parent);
|
|
const event = game.pendingTreacheryDiscard.batch.event;
|
|
if (claim) game = act(game,'atreides',{type:'semutaCommit',event});
|
|
else game = passFreshDiscard(game);
|
|
assert.equal(game.pendingTreacheryDiscard,null); assert.equal(game.truthtrance?.stage,'ask');
|
|
assert.equal(game.truthtrance?.queue[0].player,f.owner); assertAuctionParent(game,f.parent);
|
|
assert.equal(player(game,'atreides').hand.some(card => card.id === f.card),claim);
|
|
assert.equal(game.discard.some(card => card.id === semuta.id),claim);
|
|
game = answer(game); assert.equal(game.truthtrance,null); assertAuctionParent(game,f.parent);
|
|
assert.deepEqual(inventory(game),f.originalCards);
|
|
const actor = game.auction!.active;
|
|
game = act(game,actor,{type:'passBid'});
|
|
assert.equal(game.auction?.bid,1); assert.equal(game.auction?.bidder,f.parent.auction!.bidder);
|
|
assert.deepEqual(inventory(game),f.originalCards);
|
|
},
|
|
);
|