62 lines
3.8 KiB
TypeScript
62 lines
3.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 { createTruthBidFixture, truthBidInventory } from './fixture-truth-bid';
|
|
|
|
/** Genuine Harkonnen two-Truth opening at the original current ordinary lot.
|
|
* Both questions use distinct actually dealt cards; no ledger is installed. */
|
|
function ask(game: Game, asker: string, card: string, question: unknown) {
|
|
game = applyAction(game, asker, { type: 'card', card });
|
|
while (game.truthtrance?.stage === 'priority') {
|
|
const actor = game.players.find(player => !game.truthtrance!.passed.includes(player.id)); assert.ok(actor);
|
|
game = applyAction(game, actor.id, { type: 'truthPass' });
|
|
}
|
|
return applyAction(game, asker, { type: 'truthAsk', question });
|
|
}
|
|
function questions(f: ReturnType<typeof createTruthBidFixture>, minimum: number) {
|
|
return {
|
|
bid: { kind: 'bid', target: f.target, turn: f.game.turn, lot: f.game.auction!.index + 1, minimum: 4 },
|
|
travel: { kind: 'travel', target: f.target, turn: f.game.turn, mixed: { kind: 'and', terms: [
|
|
{ kind: 'action', action: { kind: 'ship', territory: 'arrakeen', minimum } },
|
|
{ kind: 'action', action: { kind: 'move', territory: 'carthag', minimum } },
|
|
] } },
|
|
};
|
|
}
|
|
void test('native compatible bid and travel answers in either order offer the required declaration before any pass', () => {
|
|
for (const bidFirst of [true, false]) {
|
|
const f = createTruthBidFixture({ window: 'active' }), q = questions(f, 1);
|
|
let game = ask(f.game, f.asker, f.truths[0], bidFirst ? q.bid : q.travel);
|
|
game = applyAction(game, f.target, { type: 'truthAnswer', answer: 'yes' });
|
|
game = ask(game, f.asker, f.truths[1], bidFirst ? q.travel : q.bid);
|
|
game = applyAction(game, f.target, { type: 'truthAnswer', answer: 'yes' });
|
|
const guide = viewGame(game, f.target).travelPromiseGuide; assert.ok(guide);
|
|
assert.equal(guide.action.type, 'bid'); assert.equal(guide.action.amount, 4);
|
|
assert.equal(guide.action.travelPromisePhase, 3); assert.ok(!('bidPromiseEvent' in guide.action));
|
|
for (const difficulty of DIFFICULTIES) {
|
|
const view = viewGame(game, f.target); view.players.find(player => player.id === f.target)!.bot = difficulty;
|
|
const action = botActions(view)[0]; assert.deepEqual(action, view.travelPromiseGuide!.action);
|
|
const done = applyAction(game, f.target, action);
|
|
assert.equal(done.bidPromises?.[0].fulfilled, true); assert.equal(done.travelPromises?.[0].released, false);
|
|
assert.equal(done.auction?.bid, 4); assert.deepEqual(truthBidInventory(done), f.originalCards);
|
|
}
|
|
assert.equal(game.truthHistory?.filter(row => row.answer === 'yes').length, 2);
|
|
}
|
|
});
|
|
void test('native pending bid invoice and future travel cannot reserve the same original spice in either answer order', () => {
|
|
for (const bidFirst of [true, false]) {
|
|
const f = createTruthBidFixture({ window: 'active' }), q = questions(f, 8);
|
|
let game = ask(f.game, f.asker, f.truths[0], bidFirst ? q.bid : q.travel);
|
|
game = applyAction(game, f.target, { type: 'truthAnswer', answer: 'yes' });
|
|
game = ask(game, f.asker, f.truths[1], bidFirst ? q.travel : q.bid);
|
|
const view = viewGame(game, f.target);
|
|
assert.deepEqual(bidFirst ? view.truthTravelAnswers : view.truthBidAnswers, ['no']);
|
|
const before = structuredClone(game);
|
|
assert.throws(() => applyAction(game, f.target, { type: 'truthAnswer', answer: 'yes' })); assert.deepEqual(game, before);
|
|
game = applyAction(game, f.target, { type: 'truthAnswer', answer: 'no' });
|
|
assert.equal(game.truthHistory?.length, 2); assert.deepEqual(truthBidInventory(game), f.originalCards);
|
|
assert.equal(game.discard.filter(card => f.truths.includes(card.id)).length, 2);
|
|
}
|
|
});
|