Files
Dune/tests/truthtrance-special-auction-knowledge.test.ts
T
Quintine a10e019b01
Verify and publish checkpoint container / container (push) Canceled after 0s
Allow Richese reveal during Emperor revival
2026-10-29 18:41:15 +11:00

94 lines
5.3 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { type Action, type Game, viewGame } from '../game/engine';
import { botActions } from '../game/bots';
import { DIFFICULTIES } from '../game/bot-profiles';
import { knowledgeFactAnswer, truthKnowledgeOf } from '../game/truthtrance-knowledge';
import { createTruthtranceRichesePromisesFixture, advanceTruthtranceRichesePromises as advance,
actTruthtranceRichesePromises as act, truthtranceRichesePromisesInventory as inventory } from './fixture-truthtrance-richese-promises';
const player = (g: Game, id: string) => {
const seat = g.players.find(candidate => candidate.id === id);
assert.ok(seat); return seat;
};
/** Genuine Advanced Richese/Atreides/Emperor setup, Black Market offer and
* successful inspection. Only the existing Truthtrance is moved between hands
* so a different faction can ask the native Atreides seat. */
function inspectedLot(method: 'silent' | 'onceAround' = 'silent') {
const fixture = createTruthtranceRichesePromisesFixture({ advanced: true });
let game = structuredClone(fixture.game);
const atreides = player(game, 'atreides'), emperor = player(game, 'emperor');
const truth = atreides.hand.find(card => card.effect === 'truthtrance'); assert.ok(truth);
atreides.hand = atreides.hand.filter(card => card.id !== truth.id);
emperor.hand.push(truth);
game = advance(game, state => state.decision?.kind === 'richeseBlackMarket');
const card = player(game, 'richese').hand.find(candidate => candidate.name === 'Shield'); assert.ok(card);
game = act(game, 'richese', { type:'decision', event:game.richeseBidding!.event,
card:card.id, method, ...(method === 'onceAround' ? { direction:'clockwise' } : {}) } as Action);
for (let count = 0; game.response && count < 20; count++) {
const respondent = game.players.find(candidate => !game.response!.passed.includes(candidate.id));
assert.ok(respondent); game = act(game, respondent.id, {type:'passResponse'});
}
assert.equal(game.response, null);
assert.equal(game.richeseAuction?.source, 'blackMarket');
assert.equal(game.richeseAuction?.cardId, card.id);
assert.equal(game.richesePeekKnown, true);
assert.deepEqual(inventory(game), fixture.originalCards);
return {game, card, truth};
}
function ask(game: Game, truth: string, name: string): Game {
game = act(game, 'emperor', {type:'card', card:truth});
for (let count = 0; game.truthtrance?.stage === 'priority' && count < game.players.length; count++) {
const respondent = game.players.find(candidate => !game.truthtrance!.passed.includes(candidate.id));
assert.ok(respondent); game = act(game, respondent.id, {type:'truthPass'});
}
return act(game, 'emperor', {type:'truthAsk', question:{kind:'fact', target:'atreides',
fact:{kind:'auctionPeek',name}}});
}
for (const method of ['silent', 'onceAround'] as const) void test(`native Advanced ${method} Black Market inspection gives Atreides a private Yes or No and all bots answer legally`, () => {
const {game, card, truth} = inspectedLot(method);
assert.equal(game.richeseAuction?.method, method);
for (const [name,answer] of [[card.name,'yes'],['Snooper','no']] as const) {
const question = ask(structuredClone(game), truth.id, name);
assert.equal(viewGame(question, 'atreides').truthAnswer, answer);
assert.equal(viewGame(question, 'emperor').truthAnswer, null);
assert.equal(viewGame(question, 'emperor').richeseAuction?.card, null);
assert.equal(viewGame(question, 'atreides').richeseAuction?.card?.id, card.id);
for (const difficulty of DIFFICULTIES) {
const view = viewGame(question, 'atreides');
const seat = view.players.find(candidate => candidate.id === 'atreides'); assert.ok(seat);
seat.bot = difficulty;
const actions = botActions(view);
assert.deepEqual(actions, [{type:'truthAnswer',answer}]);
const done = act(question, 'atreides', actions[0]);
assert.equal(done.truthHistory?.at(-1)?.answer, answer);
assert.equal(done.richeseAuction?.cardId, card.id);
assert.deepEqual(inventory(done), inventory(game));
}
}
});
void test('an uninspected, expired, missing or unauthorized Black Market observation is unknown', () => {
const {game, card} = inspectedLot();
const fact = {kind:'auctionPeek', name:card.name} as const;
const answer = (state: Game, id = 'atreides') => knowledgeFactAnswer(fact, truthKnowledgeOf(state, player(state,id)));
assert.equal(answer(game), 'yes');
assert.equal(answer(game, 'emperor'), 'unknown');
const uninspected = structuredClone(game); uninspected.richesePeekKnown = false;
assert.equal(answer(uninspected), 'unknown');
const ended = structuredClone(game); ended.richeseAuction!.outcome = {kind:'unbid'};
assert.equal(answer(ended), 'unknown');
const absent = structuredClone(game); absent.richeseAuction = null;
assert.equal(answer(absent), 'unknown');
const missing = structuredClone(game); missing.richeseOfferedCard = null;
player(missing, 'richese').hand = player(missing, 'richese').hand.filter(item => item.id !== card.id);
assert.equal(answer(missing), 'unknown');
const saved = structuredClone(game); saved.richeseOfferedCard = null;
assert.equal(answer(saved), 'yes', 'Older lot frames can still use the reserved physical seller card.');
const later = structuredClone(game); later.phase = 4;
assert.equal(answer(later), 'unknown');
});