151 lines
8.5 KiB
TypeScript
151 lines
8.5 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { applyAction, viewGame, type Game } from '../game/engine';
|
|
import { DUKE_VIDAL_ID } from '../game/duke-vidal';
|
|
import {
|
|
advancedNativePlayer as player, advancedNativeStep, finishAdvancedNativeSkillAftermath,
|
|
openAdvancedNativeSkillBattle, rejectAdvancedNativeAction as reject,
|
|
} from './fixture-advanced-native-skills';
|
|
import {
|
|
acquireNativeEcazSkillDuke, assertEcazDukeSkillsCustody,
|
|
ecazDukeSkillsAction as act, ecazDukeSkillsTrainer as trainer,
|
|
reloadEcazDukeSkills as reload, stageNativeEcazDukeSkillBattle,
|
|
} from './fixture-ecaz-duke-skills';
|
|
|
|
function resolve(game: Game, actor: string, enemy: string): Game {
|
|
game = act(game, actor, { type: 'traitorCall', call: false });
|
|
return act(game, enemy, { type: 'traitorCall', call: false });
|
|
}
|
|
|
|
for (const rules of ['basic', 'advanced'] as const) {
|
|
void test(`${rules} original Ecaz Ambassador acquisition gives separate Duke6 only the native face-up Warmaster's normal +1`, () => {
|
|
const acquired = acquireNativeEcazSkillDuke(rules);
|
|
const assignment = structuredClone(trainer(acquired));
|
|
const native = structuredClone(player(acquired, 'ecaz').leaders);
|
|
for (const [visible, dial, expectedWinner] of [
|
|
[true, 2, 'ecaz'], [false, 2, 'emperor'], [true, 3, 'emperor'],
|
|
] as const) {
|
|
const staged = stageNativeEcazDukeSkillBattle(reload(acquired));
|
|
let game = openAdvancedNativeSkillBattle(reload(staged.game), staged.actor, staged.enemy, !visible);
|
|
assert.equal(game.dukeVidal!.controller, staged.actor);
|
|
assert.equal(game.dukeVidal!.source, 'ecaz');
|
|
assert.equal(game.dukeVidal!.leader.strength, 6);
|
|
assert.equal(player(game, 'ecaz').leaders.find(l => l.id === assignment.leader)!.dead, false);
|
|
assert.equal(game.battle!.leaderSkillHidden![staged.actor], !visible);
|
|
assert.equal(viewGame(game, staged.actor).leaderSkills!.eligibleLeaders.some(l => l.id === DUKE_VIDAL_ID), false);
|
|
game = act(game, staged.actor, {
|
|
type: 'battlePlan', leader: DUKE_VIDAL_ID, dial: 0, support: 0, weapon: staged.worthless.id,
|
|
});
|
|
game = act(game, staged.enemy, {
|
|
type: 'battlePlan', leader: staged.defender, dial, support: dial,
|
|
});
|
|
assert.equal(game.battle!.plans[staged.actor].leader, DUKE_VIDAL_ID);
|
|
assert.equal(game.battle!.plans[staged.actor].weapon, staged.worthless.id);
|
|
const enemyWallet = player(game, 'emperor').spice;
|
|
game = resolve(game, staged.actor, staged.enemy);
|
|
// Duke6+normal1 ties Captain5+2: Ecaz attacks and wins. Hidden6 loses;
|
|
// visible7 loses to5+3. Giving Duke the trainer's selected-disc+3 would
|
|
// incorrectly win the last case. Every dial is paid/allocated natively.
|
|
assert.equal(game.lastBattleContext!.winner, player(game, expectedWinner).id);
|
|
assert.equal(player(game, 'emperor').spice, enemyWallet - (game.advanced ? dial : 0));
|
|
assert.equal(game.dukeVidal!.leader.dead, false);
|
|
assert.equal(game.dukeVidal!.leader.deaths, 0);
|
|
assert.equal(game.dukeVidal!.leader.usedAt, 'wind_pass');
|
|
assert.deepEqual([game.dukeVidal!.controller, game.dukeVidal!.source, game.dukeVidal!.acquiredTurn],
|
|
[null, null, null]);
|
|
assert.deepEqual(trainer(game), assignment);
|
|
assert.deepEqual(player(game, 'ecaz').leaders, native);
|
|
assert.equal(game.leaderSkills!.deck.includes('warmaster'), false);
|
|
game = finishAdvancedNativeSkillAftermath(reload(game));
|
|
assert.equal(game.battle, null);
|
|
assert.equal(game.decision, null);
|
|
assertEcazDukeSkillsCustody(game);
|
|
const refreshed = reload(game);
|
|
assert.deepEqual(refreshed.dukeVidal, game.dukeVidal);
|
|
assert.deepEqual(viewGame(refreshed, staged.actor).dukeVidal, viewGame(game, staged.actor).dukeVidal);
|
|
assert.deepEqual(trainer(refreshed), assignment);
|
|
}
|
|
});
|
|
|
|
void test(`${rules} real Duke weapon death pays printed6 only and never kills or returns his separate native trainer`, () => {
|
|
const staged = stageNativeEcazDukeSkillBattle(acquireNativeEcazSkillDuke(rules), true);
|
|
const assignment = structuredClone(trainer(staged.game));
|
|
const native = structuredClone(player(staged.game, 'ecaz').leaders);
|
|
const skills = structuredClone(staged.game.leaderSkills);
|
|
let game = openAdvancedNativeSkillBattle(reload(staged.game), staged.actor, staged.enemy, false);
|
|
game = act(game, staged.actor, {
|
|
type: 'battlePlan', leader: DUKE_VIDAL_ID, dial: 0, support: 0, weapon: staged.worthless.id,
|
|
});
|
|
game = act(game, staged.enemy, {
|
|
type: 'battlePlan', leader: staged.defender, dial: 0, support: 0, weapon: staged.weapon!.id,
|
|
});
|
|
const wallets = Object.fromEntries(game.players.map(p => [p.id, p.spice]));
|
|
game = resolve(game, staged.actor, staged.enemy);
|
|
assert.equal(game.lastBattleContext!.winner, staged.enemy);
|
|
assert.deepEqual(Object.fromEntries(game.players.map(p => [p.id, p.spice])),
|
|
{ ...wallets, [staged.enemy]: wallets[staged.enemy] + 6 });
|
|
assert.equal(game.dukeVidal!.leader.dead, true);
|
|
assert.equal(game.dukeVidal!.leader.deaths, 1);
|
|
assert.equal(game.dukeVidal!.leader.strength, 6);
|
|
assert.deepEqual([game.dukeVidal!.controller, game.dukeVidal!.source, game.dukeVidal!.acquiredTurn],
|
|
[null, null, null]);
|
|
assert.deepEqual(player(game, 'ecaz').leaders, native);
|
|
assert.deepEqual(game.leaderSkills, skills);
|
|
assert.deepEqual(trainer(game), assignment);
|
|
assert.equal(game.leaderSkills!.deck.includes('warmaster'), false);
|
|
game = finishAdvancedNativeSkillAftermath(reload(game));
|
|
assert.equal(game.battle, null);
|
|
assert.equal(game.decision, null);
|
|
assert.equal(player(game, 'emperor').hand.some(c => c.id === staged.weapon!.id), true,
|
|
'Native winner elects to keep the actual weapon.');
|
|
assert.equal(game.discard.some(c => c.id === staged.worthless.id), true,
|
|
'Native loser returns the real played Worthless to discard.');
|
|
assertEcazDukeSkillsCustody(game);
|
|
|
|
// Genuine next-turn phase transitions retain the actual battle death and
|
|
// original wallet; no fake Tanks placement or hand/deck replacement.
|
|
const deathTurn = game.turn;
|
|
for (let i = 0; (game.turn === deathTurn || game.phase !== 4 || game.phaseOpening || game.response || game.decision)
|
|
&& i < 1000; i++) {
|
|
if (game.decision?.kind === 'ecazPlacement')
|
|
game = act(game, game.decision.player, { type: 'decision', decline: true });
|
|
else game = advancedNativeStep(reload(game));
|
|
}
|
|
assert.equal(game.turn, deathTurn + 1);
|
|
assert.equal(game.phase, 4);
|
|
assert.equal(game.decision, null);
|
|
const funds = player(game, 'ecaz').spice;
|
|
const option = viewGame(game, staged.actor).revival.leaders.find(l => l.id === DUKE_VIDAL_ID)!;
|
|
assert.equal(option.normalCost, 5);
|
|
assert.equal(option.cost, 5);
|
|
assert.equal(option.affordable, true);
|
|
assert.equal(viewGame(game, staged.enemy).revival.leaders.some(l => l.id === DUKE_VIDAL_ID), false);
|
|
reject(game, staged.enemy, { type: 'reviveLeader', leader: DUKE_VIDAL_ID });
|
|
game = act(game, staged.actor, { type: 'reviveLeader', leader: DUKE_VIDAL_ID });
|
|
for (let i = 0; (game.response || game.pendingRevival) && i < 100; i++)
|
|
game = advancedNativeStep(reload(game));
|
|
assert.equal(game.pendingRevival, null);
|
|
assert.equal(player(game, 'ecaz').spice, funds - 5);
|
|
assert.equal(player(game, 'ecaz').leaderRevived, true);
|
|
assert.equal(game.dukeVidal!.leader.dead, false);
|
|
assert.equal(game.dukeVidal!.leader.deaths, 1);
|
|
assert.equal(game.dukeVidal!.leader.strength, 6);
|
|
assert.deepEqual([game.dukeVidal!.controller, game.dukeVidal!.source, game.dukeVidal!.acquiredTurn],
|
|
[null, null, null]);
|
|
assert.equal(game.leaderSkills!.offers[staged.actor], undefined);
|
|
assert.notEqual(game.decision?.kind, 'leaderSkillRevival');
|
|
assert.deepEqual(trainer(game), assignment);
|
|
assert.deepEqual(game.leaderSkills, skills);
|
|
assert.equal(viewGame(game, staged.actor).leaderSkills!.eligibleLeaders.some(l => l.id === DUKE_VIDAL_ID), false);
|
|
assertEcazDukeSkillsCustody(game);
|
|
const refreshed = reload(game);
|
|
assert.deepEqual(refreshed.dukeVidal, game.dukeVidal);
|
|
// A subsequent original action accepts the refreshed paid-revival custody.
|
|
assert.ok(refreshed.players.some(p => !refreshed.ready.includes(p.id)));
|
|
const actor = refreshed.players.find(p => !refreshed.ready.includes(p.id))!.id;
|
|
game = applyAction(refreshed, actor, { type: 'ready' });
|
|
assertEcazDukeSkillsCustody(game);
|
|
assert.deepEqual(trainer(game), assignment);
|
|
});
|
|
}
|