114 lines
6.4 KiB
TypeScript
114 lines
6.4 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { newPlayer, viewGame, type Action, type Game } from '../game/engine';
|
|
import { botActions } from '../game/bots';
|
|
import { DIFFICULTIES } from '../game/bot-profiles';
|
|
import { createTruthMovementChoamFixture } from './fixture-truth-movement-choam';
|
|
import {
|
|
actFutureBattleLeader as act, advanceFutureBattleLeader as advance,
|
|
futureBattleLeaderClean as clean, futureBattleLeaderRecords as records,
|
|
} from './fixture-truth-future-battle-leader';
|
|
import {
|
|
createTruthtranceRichesePromisesFixture, passTruthRicheseFreshDiscard,
|
|
finishTruthtranceRichesePromises, truthtranceRichesePromisesInventory,
|
|
} from './fixture-truthtrance-richese-promises';
|
|
|
|
function ask(game: Game, asker: string, card: string, target: string, leader: string): Game {
|
|
assert.ok(viewGame(game, asker).truthFutureBattleLeaderTargets.includes(target));
|
|
game = act(game, asker, { type: 'card', card });
|
|
for (let count = 0; game.truthtrance?.stage === 'priority' && count < game.players.length; count++) {
|
|
const player = game.players.find(seat => !game.truthtrance!.passed.includes(seat.id));
|
|
assert.ok(player);
|
|
game = act(game, player.id, { type: 'truthPass' });
|
|
}
|
|
assert.equal(game.truthtrance?.stage, 'ask');
|
|
return act(game, asker, { type: 'truthAsk', question: {
|
|
kind: 'futureBattleLeader', target, turn: game.turn, leader,
|
|
} } as unknown as Action);
|
|
}
|
|
|
|
for (const answer of ['yes', 'no'] as const) {
|
|
void test(`Basic CHOAM ${answer} binds its first real battle and constrains its native leader`, () => {
|
|
const f = createTruthMovementChoamFixture();
|
|
const leader = f.beforeShipment.players.find(player => player.id === f.target)!.leaders[0].id;
|
|
const other = f.beforeShipment.players.find(player => player.id === f.target)!.leaders[1].id;
|
|
let game = ask(f.beforeShipment, f.asker, f.truth, f.target, leader);
|
|
assert.ok(viewGame(game, f.target).truthFutureBattleLeaderAnswers?.includes(answer));
|
|
game = act(game, f.target, { type: 'truthAnswer', answer });
|
|
assert.equal(records(game)[0].status, 'waiting');
|
|
game = act(game, f.target, { type: 'ship', amount: 1, territory: 'arrakeen', sector: 10 });
|
|
game = advance(game, clean);
|
|
game = act(game, f.target, { type: 'endMovement' });
|
|
game = advance(game, state => clean(state) && state.phase === 5 && state.active === f.asker);
|
|
game = act(game, f.asker, { type: 'ship', amount: 1, territory: 'arrakeen', sector: 10 });
|
|
game = advance(game, clean);
|
|
game = act(game, f.asker, { type: 'endMovement' });
|
|
game = advance(game, state => clean(state) && state.phase === 6 && !!state.active && !state.battle);
|
|
const choice = viewGame(game, game.active!).battleChoices.find(row => row.territory === 'arrakeen');
|
|
assert.ok(choice);
|
|
game = act(game, choice.chooser, { type: 'chooseBattle', territory: choice.territory,
|
|
target: choice.attacker === choice.chooser ? choice.defender : choice.attacker });
|
|
game = advance(game, state => !!state.battle && clean(state) && !state.battle.preparation && state.battle.preLeader?.closed !== false);
|
|
assert.equal(records(game)[0].status, 'bound');
|
|
assert.equal(records(game)[0].battle, game.battle!.event);
|
|
const plan = (id: string): Action => ({ type: 'battlePlan', leader: id, dial: 0, support: 0, weapon: null, defense: null });
|
|
const invalid = plan(answer === 'yes' ? other : leader);
|
|
const before = structuredClone(game);
|
|
assert.throws(() => act(game, f.target, invalid));
|
|
assert.deepEqual(game, before);
|
|
game = act(game, f.target, plan(answer === 'yes' ? leader : other));
|
|
const emperorLeader = game.players.find(player => player.id === f.asker)!.leaders[0].id;
|
|
game = act(game, f.asker, plan(emperorLeader));
|
|
assert.equal(records(game)[0].status, 'settled');
|
|
});
|
|
}
|
|
|
|
void test('Basic Richese answer survives physical Truthtrance discard and Semuta continuation, then expires without a battle', () => {
|
|
const f = createTruthtranceRichesePromisesFixture();
|
|
const leader = f.game.players.find(player => player.id === f.target)!.leaders[0].id;
|
|
let game = ask(f.game, f.asker, f.truth, f.target, leader);
|
|
assert.ok(viewGame(game, f.target).truthFutureBattleLeaderAnswers?.includes('yes'));
|
|
for (const difficulty of DIFFICULTIES) {
|
|
const copy = structuredClone(game);
|
|
copy.players.find(player => player.id === f.target)!.bot = difficulty;
|
|
const reply = botActions(viewGame(copy, f.target)).find(action => action.type === 'truthAnswer');
|
|
assert.ok(reply, difficulty);
|
|
const answered = act(copy, f.target, reply);
|
|
assert.equal(records(answered)[0].status, 'waiting');
|
|
}
|
|
game = act(game, f.target, { type: 'truthAnswer', answer: 'yes' });
|
|
assert.equal(records(game)[0].status, 'waiting');
|
|
assert.equal(game.pendingTreacheryDiscard?.reaction?.stage, 'offer');
|
|
game = passTruthRicheseFreshDiscard(game);
|
|
assert.equal(records(game)[0].status, 'waiting');
|
|
assert.equal(game.discard.filter(card => card.id === f.truth).length, 1);
|
|
game = finishTruthtranceRichesePromises(game);
|
|
assert.equal(game.battle, null);
|
|
assert.equal(records(game)[0].status, 'expired');
|
|
assert.deepEqual(truthtranceRichesePromisesInventory(game), f.originalCards);
|
|
});
|
|
|
|
void test('Advanced Ecaz participation guards the future-battle question in the view and parser', () => {
|
|
const f = createTruthMovementChoamFixture();
|
|
// Controlled guard probe: this extra seat is staged on a genuine quiet
|
|
// CHOAM source; it does not claim to be an Ecaz setup or played journey.
|
|
let game = structuredClone(f.beforeShipment);
|
|
game.advanced = true;
|
|
game.expansions.push('ecaz');
|
|
game.players.push(newPlayer('ecaz-guard', 'Ecaz', 'ecaz'));
|
|
assert.deepEqual(viewGame(game, f.asker).truthFutureBattleLeaderTargets, []);
|
|
const leader = game.players.find(player => player.id === f.target)!.leaders[0].id;
|
|
game = act(game, f.asker, { type: 'card', card: f.truth });
|
|
for (let count = 0; game.truthtrance?.stage === 'priority' && count < game.players.length; count++) {
|
|
const player = game.players.find(seat => !game.truthtrance!.passed.includes(seat.id));
|
|
assert.ok(player);
|
|
game = act(game, player.id, { type: 'truthPass' });
|
|
}
|
|
assert.equal(game.truthtrance?.stage, 'ask');
|
|
const before = structuredClone(game);
|
|
assert.throws(() => act(game, f.asker, { type: 'truthAsk', question: {
|
|
kind: 'futureBattleLeader', target: f.target, turn: game.turn, leader,
|
|
} } as unknown as Action));
|
|
assert.deepEqual(game, before);
|
|
});
|