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