81 lines
5.2 KiB
TypeScript
81 lines
5.2 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { createAuditorLeader, leaders, treacheryDeck } from '../game/cards';
|
|
import { NEXUS_FACTIONS } from '../game/nexus-cards';
|
|
import { createTraitorDeclaration } from '../game/traitor-declarations';
|
|
import { traitorDeck } from '../game/traitors';
|
|
import { closeHarkonnenBetrayalSource, createHarkonnenBetrayalReplacement,
|
|
createHarkonnenBetrayalSource, drawHarkonnenBetrayalReplacement,
|
|
initialHarkonnenBetrayalCursor, validateHarkonnenBetrayalHistory,
|
|
validateHarkonnenBetrayalSource, type HarkonnenBetrayalContext } from '../game/nexus-harkonnen-betrayal';
|
|
|
|
function context(advanced = true): HarkonnenBetrayalContext {
|
|
const players: HarkonnenBetrayalContext['players'] = [
|
|
{id:'h',faction:'harkonnen',ally:null}, {id:'c',faction:'choam',ally:null},
|
|
{id:'e',faction:'ecaz',ally:null}, {id:'t',faction:'tleilaxu',ally:null},
|
|
{id:'m',faction:'moritani',ally:null}, {id:'a',faction:'atreides',ally:null},
|
|
];
|
|
const expansions = ['ix','choam','ecaz'];
|
|
const loyalty = advanced ? {player:'e',card:'ecaz-0'} : undefined;
|
|
const universe = traitorDeck(players.map(player => ({leaders:[...leaders(player.faction),
|
|
...(advanced && player.faction === 'choam' ? [createAuditorLeader()] : [])]})), true)
|
|
.filter(card => card !== loyalty?.card);
|
|
const target = advanced ? 'choam-auditor' : 'choam-0';
|
|
const dancers = ['atreides-0','tleilaxu-0','ecaz-1'];
|
|
const removed = ['moritani-0'];
|
|
const traitorPlayers = players.map(player => ({id:player.id,faction:player.faction,
|
|
traitors:player.id === 'h' ? [target] : [],
|
|
...(player.id === 't' ? {faceDancers:dancers.map(leader => ({leader,revealed:false}))} : {})}));
|
|
const occupied = [target,...dancers,...removed];
|
|
const physicalCards = treacheryDeck(expansions);
|
|
return {status:'playing',phase:6,turn:2,advanced,sequence:0,expansions,players,physicalCards,
|
|
...(loyalty ? {ecazLoyalty:loyalty} : {}), universe,
|
|
cards:{version:1,deck:NEXUS_FACTIONS.filter(card => card !== 'harkonnen'),discard:[],
|
|
hands:{h:null,c:null,e:null,t:null,m:null,a:'harkonnen'}},
|
|
traitors:{reserve:universe.filter(card => !occupied.includes(card)),players:traitorPlayers,removed},
|
|
battle:{event:'advanced-auditor-battle',attacker:'h',defender:'c',
|
|
plans:{h:{leader:'harkonnen-0'},c:{leader:target}},
|
|
heroLeaderIds:physicalCards.filter(card => card.kind === 'hero').map(card => card.id)}};
|
|
}
|
|
function authority(ctx: HarkonnenBetrayalContext) {
|
|
const declaration = createTraitorDeclaration({...ctx.battle,players:ctx.players.map(player => ({...player,
|
|
traitors:ctx.traitors.players.find(seat => seat.id === player.id)!.traitors}))},'h');
|
|
return {provider:'h',declaration,nativeWindow:'direct' as const,nativeContext:'native',nativeRequired:[],nativePassed:[]};
|
|
}
|
|
|
|
for (const advanced of [false,true]) void test(`${advanced?'Advanced Auditor and Loyalty':'Basic native'} selected census preserves the original traitor`, () => {
|
|
const ctx=context(advanced), native=authority(ctx), source=createHarkonnenBetrayalSource(ctx,native,'parent');
|
|
assert.equal(source.declaration.identity,advanced?'choam-auditor':'choam-0');
|
|
assert.equal(source.universe.includes('ecaz-0'),!advanced);
|
|
assert.equal(source.universe.includes('choam-auditor'),advanced);
|
|
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 replacement=createHarkonnenBetrayalReplacement(closed.receipt);
|
|
assert.equal(drawHarkonnenBetrayalReplacement(replacement,[closed.receipt],closed.cursor,ctx.traitors.reserve[0],ctx.universe).status,'drawn');
|
|
}
|
|
}
|
|
const stored=JSON.parse(source.inventory);
|
|
assert.deepEqual(stored.at(-1),advanced?{expansions:ctx.expansions,ecazLoyalty:ctx.ecazLoyalty}:{expansions:ctx.expansions});
|
|
});
|
|
|
|
void test('Advanced census rejects altered Loyalty, Auditor, Face Dancers and retired cards', () => {
|
|
const ctx=context(), native=authority(ctx), source=createHarkonnenBetrayalSource(ctx,native,'parent');
|
|
for (const mutate of [
|
|
(g:HarkonnenBetrayalContext) => { g.ecazLoyalty!.card='ecaz-2'; },
|
|
(g:HarkonnenBetrayalContext) => { g.ecazLoyalty!.player='h'; },
|
|
(g:HarkonnenBetrayalContext) => { g.ecazLoyalty!.card=null; },
|
|
(g:HarkonnenBetrayalContext) => { g.universe=g.universe.filter(card => card !== 'choam-auditor'); },
|
|
(g:HarkonnenBetrayalContext) => { g.traitors.reserve.push(g.traitors.players.find(p => p.id==='t')!.faceDancers![0].leader); },
|
|
(g:HarkonnenBetrayalContext) => { g.traitors.removed!.push(g.traitors.reserve[0]); },
|
|
]) {
|
|
const bad=structuredClone(ctx); mutate(bad);
|
|
assert.throws(() => createHarkonnenBetrayalSource(bad,native,'parent'));
|
|
assert.throws(() => validateHarkonnenBetrayalSource(bad,source,native,'parent'));
|
|
}
|
|
const altered=structuredClone(source), inventory=JSON.parse(altered.inventory);
|
|
inventory.at(-1).ecazLoyalty.card='ecaz-2'; altered.inventory=JSON.stringify(inventory);
|
|
assert.throws(() => validateHarkonnenBetrayalSource(ctx,altered,native,'parent'));
|
|
});
|