Files
Dune/tests/truth-tleilaxu-special-karama-runtime.test.ts
T
Quintine cfccfe5acb
Verify and publish checkpoint container / container (push) Successful in 11m6s
Prototype Tleilaxu special Karama Truthtrance
2026-10-29 18:35:24 +11:00

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])) });
});