88 lines
5.0 KiB
TypeScript
88 lines
5.0 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import {applyAction, viewGame, type Action, type Game} from '../game/engine';
|
|
import {botActions} from '../game/bots';
|
|
import {DIFFICULTIES} from '../game/bot-profiles';
|
|
import {advanceTruthAlliance, createTruthAllianceFixture, truthAllianceClean} from './fixture-truth-alliance';
|
|
|
|
const act = (game:Game, actor:string, action:Action) => applyAction(game,actor,action);
|
|
const seat = (game:Game,id:string) => {const player=game.players.find(row=>row.id===id);assert.ok(player);return player;};
|
|
function passResponse(state:Game):Game {
|
|
let game=state;
|
|
for(let n=0;game.response && n<30;n++) {
|
|
const actor=game.players.find(player=>!game.response!.passed.includes(player.id));assert.ok(actor);
|
|
game=act(game,actor.id,{type:'passResponse'});
|
|
}
|
|
return game;
|
|
}
|
|
/** Public Basic setup and real later worm/alliance, bids and payment. Only
|
|
* wallets and conserved actual card locations are staged before bidding. */
|
|
function auction(counter:boolean) {
|
|
let game=createTruthAllianceFixture({advanced:false,consent:'none'}).game;
|
|
game=act(game,'harkonnen',{type:'alliance',target:'emperor'});
|
|
game=act(game,'emperor',{type:'alliance',target:'harkonnen'});
|
|
game=advanceTruthAlliance(game,state=>state.phase===3 && !!state.auction && truthAllianceClean(state));
|
|
while(game.auction!.active!=='harkonnen') game=act(game,game.auction!.active,{type:'passBid'});
|
|
const buyer=seat(game,'harkonnen'), emperor=seat(game,'emperor');
|
|
buyer.spice=4;emperor.spice=8;
|
|
if(counter) {
|
|
const other=seat(game,'atreides');
|
|
if(!other.hand.some(card=>card.effect==='karama')) {
|
|
const sources=[game.deck,...game.players.filter(player=>player.id!=='atreides').map(player=>player.hand)];
|
|
const source=sources.find(cards=>cards.some(card=>card.effect==='karama'));assert.ok(source);
|
|
const index=source.findIndex(card=>card.effect==='karama');
|
|
const displaced=other.hand.pop();assert.ok(displaced);
|
|
[source[index],other.hand[other.hand.length]]=[displaced,source[index]];
|
|
}
|
|
}
|
|
game=act(game,'emperor',{type:'pledgeAid',amount:2});
|
|
const lot=game.auction!.cards[game.auction!.index].id;
|
|
game=act(game,'harkonnen',{type:'bid',amount:6,allyPayment:2});
|
|
for(let n=0;game.auction && !game.response && !game.decision && n<15;n++)
|
|
game=act(game,game.auction.active,{type:'passBid'});
|
|
assert.equal(game.decision?.kind,'auctionPayment');
|
|
assert.equal(game.decision.player,'harkonnen');
|
|
const offer=viewGame(game,'emperor').emperorAuctionGift;assert.ok(offer);
|
|
return {game,lot,event:offer.event};
|
|
}
|
|
|
|
void test('Basic public allied auction gift retains pledged funding and original payment',()=>{
|
|
const f=auction(false), before=structuredClone(f.game), offer=viewGame(f.game,'emperor').emperorAuctionGift!;
|
|
assert.equal(offer.recipient,'harkonnen');assert.equal(offer.maximum,6);
|
|
assert.equal(viewGame(f.game,'harkonnen').emperorAuctionGift,null);
|
|
assert.throws(()=>act(f.game,'harkonnen',{type:'decision',karama:false}));
|
|
assert.deepEqual(f.game,before);
|
|
let game=act(f.game,'emperor',{type:'emperorGift',amount:2});
|
|
game=passResponse(game);
|
|
assert.equal(seat(game,'harkonnen').spice,6);assert.equal(seat(game,'emperor').spice,4);
|
|
assert.equal(game.auction!.bid,6);assert.equal(game.auction!.allyPayment,2);
|
|
assert.equal(viewGame(game,'emperor').emperorAuctionGift!.event,f.event);
|
|
game=act(game,'emperor',{type:'emperorAuctionGiftContinue',event:f.event});
|
|
if(game.decision?.kind==='auctionPayment') game=act(game,'harkonnen',{type:'decision',karama:false});
|
|
game=passResponse(game);
|
|
assert.ok(seat(game,'harkonnen').hand.some(card=>card.id===f.lot));
|
|
assert.equal(seat(game,'harkonnen').spice,2);
|
|
});
|
|
|
|
void test('Basic native Karama cancels the gift and retains its original auction payment',()=>{
|
|
const f=auction(true), card=seat(f.game,'atreides').hand.find(row=>row.effect==='karama');assert.ok(card);
|
|
let game=act(f.game,'emperor',{type:'emperorGift',amount:2});
|
|
game=act(game,'atreides',{type:'card',mode:'cancel',card:card.id});
|
|
assert.equal(seat(game,'harkonnen').spice,4);assert.equal(seat(game,'emperor').spice,6);
|
|
assert.equal(game.auction!.bid,6);assert.equal(game.auction!.allyPayment,2);
|
|
assert.equal(viewGame(game,'emperor').emperorAuctionGift!.event,f.event);
|
|
game=act(game,'emperor',{type:'emperorAuctionGiftContinue',event:f.event});
|
|
if(game.decision?.kind==='auctionPayment') game=act(game,'harkonnen',{type:'decision',karama:false});
|
|
game=passResponse(game);
|
|
assert.ok(seat(game,'harkonnen').hand.some(row=>row.id===f.lot));
|
|
});
|
|
|
|
void test('Each minimal Basic bot can continue the owned auction gift opportunity',()=>{
|
|
const {game,event}=auction(false);
|
|
for(const difficulty of DIFFICULTIES) {
|
|
const view=viewGame(game,'emperor');view.players.find(player=>player.id==='emperor')!.bot=difficulty;
|
|
const action=botActions(view).find(row=>row.type==='emperorAuctionGiftContinue');assert.ok(action);
|
|
assert.equal(action.event,event);assert.doesNotThrow(()=>act(game,'emperor',action));
|
|
}
|
|
});
|