383 lines
23 KiB
TypeScript
383 lines
23 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import {
|
|
applyAction, createGame, joinGame, newPlayer, normalizeAutomaticGame, viewGame,
|
|
type Action, type Game,
|
|
} from '../game/engine';
|
|
import { leaders, treacheryDeck, type Card } from '../game/cards';
|
|
import { traitorDeck } from '../game/traitors';
|
|
import { DUKE_VIDAL_ID } from '../game/duke-vidal';
|
|
import { newRevivalRules } from '../game/revival';
|
|
import { botActions } from '../game/bots';
|
|
import { DIFFICULTIES } from '../game/bot-profiles';
|
|
import {
|
|
advancedNativePlayer as player, advancedNativeStep, assertAdvancedNativeCustody,
|
|
completedAdvancedNativeSkillsGame, createAdvancedNativeSkillBattle,
|
|
openAdvancedNativeSkillBattle, rejectAdvancedNativeAction as reject,
|
|
stageAdvancedNativeSkillBattle, stageAdvancedTleilaxuForeignRevival,
|
|
} from './fixture-advanced-native-skills';
|
|
import { completedTleilaxuSkillsGame } from './tleilaxu-skills-fixture';
|
|
|
|
const reload = (game: Game): Game => JSON.parse(JSON.stringify(game)) as Game;
|
|
const act = (game: Game, actor: string, action: Action): Game => applyAction(reload(game), actor, action);
|
|
const wallets = (game: Game) => Object.fromEntries(game.players.map(p => [p.id, p.spice]));
|
|
const trainer = (game: Game, actor: string) => game.leaderSkills!.assignments.find(a => a.owner === actor)!;
|
|
const setup = () => completedAdvancedNativeSkillsGame({ family: 'tleilaxu', requestedSkill: 'warmaster' });
|
|
|
|
function custody(game: Game): void {
|
|
assertAdvancedNativeCustody(game);
|
|
const cards = [...game.deck, ...game.discard, ...game.players.flatMap(p => p.hand),
|
|
...(game.auction?.cards.slice(game.auction.index) ?? [])].map(c => c.id);
|
|
assert.deepEqual(cards.sort(), treacheryDeck(game.expansions).map(c => c.id).sort());
|
|
assert.deepEqual(game.players.flatMap(p => p.leaders.map(l => l.id)).sort(),
|
|
game.players.flatMap(p => [...leaders(p.faction).map(l => l.id),
|
|
...(game.advanced && p.faction === 'choam' ? ['choam-auditor'] : [])]).sort());
|
|
const traitors = [...game.traitorReserve!, ...game.players.flatMap(p => p.traitors),
|
|
...game.players.flatMap(p => (p.faceDancers ?? []).map(c => c.leader))];
|
|
assert.deepEqual(traitors.sort(), traitorDeck(game.players, true).sort());
|
|
assert.equal(new Set(traitors).size, traitors.length);
|
|
}
|
|
|
|
function take(game: Game, actor: string, matches: (card: Card) => boolean): Card {
|
|
const index = game.deck.findIndex(matches);
|
|
assert.ok(index >= 0, 'Transfer a physical card from the original native deck.');
|
|
const card = game.deck.splice(index, 1)[0];
|
|
game.players.find(p => p.id === actor)!.hand.push(card);
|
|
return card;
|
|
}
|
|
|
|
function settleRevival(state: Game): Game {
|
|
let game = state;
|
|
for (let i = 0; (game.response || game.pendingRevival) && i < 100; i++)
|
|
game = advancedNativeStep(reload(game));
|
|
assert.equal(game.pendingRevival, null);
|
|
assert.equal(game.response, null);
|
|
return game;
|
|
}
|
|
|
|
function aftermath(state: Game, stopAtFaceDance = false, stopAtCapture = false): Game {
|
|
let game = state;
|
|
for (let i = 0; (game.response || game.decision || game.battle || game.pendingTreacheryDiscard) && i < 100; i++) {
|
|
if (stopAtCapture && game.decision?.kind === 'captureOffer') return game;
|
|
if (game.decision?.kind === 'faceDance') {
|
|
if (stopAtFaceDance) return game;
|
|
game = act(game, game.decision.player, { type: 'decision', reveal: false });
|
|
} else if (game.decision?.kind === 'battleCards') {
|
|
game = act(game, game.decision.player, { type: 'decision', discard: [] });
|
|
} else game = advancedNativeStep(reload(game));
|
|
}
|
|
assert.equal(game.battle, null);
|
|
assert.equal(game.decision, null);
|
|
return game;
|
|
}
|
|
|
|
function resolve(game: Game, actor: string, target: string): Game {
|
|
game = act(game, actor, { type: 'traitorCall', call: false });
|
|
return act(game, target, { type: 'traitorCall', call: false });
|
|
}
|
|
|
|
/** A native weapon actually kills the original assigned trainer. No staged
|
|
* skill return or invented earned currency is used to remove its assignment. */
|
|
function killedNativeTrainer(): { game: Game; actor: string; leader: string } {
|
|
let game = createAdvancedNativeSkillBattle({ family: 'tleilaxu', requestedSkill: 'warmaster' });
|
|
const actor = player(game, 'tleilaxu').id;
|
|
const enemy = player(game, 'emperor').id;
|
|
const leader = trainer(game, actor).leader;
|
|
const poison = take(game, enemy, c => c.kind === 'poison');
|
|
const defender = player(game, 'emperor').leaders.find(l => l.id !== trainer(game, enemy).leader)!;
|
|
game = openAdvancedNativeSkillBattle(reload(game), actor, enemy);
|
|
game = act(game, actor, { type: 'battlePlan', dial: 0, support: 0, leader });
|
|
game = act(game, enemy, { type: 'battlePlan', dial: 0, support: 0, leader: defender.id, weapon: poison.id });
|
|
game = aftermath(resolve(game, actor, enemy));
|
|
assert.equal(player(game, 'tleilaxu').leaders.find(l => l.id === leader)!.dead, true);
|
|
assert.equal(game.leaderSkills!.assignments.some(a => a.owner === actor), false);
|
|
assert.equal(game.leaderSkills!.deck.filter(s => s === 'warmaster').length, 1);
|
|
custody(game);
|
|
return { game, actor, leader };
|
|
}
|
|
|
|
void test('native foreign revival keeps original custody, half-price payment and no skill draw even without a trainer', () => {
|
|
for (const hasTrainer of [true, false]) {
|
|
const original = hasTrainer ? setup() : killedNativeTrainer().game;
|
|
const staged = stageAdvancedTleilaxuForeignRevival(original);
|
|
assert.deepEqual(wallets(staged.game), wallets(original));
|
|
const before = reload(staged.game);
|
|
const skillState = structuredClone(before.leaderSkills);
|
|
const disc = player(before, 'emperor').leaders.find(l => l.id === staged.leader)!;
|
|
const price = Math.ceil(disc.strength / 2);
|
|
const game = settleRevival(act(before, staged.actor, { type: 'reviveForeignGhola', leader: disc.id }));
|
|
const returned = player(game, 'emperor').leaders.find(l => l.id === disc.id)!;
|
|
assert.equal(returned.dead, false);
|
|
assert.equal(returned.gholaBy, staged.actor);
|
|
assert.equal(player(game, 'tleilaxu').leaders.some(l => l.id === disc.id), false);
|
|
assert.equal(game.players.flatMap(p => p.leaders).filter(l => l.id === disc.id).length, 1);
|
|
assert.deepEqual(wallets(game), { ...wallets(before), [staged.actor]: player(before, 'tleilaxu').spice - price });
|
|
assert.equal(player(game, 'tleilaxu').leaderRevived, true);
|
|
assert.deepEqual(game.leaderSkills, skillState);
|
|
assert.equal(viewGame(game, staged.actor).leaderSkills!.offer, null);
|
|
assert.equal(game.decision, null);
|
|
assert.deepEqual(normalizeAutomaticGame(reload(game)), game);
|
|
reject(game, staged.actor, { type: 'reviveForeignGhola', leader: disc.id });
|
|
assert.deepEqual(before, staged.game, 'Revival cannot mutate its caller-owned input.');
|
|
custody(game);
|
|
}
|
|
});
|
|
|
|
void test('a separate native Warmaster gives only its normal +1 to the selected original foreign ghola', () => {
|
|
const staged = stageAdvancedTleilaxuForeignRevival(setup());
|
|
const revived = settleRevival(act(staged.game, staged.actor, { type: 'reviveForeignGhola', leader: staged.leader }));
|
|
const assignment = structuredClone(trainer(revived, staged.actor));
|
|
const foreign = player(revived, 'emperor').leaders.find(l => l.id === staged.leader)!;
|
|
assert.equal(foreign.strength, 2);
|
|
for (const [visible, dial, expectedWinner] of [
|
|
[true, 0, staged.actor], [false, 0, staged.originalOwner], [true, 1, staged.originalOwner],
|
|
] as const) {
|
|
let game = stageAdvancedNativeSkillBattle(revived, staged.actor, staged.originalOwner);
|
|
const worthless = take(game, staged.actor, c => c.kind === 'worthless');
|
|
const defender = player(game, 'emperor').leaders.find(l => l.strength === 3 && !l.dead)!;
|
|
const funds = wallets(game);
|
|
game = openAdvancedNativeSkillBattle(reload(game), staged.actor, staged.originalOwner, !visible);
|
|
game = act(game, staged.actor, { type: 'battlePlan', dial: 0, support: 0, leader: foreign.id, weapon: worthless.id });
|
|
game = act(game, staged.originalOwner, { type: 'battlePlan', dial, support: dial, leader: defender.id });
|
|
game = aftermath(resolve(game, staged.actor, staged.originalOwner));
|
|
// 2+1 beats/ties 3; hiding the normal skill loses. 2+1 loses to 3+1,
|
|
// whereas incorrectly giving the trainer's selected-disc +3 would win.
|
|
assert.equal(game.lastBattleContext!.winner, expectedWinner);
|
|
assert.deepEqual(trainer(game, staged.actor), assignment);
|
|
assert.equal(player(game, 'emperor').leaders.find(l => l.id === foreign.id)!.gholaBy, staged.actor);
|
|
assert.deepEqual(wallets(game), { ...funds, [staged.originalOwner]: funds[staged.originalOwner] - dial });
|
|
custody(game);
|
|
}
|
|
});
|
|
|
|
void test('foreign ghola death preserves the native trainer; negotiated original-owner buyback clears control exactly once', () => {
|
|
const staged = stageAdvancedTleilaxuForeignRevival(setup());
|
|
const revived = settleRevival(act(staged.game, staged.actor, { type: 'reviveForeignGhola', leader: staged.leader }));
|
|
const skills = structuredClone(revived.leaderSkills);
|
|
let game = stageAdvancedNativeSkillBattle(revived, staged.actor, staged.originalOwner);
|
|
const poison = take(game, staged.originalOwner, c => c.kind === 'poison');
|
|
const defender = player(game, 'emperor').leaders.find(l => !l.dead && l.id !== trainer(game, staged.originalOwner).leader)!;
|
|
game = openAdvancedNativeSkillBattle(reload(game), staged.actor, staged.originalOwner, false);
|
|
game = act(game, staged.actor, { type: 'battlePlan', dial: 0, support: 0, leader: staged.leader });
|
|
game = act(game, staged.originalOwner, { type: 'battlePlan', dial: 0, support: 0, leader: defender.id, weapon: poison.id });
|
|
game = aftermath(resolve(game, staged.actor, staged.originalOwner));
|
|
const dead = player(game, 'emperor').leaders.find(l => l.id === staged.leader)!;
|
|
assert.equal(dead.dead, true);
|
|
assert.equal(dead.gholaBy, staged.actor);
|
|
assert.deepEqual(game.leaderSkills, skills);
|
|
custody(game);
|
|
// Controlled next Revival boundary, retaining actual battle deaths, cards,
|
|
// bounty and wallets. This is not claimed to be a played phase history.
|
|
Object.assign(game, { phase: 4, active: null, ready: [], decision: null, response: null,
|
|
phaseOpening: null, revivalRules: newRevivalRules() });
|
|
for (const p of game.players) p.leaderRevived = false;
|
|
const before = reload(game), funds = wallets(game);
|
|
reject(game, staged.originalOwner, { type: 'reviveLeader', leader: staged.leader });
|
|
game = act(game, staged.originalOwner, { type: 'requestLeaderRevival', leader: staged.leader });
|
|
game = act(game, staged.actor, { type: 'quoteLeaderRevival', target: staged.originalOwner, amount: 1 });
|
|
game = settleRevival(act(game, staged.originalOwner, { type: 'acceptLeaderRevival' }));
|
|
const ownAgain = player(game, 'emperor').leaders.find(l => l.id === staged.leader)!;
|
|
assert.equal(ownAgain.dead, false);
|
|
assert.equal(ownAgain.gholaBy, undefined);
|
|
assert.deepEqual(wallets(game), { ...funds, [staged.originalOwner]: funds[staged.originalOwner] - 1,
|
|
[staged.actor]: funds[staged.actor] + 1 });
|
|
assert.deepEqual(game.leaderSkills, before.leaderSkills, 'Its surviving original trainer prevents a replacement draw.');
|
|
reject(game, staged.originalOwner, { type: 'acceptLeaderRevival' });
|
|
custody(game);
|
|
});
|
|
|
|
void test('own native trainer revival still offers an optional physical card after a no-skill foreign revival', () => {
|
|
const killed = killedNativeTrainer();
|
|
const staged = stageAdvancedTleilaxuForeignRevival(killed.game);
|
|
const foreignRevived = settleRevival(act(staged.game, staged.actor, { type: 'reviveForeignGhola', leader: staged.leader }));
|
|
const own = player(foreignRevived, 'tleilaxu').leaders.find(l => l.id === killed.leader)!;
|
|
const previousDeck = [...foreignRevived.leaderSkills!.deck];
|
|
const funds = wallets(foreignRevived);
|
|
const offered = settleRevival(act(foreignRevived, staged.actor, { type: 'reviveLeader', leader: own.id }));
|
|
assert.equal(offered.decision?.kind, 'leaderSkillRevival');
|
|
const offer = offered.leaderSkills!.offers[staged.actor];
|
|
assert.equal(offer.leader, own.id);
|
|
assert.deepEqual(offer.cards, []);
|
|
assert.deepEqual(offered.leaderSkills!.deck, previousDeck);
|
|
assert.deepEqual(wallets(offered), { ...funds, [staged.actor]: funds[staged.actor] - Math.ceil(own.strength / 2) });
|
|
assert.equal(player(offered, 'emperor').leaders.find(l => l.id === staged.leader)!.gholaBy, staged.actor);
|
|
const declined = act(offered, staged.actor, { type: 'leaderSkill', event: offer.event, mode: 'decline' });
|
|
assert.deepEqual(declined.leaderSkills!.deck, previousDeck);
|
|
assert.equal(declined.leaderSkills!.offers[staged.actor], undefined);
|
|
const drawn = act(offered, staged.actor, { type: 'leaderSkill', event: offer.event, mode: 'draw' });
|
|
const choice = drawn.leaderSkills!.offers[staged.actor];
|
|
assert.equal(choice.cards.length, 2);
|
|
assert.equal(drawn.leaderSkills!.deck.length, previousDeck.length - 2);
|
|
custody(drawn);
|
|
reject(drawn, staged.actor, { type: 'leaderSkill', event: choice.event, skill: choice.cards[0], leader: staged.leader });
|
|
const trained = act(drawn, staged.actor, { type: 'leaderSkill', event: choice.event, skill: choice.cards[0], leader: own.id });
|
|
assert.equal(trainer(trained, staged.actor).leader, own.id);
|
|
assert.equal(trainer(trained, staged.actor).skill, choice.cards[0]);
|
|
custody(declined); custody(trained);
|
|
for (const difficulty of DIFFICULTIES) {
|
|
let game = reload(offered);
|
|
for (let i = 0; game.leaderSkills!.offers[staged.actor] && i < 3; i++) {
|
|
const view = viewGame(game, staged.actor);
|
|
view.players.find(p => p.id === staged.actor)!.bot = difficulty;
|
|
const action = botActions(view)[0];
|
|
assert.ok(action, difficulty);
|
|
game = act(game, staged.actor, action);
|
|
}
|
|
assert.equal(game.leaderSkills!.offers[staged.actor], undefined, difficulty);
|
|
const assigned = game.leaderSkills!.assignments.find(a => a.owner === staged.actor);
|
|
if (assigned) assert.equal(assigned.leader, own.id, difficulty);
|
|
assert.equal(game.leaderSkills!.assignments.some(a => a.leader === staged.leader), false, difficulty);
|
|
assert.deepEqual(wallets(game), wallets(offered));
|
|
custody(game);
|
|
}
|
|
});
|
|
|
|
void test('native Face Dance after an actual Advanced battle kills and returns the original trained card', () => {
|
|
let game = createAdvancedNativeSkillBattle({ family: 'tleilaxu', skillOwner: 'atreides', requestedSkill: 'warmaster' });
|
|
const actor = player(game, 'atreides').id, enemy = player(game, 'emperor').id;
|
|
const controller = player(game, 'tleilaxu').id;
|
|
const trained = trainer(game, actor);
|
|
const leader = trained.leader;
|
|
const dancers = player(game, 'tleilaxu').faceDancers!;
|
|
if (!dancers.some(c => c.leader === leader)) {
|
|
const card = dancers[0];
|
|
const reserve = game.traitorReserve!.indexOf(leader);
|
|
if (reserve >= 0) game.traitorReserve![reserve] = card.leader;
|
|
else {
|
|
const holder = game.players.find(p => p.traitors.includes(leader))!;
|
|
assert.ok(holder);
|
|
holder.traitors[holder.traitors.indexOf(leader)] = card.leader;
|
|
}
|
|
card.leader = leader;
|
|
} // Conserved physical identity swap, not a fabricated extra Face Dancer.
|
|
custody(game);
|
|
const defender = player(game, 'emperor').leaders.find(l => l.strength === 2)!;
|
|
game = openAdvancedNativeSkillBattle(reload(game), actor, enemy);
|
|
game = act(game, actor, { type: 'battlePlan', dial: 1, support: 1, leader });
|
|
game = act(game, enemy, { type: 'battlePlan', dial: 0, support: 0, leader: defender.id });
|
|
game = aftermath(resolve(game, actor, enemy), true);
|
|
assert.equal(game.lastBattleContext!.winner, actor);
|
|
assert.equal(game.decision?.kind, 'faceDance');
|
|
const funds = wallets(game), reserves = player(game, 'tleilaxu').reserves;
|
|
game = act(game, controller, { type: 'decision', reveal: true, sources: { reserves: 1 }, sector: 14 });
|
|
assert.equal(player(game, 'atreides').leaders.find(l => l.id === leader)!.dead, true);
|
|
assert.equal(game.leaderSkills!.assignments.some(a => a.leader === leader), false);
|
|
assert.equal(game.leaderSkills!.deck.filter(s => s === 'warmaster').length, 1);
|
|
assert.equal(player(game, 'tleilaxu').forces['wind_pass:14'], 1);
|
|
assert.equal(player(game, 'tleilaxu').reserves, reserves - 1);
|
|
assert.deepEqual(wallets(game), funds);
|
|
assert.equal(player(game, 'tleilaxu').faceDancers!.find(c => c.leader === leader)!.revealed, true);
|
|
custody(game);
|
|
});
|
|
|
|
void test('actual Harkonnen capture keeps a foreign ghola in its original roster and battle use returns Tleilaxu control', () => {
|
|
const original = completedAdvancedNativeSkillsGame({
|
|
family: 'tleilaxu', requestedSkill: 'warmaster', opponents: ['harkonnen', 'emperor'],
|
|
});
|
|
const staged = stageAdvancedTleilaxuForeignRevival(original);
|
|
const revived = settleRevival(act(staged.game, staged.actor, { type: 'reviveForeignGhola', leader: staged.leader }));
|
|
const captor = player(revived, 'harkonnen').id;
|
|
const skills = structuredClone(revived.leaderSkills);
|
|
let game = stageAdvancedNativeSkillBattle(revived, staged.actor, captor);
|
|
// Conserved controlled eligibility: all other living Tleilaxu discs have
|
|
// already been used elsewhere. Only the genuinely revived foreign disc is
|
|
// eligible for this battle's random capture, without changing the card pool.
|
|
for (const leader of player(game, 'tleilaxu').leaders)
|
|
if (!leader.dead) leader.usedAt = 'arrakeen';
|
|
const native = player(game, 'harkonnen').leaders.find(l => l.id !== trainer(game, captor).leader)!;
|
|
game = openAdvancedNativeSkillBattle(reload(game), staged.actor, captor, false);
|
|
game = act(game, staged.actor, { type: 'battlePlan', dial: 0, support: 0, leader: staged.leader });
|
|
game = act(game, captor, { type: 'battlePlan', dial: 0, support: 0, leader: native.id });
|
|
game = aftermath(resolve(game, staged.actor, captor), false, true);
|
|
assert.equal(game.lastBattleContext!.winner, captor);
|
|
assert.equal(game.decision?.kind, 'captureOffer');
|
|
game = act(game, captor, { type: 'decision', accept: true });
|
|
for (let i = 0; game.response && i < 100; i++) game = advancedNativeStep(reload(game));
|
|
assert.equal(game.decision?.kind, 'capturedLeader');
|
|
if (game.decision?.kind !== 'capturedLeader') throw new Error('Expected actual captured foreign leader.');
|
|
assert.equal(game.decision.leader, staged.leader);
|
|
assert.equal(game.decision.owner, staged.originalOwner);
|
|
assert.equal(game.decision.controller, staged.actor);
|
|
const captive = player(game, 'emperor').leaders.find(l => l.id === staged.leader)!;
|
|
assert.equal(captive.capturedBy, captor);
|
|
assert.equal(captive.gholaBy, staged.actor);
|
|
assert.equal(captive.concealed!.controller, staged.actor);
|
|
assert.deepEqual(game.leaderSkills, skills);
|
|
custody(game);
|
|
const choice = reload(game), funds = wallets(game);
|
|
const executed = aftermath(act(choice, captor, { type: 'decision', mode: 'execute' }));
|
|
const dead = player(executed, 'emperor').leaders.find(l => l.id === staged.leader)!;
|
|
assert.equal(dead.dead, true);
|
|
assert.equal(dead.capturedBy, undefined);
|
|
assert.equal(dead.gholaBy, staged.actor);
|
|
assert.equal(dead.concealed!.controller, staged.actor);
|
|
assert.equal(dead.concealed!.dead, false, 'The original hidden living snapshot is retained after execution.');
|
|
assert.deepEqual(wallets(executed), { ...funds, [captor]: funds[captor] + 2 });
|
|
assert.deepEqual(executed.leaderSkills, skills);
|
|
custody(executed);
|
|
game = aftermath(act(choice, captor, { type: 'decision', mode: 'keep' }));
|
|
assert.equal(player(game, 'emperor').leaders.find(l => l.id === staged.leader)!.capturedBy, captor);
|
|
assert.deepEqual(wallets(game), funds);
|
|
game = stageAdvancedNativeSkillBattle(game, captor, staged.originalOwner);
|
|
const defender = player(game, 'emperor').leaders.find(l => l.strength === 3)!;
|
|
game = openAdvancedNativeSkillBattle(reload(game), captor, staged.originalOwner, false);
|
|
// The captured foreign disc is genuinely selectable by Harkonnen, not by
|
|
// either its original Emperor owner or its pre-capture Tleilaxu controller.
|
|
reject(game, staged.originalOwner, { type: 'battlePlan', dial: 0, support: 0, leader: staged.leader });
|
|
game = act(game, captor, { type: 'battlePlan', dial: 0, support: 0, leader: staged.leader });
|
|
game = act(game, staged.originalOwner, { type: 'battlePlan', dial: 0, support: 0, leader: defender.id });
|
|
game = aftermath(resolve(game, captor, staged.originalOwner));
|
|
assert.equal(game.lastBattleContext!.winner, staged.originalOwner);
|
|
const returned = player(game, 'emperor').leaders.find(l => l.id === staged.leader)!;
|
|
assert.equal(returned.dead, false);
|
|
assert.equal(returned.capturedBy, undefined);
|
|
assert.equal(returned.concealed, undefined);
|
|
assert.equal(returned.gholaBy, staged.actor);
|
|
assert.equal(player(game, 'tleilaxu').leaders.some(l => l.id === staged.leader), false);
|
|
assert.deepEqual(wallets(game), funds);
|
|
assert.deepEqual(game.leaderSkills, skills);
|
|
assert.deepEqual(normalizeAutomaticGame(reload(game)), game);
|
|
custody(game);
|
|
});
|
|
|
|
void test('foreign revival retains Basic, controller, full-pool, capture, Auditor, Duke and foreign-training boundaries', () => {
|
|
const staged = stageAdvancedTleilaxuForeignRevival(setup());
|
|
const action: Action = { type: 'reviveForeignGhola', leader: staged.leader };
|
|
reject(staged.game, staged.originalOwner, action);
|
|
const full = reload(staged.game);
|
|
player(full, 'tleilaxu').leaders.find(l => l.id === staged.ownDead)!.dead = false;
|
|
reject(full, staged.actor, action);
|
|
const captured = reload(staged.game);
|
|
player(captured, 'emperor').leaders.find(l => l.id === staged.leader)!.capturedBy = staged.actor;
|
|
reject(captured, staged.actor, action);
|
|
reject(staged.game, staged.actor, { type: 'reviveForeignGhola', leader: DUKE_VIDAL_ID });
|
|
const basic = completedTleilaxuSkillsGame();
|
|
Object.assign(basic, { phase: 4, phaseOpening: null, response: null, decision: null });
|
|
player(basic, 'emperor').leaders[1].dead = true;
|
|
reject(basic, player(basic, 'tleilaxu').id, { type: 'reviveForeignGhola', leader: player(basic, 'emperor').leaders[1].id });
|
|
const advanced = settleRevival(act(staged.game, staged.actor, action));
|
|
const assignment = reload(advanced);
|
|
const native = trainer(assignment, staged.actor);
|
|
native.leader = staged.leader;
|
|
reject(assignment, staged.actor, { type: 'ready' });
|
|
const wrongController = reload(advanced);
|
|
player(wrongController, 'emperor').leaders.find(l => l.id === staged.leader)!.gholaBy = staged.originalOwner;
|
|
reject(wrongController, staged.actor, { type: 'ready' });
|
|
const capturedGhola = reload(advanced);
|
|
player(capturedGhola, 'emperor').leaders.find(l => l.id === staged.leader)!.capturedBy = staged.actor;
|
|
reject(capturedGhola, staged.actor, { type: 'ready' });
|
|
const basicGhola = reload(advanced);
|
|
basicGhola.advanced = false;
|
|
reject(basicGhola, staged.actor, { type: 'ready' });
|
|
const lobby = createGame('ADVTLEIAUD', newPlayer('human-t', 'Tleilaxu', 'tleilaxu'), true, ['ix', 'choam']);
|
|
joinGame(lobby, newPlayer('human-c', 'CHOAM', 'choam'));
|
|
joinGame(lobby, newPlayer('human-e', 'Emperor', 'emperor'));
|
|
const admitted = completedAdvancedNativeSkillsGame({ family: 'tleilaxu', requestedSkill: 'warmaster', initial: lobby });
|
|
assert.deepEqual(admitted.players.map(p => p.id), ['human-t', 'human-c', 'human-e']);
|
|
const audit = stageAdvancedTleilaxuForeignRevival(admitted);
|
|
const auditor = player(audit.game, 'choam').leaders.find(l => l.id === 'choam-auditor')!;
|
|
auditor.dead = true; auditor.deaths = 1;
|
|
reject(audit.game, audit.actor, { type: 'reviveForeignGhola', leader: auditor.id });
|
|
custody(audit.game);
|
|
});
|