118 lines
5.7 KiB
TypeScript
118 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 { botActions } from '../game/bots';
|
|
import { DIFFICULTIES } from '../game/bot-profiles';
|
|
import { windowCardOpportunity } from '../game/window-card-play';
|
|
import {
|
|
createTleilaxuSpecialKaramaFixture,
|
|
actTleilaxuSpecialKarama as act,
|
|
tleilaxuSpecialKaramaPlayer as player,
|
|
tleilaxuSpecialKaramaInventory as inventory,
|
|
} from './fixture-tleilaxu-special-karama';
|
|
|
|
function fixture() {
|
|
const f = createTleilaxuSpecialKaramaFixture();
|
|
const game = structuredClone(f.game), asker = f.observer;
|
|
const hand = player(game, asker).hand;
|
|
// Stage one original Truthtrance by exchanging physical cards with an
|
|
// existing card zone. Neither Karama nor Ghola is displaced or duplicated.
|
|
let truth = hand.find(card => card.effect === 'truthtrance');
|
|
if (!truth) {
|
|
const zones = [game.deck, game.discard, player(game, f.target).hand];
|
|
const zone = zones.find(cards => cards.some(card => card.effect === 'truthtrance'));
|
|
assert.ok(zone);
|
|
const index = zone.findIndex(card => card.effect === 'truthtrance');
|
|
assert.ok(hand.length > 0);
|
|
[hand[0], zone[index]] = [zone[index], hand[0]];
|
|
truth = hand[0];
|
|
}
|
|
assert.deepEqual(inventory(game), f.inventory);
|
|
const source = JSON.stringify(['karamaTleilaxuSpecial', game.turn, f.owner]);
|
|
assert.equal(windowCardOpportunity(game, 'karamaTleilaxuSpecial')?.source, source);
|
|
return { ...f, game, asker, truth: truth.id, windowSource: source };
|
|
}
|
|
type Fixture = ReturnType<typeof fixture>;
|
|
function question(f: Fixture, target = f.owner, source = f.windowSource) {
|
|
return { kind: 'turnActions', target, turn: f.game.turn, mixed: { kind: 'action', action: {
|
|
kind: 'windowCardPlay', effect: 'karamaTleilaxuSpecial', source,
|
|
} } };
|
|
}
|
|
function declare(f: Fixture) {
|
|
let game = act(f.game, f.asker, { type: 'card', card: f.truth });
|
|
for (let count = 0; game.truthtrance?.stage === 'priority' && count < game.players.length; count++) {
|
|
const actor = game.players.find(seat => !game.truthtrance!.passed.includes(seat.id));
|
|
assert.ok(actor);
|
|
game = act(game, actor.id, { type: 'truthPass' });
|
|
}
|
|
assert.equal(game.truthtrance?.stage, 'ask');
|
|
return game;
|
|
}
|
|
function ask(f: Fixture) {
|
|
return act(declare(f), f.asker, { type: 'truthAsk', question: question(f) });
|
|
}
|
|
function reject(game: Game, actor: string, action: Action) {
|
|
const before = structuredClone(game);
|
|
assert.throws(() => act(game, actor, action));
|
|
assert.deepEqual(game, before);
|
|
}
|
|
|
|
void test('Tleilaxu special Karama Yes spends the physical card and prevents a real revival', () => {
|
|
const f = fixture(), asked = ask(f);
|
|
assert.ok(viewGame(asked, f.owner).truthTurnActionAnswers?.includes('yes'));
|
|
assert.equal(viewGame(asked, f.target).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(seat => seat.id === f.owner)!.bot = difficulty;
|
|
assert.deepEqual(botActions(bot)[0], guide.action);
|
|
}
|
|
game = act(game, f.owner, f.special);
|
|
assert.equal(player(game, f.owner).specialKaramaUsed, true);
|
|
assert.equal(player(game, f.owner).hand.some(card => card.id === f.karama), false);
|
|
assert.equal(game.discard.filter(card => card.id === f.karama).length, 1);
|
|
assert.deepEqual(game.revivalPrevention, { player: f.target, turn: game.turn });
|
|
const promise = game.turnActionPromises!.find(row => row.player === f.owner);
|
|
assert.equal(promise?.windowCards?.karamaTleilaxuSpecial?.played, true);
|
|
assert.equal(promise?.fulfilled, true);
|
|
reject(game, f.target, f.ordinaryRevival);
|
|
reject(game, f.target, f.gholaAction);
|
|
assert.equal(viewGame(game, f.asker).players.find(seat => seat.id === f.owner)!.hand, undefined);
|
|
assert.deepEqual(viewGame(game, f.asker).turnActionPromises, []);
|
|
assert.deepEqual(inventory(game), f.inventory);
|
|
});
|
|
|
|
void test('Tleilaxu special Karama No prevents its use and preserves the 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.special);
|
|
reject(game, f.owner, { ...f.special, target: f.owner });
|
|
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.inventory);
|
|
});
|
|
|
|
void test('Tleilaxu may fulfill Yes by preventing its own revival', () => {
|
|
const f = fixture();
|
|
let game = act(ask(f), f.owner, { type: 'truthAnswer', answer: 'yes' });
|
|
game = act(game, f.owner, { ...f.special, target: f.owner });
|
|
assert.deepEqual(game.revivalPrevention, { player: f.owner, turn: game.turn });
|
|
assert.equal(game.turnActionPromises!.find(row => row.player === f.owner)?.fulfilled, true);
|
|
assert.equal(game.discard.filter(card => card.id === f.karama).length, 1);
|
|
assert.deepEqual(inventory(game), f.inventory);
|
|
});
|
|
|
|
void test('Tleilaxu special source rejects another respondent and forged turns or owners', () => {
|
|
const f = fixture(), game = declare(f);
|
|
reject(game, f.asker, { type: 'truthAsk', question: question(f, f.target) });
|
|
reject(game, f.asker, { type: 'truthAsk', question: question(f, f.owner,
|
|
JSON.stringify(['karamaTleilaxuSpecial', game.turn + 1, f.owner])) });
|
|
reject(game, f.asker, { type: 'truthAsk', question: question(f, f.owner,
|
|
JSON.stringify(['karamaTleilaxuSpecial', game.turn, f.target])) });
|
|
});
|