94 lines
5.3 KiB
TypeScript
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');
|
|
});
|