Files
Dune/tests/nexus-harkonnen-betrayal-expanded.test.ts
T
Quintine 7680e1ee69
Verify and publish checkpoint container / container (push) Canceled after 0s
Fix mixed-deck Nexus Betrayal continuations
2026-10-30 18:12:45 +11:00

66 lines
3.8 KiB
TypeScript

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