101 lines
7.5 KiB
TypeScript
101 lines
7.5 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { viewGame } from '../game/engine';
|
|
import { quoteSpiceCollection } from '../game/board-resolution-quote';
|
|
import { TECH_TOKENS } from '../game/tech-tokens';
|
|
import {
|
|
completeWorldSandtrout, createWorldSandtroutFixture, worldSandtroutDeferred as deferred,
|
|
worldSandtroutInventory as inventory, worldSandtroutPlayer as player,
|
|
worldSandtroutSkills as skills, worldSandtroutSpice as spice, worldSandtroutStocks as stocks,
|
|
} from './fixture-world-sandtrout';
|
|
|
|
for (const advanced of [false, true]) {
|
|
void test(`native ${advanced ? 'Advanced Worlds Skills Tech Strongholds' : 'Basic Worlds Tech'} Sandtrout waits through real phases and suppresses only the later worm`, () => {
|
|
const f = createWorldSandtroutFixture({ advanced });
|
|
assert.equal(f.initial.status, 'lobby'); assert.equal(f.setup.status, 'setup');
|
|
assert.deepEqual(f.game.players.map(seat => seat.faction), ['atreides', 'emperor', 'ixians']);
|
|
assert.deepEqual(f.game.expansions, ['ix']); assert.ok(f.game.homeworlds && f.game.techTokens);
|
|
assert.ok(f.game.homeworldOccupationPreview && f.game.homeworldOccupationHistory);
|
|
assert.equal(!!f.game.leaderSkills, advanced); assert.equal(!!f.game.strongholdCards, advanced);
|
|
assert.ok(!f.game.nexusCards && !f.game.discoveryEnabled && !f.game.semutaPreview);
|
|
assert.equal(f.originalInventory.length, 47); assert.equal(f.originalSkills.length, advanced ? 14 : 0);
|
|
assert.equal(f.originalSpice.length, 22); assert.deepEqual(f.staging, []);
|
|
assert.equal(f.sourceActionCount, f.sourceActions.length);
|
|
assert.equal(f.beforeDraw.sandtrout, false); assert.equal(f.game.sandtrout, true);
|
|
assert.equal(viewGame(f.game, f.actor).sandtrout, true);
|
|
assert.deepEqual(stocks(f.game), stocks(f.beforeDraw));
|
|
assert.deepEqual(f.game.players.map(seat => seat.spice), f.beforeDraw.players.map(seat => seat.spice));
|
|
assert.deepEqual(inventory(f.game), f.originalInventory); assert.deepEqual(spice(f.game), f.originalSpice);
|
|
if (advanced) for (const [owner, skill] of Object.entries(f.training!)) {
|
|
const assignment = f.afterSetup.leaderSkills!.assignments.find(row => row.owner === owner); assert.ok(assignment);
|
|
assert.equal(assignment.skill, skill);
|
|
assert.ok(f.sourceActions.some(step => step.actor === owner && step.action.type === 'leaderSkill' &&
|
|
step.action.skill === skill && step.action.leader === assignment.leader));
|
|
}
|
|
const before = structuredClone(f.game), result = completeWorldSandtrout(f);
|
|
assert.deepEqual(f.game, before);
|
|
assert.equal(result.firstSpice.after.sandtrout, true); assert.equal(result.firstSpice.after.phase, 2);
|
|
assert.deepEqual(result.shipmentSources, { 'homeworld:atreides': { normal: 2, elite: 0 } });
|
|
assert.equal(player(result.shipment.after, f.actor).reserves, player(result.shipment.before, f.actor).reserves - 2);
|
|
assert.equal(player(result.shipment.after, f.actor).forces[f.targetLocation], 2);
|
|
assert.equal(player(result.shipment.before, f.actor).spice - player(result.shipment.after, f.actor).spice, 4);
|
|
const ordinary = quoteSpiceCollection(result.collection.before).receipts;
|
|
for (const seat of result.collection.before.players) {
|
|
const receipt = ordinary.find(row => row.player === seat.id);
|
|
assert.equal(player(result.collection.after, seat.id).spice - seat.spice,
|
|
(receipt ? receipt.strongholds + receipt.collected : 0) + (seat.id === result.techOwner ? result.techAmount : 0));
|
|
}
|
|
assert.equal(result.collection.after.sandtrout, true);
|
|
assert.ok(result.techAmount > 0);
|
|
assert.equal(result.collection.after.techTokens!.heighliners.spice, 0);
|
|
assert.equal(result.mentat.after.turn, 2); assert.equal(result.mentat.after.phase, 0);
|
|
assert.equal(result.mentat.after.sandtrout, true); assert.equal(result.storm.after.sandtrout, true);
|
|
assert.equal(player(result.mentat.after, 'emperor').spice - player(result.mentat.before, 'emperor').spice, advanced ? 1 : 0);
|
|
if (advanced) {
|
|
assert.deepEqual(result.bankerSource!.source.bankLegs, [{ payer: f.actor, amount: 4 }]);
|
|
assert.equal(result.bankerSource!.grant!.owner, f.banker);
|
|
assert.equal(deferred(result.collection.after), 1);
|
|
assert.deepEqual(result.bankerCollection!.credits, [{ owner: f.banker, amount: 1 }]);
|
|
assert.equal(result.mentat.after.strongholdCards!.claimedTurn, 1);
|
|
assert.equal(result.mentat.after.strongholdCards!.owners.arrakeen, f.actor);
|
|
assert.equal(result.mentat.after.strongholdCards!.owners.hidden_mobile_stronghold, 'ixians');
|
|
} else assert.equal(result.bankerSource, null);
|
|
assert.equal(deferred(result.mentat.after), 0);
|
|
const land = result.secondSpiceOrder[1].card; assert.ok('territory' in land);
|
|
assert.equal(result.revealed.spiceWindow!.amount, land.amount * 2);
|
|
assert.equal(result.revealed.spice[`${land.territory}:${land.sector}`] -
|
|
(result.beforeSuppression.spice[`${land.territory}:${land.sector}`] ?? 0), land.amount * 2);
|
|
assert.equal(result.revealed.sandtrout, false); assert.equal(result.revealed.nexus, false);
|
|
assert.deepEqual(stocks(result.revealed), stocks(result.beforeSuppression), 'The suppressed worm destroys no physical force');
|
|
assert.equal(result.revealed.spice[f.targetLocation], result.beforeSuppression.spice[f.targetLocation],
|
|
'The suppressed worm leaves its actual previous pile intact');
|
|
assert.deepEqual(result.revealed.players.map(seat => seat.spice), result.beforeSuppression.players.map(seat => seat.spice));
|
|
assert.equal(result.revealed.decision, null); assert.deepEqual(result.revealed.wormRides, []);
|
|
assert.equal(result.revealed.spiceDiscard.flat().filter(card => 'worm' in card && card.suppressed).length, 1);
|
|
assert.equal(result.revealed.spiceDiscard.flat().filter(card => 'sandtrout' in card).length, 1);
|
|
if (advanced) {
|
|
const next = result.secondSpiceOrder[2].card; assert.ok('territory' in next);
|
|
assert.equal(result.secondWindow!.spiceWindow!.territory, next.territory);
|
|
assert.equal(result.secondWindow!.spiceWindow!.amount, next.amount, 'Doubling is limited to the immediate replacement');
|
|
}
|
|
assert.equal(result.game.phase, 2); assert.equal(result.game.turn, 2); assert.equal(result.game.sandtrout, false);
|
|
for (const { id } of TECH_TOKENS) assert.equal(result.game.techTokens![id].owner, result.beforeSuppression.techTokens![id].owner);
|
|
assert.deepEqual(inventory(result.game), f.originalInventory); assert.deepEqual(skills(result.game), f.originalSkills);
|
|
assert.deepEqual(spice(result.game), f.originalSpice);
|
|
});
|
|
}
|
|
|
|
void test('native Basic World Sandtrout keeps the original ignored first-turn worm and waits for its later encounter', () => {
|
|
const f = createWorldSandtroutFixture({ firstTurnWorm: true });
|
|
assert.equal(f.game.turn, 1); assert.equal(f.game.sandtrout, true); assert.equal(f.game.nexus, false);
|
|
const land = f.spiceOrder[2].card; assert.ok('territory' in land);
|
|
assert.equal(f.game.spiceWindow!.amount, land.amount);
|
|
assert.equal(f.game.spiceSequence!.skipped.filter(card => 'worm' in card).length, 1);
|
|
assert.ok(f.game.log.some(entry => /first-turn worm rule leaves Sandtrout waiting/.test(entry.text)));
|
|
assert.deepEqual(spice(f.game), f.originalSpice);
|
|
const result = completeWorldSandtrout(f);
|
|
assert.equal(result.beforeSuppression.sandtrout, true); assert.equal(result.beforeSuppression.turn, 2);
|
|
assert.equal(result.revealed.nexus, false); assert.equal(result.revealed.sandtrout, false);
|
|
assert.equal(result.game.phase, 2); assert.deepEqual(spice(result.game), f.originalSpice);
|
|
});
|