Files
Dune/tests/truth-turn-bid-runtime.test.ts

89 lines
5.3 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 { truthBidInventory } from './fixture-truth-bid';
import { actTruthTurnBid as act, advanceTruthTurnBid as advance, bindTruthTurnBid,
createTruthTurnBidFixture, finishTruthTurnBidLot, purchaseTruthTurnBidLot,
truthTurnBidClean as clean, truthTurnBidPlayer as player, type TruthTurnBidStep } from './fixture-truth-turn-bid';
function reject(game: Game, actor: string, action: Action) {
const before = structuredClone(game);
assert.throws(() => act(game,actor,action));
assert.deepEqual(game,before);
}
function firstContested(game: Game, asker: string, rival: string, target: string, amount: number | null) {
assert.equal(game.auction?.index,0); assert.equal(game.auction?.active,asker);
const actions: TruthTurnBidStep[] = [];
game = act(game,asker,{type:'passBid'},actions);
assert.equal(game.auction?.active,rival);
game = act(game,rival,{type:'bid',amount:1,allyPayment:0},actions);
assert.equal(game.auction?.active,target);
game = act(game,target,amount === null ? {type:'passBid'} : {type:'bid',amount,allyPayment:0},actions);
return {game,actions};
}
for (const advanced of [false,true]) {
const prefix = advanced ? 'Advanced' : 'Basic';
void test(`${prefix} phase bid Yes survives a real first-lot pass then fulfills on a later accepted bid`, () => {
const f = createTruthTurnBidFixture({advanced});
const bound = bindTruthTurnBid(f,'yes',4);
const first = firstContested(bound.game,f.asker,f.opponent,f.target,null);
let game = finishTruthTurnBidLot(first.game,first.actions);
assert.equal(game.auction?.index,1);
assert.ok(player(game,f.opponent).hand.some(c => c.id === f.lotCards[0]));
const promise = viewGame(game,f.target).phaseBidPromises.find(p => p.minimum === 4)!;
assert.ok(promise); assert.equal(promise.fulfilled ?? false,false); assert.equal(promise.released ?? false,false);
game = advance(game,state => clean(state) && state.auction?.active === f.target);
for (const difficulty of DIFFICULTIES) {
const view = viewGame(game,f.target);
view.players.find(p => p.id === f.target)!.bot = difficulty;
const action = botActions(view)[0]; assert.ok(action);
const accepted = act(game,f.target,action);
assert.ok(accepted,`${difficulty} supplies a legal current action`);
}
game = act(game,f.target,{type:'bid',amount:4,allyPayment:0});
assert.ok(viewGame(game,f.target).phaseBidPromises.some(p => p.minimum === 4 && p.fulfilled));
assert.deepEqual(truthBidInventory(game),f.originalCards);
});
void test(`${prefix} phase bid No forbids qualifying bids across both original lots`, () => {
const f = createTruthTurnBidFixture({advanced});
const bound = bindTruthTurnBid(f,'no',4);
let game = firstContested(bound.game,f.asker,f.opponent,f.target,3).game;
game = finishTruthTurnBidLot(game);
assert.equal(game.auction?.index,1);
assert.ok(player(game,f.target).hand.some(c => c.id === f.lotCards[0]));
game = advance(game,state => clean(state) && state.auction?.active === f.target);
assert.ok(viewGame(game,f.target).phaseBidPromises.some(p => p.minimum === 4 && !p.fulfilled && !p.released));
reject(game,f.target,{type:'bid',amount:4,allyPayment:0});
game = act(game,f.target,{type:'bid',amount:3,allyPayment:0});
assert.deepEqual(truthBidInventory(game),f.originalCards);
});
void test(`${prefix} a live phase bid Yes cannot be abandoned on the final original lot`, () => {
const f = createTruthTurnBidFixture({advanced}); let game = f.game;
for (let n=0;n<2;n++) game = purchaseTruthTurnBidLot(game,f.opponent).game;
assert.equal(game.auction?.index,2);
game = advance(game,state => clean(state) && state.auction?.active === f.target);
game = bindTruthTurnBid(f,'yes',4,game).game;
reject(game,f.target,{type:'passBid'});
reject(game,f.target,{type:'bid',amount:3,allyPayment:0});
game = act(game,f.target,{type:'bid',amount:4,allyPayment:0});
assert.ok(viewGame(game,f.target).phaseBidPromises.some(p => p.minimum === 4 && p.fulfilled));
assert.deepEqual(truthBidInventory(game),f.originalCards);
});
void test(`${prefix} an actual opposing last-lot raise releases an unaffordable phase bid Yes`, () => {
const f = createTruthTurnBidFixture({advanced,extraGuild:true}); let game = f.game;
// Two real E purchases cost him two; a real A purchase pays E one. The
// original fourth lot remains, E has one hand slot and nine spice, and H
// has its real ten spice. This is no staged budget or future rival bid.
for (const buyer of [f.target,f.target,f.opponent]) game = purchaseTruthTurnBidLot(game,buyer).game;
assert.equal(game.auction?.index,3); assert.equal(player(game,f.target).spice,9);
game = advance(game,state => clean(state) && state.auction?.active === f.asker);
assert.ok(!game.auction!.passed.includes(f.target));
game = bindTruthTurnBid(f,'yes',8,game).game;
game = act(game,f.asker,{type:'bid',amount:10,allyPayment:0});
assert.ok(viewGame(game,f.target).phaseBidPromises.some(p => p.minimum === 8 && p.released));
assert.deepEqual(truthBidInventory(game),f.originalCards);
});
}