120 lines
6.7 KiB
TypeScript
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);
|
|
});
|