Files
Dune/tests/homeworld-revival-prevention-runtime.test.ts
T

142 lines
8.8 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { applyAction, viewGame, type Action, type Game } from '../game/engine';
import { botActions } from '../game/bots';
import { DIFFICULTIES } from '../game/bot-profiles';
import { freeRevivalRate } from '../game/revival';
import {
createHomeworldRevivalPreventionFixture as fixture,
actHomeworldRevivalPrevention as act,
requestHomeworldRevivalPrevention as request,
allowHomeworldRevivalPrevention as allow,
advanceHomeworldRevivalPrevention as advance,
finishHomeworldRevivalPrevention as finish,
homeworldRevivalPreventionPlayer as player,
homeworldRevivalPreventionClean as clean,
homeworldRevivalPreventionInventory as inventory,
homeworldRevivalPreventionPopulation as population,
} from './fixture-homeworld-revival-prevention';
function reject(game: Game, actor: string, action: Action, reason?: RegExp) {
const before = structuredClone(game);
if (reason) assert.throws(() => applyAction(game, actor, action), reason);
else assert.throws(() => applyAction(game, actor, action));
assert.deepEqual(game, before);
}
void test('native LOW Fremen stays an eligible quiet special-Karama target and retains the printed four-free rate', () => {
const f = fixture(), before = structuredClone(player(f.game, f.target)), owner = structuredClone(player(f.game, f.owner));
assert.equal(f.game.turn, 2); assert.equal(f.game.phase, 4);
assert.equal(population(f.game).population, 0); assert.equal(population(f.game).side, 'low');
assert.equal(before.tanks, 5); assert.equal(before.elites?.tanks, 0);
assert.deepEqual(before.elites?.forces, { 'sietch_tabr:14': 3 });
assert.equal(f.stormProtection.response?.kind, 'stormProtection');
assert.equal(f.stormProtection.response?.amount, 10);
assert.equal(player(f.afterLoss, f.target).forces[f.location], 5);
const view = viewGame(f.game, f.owner), offer = view.tleilaxuSpecialKarama;
assert.equal(offer?.blocked, null); assert.equal(offer?.pending, false);
assert.deepEqual(offer?.cards, [f.karama]);
assert.equal(offer?.targets.find(choice => choice.player === f.target)?.blocked, null);
assert.equal(viewGame(f.game, f.target).revival.specialKaramaBlock, null);
assert.equal(freeRevivalRate(f.game, before), 4);
const stopped = act(f.game, f.owner, f.special), after = player(stopped, f.target);
assert.deepEqual(after, before, 'Preventing the event returns neither the ordinary three nor the independent low increment');
assert.equal(player(stopped, f.owner).spice, owner.spice);
assert.equal(player(stopped, f.owner).specialKaramaUsed, true);
assert.equal(player(stopped, f.owner).hand.some(card => card.id === f.karama), false);
assert.equal(stopped.discard.filter(card => card.id === f.karama).length, 1);
assert.deepEqual(stopped.revivalPrevention, { player: f.target, turn: stopped.turn });
assert.equal(stopped.pendingRevival ?? null, null);
assert.deepEqual(stopped.homeworldRevivalReturn, f.game.homeworldRevivalReturn);
assert.notEqual(stopped.revivalFreeIncome?.[f.target], stopped.turn);
assert.equal(freeRevivalRate(stopped, after), 4);
assert.equal(viewGame(stopped, f.target).revival.homeworldBonus, 1);
assert.equal(viewGame(stopped, f.target).revival.prevented, true);
reject(stopped, f.target, f.request, /prevented/);
reject(stopped, f.owner, f.special);
assert.deepEqual(inventory(stopped), f.originalInventory);
assert.equal(f.sourceActionCount, f.actions.length);
});
void test('the original reactive revivalStop may prevent all four requested free counters before any return, quota or income', () => {
const f = fixture(), before = structuredClone(player(f.game, f.target)), ownerSpice = player(f.game, f.owner).spice;
const pending = request(f.game, f).game;
assert.equal(pending.decision?.kind, 'revivalStop'); assert.equal(pending.decision?.player, f.owner);
assert.equal(pending.pendingRevival?.player, f.target);
assert.equal(pending.pendingRevival?.amount, 4); assert.equal(pending.pendingRevival?.free, 4);
assert.equal(pending.pendingRevival?.cost, 0);
assert.deepEqual(player(pending, f.target), before);
const offer = viewGame(pending, f.owner).tleilaxuSpecialKarama;
assert.equal(offer?.blocked, null); assert.equal(offer?.pending, true);
assert.deepEqual(offer?.targets, [{ player: f.target, blocked: null }]);
const stopped = act(pending, f.owner, f.special);
assert.equal(stopped.pendingRevival ?? null, null); assert.equal(stopped.decision, null);
assert.deepEqual(player(stopped, f.target), before);
assert.equal(player(stopped, f.owner).spice, ownerSpice);
assert.equal(player(stopped, f.owner).specialKaramaUsed, true);
assert.equal(stopped.discard.filter(card => card.id === f.karama).length, 1);
assert.deepEqual(stopped.homeworldRevivalReturn, f.game.homeworldRevivalReturn);
assert.notEqual(stopped.revivalFreeIncome?.[f.target], stopped.turn);
assert.equal(freeRevivalRate(stopped, player(stopped, f.target)), 4);
reject(stopped, f.target, f.request, /prevented/);
assert.deepEqual(inventory(stopped), f.originalInventory);
});
void test('declining that original counter really returns four free normals and pays the separate native free-revival income', () => {
const f = fixture(), before = structuredClone(player(f.game, f.target)), ownerSpice = player(f.game, f.owner).spice;
const pending = request(f.game, f).game;
const allowed = allow(pending, f), after = player(allowed.game, f.target);
assert.equal(after.reserves, 4); assert.equal(after.tanks, 1);
assert.equal(after.spice, before.spice); assert.equal(after.revived, 4); assert.equal(after.freeForcesRevived, 4);
assert.deepEqual(after.forces, before.forces); assert.deepEqual(after.elites, before.elites);
assert.equal(population(allowed.game).side, 'high');
assert.equal(player(allowed.game, f.owner).spice, ownerSpice + 1);
assert.ok(!player(allowed.game, f.owner).specialKaramaUsed);
assert.ok(player(allowed.game, f.owner).hand.some(card => card.id === f.karama));
assert.equal(allowed.game.pendingRevival, null); assert.ok(clean(allowed.game));
assert.equal(allowed.game.revivalFreeIncome?.[f.target], allowed.game.turn);
assert.deepEqual(inventory(allowed.game), f.originalInventory);
});
void test('original Ghola stays blocked during the prevented Revival but works through real readiness after phase4', () => {
const f = fixture(); assert.ok(f.ghola && f.gholaAction);
const stopped = act(f.game, f.owner, f.special), before = structuredClone(player(stopped, f.target));
assert.ok(before.hand.some(card => card.id === f.ghola));
reject(stopped, f.target, f.gholaAction, /prevented.*Ghola/);
assert.equal(viewGame(stopped, f.target).ghola?.available, false);
assert.ok(player(stopped, f.target).hand.some(card => card.id === f.ghola));
const movement = finish(stopped);
assert.equal(movement.turn, 2); assert.equal(movement.phase, 5);
assert.equal(player(movement, f.target).reserves, 0); assert.equal(player(movement, f.target).tanks, 5);
const requested = act(movement, f.target, f.gholaAction);
const completed = advance(requested, clean), after = player(completed, f.target);
assert.equal(after.reserves, 5); assert.equal(after.tanks, 0);
assert.equal(after.spice, before.spice); assert.equal(after.revived, before.revived);
assert.equal(after.freeForcesRevived, before.freeForcesRevived);
assert.deepEqual(after.forces, before.forces); assert.deepEqual(after.elites, before.elites);
assert.equal(after.hand.some(card => card.id === f.ghola), false);
assert.equal(completed.discard.filter(card => card.id === f.ghola).length, 1);
assert.deepEqual(inventory(completed), f.originalInventory);
});
void test('all four existing profiles legally answer the genuine five-Tank reactive stop without changing their thresholds', () => {
const f = fixture(), pending = request(f.game, f).game;
for (const difficulty of DIFFICULTIES) {
const view = viewGame(pending, f.owner); view.players.find(player => player.id === f.owner)!.bot = difficulty;
const action = botActions(view)[0]; assert.ok(action);
const answered = act(structuredClone(pending), f.owner, action);
const completed = advance(answered, clean), after = player(completed, f.target);
if (action.type === 'card') {
assert.equal(after.reserves, 0); assert.equal(after.tanks, 5); assert.equal(after.revived, 0);
assert.equal(freeRevivalRate(completed, after), 4);
assert.equal(player(completed, f.owner).specialKaramaUsed, true);
} else {
assert.equal(action.type, 'decision'); assert.equal(action.decline, true);
assert.equal(after.reserves, 4); assert.equal(after.tanks, 1); assert.equal(after.revived, 4);
assert.ok(player(completed, f.owner).hand.some(card => card.id === f.karama));
}
assert.deepEqual(inventory(completed), f.originalInventory);
assert.equal(finish(completed).phase, 5);
}
});