Retain original Advanced Strongholds in fresh Semuta games
This commit is contained in:
1 parent
bab8f3403d
commit
03e3d9bdbf
10 files changed
+208
-16
No files matched your search
@@ -0,0 +1,43 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import {applyAction,createGame,joinGame,newPlayer,initializeSemutaGameForAudit,viewGame,type Game} from '../game/engine';
|
||||
import {botActions} from '../game/bots';
|
||||
import {SEMUTA_DRUG_ID} from '../game/semuta-drug';
|
||||
|
||||
export function initializeSemutaStrongholdsGame(count=3,tech=false):Game {
|
||||
const factions=['richese','atreides','choam','ixians','tleilaxu','fremen'] as const;
|
||||
let game=createGame(`SEMUTASH${count}${tech?'T':''}`,newPlayer('r','Richese','richese'),true,count>=4?['choam','ix']:['choam']);
|
||||
const ids=['r','a','e','i','t','f'];
|
||||
for(let n=1;n<count;n++)joinGame(game,newPlayer(ids[n],factions[n],factions[n]));
|
||||
game=applyAction(game,'r',{type:'strongholdCards',enabled:true});
|
||||
if(tech)game=applyAction(game,'r',{type:'techTokens',enabled:true});
|
||||
for(const player of game.players){player.bot='Medium';player.ready=true;}
|
||||
game=initializeSemutaGameForAudit(game);
|
||||
for(let step=0;(game.status==='setup'||game.phase===0||game.phase===1)&&step<240;step++) {
|
||||
let next:Game|undefined;
|
||||
for(const player of game.players){const action=botActions(viewGame(game,player.id))[0];if(action){next=applyAction(game,player.id,action);break;}}
|
||||
assert.ok(next,'Original native setup/first Storm needs a legal continuation.');game=next;
|
||||
}
|
||||
assert.equal(game.status,'playing');assert.equal(game.phase,2);
|
||||
if(tech)assert.ok(Object.values(game.techTokens!).every(token=>token.owner));
|
||||
return game;
|
||||
}
|
||||
export function semutaStrongholdsClosingFixture(tech=false) {
|
||||
const game=initializeSemutaStrongholdsGame(3,tech);
|
||||
const cards=[...game.deck,...game.discard,...game.richeseCache!,...game.players.flatMap(p=>p.hand)].map(c=>c.id).sort();
|
||||
game.deck.push(...game.players.flatMap(p=>p.hand));for(const player of game.players)player.hand=[];
|
||||
const take=(id:string)=>{for(const zone of [game.deck,game.discard,game.richeseCache!]){const index=zone.findIndex(card=>card.id===id);if(index>=0)return zone.splice(index,1)[0];}throw Error(`Missing physical ${id}`);};
|
||||
const semuta=take(SEMUTA_DRUG_ID),gamont=take(game.deck.find(c=>c.name==='Trip to Gamont')!.id);
|
||||
game.players.find(p=>p.id==='r')!.hand.push(semuta);game.players.find(p=>p.id==='e')!.hand.push(gamont);
|
||||
for(const player of game.players){player.reserves+=player.tanks+Object.values(player.forces).reduce((sum,n)=>sum+n,0);player.forces={};player.tanks=0;}
|
||||
const target=game.players.find(p=>p.id==='a')!;
|
||||
target.reserves-=4;target.forces={'arrakeen:10':3,'carthag:11':1};
|
||||
Object.assign(game,{turn:2,phase:7,active:null,movementRemaining:[],ready:[],response:null,decision:null,phaseOpening:null,stormPending:null});
|
||||
let state=game;
|
||||
for(const id of ['a','e','r'])state=applyAction(state,id,{type:'ready'});
|
||||
state=applyAction(state,'e',{type:'decision',done:true});
|
||||
assert.equal(state.phase,8);
|
||||
for(const id of ['a','e','r'])state=applyAction(state,id,{type:'ready'});
|
||||
state=applyAction(state,'e',{type:'decision',done:true});
|
||||
assert.equal(state.decision?.kind,'choamMentat');
|
||||
return {game:state,semuta:semuta.id,gamont:gamont.id,cards};
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {applyAction,type Game} from '../game/engine';
|
||||
import {semutaStrongholdsClosingFixture} from './fixture-semuta-strongholds';
|
||||
const reload=(game:Game):Game=>JSON.parse(JSON.stringify(game));
|
||||
const inventory=(game:Game)=>[...game.deck,...game.discard,...game.richeseCache!,...game.players.flatMap(p=>p.hand)].map(c=>c.id).sort();
|
||||
for(const tech of [false,true]) void test(`Closing Semuta Gamont preserves original Stronghold claim timing${tech?' with native Tech':''}`,()=>{
|
||||
const f=semutaStrongholdsClosingFixture(tech),before=f.game;
|
||||
assert.equal(before.strongholdCards!.claimedTurn,0);
|
||||
assert.equal(before.strongholdCards!.owners.arrakeen,null);
|
||||
assert.equal(before.strongholdCards!.owners.carthag,null);
|
||||
let pending=applyAction(before,'e',{type:'card',mode:'choam',card:f.gamont,target:'a',from:'carthag:11',elite:0});
|
||||
while(pending.response){const actor=pending.players.find(p=>!pending.response!.passed.includes(p.id))!;pending=applyAction(pending,actor.id,{type:'passResponse'});}
|
||||
assert.equal(pending.pendingTreacheryDiscard?.reaction?.stage,'offer');
|
||||
const event=pending.pendingTreacheryDiscard!.batch.event;
|
||||
const originalCards=structuredClone(pending.strongholdCards),originalTech=structuredClone(pending.techTokens);
|
||||
for(const claim of [false,true]) {
|
||||
let resumed=reload(pending);
|
||||
if(claim)resumed=applyAction(resumed,'r',{type:'semutaCommit',event});
|
||||
else for(const id of ['a','e','r'])resumed=applyAction(resumed,id,{type:'semutaPass',event});
|
||||
assert.equal(resumed.decision?.kind,'choamMentat');assert.equal(resumed.decision?.player,'e');
|
||||
assert.deepEqual(resumed.players.find(p=>p.id==='a')!.forces,{'arrakeen:10':3});
|
||||
assert.equal(resumed.players.find(p=>p.id==='a')!.reserves,17);
|
||||
assert.deepEqual(resumed.strongholdCards,originalCards);assert.deepEqual(resumed.techTokens,originalTech);
|
||||
assert.throws(()=>applyAction(resumed,'a',{type:'decision',done:true}));
|
||||
const next=applyAction(resumed,'e',{type:'decision',done:true});
|
||||
assert.equal(next.strongholdCards!.claimedTurn,2);
|
||||
assert.equal(next.strongholdCards!.owners.arrakeen,'a');
|
||||
assert.equal(next.strongholdCards!.owners.carthag,null);
|
||||
assert.equal(next.status,'playing');assert.equal(next.turn,3);assert.equal(next.phase,0);
|
||||
assert.deepEqual(next.techTokens,originalTech);
|
||||
assert.equal(next.players.find(p=>p.id==='r')!.hand.some(c=>c.id===f.gamont),claim);
|
||||
assert.equal(next.discard.some(c=>c.id===f.semuta),claim);
|
||||
assert.deepEqual(inventory(next),f.cards);
|
||||
assert.throws(()=>applyAction(next,'r',{type:'semutaCommit',event}));
|
||||
assert.throws(()=>applyAction(next,'e',{type:'decision',done:true}));
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user