142 lines
8.8 KiB
TypeScript
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);
|
|
}
|
|
});
|