124 lines
6.7 KiB
TypeScript
124 lines
6.7 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { applyAction, viewGame, type Action, type Game } from '../game/engine';
|
|
import { richeseCards } from '../game/richese-cards';
|
|
import { settleIxianReplacementFixture } from './fixture-nexus-ixian-replacement';
|
|
import {
|
|
createRicheseIxianReplacementFixture,
|
|
type RicheseIxianReplacementFixture,
|
|
} from './fixture-richese-ixian-replacement';
|
|
|
|
const owner = (game: Game, id: string) => game.players.find(player => player.id === id)!;
|
|
const reload = (game: Game): Game => JSON.parse(JSON.stringify(game));
|
|
function reject(game: Game, id: string, action: Action): void {
|
|
const before = structuredClone(game);
|
|
assert.throws(() => applyAction(game, id, action));
|
|
assert.deepEqual(game, before);
|
|
}
|
|
|
|
const BUILT: Record<string, RicheseIxianReplacementFixture> = {};
|
|
/** One genuine native Richese run per roster/mode; each test reloads its own copy. */
|
|
function fixtureFor(
|
|
advanced: boolean,
|
|
buyerFaction: 'atreides' | 'richese',
|
|
endingAuction = false,
|
|
): RicheseIxianReplacementFixture {
|
|
const key = `${advanced}:${buyerFaction}:${endingAuction}`;
|
|
const held = BUILT[key];
|
|
if (held) return held;
|
|
const created = createRicheseIxianReplacementFixture({ advanced, buyerFaction, endingAuction });
|
|
BUILT[key] = created;
|
|
return created;
|
|
}
|
|
|
|
for (const advanced of [false, true])
|
|
for (const buyerFaction of ['atreides', 'richese'] as const)
|
|
void test(`${advanced ? 'Advanced' : 'Basic'} native ${buyerFaction} normal CHOAM purchase spends the Nexus for the real top replacement`, () => {
|
|
const f = fixtureFor(advanced, buyerFaction);
|
|
assert.equal(f.purchased.id, 'ix-poison-tooth');
|
|
assert.equal(f.nextCard.id, 'ix-artillery');
|
|
assert.equal(f.rowSize, 2);
|
|
// The cache stayed its own original collection: only the one genuine Once
|
|
// Around keep left it, and the normal purchase itself never touched it.
|
|
assert.equal(f.keptCacheCards.length, 1);
|
|
assert.ok(f.setup.richeseCache!.some(card => card.id === f.keptCacheCards[0]));
|
|
assert.equal(f.cacheAtPurchase.length, richeseCards().length - 1);
|
|
assert.equal(f.beforePayment.currentAuctionSale, null);
|
|
assert.equal(f.beforePayment.auction!.cards[f.auctionIndex].id, f.purchased.id);
|
|
const offer = viewGame(f.game, f.buyer).nexusIxianReplacement!;
|
|
assert.equal(offer.canUse, true);
|
|
assert.equal(offer.canPass, true);
|
|
assert.deepEqual(offer.purchased, f.purchased);
|
|
assert.equal(owner(f.game, f.buyer).spice, f.beforeSpice - 2);
|
|
assert.equal(owner(f.game, f.emperor!).spice, f.beforeEmperorSpice);
|
|
const partners = f.beforeNexusDraw.players.filter(player => player.id !== f.buyer);
|
|
for (const partner of partners)
|
|
for (const type of ['nexusIxianReplacementUse', 'nexusIxianReplacementPass'] as const)
|
|
reject(f.game, partner.id, { type, event: offer.event });
|
|
|
|
const deckBefore = structuredClone(f.game.deck);
|
|
const unsold = structuredClone(f.game.auction!.cards.slice(f.auctionIndex + 1));
|
|
const use = settleIxianReplacementFixture(applyAction(reload(f.game), f.buyer,
|
|
{ type: 'nexusIxianReplacementUse', event: offer.event }));
|
|
assert.deepEqual(owner(use, f.buyer).hand, [...owner(f.beforePayment, f.buyer).hand, f.nextCard]);
|
|
assert.deepEqual(use.deck, deckBefore.slice(1));
|
|
assert.deepEqual(use.discard, [...f.game.discard, f.purchased]);
|
|
assert.equal(use.nexusCards!.cards!.hands[f.buyer], null);
|
|
assert.deepEqual(use.nexusCards!.cards!.discard, [...f.game.nexusCards!.cards!.discard, 'ixians']);
|
|
assert.equal(owner(use, f.buyer).spice, f.paidSpice);
|
|
assert.equal(owner(use, f.emperor!).spice, f.beforeEmperorSpice! + 2);
|
|
assert.equal(use.currentAuctionSale, null);
|
|
assert.equal(use.pendingNexusIxianReplacement, null);
|
|
assert.deepEqual(use.richeseCache, f.cacheAtPurchase);
|
|
assert.equal(use.richeseBidding!.stage, 'normal');
|
|
assert.equal(use.auction!.index, f.auctionIndex + 1);
|
|
assert.deepEqual(use.auction!.cards.slice(use.auction!.index), unsold);
|
|
reject(use, f.buyer, { type: 'nexusIxianReplacementUse', event: f.event });
|
|
});
|
|
|
|
for (const advanced of [false, true])
|
|
for (const buyerFaction of ['atreides', 'richese'] as const)
|
|
void test(`${advanced ? 'Advanced' : 'Basic'} native ${buyerFaction} normal CHOAM purchase retains its card and Nexus card`, () => {
|
|
const f = fixtureFor(advanced, buyerFaction);
|
|
const offer = viewGame(f.game, f.buyer).nexusIxianReplacement!;
|
|
const pass = settleIxianReplacementFixture(applyAction(reload(f.game), f.buyer,
|
|
{ type: 'nexusIxianReplacementPass', event: offer.event }));
|
|
assert.deepEqual(owner(pass, f.buyer).hand, owner(f.game, f.buyer).hand);
|
|
assert.ok(owner(pass, f.buyer).hand.some(card => card.id === f.purchased.id));
|
|
assert.deepEqual(pass.deck, f.game.deck);
|
|
assert.deepEqual(pass.discard, f.game.discard);
|
|
assert.deepEqual(pass.nexusCards!.cards, f.game.nexusCards!.cards);
|
|
assert.equal(pass.nexusCards!.cards!.hands[f.buyer], 'ixians');
|
|
assert.equal(owner(pass, f.buyer).spice, f.paidSpice);
|
|
assert.equal(owner(pass, f.emperor!).spice, f.beforeEmperorSpice! + 2);
|
|
assert.equal(pass.currentAuctionSale, null);
|
|
assert.equal(pass.pendingNexusIxianReplacement, null);
|
|
assert.deepEqual(pass.richeseCache, f.cacheAtPurchase);
|
|
assert.equal(pass.auction!.index, f.auctionIndex + 1);
|
|
});
|
|
|
|
for (const advanced of [false, true])
|
|
void test(`${advanced ? 'Advanced' : 'Basic'} native final-lot CHOAM purchase closes the pool into the untouched Richese cache offer`, () => {
|
|
const f = fixtureFor(advanced, 'atreides', true);
|
|
assert.equal(f.auctionIndex, f.rowSize - 1);
|
|
assert.equal(f.purchased.id, 'ix-poison-tooth');
|
|
assert.equal(f.nextCard.id, 'ix-artillery');
|
|
const offer = viewGame(f.game, f.buyer).nexusIxianReplacement!;
|
|
assert.deepEqual(offer.purchased, f.purchased);
|
|
const richese = f.game.players.find(player => player.faction === 'richese')!.id;
|
|
const use = settleIxianReplacementFixture(applyAction(reload(f.game), f.buyer,
|
|
{ type: 'nexusIxianReplacementUse', event: offer.event }));
|
|
assert.deepEqual(owner(use, f.buyer).hand, [...owner(f.beforePayment, f.buyer).hand, f.nextCard]);
|
|
assert.deepEqual(use.discard, [...f.game.discard, f.purchased]);
|
|
assert.equal(owner(use, f.buyer).spice, f.paidSpice);
|
|
assert.equal(owner(use, f.emperor!).spice, f.beforeEmperorSpice! + 2);
|
|
assert.equal(use.auction, null);
|
|
assert.deepEqual(use.decision, { kind: 'richeseCache', player: richese });
|
|
assert.equal(use.richeseBidding!.stage, 'cacheOffer');
|
|
assert.equal(use.richeseBidding!.cacheCanceled, false);
|
|
assert.deepEqual(use.richeseCache, f.cacheAtPurchase);
|
|
assert.equal(use.pendingNexusIxianReplacement, null);
|
|
assert.equal(use.nexusCards!.cards!.hands[f.buyer], null);
|
|
});
|
|
|