Files
Dune/tests/planetologist-independent.test.ts

153 lines
8.4 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { applyAction, viewGame } from '../game/engine';
import {
createPlanetologistIndependentFixture, createPlanetologistEmptyReserveFixture, sealPlanetologistIndependent,
finishPlanetologistIndependent,
} from './fixture-planetologist-independent';
import {
assertIndependentSkillsBattleCustody, independentSkillsBattlePlayer,
quoteIndependentSkillsBattle,
} from './fixture-independent-skills-battle';
void test('native Reinforcements and its Planetologist alternative keep the same score but only the native role spends three reserves', () => {
for (const alternate of [false, true]) {
const f = createPlanetologistIndependentFixture({ alternate });
const before = independentSkillsBattlePlayer(f.game, f.owner);
const sealed = sealPlanetologistIndependent(f);
const quote = quoteIndependentSkillsBattle(sealed, { returns: f.returns });
const trainer = before.leaders.find(leader => leader.id === f.trainer)!;
assert.equal(quote.scores!.attacker, trainer.strength + 2 + 2);
assert.equal(quote.leaderSkillBonuses!.attacker.bonus, alternate ? 2 : 0);
assert.equal(quote.reinforcements?.[0]?.normal ?? 0, alternate ? 0 : 3);
const done = finishPlanetologistIndependent(sealed, f);
const own = independentSkillsBattlePlayer(done, f.owner);
assert.equal(done.phase, 7);
assert.equal(own.reserves, before.reserves - (alternate ? 0 : 3) + 2);
assert.equal(own.tanks, before.tanks + (alternate ? 0 : 3) + 2);
assert.deepEqual(own.forces, {});
assert.equal(own.spice, before.spice - 2);
for (const card of ['ecaz-reinforcements', 'ecaz-harass-withdraw'])
assert.equal(done.discard.filter(entry => entry.id === card).length, 1);
assertIndependentSkillsBattleCustody(done);
}
});
void test('Harass as the Planetologist Special makes no return while the distinct defense-slot Reinforcements still pays its own fee', () => {
const f = createPlanetologistIndependentFixture({ effect: 'harassWithdraw' });
const before = independentSkillsBattlePlayer(f.game, f.owner);
const sealed = sealPlanetologistIndependent(f);
const quote = quoteIndependentSkillsBattle(sealed);
assert.deepEqual(quote.harassWithdraw ?? [], []);
assert.deepEqual(quote.reinforcements, [{ player: f.owner, card: 'ecaz-reinforcements', normal: 3, elite: 0 }]);
assert.equal(quote.leaderSkillBonuses!.attacker.bonus, 2);
const done = finishPlanetologistIndependent(sealed, f);
const own = independentSkillsBattlePlayer(done, f.owner);
assert.equal(own.reserves, before.reserves - 3);
assert.equal(own.tanks, before.tanks + 3 + 2);
assert.equal(Object.values(own.forces).reduce((sum, count) => sum + count, 0), 2);
assertIndependentSkillsBattleCustody(done);
});
void test('Recruits can be spent as a green Planetologist Special without activating global Revival or spending an unused Reinforcements', () => {
const f = createPlanetologistIndependentFixture({ effect: 'recruits' });
const before = independentSkillsBattlePlayer(f.game, f.owner);
const sealed = sealPlanetologistIndependent(f);
const quote = quoteIndependentSkillsBattle(sealed, { returns: f.returns });
assert.equal(quote.leaderSkillBonuses!.attacker.bonus, 2);
assert.deepEqual(quote.reinforcements ?? [], []);
const done = finishPlanetologistIndependent(sealed, f);
const own = independentSkillsBattlePlayer(done, f.owner);
assert.ok(!done.recruits);
assert.equal(own.reserves, before.reserves + 2);
assert.equal(own.tanks, before.tanks + 2);
assert.equal(done.discard.filter(card => card.id === 'ecaz-recruits').length, 1);
assert.ok(own.hand.some(card => card.id === 'ecaz-reinforcements'));
assertIndependentSkillsBattleCustody(done);
});
void test('a poisoned Planetologist loses alternate strength without retroactively activating Reinforcements or suppressing normal Harass', () => {
const f = createPlanetologistIndependentFixture({ poison: true });
const before = independentSkillsBattlePlayer(f.game, f.owner);
const sealed = sealPlanetologistIndependent(f);
const quote = quoteIndependentSkillsBattle(sealed, { returns: f.returns });
assert.equal(quote.leaderDeaths.attacker, true);
assert.equal(quote.leaderSkillBonuses!.attacker.bonus, 0);
assert.deepEqual(quote.reinforcements ?? [], []);
assert.deepEqual(quote.harassWithdraw![0].returned, { normal: 2, elite: 0 });
const done = finishPlanetologistIndependent(sealed, f);
const own = independentSkillsBattlePlayer(done, f.owner);
assert.equal(own.leaders.find(leader => leader.id === f.trainer)!.dead, true);
assert.equal(own.reserves, before.reserves + 2);
assert.equal(own.tanks, before.tanks + 2);
assert.ok(done.leaderSkills!.deck.includes('planetologist'));
assertIndependentSkillsBattleCustody(done);
});
void test('the independent green alternate role also preserves Basic support and cannot be declared using another leader', () => {
const f = createPlanetologistIndependentFixture({ advanced: false });
const own = independentSkillsBattlePlayer(f.game, f.owner);
const other = own.leaders.find(leader => !leader.dead && leader.id !== f.trainer)!;
assert.throws(() => applyAction(f.game, f.owner, { ...f.plans[0].action, leader: other.id }));
const done = finishPlanetologistIndependent(sealPlanetologistIndependent(f), f);
assert.equal(done.phase, 7);
assert.equal(independentSkillsBattlePlayer(done, f.owner).spice, own.spice);
assert.equal(independentSkillsBattlePlayer(done, f.owner).tanks, own.tanks + 2);
assertIndependentSkillsBattleCustody(done);
});
void test('zero reserves block native Reinforcements before a later Harass return but not its printed Planetologist substitution', () => {
const f = createPlanetologistEmptyReserveFixture();
const before = independentSkillsBattlePlayer(f.game, f.owner);
assert.equal(before.reserves, 0);
assert.throws(() => applyAction(f.game, f.owner, { ...f.plans[0].action, planetologistSpecial: false }));
const sealed = sealPlanetologistIndependent(f);
const quote = quoteIndependentSkillsBattle(sealed);
assert.deepEqual(quote.reinforcements ?? [], []);
assert.equal(quote.payments.find(payment => payment.player === f.owner)!.cost, 0);
assert.deepEqual(quote.harassWithdraw![0].returned, { normal: 4, elite: 0 });
const done = finishPlanetologistIndependent(sealed, f);
const own = independentSkillsBattlePlayer(done, f.owner);
assert.equal(done.phase, 7);
assert.equal(own.reserves, 4);
assert.equal(own.tanks, 1);
assert.equal(Object.values(own.forces).reduce((sum, count) => sum + count, 0), 15);
assertIndependentSkillsBattleCustody(done);
});
void test('a truthful named Reinforcements promise at zero reserves finds and executes the Planetologist alternative', () => {
const f = createPlanetologistEmptyReserveFixture();
let game = structuredClone(f.game);
const index = game.deck.findIndex(card => card.effect === 'truthtrance');
assert.ok(index >= 0);
const truth = game.deck.splice(index, 1)[0];
independentSkillsBattlePlayer(game, f.observer).hand.push(truth);
game = applyAction(game, f.observer, { type: 'card', card: truth.id });
while (game.truthtrance?.stage === 'priority') {
const actor = game.players.find(player => !game.truthtrance!.passed.includes(player.id))!;
game = applyAction(game, actor.id, { type: 'truthPass' });
}
game = applyAction(game, f.observer, { type: 'truthAsk', question: {
kind: 'battlePlan', target: f.owner, claim: { kind: 'and', terms: [
{ kind: 'weapon', name: 'Reinforcements' },
{ kind: 'dial', compare: 'eq', value: 0.5 },
{ kind: 'support', compare: 'eq', value: 0 },
] },
} });
assert.ok(viewGame(game, f.owner).truthBattleAnswers!.includes('yes'));
game = applyAction(game, f.owner, { type: 'truthAnswer', answer: 'yes' });
const witness = viewGame(game, f.owner).battle!.compliantPlan;
assert.ok(witness);
game = applyAction(game, f.owner, { type: 'battlePlan', ...witness });
game = applyAction(game, f.opponent, f.plans[1].action);
const done = finishPlanetologistIndependent(game, f);
const own = independentSkillsBattlePlayer(done, f.owner);
assert.equal(done.phase, 7);
assert.equal(own.reserves, 0);
assert.equal(own.tanks, 1);
assert.equal(Object.values(own.forces).reduce((sum, count) => sum + count, 0), 19);
assert.ok(own.hand.some(card => card.id === 'ecaz-harass-withdraw'));
assert.equal(done.discard.filter(card => card.id === 'ecaz-reinforcements').length, 1);
assertIndependentSkillsBattleCustody(done);
});