Repair deterministic Occupy test transitions and card staging
This commit is contained in:
1 parent
38b1ba3419
commit
f2fd274502
5 files changed
+61
-18
No files matched your search
@@ -5,6 +5,7 @@ import { botActions } from '../game/bots';
|
||||
import { treacheryDeck } from '../game/cards';
|
||||
import { ecazTreacheryCards } from '../game/ecaz-cards';
|
||||
import { splitLocation } from '../game/board';
|
||||
import { pinDeterministicRandom } from './deterministic-random';
|
||||
import {
|
||||
createEcazOccupySetup, ecazOccupyTerritoryForces, type EcazOccupyFixtureOptions,
|
||||
} from './fixture-ecaz-occupy';
|
||||
@@ -121,7 +122,8 @@ for (const lead of ['ecaz', 'ally'] as const) for (const slot of ['weapon', 'def
|
||||
|
||||
for (const lead of ['ecaz', 'ally'] as const) {
|
||||
for (const outcome of ['loss', 'soleTraitor', 'opposingTraitor', 'mutualTraitors', 'explosion'] as const) {
|
||||
void test(`Harass ${lead} lead, actual ${outcome}: returns precede losses except an opposing successful traitor`, () => {
|
||||
void test(`Harass ${lead} lead, actual ${outcome}: returns precede losses except an opposing successful traitor`, (t) => {
|
||||
pinDeterministicRandom(t, 62);
|
||||
const result = ecazOccupyCardsCase({ lead, outcome, slot: 'defense' });
|
||||
const { fixture, actor, game } = result;
|
||||
const canceledReturn = outcome === 'opposingTraitor' || outcome === 'mutualTraitors';
|
||||
|
||||
@@ -4,6 +4,7 @@ import { applyAction, viewGame } from '../game/engine';
|
||||
import { botActions } from '../game/bots';
|
||||
import { DIFFICULTIES } from '../game/bot-profiles';
|
||||
import { controlsLeader } from '../game/leader-control';
|
||||
import { pinDeterministicRandom } from './deterministic-random';
|
||||
import {
|
||||
ecazOccupyDialChoices,
|
||||
ecazOccupyLeadOptions,
|
||||
@@ -107,21 +108,28 @@ void test('typed Fremen and Emperor variable armies retain native legal incremen
|
||||
assert.equal(ecazOccupyPlanControl(emperorView, 8, 4)?.blocked, null);
|
||||
});
|
||||
|
||||
void test('all four native policies can seal a legal total plan for either lead, active or canceled', () => {
|
||||
void test('all four native policies finish Occupy planning through optional powers for either lead, active or canceled', (t) => {
|
||||
pinDeterministicRandom(t, 8);
|
||||
for (const lead of ['ecaz', 'ally'] as const) for (const canceled of [false, true]) {
|
||||
const fixture = ecazOccupyFixture({ allyFaction: 'guild', ecazForces: 3 });
|
||||
const game = openEcazOccupyPlans(canceled
|
||||
? cancelEcazOccupy(fixture, lead) : chooseEcazOccupyLead(fixture, lead));
|
||||
const actor = lead === 'ecaz' ? fixture.ecaz : fixture.ally;
|
||||
for (const policy of DIFFICULTIES) {
|
||||
const view = viewGame(game, actor);
|
||||
view.players.find((player) => player.id === actor)!.bot = policy;
|
||||
const plan = botActions(view).find((action) => action.type === 'battlePlan');
|
||||
assert.ok(plan, `${policy}/${lead}/${canceled}: native policy must supply a sealable plan`);
|
||||
assert.equal(ecazOccupyPlanControl(view, Number(plan.dial), Number(plan.support ?? 0))?.blocked, null);
|
||||
const next = applyAction(game, actor, plan);
|
||||
assert.ok(viewGame(next, actor).battle?.submitted.includes(actor),
|
||||
`${policy}/${lead}/${canceled}: the native table must show the selected lead's sealed plan`);
|
||||
let state = structuredClone(game);
|
||||
for (let step = 0; step < 4; step++) {
|
||||
const view = viewGame(state, actor);
|
||||
view.players.find((player) => player.id === actor)!.bot = policy;
|
||||
const actions = botActions(view);
|
||||
const action = actions[0];
|
||||
assert.ok(action, `${policy}/${lead}/${canceled}: planning must retain a legal continuation`);
|
||||
if (action.type === 'battlePlan')
|
||||
assert.equal(ecazOccupyPlanControl(view, Number(action.dial), Number(action.support ?? 0))?.blocked, null);
|
||||
state = applyAction(state, actor, action);
|
||||
if (viewGame(state, actor).battle?.submitted.includes(actor)) break;
|
||||
}
|
||||
assert.ok(viewGame(state, actor).battle?.submitted.includes(actor),
|
||||
`${policy}/${lead}/${canceled}: optional powers must finish and the selected lead must seal a plan`);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -157,7 +157,10 @@ export function stageEcazOccupyCard(game: Game, owner: string,
|
||||
assert.ok(donor, 'A conserved original physical card must supply this rule case.');
|
||||
const donorIndex = donor.hand.findIndex(predicate);
|
||||
card = donor.hand[donorIndex];
|
||||
const replacement = player.hand.shift() ?? game.deck.shift();
|
||||
// Keep previously staged battle cards when there is still hand capacity.
|
||||
// A donor's replacement comes from the original deck, just as a direct
|
||||
// staging draw does; only a full hand must fund it with a held card.
|
||||
const replacement = player.hand.length >= 4 ? player.hand.shift() : game.deck.shift();
|
||||
assert.ok(replacement, 'A conserved physical face must preserve the donor hand.');
|
||||
donor.hand[donorIndex] = replacement;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user