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)); } });