329 lines
24 KiB
TypeScript
329 lines
24 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { applyAction, createGame, joinGame, newPlayer, viewGame, type Action, type Game } from '../game/engine';
|
|
import { baseDeck } from '../game/cards';
|
|
import { LEADER_SKILL_CARDS } from '../game/leader-skill-cards';
|
|
import { validateLeaderSkills } from '../game/leader-skills';
|
|
import { validateDiscoveryState } from '../game/discoveries';
|
|
import { validateNexusCards } from '../game/nexus-cards';
|
|
import { forceRevivalRemaining } from '../game/revival';
|
|
import { ownedTech } from '../game/tech-tokens';
|
|
import { DIFFICULTIES } from '../game/bot-profiles';
|
|
import { botActions } from '../game/bots';
|
|
import { quoteClassicSkillsStrongholdBattle } from './fixture-classic-skills-stronghold';
|
|
import {
|
|
advanceDiscoveryClassicNexusSkills, closeDiscoveryClassicNexusSkills,
|
|
createDiscoveryClassicNexusSkillsFixture, discoveryClassicNexusSkillsClean,
|
|
discoveryClassicNexusSkillsPlayer as player, enterDiscoveryClassicNexusSkills,
|
|
finishDiscoveryClassicNexusSkillsBattle, initializeDiscoveryClassicNexusSkillsSetup,
|
|
openDiscoveryClassicNexusSkillsBattle, revealDiscoveryClassicNexusSkillsBattle,
|
|
stepDiscoveryClassicNexusSkills,
|
|
} from './fixture-discovery-classic-nexus-skills';
|
|
import {
|
|
finishClassicDiscoveryNexusMentat, formClassicDiscoveryNexusAlliance, openClassicDiscoveryNexusAlliance,
|
|
} from './fixture-discovery-classic-nexus';
|
|
|
|
function inventory(game: Game): void {
|
|
validateLeaderSkills(game.leaderSkills!, game.players);
|
|
validateDiscoveryState(game.discoveries!);
|
|
validateNexusCards(game.nexusCards!.cards!, game.players);
|
|
assert.deepEqual([...game.leaderSkills!.deck, ...Object.values(game.leaderSkills!.offers).flatMap(offer => offer.cards),
|
|
...game.leaderSkills!.assignments.map(assignment => assignment.skill)].sort(), LEADER_SKILL_CARDS.map(card => card.id).sort());
|
|
assert.deepEqual([...game.deck, ...game.discard, ...game.players.flatMap(seat => seat.hand)].map(card => card.id).sort(),
|
|
baseDeck().map(card => card.id).sort());
|
|
for (const seat of game.players) {
|
|
assert.equal(seat.reserves + seat.tanks + Object.values(seat.forces).reduce((sum, amount) => sum + amount, 0), 20);
|
|
if (seat.elites) assert.equal(seat.elites.reserves + seat.elites.tanks + Object.values(seat.elites.forces).reduce((sum, amount) => sum + amount, 0),
|
|
seat.faction === 'emperor' ? 5 : 3);
|
|
}
|
|
assert.ok(!(game.discoveryEntry && game.nexusCards!.phase?.stage === 'drawing'));
|
|
assert.ok(!(game.greatMaker?.stage === 'vote' && game.nexusCards!.phase?.stage === 'drawing'));
|
|
}
|
|
function reject(game: Game, actor: string, action: Action): void {
|
|
const before = structuredClone(game);
|
|
assert.throws(() => applyAction(game, actor, action)); assert.deepEqual(game, before);
|
|
}
|
|
|
|
void test('original classic all14/all12/Discovery setup trains before traitors and reaches physical next-turn entry at Basic/Advanced two through six seats', () => {
|
|
for (const advanced of [false, true]) for (const seats of [2, 3, 4, 5, 6] as const) {
|
|
const fixture = createDiscoveryClassicNexusSkillsFixture({ advanced, seats, tech: seats >= 3, strongholds: advanced });
|
|
assert.equal(fixture.setup.status, 'setup'); assert.equal(fixture.setup.leaderSkills!.assignments.length, 0);
|
|
assert.equal(fixture.setup.spiceDeck.length, 28); assert.equal(fixture.setup.discoveries!.tokens.length, 8);
|
|
assert.ok(fixture.setup.players.every(seat => !seat.traitors.length && !seat.traitorChoices.length));
|
|
assert.equal(fixture.trained.leaderSkills!.assignments.length, seats);
|
|
const trainingSteps = fixture.actions.filter(step => step.action.type === 'leaderSkill');
|
|
assert.equal(trainingSteps.length, seats);
|
|
const lastTraining = fixture.actions.findLastIndex(step => step.action.type === 'leaderSkill');
|
|
const firstTraitor = fixture.actions.findIndex(step => step.action.type === 'traitor');
|
|
assert.ok(firstTraitor > lastTraining, 'Actual training precedes the original Traitor selection.');
|
|
assert.equal(fixture.firstMentat.before.phase, 8); assert.equal(fixture.firstMentat.after.turn, 2);
|
|
assert.equal(fixture.game.phase, 0); assert.equal(fixture.game.storm, fixture.firstMentat.before.storm);
|
|
const before = player(fixture.game, fixture.owner), entered = enterDiscoveryClassicNexusSkills(fixture), after = player(entered, fixture.owner);
|
|
assert.equal(after.forces['cistern:0'], 3); assert.equal(after.forces[fixture.source] ?? 0, 0);
|
|
assert.equal(after.reserves, before.reserves); assert.equal(after.spice, before.spice);
|
|
assert.equal(after.shipped, before.shipped); assert.equal(after.moved, before.moved);
|
|
assert.deepEqual(entered.leaderSkills, fixture.game.leaderSkills);
|
|
if (advanced) {
|
|
assert.equal(entered.strongholdCards!.owners.arrakeen, fixture.owner);
|
|
assert.equal(entered.strongholdCards!.claimedTurn, 1);
|
|
}
|
|
inventory(fixture.setup); inventory(entered);
|
|
}
|
|
});
|
|
|
|
void test('authenticated original lobby and undealt human setup retain actual offered training, original hands and actors without demanding a specific skill', () => {
|
|
const lobby = createGame('HUMANCLASSICNXSKILLDISCOVERY', newPlayer('human-collector', 'Guild', 'guild'), true);
|
|
joinGame(lobby, newPlayer('human-emperor', 'Emperor', 'emperor'));
|
|
joinGame(lobby, newPlayer('human-fremen', 'Fremen', 'fremen'));
|
|
const before = structuredClone(lobby);
|
|
const setup = initializeDiscoveryClassicNexusSkillsSetup({ initial: lobby, tech: true, strongholds: true });
|
|
assert.deepEqual(lobby, before);
|
|
const offered = structuredClone(setup);
|
|
const fixture = createDiscoveryClassicNexusSkillsFixture({ initial: setup });
|
|
assert.deepEqual(setup, offered); assert.deepEqual(fixture.setup, offered);
|
|
assert.deepEqual(fixture.game.players.map(seat => seat.id), ['human-collector', 'human-emperor', 'human-fremen']);
|
|
for (const step of fixture.actions.filter(step => step.action.type === 'leaderSkill')) {
|
|
assert.ok(offered.leaderSkills!.offers[step.actor].cards.some(skill => skill === step.action.skill));
|
|
assert.ok(offered.players.find(seat => seat.id === step.actor)!.leaders.some(leader => leader.id === step.action.leader));
|
|
}
|
|
assert.deepEqual(fixture.setup.players.map(seat => seat.hand), offered.players.map(seat => seat.hand));
|
|
assert.equal(fixture.game.decision?.player, 'human-collector');
|
|
const closing = closeDiscoveryClassicNexusSkills(fixture);
|
|
const originalTop = closing.beforeDraw.nexusCards!.cards!.deck[0];
|
|
assert.equal(closing.game.nexusCards!.cards!.hands[fixture.owner], originalTop,
|
|
'Human closing draw consumes the actual original top card, not a demanded Nexus identity.');
|
|
assert.equal(closing.game.nexusCards!.cards!.deck.length, closing.beforeDraw.nexusCards!.cards!.deck.length - 1);
|
|
inventory(closing.game);
|
|
assert.throws(() => createDiscoveryClassicNexusSkillsFixture({ initial: fixture.game }));
|
|
});
|
|
|
|
void test('actual Great Maker losses and all votes precede both Advanced piles, settled alliance and the qualified closing draw', () => {
|
|
const fixture = createDiscoveryClassicNexusSkillsFixture({ advanced: true, tech: true, strongholds: true });
|
|
const closing = closeDiscoveryClassicNexusSkills(fixture, 'emperor');
|
|
assert.equal(player(closing.vote, fixture.owner).tanks, 6);
|
|
assert.equal(player(closing.vote, fixture.owner).forces[fixture.wormSource], undefined);
|
|
assert.equal(player(closing.vote, fixture.owner).forces['cistern:0'], 3);
|
|
assert.equal(closing.vote.nexusCards!.cards!.hands[fixture.owner], null);
|
|
assert.notEqual(closing.vote.nexusCards!.phase?.stage, 'drawing');
|
|
assert.equal(closing.alliance.greatMaker?.stage, 'complete');
|
|
assert.equal(closing.beforeDraw.phase, 1); assert.equal(closing.beforeDraw.nexus, false);
|
|
assert.deepEqual(closing.beforeDraw.nexusCards!.phase!.eligible, [fixture.owner]);
|
|
assert.equal(closing.beforeDraw.spiceWindow, null); assert.equal(closing.beforeDraw.spiceResolution, null);
|
|
assert.ok(closing.beforeDraw.spiceDiscard.flat().some(card => 'territory' in card && card.territory === 'broken_land'));
|
|
assert.ok(closing.beforeDraw.spiceDiscard.flat().filter(card => 'territory' in card && card.territory === 'rock_outcroppings').length >= 1);
|
|
assert.equal(closing.game.nexusCards!.cards!.hands[fixture.owner], 'emperor');
|
|
assert.equal(closing.game.phase, 2); assert.equal(closing.game.nexusCards!.phase!.stage, 'complete');
|
|
assert.deepEqual(closing.game.leaderSkills, fixture.game.leaderSkills);
|
|
reject(closing.game, fixture.owner, closing.drawStep.action);
|
|
inventory(closing.vote); inventory(closing.game);
|
|
});
|
|
|
|
void test('own-faction redraw preference and later allied forfeiture use cards supplied by genuine closing draws only', () => {
|
|
const fixture = createDiscoveryClassicNexusSkillsFixture();
|
|
const closing = closeDiscoveryClassicNexusSkills(fixture, 'atreides');
|
|
const before = structuredClone(closing.beforeDraw), cards = before.nexusCards!.cards!;
|
|
const index = cards.deck.indexOf('emperor'); assert.ok(index > 0);
|
|
cards.deck.splice(1, 0, cards.deck.splice(index, 1)[0]);
|
|
const redraw = stepDiscoveryClassicNexusSkills(before, { actor: fixture.owner,
|
|
action: { ...closing.drawStep.action, ownRedraws: 1 } });
|
|
assert.equal(redraw.nexusCards!.cards!.hands[fixture.owner], 'emperor');
|
|
assert.equal(redraw.nexusCards!.cards!.discard.filter(card => card === 'atreides').length, 1);
|
|
assert.equal(redraw.nexusCards!.cards!.deck.length, before.nexusCards!.cards!.deck.length - 2);
|
|
inventory(redraw);
|
|
let game = finishClassicDiscoveryNexusMentat(closing.game).after;
|
|
game = advanceDiscoveryClassicNexusSkills(game, game => game.phase === 1 && discoveryClassicNexusSkillsClean(game));
|
|
const wormIndex = game.spiceDeck.findIndex(card => 'worm' in card && !card.greatMaker); assert.ok(wormIndex >= 0);
|
|
const worm = game.spiceDeck.splice(wormIndex, 1)[0];
|
|
const landIndex = game.spiceDeck.findIndex(card => 'territory' in card && !card.discovery); assert.ok(landIndex >= 0);
|
|
game.spiceDeck.unshift(worm, game.spiceDeck.splice(landIndex, 1)[0]);
|
|
game = openClassicDiscoveryNexusAlliance(game);
|
|
const heldWindow = structuredClone(game);
|
|
const keep = advanceDiscoveryClassicNexusSkills(heldWindow, game => game.nexusCards?.phase?.stage === 'drawing');
|
|
const kept = stepDiscoveryClassicNexusSkills(keep, { actor: fixture.owner, action: {
|
|
type: 'nexusCardChoice', turn: keep.turn, card: 'atreides', choice: 'keep', ownRedraws: 0 } });
|
|
assert.equal(kept.nexusCards!.cards!.hands[fixture.owner], 'atreides');
|
|
assert.deepEqual(kept.nexusCards!.cards!.deck, keep.nexusCards!.cards!.deck);
|
|
game = stepDiscoveryClassicNexusSkills(game, { actor: fixture.opponent, action: { type: 'alliance', target: null } });
|
|
game = formClassicDiscoveryNexusAlliance(game, fixture.owner, fixture.opponent);
|
|
assert.equal(game.nexusCards!.cards!.hands[fixture.owner], null);
|
|
assert.equal(game.nexusCards!.cards!.discard.filter(card => card === 'atreides').length, 1);
|
|
game = advanceDiscoveryClassicNexusSkills(game, game => game.nexusCards?.phase?.stage === 'drawing');
|
|
assert.ok(!game.nexusCards!.phase!.eligible.includes(fixture.owner));
|
|
assert.deepEqual(viewGame(game, fixture.owner).nexusCards!.choices, []);
|
|
inventory(kept); inventory(game);
|
|
});
|
|
|
|
for (const advanced of [false, true]) for (const kind of ['emperor', 'fremen'] as const) {
|
|
void test(`${advanced ? 'Advanced' : 'Basic'} trained Discovery ${kind} returns native Great Maker casualties with distinct ordinary ledgers and actual Axlotl settlement`, () => {
|
|
const fixture = createDiscoveryClassicNexusSkillsFixture({ advanced, tech: true, strongholds: advanced });
|
|
const closing = closeDiscoveryClassicNexusSkills(fixture, kind);
|
|
const game = advanceDiscoveryClassicNexusSkills(closing.game, game => game.phase === 4 && discoveryClassicNexusSkillsClean(game));
|
|
assert.equal(player(game, fixture.owner).tanks, 6, 'Tanks arise from the original worm action, never an injected death.');
|
|
const view = viewGame(game, fixture.owner), action: Action = kind === 'emperor'
|
|
? { type: 'nexusEmperorRevive', event: view.nexusEmperorSecretAlly!.event, elite: 0 }
|
|
: { type: 'nexusFremenRevival', event: view.nexusFremenRevival!.event };
|
|
reject(game, fixture.opponent, action);
|
|
reject(game, fixture.owner, { ...action, event: 'stale-native-revival' });
|
|
if (kind === 'fremen') assert.equal(viewGame(game, fixture.opponent).nexusFremenRevival, null);
|
|
const before = player(game, fixture.owner);
|
|
let paid = stepDiscoveryClassicNexusSkills(structuredClone(game), { actor: fixture.owner, action });
|
|
if (kind === 'fremen') {
|
|
assert.deepEqual(player(paid, fixture.owner), before, 'Spending grants quota without returning casualties');
|
|
assert.deepEqual(paid.techTokens, game.techTokens);
|
|
paid = stepDiscoveryClassicNexusSkills(paid, { actor: fixture.owner, action: { type: 'revive', amount: 3, elite: 0 } });
|
|
paid = advanceDiscoveryClassicNexusSkills(paid, discoveryClassicNexusSkillsClean);
|
|
}
|
|
const after = player(paid, fixture.owner);
|
|
assert.equal(after.reserves, before.reserves + 3); assert.equal(after.tanks, 3); assert.equal(after.spice, before.spice);
|
|
assert.equal(after.revived, kind === 'fremen' ? 3 : 0);
|
|
assert.equal(after.freeForcesRevived ?? 0, kind === 'fremen' ? 3 : 0);
|
|
assert.equal(forceRevivalRemaining(paid, after), kind === 'fremen' ? 0 : 3);
|
|
assert.equal(paid.nexusCards!.cards!.hands[fixture.owner], null);
|
|
assert.equal(paid.nexusCards!.cards!.discard.filter(card => card === kind).length, 1);
|
|
assert.deepEqual(paid.leaderSkills, game.leaderSkills);
|
|
const history = kind === 'emperor' ? paid.nexusEmperorSecretHistory! : paid.nexusFremenRevivalHistory!;
|
|
assert.equal(history.length, 1); assert.equal(history[0].owner, fixture.owner);
|
|
if (kind === 'emperor') {
|
|
const receipt = paid.nexusEmperorSecretHistory![0];
|
|
assert.ok(receipt.after, 'The requested Nexus return must settle before physical receipt consumption.');
|
|
assert.equal(receipt.before.tanks - receipt.after.tanks, 3);
|
|
} else {
|
|
const grant = paid.nexusFremenRevivalHistory![0];
|
|
assert.equal(grant.revivedAtUse, 0); assert.equal(grant.freeAtUse, 0);
|
|
}
|
|
reject(paid, fixture.owner, action);
|
|
if (kind === 'emperor') {
|
|
paid = stepDiscoveryClassicNexusSkills(paid, { actor: fixture.owner, action: { type: 'revive', amount: 3 } });
|
|
paid = advanceDiscoveryClassicNexusSkills(paid, discoveryClassicNexusSkillsClean);
|
|
assert.equal(player(paid, fixture.owner).tanks, 0); assert.equal(player(paid, fixture.owner).revived, 3);
|
|
assert.equal(player(paid, fixture.owner).freeForcesRevived, 2); assert.equal(player(paid, fixture.owner).spice, before.spice - 2);
|
|
} else reject(paid, fixture.owner, { type: 'revive', amount: 1 });
|
|
const owner = paid.techTokens!.axlotl.owner!;
|
|
const credit = ownedTech(paid.techTokens, owner).length;
|
|
assert.equal(paid.techTokens!.axlotl.triggeredTurn, 2); assert.equal(paid.techTokens!.axlotl.spice, credit);
|
|
const wallet = player(paid, owner).spice;
|
|
const movement = advanceDiscoveryClassicNexusSkills(paid, game => game.phase === 5 && discoveryClassicNexusSkillsClean(game));
|
|
assert.equal(player(movement, owner).spice, wallet + credit); assert.equal(movement.techTokens!.axlotl.spice, 0);
|
|
assert.deepEqual(movement.leaderSkills, fixture.game.leaderSkills); inventory(movement);
|
|
let ordinaryFirst = stepDiscoveryClassicNexusSkills(structuredClone(game), { actor: fixture.owner,
|
|
action: { type: 'revive', amount: kind === 'emperor' ? 3 : 1 } });
|
|
ordinaryFirst = advanceDiscoveryClassicNexusSkills(ordinaryFirst, discoveryClassicNexusSkillsClean);
|
|
if (kind === 'emperor') {
|
|
const extra = stepDiscoveryClassicNexusSkills(ordinaryFirst, { actor: fixture.owner, action });
|
|
assert.equal(player(extra, fixture.owner).tanks, 0);
|
|
assert.equal(player(extra, fixture.owner).revived, 3);
|
|
assert.equal(player(extra, fixture.owner).freeForcesRevived, 2);
|
|
assert.equal(player(extra, fixture.owner).spice, player(ordinaryFirst, fixture.owner).spice);
|
|
assert.deepEqual(extra.techTokens, ordinaryFirst.techTokens, 'Extra three cannot retrigger the already earned original Axlotl activity.');
|
|
inventory(extra);
|
|
} else {
|
|
const granted = stepDiscoveryClassicNexusSkills(ordinaryFirst, { actor: fixture.owner, action });
|
|
assert.deepEqual(player(granted, fixture.owner), player(ordinaryFirst, fixture.owner));
|
|
reject(granted, fixture.owner, { type: 'revive', amount: 3, elite: 0 });
|
|
const returned = advanceDiscoveryClassicNexusSkills(stepDiscoveryClassicNexusSkills(granted,
|
|
{ actor: fixture.owner, action: { type: 'revive', amount: 2, elite: 0 } }), discoveryClassicNexusSkillsClean);
|
|
assert.equal(player(returned, fixture.owner).tanks, 3);
|
|
assert.equal(player(returned, fixture.owner).revived, 3);
|
|
assert.equal(player(returned, fixture.owner).freeForcesRevived, 3);
|
|
assert.equal(player(returned, fixture.owner).spice, player(ordinaryFirst, fixture.owner).spice);
|
|
assert.equal(returned.nexusCards!.cards!.hands[fixture.owner], null);
|
|
assert.deepEqual(returned.techTokens, ordinaryFirst.techTokens, 'The ordinary return cannot retrigger already earned Axlotl activity');
|
|
assert.deepEqual(returned.leaderSkills, game.leaderSkills);
|
|
inventory(returned);
|
|
}
|
|
});
|
|
}
|
|
|
|
void test('borrowed Guild nested shipment retains physical training and held Arrakeen, then actual skilled Warmaster cleanup and mandatory Tech transfer occur in order', () => {
|
|
const fixture = createDiscoveryClassicNexusSkillsFixture({ advanced: true, tech: true, strongholds: true, skill: 'warmaster' });
|
|
const closing = closeDiscoveryClassicNexusSkills(fixture, 'guild');
|
|
const battle = openDiscoveryClassicNexusSkillsBattle(fixture, closing.game);
|
|
const before = player(battle.shipmentWindow, fixture.owner), arrived = player(battle.arrived, fixture.owner);
|
|
assert.equal(arrived.forces['cistern:0'], 6); assert.equal(arrived.reserves, before.reserves - 3);
|
|
assert.equal(arrived.spice, before.spice - 2); assert.equal(arrived.shipped, true); assert.equal(arrived.moved, before.moved);
|
|
assert.equal(battle.arrived.nexusCards!.cards!.hands[fixture.owner], null);
|
|
assert.equal(battle.arrived.nexusGuildSecretHistory!.length, 1);
|
|
assert.equal(battle.arrived.nexusGuildSecretHistory![0].receipt.owner, fixture.owner);
|
|
assert.equal(battle.arrived.strongholdCards!.owners.arrakeen, fixture.owner);
|
|
assert.deepEqual(battle.arrived.leaderSkills, battle.shipmentWindow.leaderSkills);
|
|
reject(battle.shipmentWindow, fixture.owner, { ...battle.shipmentStep.action, smuggler: true });
|
|
const industryOwner = battle.arrived.techTokens!.heighliners.owner!;
|
|
const industryCredit = ownedTech(battle.arrived.techTokens, industryOwner).length;
|
|
assert.equal(battle.arrived.techTokens!.heighliners.triggeredTurn, 2);
|
|
assert.equal(battle.arrived.techTokens!.heighliners.spice, industryCredit);
|
|
assert.equal(battle.game.techTokens!.heighliners.spice, 0);
|
|
assert.equal(player(battle.game, industryOwner).spice, player(battle.arrived, industryOwner).spice + industryCredit,
|
|
'Actual movement end settles shipping industry exactly once before the private nested plans.');
|
|
const revealed = revealDiscoveryClassicNexusSkillsBattle(battle), quote = quoteClassicSkillsStrongholdBattle(revealed);
|
|
assert.equal(quote.winner, fixture.owner);
|
|
assert.equal(viewGame(revealed, fixture.owner).battle!.strongholdEffects[fixture.owner], null);
|
|
const payment = quote.payments.find(receipt => receipt.player === fixture.owner)!;
|
|
assert.equal(payment.bankSupport, 0); assert.equal(payment.ownPayment, 1);
|
|
const side = revealed.battle!.attacker === fixture.owner ? 'attacker' : 'defender';
|
|
assert.deepEqual(quote.leaderSkillBonuses[side].applied, [{ skill: 'warmaster', amount: 3, mode: 'skilled' }]);
|
|
const loser = player(revealed, fixture.opponent), reward = ownedTech(revealed.techTokens, fixture.opponent);
|
|
assert.ok(reward.length > 0);
|
|
const cleanup = finishDiscoveryClassicNexusSkillsBattle(revealed, true);
|
|
assert.equal(player(cleanup, fixture.owner).forces['cistern:0'], 5);
|
|
assert.equal(player(cleanup, fixture.opponent).forces['cistern:0'] ?? 0, 0);
|
|
assert.equal(player(cleanup, fixture.opponent).tanks, loser.tanks + 2);
|
|
assert.deepEqual(cleanup.techTokens, revealed.techTokens, 'Battle cleanup still owns its priority before the winner reward.');
|
|
const done = finishDiscoveryClassicNexusSkillsBattle(cleanup);
|
|
assert.equal(done.lastBattleContext!.winner, fixture.owner);
|
|
assert.ok(player(done, fixture.owner).hand.some(card => card.id === battle.weapon));
|
|
assert.ok(!done.discard.some(card => card.id === battle.weapon));
|
|
assert.equal(viewGame(done, fixture.owner).leaderSkills!.assignments.find(assignment => assignment.leader === fixture.leader)!.faceUp, true);
|
|
for (const token of reward) assert.equal(done.techTokens![token].owner, fixture.owner);
|
|
assert.deepEqual(ownedTech(done.techTokens, fixture.opponent), []);
|
|
assert.deepEqual(done.nexusGuildSecretHistory, battle.arrived.nexusGuildSecretHistory);
|
|
assert.equal(done.strongholdCards!.owners.arrakeen, fixture.owner);
|
|
inventory(done);
|
|
});
|
|
|
|
void test('all four minimal policies produce actual offered training, physical entry, qualified Nexus draw and source-clear return outcomes', () => {
|
|
const fixture = createDiscoveryClassicNexusSkillsFixture({ advanced: true, tech: true });
|
|
const closing = closeDiscoveryClassicNexusSkills(fixture, 'emperor');
|
|
const revival = advanceDiscoveryClassicNexusSkills(closing.game, game => game.phase === 4 && discoveryClassicNexusSkillsClean(game));
|
|
const training = advanceDiscoveryClassicNexusSkills(structuredClone(fixture.setup), game => game.setupStage === 'leaderSkills');
|
|
for (const difficulty of DIFFICULTIES) {
|
|
const cases = [
|
|
{ game: training, actor: Object.keys(training.leaderSkills!.offers)[0], type: 'leaderSkill' },
|
|
{ game: fixture.game, actor: fixture.owner, type: 'decision' },
|
|
{ game: closing.beforeDraw, actor: fixture.owner, type: 'nexusCardChoice' },
|
|
{ game: revival, actor: fixture.owner, type: 'nexusEmperorRevive' },
|
|
];
|
|
for (const current of cases) {
|
|
const view = viewGame(current.game, current.actor); view.players.find(seat => seat.id === current.actor)!.bot = difficulty;
|
|
const untouched = structuredClone(view), action = botActions(view).find(action => action.type === current.type);
|
|
assert.ok(action, `${difficulty} needs the actual owned ${current.type}.`);
|
|
const after = stepDiscoveryClassicNexusSkills(structuredClone(current.game), { actor: current.actor, action });
|
|
assert.deepEqual(view, untouched);
|
|
if (current.type === 'leaderSkill') {
|
|
assert.equal(after.leaderSkills!.assignments.length, 1);
|
|
const assignment = after.leaderSkills!.assignments[0]; assert.equal(assignment.owner, current.actor);
|
|
assert.ok(current.game.leaderSkills!.offers[current.actor].cards.includes(assignment.skill));
|
|
assert.equal(after.leaderSkills!.offers[current.actor], undefined);
|
|
assert.deepEqual(after.players.map(seat => seat.hand), current.game.players.map(seat => seat.hand));
|
|
} else if (current.type === 'decision') {
|
|
assert.equal(after.discoveryEntry, undefined);
|
|
const entered = player(after, current.actor).forces['cistern:0'] ?? 0;
|
|
assert.equal((player(after, current.actor).forces[fixture.source] ?? 0) + entered, 3);
|
|
assert.equal(player(after, current.actor).spice, player(current.game, current.actor).spice);
|
|
assert.deepEqual(after.leaderSkills, current.game.leaderSkills);
|
|
} else if (current.type === 'nexusCardChoice') {
|
|
assert.equal(after.nexusCards!.phase!.stage, 'complete');
|
|
assert.ok(after.nexusCards!.cards!.hands[current.actor]);
|
|
assert.equal(after.phase, 2);
|
|
} else {
|
|
assert.equal(player(after, current.actor).tanks, 3);
|
|
assert.equal(player(after, current.actor).reserves, player(current.game, current.actor).reserves + 3);
|
|
assert.equal(player(after, current.actor).revived, 0);
|
|
assert.equal(after.nexusCards!.cards!.hands[current.actor], null);
|
|
assert.equal(after.techTokens!.axlotl.triggeredTurn, 2);
|
|
assert.deepEqual(after.leaderSkills, current.game.leaderSkills);
|
|
}
|
|
inventory(after);
|
|
}
|
|
}
|
|
});
|