Files
Dune/tests/planetologist-advisor-gather-runtime.test.ts

84 lines
4.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from 'node:assert/strict';
import test from 'node:test';
import { applyAction, RuleError, viewGame } from '../game/engine';
import { isAdvisor } from '../game/advisors';
import { planetologistMoveDraft } from '../components/planetologist-movement';
import {
advisorGatherPlayer, allowPlanetologistAdvisorGather, createPlanetologistAdvisorGatherFixture,
} from './fixture-planetologist-advisor-gather';
void test('a real mixed gather declares one native flip before either source moves, then arrives as fighters for one movement', () => {
const f = createPlanetologistAdvisorGatherFixture();
// Advisor first: simultaneous physical custody cannot depend on source order.
const draft = planetologistMoveDraft(viewGame(f.game, f.owner), 'gather', 'arrakeen', 10,
{ 'imperial_basin:9': 1, 'carthag:11': 1 }, {});
assert.equal(draft.blocked, null);
assert.ok(draft.action);
const before = advisorGatherPlayer(f.game), rival = advisorGatherPlayer(f.game, f.opponent);
const pending = applyAction(f.game, f.owner, draft.action);
const paused = advisorGatherPlayer(pending);
assert.equal(pending.response?.kind, 'advisorFlip');
assert.deepEqual(paused.forces, before.forces);
assert.deepEqual(paused.advisors, before.advisors);
assert.equal(paused.moved, before.moved);
assert.equal(paused.shipped, before.shipped);
assert.equal(paused.spice, before.spice);
const done = allowPlanetologistAdvisorGather(pending), after = advisorGatherPlayer(done);
assert.equal(after.forces['arrakeen:10'], 2);
assert.equal(after.forces['carthag:11'] ?? 0, 0);
assert.equal(after.forces['imperial_basin:9'] ?? 0, 0);
assert.equal(isAdvisor(after, 'arrakeen'), false);
assert.equal(after.moved, before.moved + 1);
assert.equal(after.shipped, true);
assert.equal(after.spice, before.spice);
assert.equal(after.reserves, before.reserves);
assert.equal(after.tanks, before.tanks);
assert.deepEqual(advisorGatherPlayer(done, f.opponent).forces, rival.forces);
assert.equal(advisorGatherPlayer(done, f.canceler).hand.filter(card => card.id === f.karama).length, 1,
'Allowing the native flip does not spend the opponent’s physical counter.');
});
void test('one original Karama cancels the entire uncommitted mixed gather and a legal fighter-only replacement retains the movement', () => {
const f = createPlanetologistAdvisorGatherFixture();
const before = advisorGatherPlayer(f.game);
const pending = applyAction(f.game, f.owner, f.action);
const canceled = applyAction(pending, f.canceler, { type: 'card', card: f.karama, mode: 'cancel' });
const stopped = advisorGatherPlayer(canceled);
assert.deepEqual(stopped.forces, before.forces);
assert.deepEqual(stopped.advisors, before.advisors);
assert.equal(stopped.moved, before.moved);
assert.equal(stopped.shipped, before.shipped);
assert.equal(stopped.spice, before.spice);
assert.equal(canceled.discard.filter(card => card.id === f.karama).length, 1);
assert.ok(canceled.players.every(player => player.hand.every(card => card.id !== f.karama)));
const snapshot = structuredClone(canceled);
assert.throws(() => applyAction(canceled, f.owner, f.action), RuleError);
assert.deepEqual(canceled, snapshot, 'An immediate repeated flip cannot bypass the spent Karama.');
const retry = planetologistMoveDraft(viewGame(canceled, f.owner), 'gather', 'arrakeen', 10, f.forces, {});
assert.equal(retry.action, null);
const replacement = applyAction(canceled, f.owner, {
type: 'move', from: 'carthag:11', amount: 1, territory: 'arrakeen', sector: 10,
});
const after = advisorGatherPlayer(replacement);
assert.equal(after.forces['arrakeen:10'], 1);
assert.equal(after.forces['imperial_basin:9'], 1);
assert.equal(isAdvisor(after, 'imperial_basin'), true);
assert.equal(isAdvisor(after, 'arrakeen'), false);
assert.equal(after.moved, before.moved + 1);
assert.equal(after.spice, before.spice);
assert.equal(replacement.discard.filter(card => card.id === f.karama).length, 1);
});
void test('a genuine fresh accompanying advisor cannot use mixed gather to become a fighter during its arrival turn', () => {
const f = createPlanetologistAdvisorGatherFixture({ fresh: true });
const draft = planetologistMoveDraft(viewGame(f.game, f.owner), 'gather', 'arrakeen', 10, f.forces, {});
assert.equal(draft.action, null);
const snapshot = structuredClone(f.game);
assert.throws(() => applyAction(f.game, f.owner, f.action), RuleError);
assert.deepEqual(f.game, snapshot);
assert.equal(advisorGatherPlayer(f.game).forces['imperial_basin:9'], 1);
assert.equal(isAdvisor(advisorGatherPlayer(f.game), 'imperial_basin'), true);
assert.equal(advisorGatherPlayer(f.game).moved, 0);
assert.equal(advisorGatherPlayer(f.game, f.canceler).hand.filter(card => card.id === f.karama).length, 1);
});