74 lines
5.4 KiB
TypeScript
74 lines
5.4 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { applyAction, viewGame } from '../game/engine';
|
|
import { RECRUITS_CARD_ID } from '../game/recruits';
|
|
import {
|
|
completeSelectedPhaseRecruits, createSelectedPhaseRecruitsFixture,
|
|
selectedPhaseRecruitsInventory as inventory, selectedPhaseRecruitsPlayer as player,
|
|
} from './fixture-selected-phase-recruits';
|
|
|
|
for (const advanced of [false, true]) {
|
|
void test(`public all-six-native selected-family ${advanced ? 'Advanced Stronghold and Tech' : 'Basic Tech'} Recruits earns an extra free return and separate native/token income`, () => {
|
|
const f = createSelectedPhaseRecruitsFixture({ advanced });
|
|
assert.equal(f.initial.status, 'lobby'); assert.equal(f.setup.status, 'setup');
|
|
assert.deepEqual(f.setup.players.map(seat => seat.faction), ['ixians', 'tleilaxu', 'choam', 'richese', 'ecaz', 'moritani']);
|
|
assert.deepEqual(f.setup.expansions, ['ix', 'choam', 'ecaz']);
|
|
assert.ok(f.setup.ecazTreachery && f.setup.techTokens);
|
|
assert.equal(!!f.setup.strongholdCards, advanced);
|
|
assert.ok(f.actions.some(step => step.action.type === 'moduleOption' && step.action.module === 'ecazTreachery' && step.action.enabled === true));
|
|
assert.ok(!f.game.homeworlds && !f.game.leaderSkills && !f.game.nexusCards && !f.game.discoveryEnabled && !f.game.semutaPreview);
|
|
assert.equal(f.originalInventory.length, 60); assert.deepEqual(inventory(f.game), f.originalInventory);
|
|
assert.deepEqual(f.staging, []); assert.equal(f.sourceActionCount, f.sourceActions.length);
|
|
assert.equal(f.beforeAuctionOrder.phase, 1); assert.equal(f.unopenedAuctionOrder.card, RECRUITS_CARD_ID);
|
|
assert.equal(f.acquisition.before.auction?.index, 0);
|
|
assert.equal(f.acquisition.before.auction?.cards[0].id, RECRUITS_CARD_ID);
|
|
assert.equal(f.acquisition.bid.actor, f.owner); assert.equal(f.acquisition.bid.action.amount, 1);
|
|
assert.equal(player(f.acquisition.before, f.owner).spice - player(f.acquisition.after, f.owner).spice, 1);
|
|
assert.ok(player(f.acquisition.after, f.owner).hand.some(card => card.id === RECRUITS_CARD_ID));
|
|
assert.equal(player(f.shipment.after, f.owner).forces[f.casualtyLocation], 3);
|
|
assert.equal(player(f.shipment.before, f.owner).spice - player(f.shipment.after, f.owner).spice, 6);
|
|
assert.equal(f.storm.before.stormMovementSource?.kind, advanced ? 'card' : 'dials');
|
|
assert.equal(player(f.storm.after, f.owner).forces[f.casualtyLocation], undefined);
|
|
assert.equal(player(f.game, f.owner).tanks, 3); assert.equal(f.game.turn, 2); assert.equal(f.game.phase, 4);
|
|
const ordinary = viewGame(f.game, f.owner).revival;
|
|
assert.equal(ordinary.freeRemaining, 2); assert.equal(ordinary.limit, 3);
|
|
if (advanced) {
|
|
assert.equal(f.game.revivalOrder?.remaining[0], f.owner);
|
|
assert.equal(f.mentat.after.strongholdCards?.claimedTurn, 1);
|
|
assert.equal(f.mentat.after.strongholdCards?.owners.carthag, f.owner);
|
|
}
|
|
|
|
const before = structuredClone(f.game);
|
|
const result = completeSelectedPhaseRecruits(f);
|
|
assert.deepEqual(f.game, before, 'Completing the native programme retains its reusable pre-play source');
|
|
assert.deepEqual(result.play.steps[0], { actor: f.owner, action: f.playAction });
|
|
assert.equal(result.play.after.recruits?.turn, 2);
|
|
assert.equal(player(result.play.after, f.owner).hand.some(card => card.id === RECRUITS_CARD_ID), false);
|
|
assert.equal(result.play.after.discard.filter(card => card.id === RECRUITS_CARD_ID).length, 1);
|
|
const doubled = viewGame(result.play.after, f.owner).revival;
|
|
assert.equal(doubled.freeRemaining, 4); assert.equal(doubled.limit, 7);
|
|
assert.equal(player(result.play.after, f.owner).tanks, 3);
|
|
assert.equal(player(result.play.after, f.owner).spice, player(f.game, f.owner).spice);
|
|
assert.throws(() => applyAction(result.play.after, f.owner, f.playAction));
|
|
|
|
assert.deepEqual(result.revival.steps[0], { actor: f.owner, action: f.revivalAction });
|
|
const returned = player(result.revival.after, f.owner), prior = player(result.revival.before, f.owner);
|
|
assert.equal(returned.tanks, 0); assert.equal(returned.reserves - prior.reserves, 3);
|
|
assert.equal(returned.revived, 3); assert.equal(returned.freeForcesRevived, 3);
|
|
assert.equal(returned.spice, prior.spice, 'The third counter uses the additional free rate rather than a paid return');
|
|
assert.equal(player(result.revival.after, f.bankOwner).spice - player(result.revival.before, f.bankOwner).spice, 1,
|
|
'Native Tleilaxu free-revival income is one separate bank spice');
|
|
assert.equal(result.revival.after.techTokens?.axlotl.owner, f.bankOwner);
|
|
assert.equal(result.revival.after.techTokens?.axlotl.spice, 1);
|
|
assert.equal(result.revival.after.techTokens?.axlotl.triggeredTurn, 2);
|
|
assert.equal(result.revivalPayout.after.techTokens?.axlotl.spice, 0);
|
|
assert.equal(player(result.revivalPayout.after, f.bankOwner).spice - player(result.revivalPayout.before, f.bankOwner).spice, 1,
|
|
'Original Axlotl payout waits for the actual Revival phase boundary');
|
|
assert.equal(result.game.turn, 2); assert.equal(result.game.phase, 5);
|
|
assert.deepEqual(inventory(result.game), f.originalInventory);
|
|
assert.equal(result.game.discard.filter(card => card.id === RECRUITS_CARD_ID).length, 1);
|
|
for (const seat of result.game.players)
|
|
assert.equal(seat.reserves + seat.tanks + Object.values(seat.forces).reduce((sum, amount) => sum + amount, 0), 20);
|
|
});
|
|
}
|