103 lines
5.7 KiB
TypeScript
103 lines
5.7 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { viewGame, type Action, type Game } from '../game/engine';
|
|
import { windowCardOpportunity } from '../game/window-card-play';
|
|
import { botActions } from '../game/bots';
|
|
import { DIFFICULTIES } from '../game/bot-profiles';
|
|
import { createAtreidesFullPlanWindowFixture, actAtreidesFullPlanWindow as act,
|
|
atreidesFullPlanWindowPlayer as player, atreidesFullPlanWindowInventory as inventory,
|
|
finishAtreidesFullPlanWindow as finish } from './fixture-atreides-full-plan-window';
|
|
|
|
/** The underlying battle and Karama are earned by native actions. Stage one
|
|
* original Truthtrance into Guild's hand by exchanging it with an undealt card;
|
|
* this does not claim the question card was earned through play. */
|
|
function fixture() {
|
|
const f = createAtreidesFullPlanWindowFixture(), game = structuredClone(f.game);
|
|
const asker = player(game, f.guild);
|
|
const index = game.deck.findIndex(card => card.effect === 'truthtrance'); assert.ok(index >= 0);
|
|
const original = asker.hand.findIndex(card => card.effect !== 'truthtrance'); assert.ok(original >= 0);
|
|
[game.deck[index], asker.hand[original]] = [asker.hand[original], game.deck[index]];
|
|
const truth = asker.hand[original].id;
|
|
assert.deepEqual(inventory(game), f.originalInventory);
|
|
const window = windowCardOpportunity(game, 'karamaAtreidesSpecial'); assert.ok(window);
|
|
return { ...f, game, asker: asker.id, truth, source: window.source };
|
|
}
|
|
function reject(game: Game, actor: string, action: Action) {
|
|
const before = structuredClone(game); assert.throws(() => act(game, actor, action)); assert.deepEqual(game, before);
|
|
}
|
|
function declare(f: ReturnType<typeof fixture>, source = f.game) {
|
|
let game = act(source, f.asker, { type: 'card', card: f.truth });
|
|
for (let i = 0; game.truthtrance?.stage === 'priority' && i < game.players.length; i++) {
|
|
const actor = game.players.find(p => !game.truthtrance!.passed.includes(p.id)); assert.ok(actor);
|
|
game = act(game, actor.id, { type: 'truthPass' });
|
|
}
|
|
assert.equal(game.truthtrance?.stage, 'ask'); return game;
|
|
}
|
|
function question(f: ReturnType<typeof fixture>, target = f.owner) {
|
|
return { kind: 'turnActions', target, turn: f.game.turn, mixed: { kind: 'action', action: {
|
|
kind: 'windowCardPlay', effect: 'karamaAtreidesSpecial', source: f.source,
|
|
} } };
|
|
}
|
|
function ask(f: ReturnType<typeof fixture>, source = f.game) {
|
|
return act(declare(f, source), f.asker, { type: 'truthAsk', question: question(f) });
|
|
}
|
|
|
|
void test('Advanced Atreides special Yes uses the original physical card and target-first plan', () => {
|
|
const f = fixture(), asked = ask(f);
|
|
assert.ok(viewGame(asked, f.owner).truthTurnActionAnswers?.includes('yes'));
|
|
assert.equal(viewGame(asked, f.opponent).truthTurnActionAnswers, null);
|
|
let game = act(asked, f.owner, { type: 'truthAnswer', answer: 'yes' });
|
|
const guide = viewGame(game, f.owner).turnActionGuide; assert.ok(guide);
|
|
assert.equal(guide.action.type, 'card'); assert.equal(guide.action.mode, 'special');
|
|
assert.equal(guide.action.card, f.karama);
|
|
for (const difficulty of DIFFICULTIES) {
|
|
const bot = viewGame(game, f.owner); bot.players.find(p => p.id === f.owner)!.bot = difficulty;
|
|
assert.deepEqual(botActions(bot)[0], guide.action);
|
|
}
|
|
game = act(game, f.owner, f.inspect);
|
|
assert.equal(player(game, f.owner).specialKaramaUsed, true);
|
|
assert.equal(game.turnActionPromises!.find(p => p.player === f.owner)!.fulfilled, true);
|
|
assert.equal(game.turnActionPromises!.find(p => p.player === f.owner)!.windowCards!.karamaAtreidesSpecial!.played, true);
|
|
assert.equal(game.discard.filter(card => card.id === f.karama).length, 1);
|
|
assert.equal(viewGame(game, f.asker).battle?.fullPlanInsight, null);
|
|
reject(game, f.owner, f.ownPlan);
|
|
game = act(game, f.opponent, f.opponentPlan);
|
|
assert.ok(viewGame(game, f.owner).battle?.fullPlanInsight);
|
|
assert.equal(viewGame(game, f.asker).battle?.fullPlanInsight, null);
|
|
game = finish(game, f);
|
|
assert.deepEqual(inventory(game), f.originalInventory);
|
|
});
|
|
|
|
void test('Advanced Atreides special No blocks that role but preserves the held card', () => {
|
|
const f = fixture(), asked = ask(f);
|
|
assert.ok(viewGame(asked, f.owner).truthTurnActionAnswers?.includes('no'));
|
|
const game = act(asked, f.owner, { type: 'truthAnswer', answer: 'no' });
|
|
reject(game, f.owner, f.inspect);
|
|
assert.ok(player(game, f.owner).hand.some(card => card.id === f.karama));
|
|
assert.notEqual(player(game, f.owner).specialKaramaUsed, true);
|
|
assert.deepEqual(inventory(game), f.originalInventory);
|
|
});
|
|
|
|
void test('a sealed target stays private and does not alter the availability of the same commitment', () => {
|
|
const f = fixture();
|
|
const sealed = act(f.game, f.opponent, f.opponentPlan);
|
|
assert.equal(viewGame(sealed, f.asker).battle?.plans[f.opponent], undefined);
|
|
const asked = ask(f, sealed);
|
|
assert.ok(viewGame(asked, f.owner).truthTurnActionAnswers?.includes('yes'));
|
|
let game = act(asked, f.owner, { type: 'truthAnswer', answer: 'yes' });
|
|
const before = structuredClone(game.battle!.plans);
|
|
game = act(game, f.owner, f.inspect);
|
|
assert.deepEqual(game.battle!.plans, before);
|
|
assert.ok(viewGame(game, f.owner).battle?.fullPlanInsight);
|
|
assert.equal(viewGame(game, f.asker).battle?.fullPlanInsight, null);
|
|
assert.deepEqual(inventory(game), f.originalInventory);
|
|
});
|
|
|
|
void test('Atreides source rejects a different respondent or changed battle event', () => {
|
|
const f = fixture(), game = declare(f);
|
|
reject(game, f.asker, { type: 'truthAsk', question: question(f, f.opponent) });
|
|
const forged = question(f);
|
|
forged.mixed.action.source = JSON.stringify(['karamaAtreidesSpecial', game.turn, f.owner, 'other-battle', f.territory]);
|
|
reject(game, f.asker, { type: 'truthAsk', question: forged });
|
|
});
|