import assert from 'node:assert/strict'; import test from 'node:test'; import { leaders, treacheryDeck } from '../game/cards'; import { ecazTreacheryCards } from '../game/ecaz-cards'; import { richeseCards } from '../game/richese-cards'; import { NEXUS_FACTIONS } from '../game/nexus-cards'; import { createTraitorDeclaration } from '../game/traitor-declarations'; import { CHEAP_HERO_TRAITOR, traitorDeck } from '../game/traitors'; import { closeHarkonnenBetrayalSource, createHarkonnenBetrayalReplacement, createHarkonnenBetrayalSource, drawHarkonnenBetrayalReplacement, initialHarkonnenBetrayalCursor, validateHarkonnenBetrayalFrame, validateHarkonnenBetrayalHistory, type HarkonnenBetrayalContext, } from '../game/nexus-harkonnen-betrayal'; function expanded(hero: boolean): HarkonnenBetrayalContext { const players: HarkonnenBetrayalContext['players'] = [ {id:'h', faction:'harkonnen', ally:null}, {id:'i', faction:'ixians', ally:null}, {id:'r', faction:'richese', ally:null}, {id:'a', faction:'atreides', ally:null}, ]; const expansions = ['ix', 'choam']; const physicalCards = [...treacheryDeck(expansions), ...ecazTreacheryCards(), ...richeseCards()]; const heroLeaderIds = physicalCards.filter(card => card.kind === 'hero').map(card => card.id); const identity = hero ? CHEAP_HERO_TRAITOR : leaders('ixians')[0].id; const universe = traitorDeck(players.map(player => ({leaders:leaders(player.faction)})), true); return { status:'playing', phase:6, turn:2, advanced:false, sequence:0, expansions, ecazTreachery:true, players, physicalCards, universe, cards:{version:1, deck:NEXUS_FACTIONS.filter(faction => faction !== 'harkonnen'), discard:[], hands:{h:null, i:null, r:null, a:'harkonnen'}}, traitors:{reserve:universe.filter(card => card !== identity), players:players.map(player => ({ id:player.id, faction:player.faction, traitors:player.id === 'h' ? [identity] : [], }))}, battle:{event:'expanded-battle', attacker:'h', defender:'i', heroLeaderIds, plans:{h:{leader:leaders('harkonnen')[0].id}, i:{leader:hero ? heroLeaderIds[0] : identity}}}, }; } for (const hero of [false, true]) void test(`Basic selected Ix and CHOAM census binds a ${hero ? 'Cheap Hero' : 'native Ixian'} traitor and continues`, () => { const ctx = expanded(hero); const declaration = createTraitorDeclaration({...ctx.battle, players:ctx.players.map(player => ({...player, traitors:ctx.traitors.players.find(seat => seat.id === player.id)!.traitors}))}, 'h'); const authority = {provider:'h', declaration, nativeWindow:'direct' as const, nativeContext:'original-direct-call', nativeRequired:[], nativePassed:[]}; const source = createHarkonnenBetrayalSource(ctx, authority, 'original-battle'); assert.equal(source.declaration.identity, hero ? CHEAP_HERO_TRAITOR : leaders('ixians')[0].id); validateHarkonnenBetrayalFrame(ctx, source, authority, 'original-battle', ['a'], []); for (const outcome of ['pass', 'use'] as const) { const closed = closeHarkonnenBetrayalSource(source, outcome, outcome === 'use' ? 'a' : null, initialHarkonnenBetrayalCursor()); validateHarkonnenBetrayalHistory([closed.receipt], closed.cursor, ctx.universe); if (outcome === 'use') { const due = createHarkonnenBetrayalReplacement(closed.receipt); const done = drawHarkonnenBetrayalReplacement(due, [closed.receipt], closed.cursor, ctx.traitors.reserve[0], ctx.universe); assert.equal(done.status, 'drawn'); } } for (const bad of [ {...ctx, expansions:['choam']}, {...ctx, physicalCards:ctx.physicalCards.slice(1)}, {...ctx, universe:ctx.universe.filter(id => id !== CHEAP_HERO_TRAITOR)}, {...ctx, battle:{...ctx.battle, heroLeaderIds:[]}}, {...ctx, advanced:true}, ]) assert.throws(() => createHarkonnenBetrayalSource(bad, authority, 'original-battle')); });