import assert from 'node:assert/strict'; import test from 'node:test'; import { type Action, type Game } from '../game/engine'; import { createTruthtranceRichesePromisesFixture, advanceTruthtranceRichesePromises as advance, actTruthtranceRichesePromises as act, truthtranceRichesePromisesInventory as inventory } from './fixture-truthtrance-richese-promises'; const seat = (game: Game, id: string) => { const player = game.players.find(row => row.id === id); assert.ok(player); return player; }; function choose(game: Game, fields: Record): Game { assert.ok(game.decision && game.richeseBidding); return act(game, game.decision.player, { type: 'decision', event: game.richeseBidding.event, ...fields } as Action); } function responses(game: Game): Game { for (let count = 0; game.response && count < 20; count++) { const player = game.players.find(row => !game.response!.passed.includes(row.id)); assert.ok(player); game = act(game, player.id, { type: 'passResponse' }); } assert.equal(game.response, null); return game; } /** Native Advanced public Richese/Atreides/Emperor setup and original card * chronology. The pair is explicitly staged at Charity; wallets are staged only * once the native silent lot is open. No lot, payment or held card is created. */ function lot(source: 'cache' | 'blackMarket', buyer: 'atreides' | 'richese') { const f = createTruthtranceRichesePromisesFixture({ advanced: true }); let game = structuredClone(f.game); seat(game, buyer).ally = 'emperor'; seat(game, 'emperor').ally = buyer; seat(game, buyer).allySinceTurn = game.turn; seat(game, 'emperor').allySinceTurn = game.turn; game = advance(game, state => state.decision?.kind === 'richeseBlackMarket'); if (source === 'blackMarket') { const card = seat(game, 'richese').hand[0]; assert.ok(card); game = choose(game, { card: card.id, method: 'silent' }); } else { game = choose(game, { decline: true }); game = advance(game, state => state.decision?.kind === 'richeseDeclaration'); game = choose(game, { position: 'first' }); game = responses(game); game = advance(game, state => state.decision?.kind === 'richeseCache'); const card = game.richeseCache?.[0]; assert.ok(card); game = choose(game, { card: card.id, method: 'silent' }); } game = responses(game); assert.equal(game.richeseAuction?.source, source); assert.equal(game.richeseAuction?.method, 'silent'); seat(game, buyer).spice = 10; seat(game, 'emperor').spice = 10; if (buyer !== 'richese') seat(game, 'richese').spice = 10; assert.deepEqual(inventory(game), f.originalCards); return { game, buyer, card: game.richeseAuction!.cardId, originalCards: f.originalCards }; } function purchase(f: ReturnType): Game { let game = act(f.game, 'emperor', { type: 'pledgeAid', amount: 2 }); const event = game.richeseAuction!.event, order = [...game.richeseAuction!.order]; for (const id of order) game = act(game, id, { type: 'richeseBid', event, amount: id === f.buyer ? 6 : 0, allyPayment: id === f.buyer ? 2 : 0 }); return responses(game); } for (const source of ['cache', 'blackMarket'] as const) { void test(`Advanced ${source} sends an Emperor ally's contribution to bank, not Richese`, () => { const f = lot(source, 'atreides'); const game = purchase(f); assert.equal(seat(game, 'atreides').spice, 6); assert.equal(seat(game, 'emperor').spice, 8); assert.equal(seat(game, 'richese').spice, 14, 'Seller receives only the buyer-funded four spice.'); assert.equal(seat(game, 'atreides').hand.filter(card => card.id === f.card).length, 1); assert.deepEqual(inventory(game), f.originalCards); }); void test(`Advanced ${source} paid Richese self-purchase sends only its own share to Emperor`, () => { const f = lot(source, 'richese'); const game = purchase(f); assert.equal(seat(game, 'richese').spice, 6); assert.equal(seat(game, 'emperor').spice, 12, 'Emperor gets four from Richese; its own two goes to bank.'); assert.equal(seat(game, 'richese').hand.filter(card => card.id === f.card).length, 1); assert.deepEqual(inventory(game), f.originalCards); }); }