import assert from 'node:assert/strict'; import test from 'node:test'; import { botActions } from '../game/bots'; import { DIFFICULTIES } from '../game/bot-profiles'; import { coalitionOf } from '../game/coalition-alliance'; import { applyAction, createGame, joinGame, newPlayer, viewGame, type Action, type Game } from '../game/engine'; import { advanceShortAlliance, type ShortAllianceStep } from './fixture-short-alliance-victory'; const apply = (game: Game, actor: string, action: Action) => applyAction(game, actor, action); function lobby(): Game { const game = createGame('ADVGROUPPREVIEW',newPlayer('at','Atreides','atreides')); joinGame(game,newPlayer('em','Emperor','emperor')); joinGame(game,newPlayer('ha','Harkonnen','harkonnen')); return game; } function prepare(variant: 'three' | 'three-short' | 'unlimited'): Game { let game = apply(lobby(),'at',{type:'rules',advanced:true}); game = apply(game,'at',{type:'coalitionPreview',variant}); for (const seat of game.players) game = apply(game,seat.id,{type:'ready'}); assert.throws(() => apply(game,'at',{type:'start'}), /advanced|preview/i); return apply(game,'at',{type:'start',advancedPreview:true}); } function accept(game: Game, actor: string): Game { const offer = game.coalitionState?.proposal; assert.ok(offer); return apply(game,actor,{type:'coalitionAnswer',event:offer.event,nexus:offer.nexus, turn:offer.turn,accept:true,expectedRevision:game.coalitionState!.revision}); } void test('fresh Advanced classic group variants require explicit preview and retain their selected target', () => { for (const variant of ['three','three-short','unlimited'] as const) { const game = prepare(variant); assert.equal(game.status,'setup'); assert.equal(game.advanced,true); assert.equal(game.advancedPreview,true); assert.equal(viewGame(game,'em').coalitionPreview,variant); assert.ok(game.coalitionState); assert.equal(game.coalitionState.variant,variant === 'three' ? undefined : variant); assert.deepEqual(game.coalitionState.groups,[]); assert.equal(game.coalitionState.proposal,null); } }); /** Actual Advanced setup and first turn. Only unopened original Spice cards * are reordered to select a normal worm at the next Spice Blow. */ function pairAtNexus(): Game { let game = prepare('three-short'); const actions: ShortAllianceStep[] = []; game = advanceShortAlliance(game, state => state.status === 'playing' && state.turn === 2 && state.phase === 0, actions); const worm = game.spiceDeck.findIndex(card => 'worm' in card && !card.greatMaker && !card.suppressed && !card.thumper); assert.ok(worm >= 0); const inventory = game.spiceDeck.map(card => JSON.stringify(card)).sort(); game.spiceDeck.unshift(game.spiceDeck.splice(worm,1)[0]); assert.deepEqual(game.spiceDeck.map(card => JSON.stringify(card)).sort(),inventory); game = advanceShortAlliance(game, state => state.nexus && !state.spiceWindow && !state.spiceResolution && !state.response && !state.decision && !state.truthtrance && !state.phaseOpening, actions); game = apply(game,'at',{type:'alliance',target:'em'}); return apply(game,'em',{type:'alliance',target:'at'}); } void test('a genuine Advanced preview reaches group formation, legal bots, and the next turn', () => { const original = pairAtNexus(); const pair = coalitionOf(original.players,original.coalitionState,'at'); assert.ok(pair); assert.deepEqual(new Set(pair.members),new Set(['at','em'])); let formed: Game | undefined; for (const difficulty of DIFFICULTIES) { const view = viewGame(original,'ha'); view.players.find(player => player.id === 'ha')!.bot = difficulty; const proposal = botActions(view).find(action => action.type === 'coalitionPropose'); assert.ok(proposal,difficulty); let game = apply(original,'ha',proposal); game = accept(game,'at'); game = accept(game,'em'); assert.deepEqual(new Set(coalitionOf(game.players,game.coalitionState,'at')!.members),new Set(['at','em','ha'])); assert.equal(game.coalitionState!.proposal,null); const progress = viewGame(game,'at').victoryProgress.find(row => row.player === 'at'); assert.ok(progress); assert.equal(progress.target,3); formed = game; } assert.ok(formed); for (const seat of formed.players) if (formed.nexus && !formed.ready.includes(seat.id)) formed = apply(formed,seat.id,{type:'ready'}); const continued = advanceShortAlliance(formed, state => state.status === 'playing' && state.turn === 3 && state.phase === 0, []); assert.deepEqual(new Set(coalitionOf(continued.players,continued.coalitionState,'at')!.members),new Set(['at','em','ha'])); });