85 lines
4.8 KiB
TypeScript
85 lines
4.8 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { applyAction, viewGame, type Game } from '../game/engine';
|
|
import { botActions } from '../game/bots';
|
|
import { DIFFICULTIES } from '../game/bot-profiles';
|
|
import { knowledgeGame, knowledgePhysical, rejectKnowledge } from './truthtrance-knowledge-fixture';
|
|
|
|
/** Actual Basic setup and locked prediction; acquisition of Truthtrance is
|
|
* staged by the shared fixture while preserving the physical card inventory. */
|
|
function ask(target: string, text: string, scope: 'fact' | 'currentTurn' = 'fact') {
|
|
let game = knowledgeGame();
|
|
const card = game.players[0].hand.find(candidate => candidate.effect === 'truthtrance'); assert.ok(card);
|
|
game = applyAction(game, 'a', { type: 'card', card: card.id });
|
|
while (game.truthtrance?.stage === 'priority') {
|
|
const player = game.players.find(candidate => !game.truthtrance!.passed.includes(candidate.id)); assert.ok(player);
|
|
game = applyAction(game, player.id, { type: 'truthPass' });
|
|
}
|
|
return applyAction(game, 'a', { type: 'truthAsk', question: { kind: 'freeform', target, scope, text } });
|
|
}
|
|
function question(game: Game) {
|
|
const value = game.truthtrance?.question;
|
|
assert.equal(value?.kind, 'freeform');
|
|
if (value?.kind !== 'freeform') throw Error('Expected a freeform question');
|
|
return value;
|
|
}
|
|
|
|
for (const row of [
|
|
{ name: 'all three AND clauses are true', target: 'b', answer: 'yes',
|
|
text: 'Did you predict Guild and do you have at least 3 forces in your reserves and do you have at least 0 spice?' },
|
|
{ name: 'a false prediction makes AND false', target: 'b', answer: 'no',
|
|
text: 'Did you predict Harkonnen and do you have at least 3 forces in your reserves?' },
|
|
{ name: 'an unavailable prediction leaves AND unknown', target: 'o', answer: 'unknown',
|
|
text: 'Did you predict Guild and do you have at least 0 forces in your reserves?' },
|
|
{ name: 'a true force clause makes OR true despite unknown knowledge', target: 'o', answer: 'yes',
|
|
text: 'Did you predict Guild or do you have at least 0 forces in your reserves?' },
|
|
{ name: 'both OR clauses are false', target: 'b', answer: 'no',
|
|
text: 'Did you predict Harkonnen or do you have at least 100 forces in your reserves?' },
|
|
{ name: 'unavailable knowledge leaves OR unknown', target: 'o', answer: 'unknown',
|
|
text: 'Did you predict Guild or do you have at least 100 forces in your reserves?' },
|
|
] as const) {
|
|
void test(`V7 ${row.name} publishes only the checked aggregate`, () => {
|
|
const game = ask(row.target, row.text), physical = knowledgePhysical(game);
|
|
assert.equal(question(game).interpreted?.version, 7);
|
|
assert.equal(viewGame(game, row.target).truthAnswer, row.answer);
|
|
for (const observer of game.players.filter(player => player.id !== row.target)) {
|
|
const view = viewGame(game, observer.id);
|
|
assert.equal(view.truthAnswer, null);
|
|
if (observer.id !== 'b')
|
|
assert.equal('prediction' in view.players.find(player => player.id === 'b')!, false);
|
|
}
|
|
for (const difficulty of DIFFICULTIES) {
|
|
const view = viewGame(game, row.target);
|
|
view.players.find(player => player.id === row.target)!.bot = difficulty;
|
|
assert.deepEqual(botActions(view), [{ type: 'truthAnswer', answer: row.answer }]);
|
|
}
|
|
for (const answer of (['yes', 'no', 'unknown'] as const).filter(answer => answer !== row.answer))
|
|
rejectKnowledge(game, row.target, { type: 'truthAnswer', answer });
|
|
const restored = JSON.parse(JSON.stringify(game)) as Game;
|
|
const done = applyAction(restored, row.target, { type: 'truthAnswer', answer: row.answer });
|
|
if (row.answer === 'unknown') assert.equal(done.truthtrance?.stage, 'unknown');
|
|
else assert.equal(done.truthHistory!.at(-1)!.answer, row.answer);
|
|
assert.deepEqual(knowledgePhysical(done), physical);
|
|
});
|
|
}
|
|
|
|
void test('old unstamped and current-turn compound questions remain manually managed', () => {
|
|
const text = 'Did you predict Guild and do you have at least 3 forces in your reserves?';
|
|
const future = ask('b', text, 'currentTurn');
|
|
assert.equal(question(future).interpreted, undefined);
|
|
const old = ask('b', text);
|
|
delete question(old).interpreted;
|
|
assert.equal(viewGame(old, 'b').truthAnswer, null);
|
|
const done = applyAction(old, 'b', { type: 'truthAnswer', answer: 'no' });
|
|
assert.equal(done.truthHistory!.at(-1)!.answer, 'no');
|
|
});
|
|
|
|
void test('changing a V7 receipt cannot alter the checked aggregate', () => {
|
|
const game = ask('b', 'Did you predict Guild and do you have at least 3 forces in your reserves?');
|
|
const value = question(game); assert.equal(value.interpreted?.version, 7);
|
|
if (!value.interpreted) throw Error('Expected a recognized receipt');
|
|
value.interpreted.fact = { kind: 'prediction', field: 'faction', faction: 'harkonnen' };
|
|
assert.throws(() => viewGame(game, 'b'));
|
|
assert.throws(() => applyAction(game, 'b', { type: 'truthAnswer', answer: 'yes' }));
|
|
});
|