Files
Dune/tests/distrans-started-richese-auction.test.ts
T
Quintine f8078ed5e2
Verify and publish checkpoint container / container (push) Successful in 11m24s
Resume Richese auctions after Distrans Semuta reaction
2026-10-29 10:38:59 +11:00

120 lines
6.7 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { handLimit, viewGame, type Action } from '../game/engine';
import { SEMUTA_DRUG_ID } from '../game/semuta-drug';
import { actDistransPrebid, advanceDistransPrebid, distransPrebidClean,
distransPrebidInventory, distransPrebidPlayer as player } from './fixture-distrans-prebid';
import { createDistransRichesePrebidFixture } from './fixture-distrans-richese-prebid';
import { moveSemutaEmperorCard } from './fixture-semuta-emperor-share';
for (const method of ['onceAround', 'silent'] as const) {
for (const claim of [false, true]) {
void test(`Basic ${method} started auction resumes its original lot after Semuta ${claim ? 'claims' : 'passes'} Distrans`, () => {
const f = createDistransRichesePrebidFixture({ method });
// The game and bidding phase are reached through native actions. Stage
// only the conserved physical Semuta card into another player's hand
// before the real cache declaration; this is not an earned purchase.
let game = structuredClone(f.beforeDeclaration);
const semutaHolder = 'e';
moveSemutaEmperorCard(game, SEMUTA_DRUG_ID, semutaHolder);
assert.ok(player(game, semutaHolder).hand.length <= handLimit(player(game, semutaHolder)));
const offer = game.richeseCache![0]; assert.ok(offer);
const declaration = { ...f.declaration, card: offer.id } as Action;
game = actDistransPrebid(game, game.decision!.player, declaration);
const lot = game.richeseAuction!;
const bidder = method === 'silent'
? lot.order.find(id => id !== f.holder && player(game, id).spice >= 1)!
: lot.active!;
assert.ok(bidder && player(game, bidder).spice >= 1);
game = actDistransPrebid(game, bidder, {
type: 'richeseBid', event: lot.event, amount: 1, allyPayment: 0,
});
assert.ok(game.richeseAuction && !game.richeseAuction.outcome);
assert.ok(game.richeseAuction.acted.includes(bidder));
const recipient = game.players.find(p => p.id !== f.holder && p.id !== bidder &&
p.hand.length < handLimit(p));
assert.ok(recipient);
const transfer = { ...f.action, target: recipient.id } as Action;
const quote = viewGame(game, f.holder).distrans!;
assert.equal(quote.blocked, null);
assert.ok(quote.choices.find(c => c.recipient === recipient.id)!.cards.some(c => c.id === f.give));
const originalLot = structuredClone(game.richeseAuction);
const originalFunding = structuredClone(game.richeseFunding);
const originalInventory = distransPrebidInventory(game);
const originalHand = structuredClone(player(game, recipient.id).hand);
const transferred = player(game, f.holder).hand.find(c => c.id === f.give)!;
game = actDistransPrebid(game, f.holder, transfer);
const pending = game.pendingTreacheryDiscard;
assert.ok(pending?.reaction, 'The public Distrans discard must offer a Semuta reaction');
assert.equal(pending.batch.entries[0].card.id, f.card);
const event = pending.batch.event;
if (method === 'silent' && claim) {
const altered = structuredClone(game);
altered.richeseAuction!.sealed[bidder] = 2;
assert.throws(() => actDistransPrebid(altered, semutaHolder, { type: 'semutaCommit', event }),
/completed private Distrans transfer|Semuta/);
}
assert.equal(viewGame(game, semutaHolder).semutaReaction!.canCommit, true);
if (claim) {
game = actDistransPrebid(game, semutaHolder, { type: 'semutaCommit', event });
} else {
for (const seat of game.players)
game = actDistransPrebid(game, seat.id, { type: 'semutaPass', event });
}
assert.equal(game.pendingTreacheryDiscard, null);
assert.deepEqual(game.richeseAuction, originalLot);
assert.deepEqual(game.richeseFunding, originalFunding);
assert.deepEqual(player(game, recipient.id).hand,
recipient.id === semutaHolder && claim
? [...originalHand.filter(c => c.id !== SEMUTA_DRUG_ID), transferred,
pending.batch.entries[0].card]
: [...originalHand, transferred]);
assert.equal(player(game, semutaHolder).hand.some(c => c.id === f.card), claim);
assert.equal(game.discard.some(c => c.id === SEMUTA_DRUG_ID), claim);
assert.equal(game.discard.some(c => c.id === f.card), !claim);
assert.deepEqual(distransPrebidInventory(game), originalInventory);
if (method === 'silent') {
const observer = game.players.find(p => p.id !== bidder)!;
const hidden = viewGame(game, observer.id).richeseAuction!;
assert.equal(hidden.ownBid, null);
assert.equal(hidden.revealedBids, null);
assert.equal('sealed' in hidden, false);
assert.equal(viewGame(game, bidder).richeseAuction!.ownBid, 1);
}
game = advanceDistransPrebid(game, state => distransPrebidClean(state) &&
state.richeseAuction?.event !== lot.event);
assert.deepEqual(distransPrebidInventory(game), originalInventory);
});
}
}
void test('a recipient can use the received Semuta to claim Distrans during a started Silent lot', () => {
const f = createDistransRichesePrebidFixture({ method: 'silent' });
let game = structuredClone(f.beforeDeclaration);
// Stage the one physical Semuta card in the donor's hand before the real
// lot declaration; the recipient acquires it only through native Distrans.
moveSemutaEmperorCard(game, SEMUTA_DRUG_ID, f.holder);
assert.ok(player(game, f.holder).hand.length <= handLimit(player(game, f.holder)));
game = actDistransPrebid(game, game.decision!.player,
{ ...f.declaration, card: game.richeseCache![0].id } as Action);
const lot = game.richeseAuction!;
const bidder = lot.order.find(id => id !== f.holder && id !== f.recipient &&
player(game, id).spice >= 1)!;
assert.ok(bidder);
game = actDistransPrebid(game, bidder,
{ type: 'richeseBid', event: lot.event, amount: 1, allyPayment: 0 });
const originalLot = structuredClone(game.richeseAuction), funding = structuredClone(game.richeseFunding);
const inventory = distransPrebidInventory(game);
game = actDistransPrebid(game, f.holder, { ...f.action, give: SEMUTA_DRUG_ID } as Action);
const pending = game.pendingTreacheryDiscard!;
assert.ok(pending?.reaction);
assert.equal(viewGame(game, f.recipient).semutaReaction!.canCommit, true);
game = actDistransPrebid(game, f.recipient, { type: 'semutaCommit', event: pending.batch.event });
assert.equal(game.pendingTreacheryDiscard, null);
assert.ok(player(game, f.recipient).hand.some(c => c.id === f.card));
assert.ok(game.discard.some(c => c.id === SEMUTA_DRUG_ID));
assert.deepEqual(game.richeseAuction, originalLot);
assert.deepEqual(game.richeseFunding, funding);
assert.deepEqual(distransPrebidInventory(game), inventory);
});