Extend Basic Emperor auction gifts to Nexus and Richese
Verify and publish checkpoint container / container (push) Canceled after 0s
Verify and publish checkpoint container / container (push) Canceled after 0s
This commit is contained in:
1 parent
b258c6867f
commit
dac1b432db
11 files changed
+509
-14
No files matched your search
@@ -0,0 +1,126 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {applyAction, createGame, joinGame, newPlayer, viewGame, type Action, type Game} from '../game/engine';
|
||||
import {takeKullCard} from './fixture-choam-kull';
|
||||
import {advanceSemutaNexusTrade, nextSemutaNexusTradeNativeStep} from './fixture-semuta-nexus-trade';
|
||||
import {beforeIxSpiceDraw, orderIxSpiceCards} from './fixture-ix-spice-cards';
|
||||
|
||||
const act=(g:Game,id:string,action:Action)=>applyAction(g,id,action);
|
||||
const player=(g:Game,id:string)=>{const p=g.players.find(row=>row.id===id);assert.ok(p);return p;};
|
||||
const quiet=(g:Game)=>!g.phaseOpening&&!g.response&&!g.decision&&!g.truthtrance&&!g.pendingTreacheryDiscard;
|
||||
function inventory(g:Game) {
|
||||
const cards=[...g.deck,...g.discard,...(g.richeseCache??[]),...(g.richeseRemoved??[]),
|
||||
...(g.auction?.cards.slice(g.auction.index+Number(g.currentAuctionSale?.origin==='normal'))??[]),
|
||||
...(g.ixAuction?.cards??[]),...g.players.flatMap(row=>row.hand)].map(card=>card.id).sort();
|
||||
assert.equal(new Set(cards).size,cards.length);return cards;
|
||||
}
|
||||
function passResponse(g:Game) {
|
||||
for(let n=0;g.response&&n<g.players.length+2;n++) {
|
||||
const seat=g.players.find(p=>!g.response!.passed.includes(p.id));assert.ok(seat);
|
||||
g=act(g,seat.id,{type:'passResponse'});
|
||||
}
|
||||
return g;
|
||||
}
|
||||
function passSemuta(g:Game) {
|
||||
for(let n=0;g.pendingTreacheryDiscard?.reaction?.stage==='offer'&&n<g.players.length;n++) {
|
||||
const pending=g.pendingTreacheryDiscard, reaction=pending.reaction;
|
||||
assert.ok(reaction&&reaction.stage==='offer');
|
||||
const seat=g.players.find(p=>!reaction.passed.includes(p.id));assert.ok(seat);
|
||||
g=act(g,seat.id,{type:'semutaPass',event:pending.batch.event});
|
||||
}
|
||||
return g;
|
||||
}
|
||||
|
||||
/** Genuine public Basic setup, qualifying worm, alliance and paid cache bid.
|
||||
* Staged only unopened Spice, reserve positions, purses and conserved physical
|
||||
* hand/deck ownership before Ix's auction pool is drawn. */
|
||||
function fixture(method:'onceAround'|'silent') {
|
||||
let g=createGame('BASICRICHGIFT',newPlayer('e','Emperor','emperor'),false,['choam','ix']);
|
||||
for(const [id,faction] of [['g','guild'],['c','choam'],['r','richese'],['a','atreides']] as const)
|
||||
joinGame(g,newPlayer(id,faction,faction));
|
||||
for(const p of g.players) g=act(g,p.id,{type:'ready'});
|
||||
g=act(g,'e',{type:'start'});
|
||||
assert.equal(g.kullPreview,true);assert.equal(g.semutaPreview,true);
|
||||
g=advanceSemutaNexusTrade(g,s=>s.status==='playing');
|
||||
for(const p of g.players){p.reserves+=Object.values(p.forces).reduce((sum,n)=>sum+n,0);p.forces={};}
|
||||
g=advanceSemutaNexusTrade(g,beforeIxSpiceDraw);orderIxSpiceCards(g,['land','land']);
|
||||
g=advanceSemutaNexusTrade(g,s=>s.turn===2&&beforeIxSpiceDraw(s));orderIxSpiceCards(g,['worm','land','land']);
|
||||
g=advanceSemutaNexusTrade(g,s=>!!s.nexus&&!s.spiceWindow&&quiet(s));
|
||||
g=act(g,'e',{type:'alliance',target:'g'});g=act(g,'g',{type:'alliance',target:'e'});
|
||||
g=advanceSemutaNexusTrade(g,s=>s.phase===3&&s.phaseOpening?.initialize===true);
|
||||
for(const p of g.players)g.deck.push(...p.hand.splice(0));
|
||||
const semuta=takeKullCard(g,'richese-semuta-drug');player(g,'r').hand.push(semuta);
|
||||
const kull=takeKullCard(g,'ix-kull-wahad');player(g,'c').hand.push(kull);
|
||||
const karama=takeKullCard(g,'karama');player(g,'a').hand.push(karama);
|
||||
const counter=takeKullCard(g,'karama');player(g,'g').hand.push(counter);
|
||||
player(g,'g').spice=7;player(g,'e').spice=8;
|
||||
g=advanceSemutaNexusTrade(g,s=>s.decision?.kind==='richeseCache');
|
||||
const selected=g.richeseCache!.find(card=>card.id!==semuta.id);assert.ok(selected);
|
||||
const original=inventory(g),card=selected.id;
|
||||
g=act(g,'r',{type:'decision',event:g.richeseBidding!.event,card,method,
|
||||
...(method==='onceAround'?{direction:'clockwise'}:{})});
|
||||
g=act(g,'e',{type:'pledgeAid',amount:1});
|
||||
let bid=false;
|
||||
for(let n=0;!g.pendingEmperorAuctionGift&&n<300;n++) {
|
||||
const lot=g.richeseAuction;
|
||||
if(lot&&!lot.outcome&&!g.response&&!g.decision&&lot.method==='onceAround'&&lot.active) {
|
||||
const seat=lot.active, buy=seat==='g'&&!bid;
|
||||
g=act(g,seat,{type:'richeseBid',event:lot.event,amount:buy?3:null,...(buy?{allyPayment:1}:{})});
|
||||
if(buy)bid=true;
|
||||
} else if(lot&&!lot.outcome&&!g.response&&!g.decision&&lot.method==='silent') {
|
||||
const seat=lot.eligible.find(id=>!Object.hasOwn(lot.sealed,id));assert.ok(seat);
|
||||
const buy=seat==='g'&&!bid;
|
||||
g=act(g,seat,{type:'richeseBid',event:lot.event,amount:buy?3:0,...(buy?{allyPayment:1}:{})});
|
||||
if(buy)bid=true;
|
||||
} else {
|
||||
const next=nextSemutaNexusTradeNativeStep(g);g=act(g,next.actor,next.action);
|
||||
}
|
||||
}
|
||||
assert.equal(bid,true);assert.ok(g.pendingEmperorAuctionGift,'Emperor receives a prepayment opportunity');
|
||||
assert.equal(g.richeseAuction?.outcome?.kind,'sold');assert.equal(g.currentAuctionSale??null,null);
|
||||
assert.equal(viewGame(g,'e').emperorAuctionGift?.recipient,'g');
|
||||
return {g,card,kull:kull.id,karama:karama.id,counter:counter.id,semuta:semuta.id,original};
|
||||
}
|
||||
function finishSale(g:Game, card:string) {
|
||||
const pending=g.pendingEmperorAuctionGift;assert.ok(pending);
|
||||
g=act(g,'e',{type:'emperorAuctionGiftContinue',event:pending.event});
|
||||
g=passResponse(g);
|
||||
assert.ok(player(g,'g').hand.some(row=>row.id===card));
|
||||
return g;
|
||||
}
|
||||
|
||||
for(const counterKull of [false,true]) for(const claim of [false,true]) void test(
|
||||
`Basic cache gift Kull ${counterKull?'countered':'succeeds'} and Semuta ${claim?'claims':'passes'} preserve the original sale`,()=>{
|
||||
const f=fixture('onceAround'), auction=structuredClone(f.g.richeseAuction);
|
||||
const funding=structuredClone(f.g.richeseFunding), pending=structuredClone(f.g.pendingEmperorAuctionGift);
|
||||
const emperor=player(f.g,'e').spice, buyer=player(f.g,'g').spice, seller=player(f.g,'r').spice;
|
||||
let g=act(f.g,'e',{type:'emperorGift',amount:2});
|
||||
g=act(g,'a',{type:'card',mode:'cancel',card:f.karama});
|
||||
assert.equal(g.pendingKull?.stage,'offer');
|
||||
g=act(g,'c',{type:'kullDecision',event:g.pendingKull!.event,source:'printed',card:f.kull});
|
||||
assert.equal(g.pendingKull?.stage,'counter');
|
||||
if(counterKull) {
|
||||
assert.ok(viewGame(g,'g').responseControls?.cancelCards.includes(f.counter));
|
||||
g=act(g,'g',{type:'card',mode:'cancel',card:f.counter});
|
||||
} else g=passResponse(g);
|
||||
assert.equal(g.pendingTreacheryDiscard?.reaction?.stage,'offer');
|
||||
assert.equal(g.pendingTreacheryDiscard!.batch.entries[0].card.id,counterKull?f.counter:f.kull);
|
||||
const discard=g.pendingTreacheryDiscard!.batch.event;
|
||||
if(claim)g=act(g,'r',{type:'semutaCommit',event:discard});else g=passSemuta(g);
|
||||
assert.equal(player(g,'r').hand.some(card=>card.id===(counterKull?f.counter:f.kull)),claim);
|
||||
if(g.pendingTreacheryDiscard?.reaction?.stage==='offer')g=passSemuta(g);
|
||||
if(g.response)g=passResponse(g);
|
||||
assert.deepEqual(g.pendingEmperorAuctionGift,pending);
|
||||
assert.deepEqual(g.richeseAuction,auction);assert.deepEqual(g.richeseFunding,funding);
|
||||
assert.equal(player(g,'e').spice,emperor-(counterKull?0:2));
|
||||
assert.equal(player(g,'g').spice,buyer+(counterKull?0:2));
|
||||
assert.equal(player(g,'a').hand.some(card=>card.id===f.karama),!counterKull);
|
||||
assert.equal(g.discard.some(card=>card.id===f.semuta),claim);
|
||||
g=finishSale(g,f.card);
|
||||
assert.equal(player(g,'g').hand.filter(card=>card.id===f.card).length,1);
|
||||
assert.equal(player(g,'g').spice,buyer+(counterKull?0:2)-2);
|
||||
assert.equal(player(g,'e').spice,emperor-(counterKull?0:2));
|
||||
assert.equal(player(g,'r').spice,seller+3);
|
||||
assert.equal(g.aid.e?.amount??0,0);
|
||||
assert.deepEqual(inventory(g),f.original);
|
||||
});
|
||||
Reference in new issue
Block a user