Files
Dune/tests/kull-whole-counter-owner-guard.test.ts
T
Quintine 07f1f1f2ae
Verify and publish checkpoint container / container (push) Successful in 10m21s
Support whole-turn answers with Basic Kull
2026-10-29 01:37:33 +11:00

75 lines
4.6 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { handLimit, viewGame, type Game } from '../game/engine';
import { act, advance, clean, createKullRicheseSemutaFixture, inventory, player } from './fixture-kull-richese-semuta';
/** Native Basic setup, physical deal, Richese cache purchase and the active
* Emperor shipment window are inherited. Existing Truthtrance and Richese
* Karama cards are moved to Richese's hand; no cash, pending actions or
* promises are staged. The distinct Karama keeps the counter window open. */
function source() {
const f = createKullRicheseSemutaFixture();
const game = structuredClone(f.game);
const zones = [game.deck, game.discard, ...(game.richeseCache ? [game.richeseCache] : []),
...(game.richeseRemoved ? [game.richeseRemoved] : []), ...game.players.map(seat => seat.hand)];
const zone = zones.find(cards => cards.some(card => card.effect === 'truthtrance')); assert.ok(zone);
const [truth] = zone.splice(zone.findIndex(card => card.effect === 'truthtrance'), 1);
assert.ok(player(game,'richese').hand.length < handLimit(player(game,'richese')));
player(game,'richese').hand.push(truth);
const cache = game.richeseCache; assert.ok(cache);
const karamaIndex = cache.findIndex(card => card.id === 'richese-karama'); assert.ok(karamaIndex >= 0);
const [additionalKarama] = cache.splice(karamaIndex,1);
assert.ok(player(game,'richese').hand.length < handLimit(player(game,'richese')));
player(game,'richese').hand.push(additionalKarama);
assert.deepEqual(inventory(game),f.originalCards);
assert.deepEqual(player(game,'harkonnen').hand.filter(card=>card.effect==='karama').map(card=>card.id),[f.counter]);
return {...f,game,truth:truth.id};
}
function passDiscard(game:Game):Game {
const pending = game.pendingTreacheryDiscard; assert.ok(pending?.reaction);
const event = pending.batch.event;
for (let count=0; game.pendingTreacheryDiscard?.batch.event === event && count<game.players.length; count++) {
const reaction = game.pendingTreacheryDiscard!.reaction; assert.ok(reaction?.stage === 'offer');
const seat = game.players.find(row=>!reaction.passed.includes(row.id)); assert.ok(seat);
game = act(game,seat.id,{type:'semutaPass',event});
}
assert.notEqual(game.pendingTreacheryDiscard?.batch.event,event);
return game;
}
void test('a Kull counter cannot spend the sole Karama promised for the open shipment-price window',()=>{
const f=source(); let game=act(f.game,'richese',{type:'card',card:f.truth});
while (game.truthtrance?.stage==='priority') {
const seat=game.players.find(row=>!game.truthtrance!.passed.includes(row.id)); assert.ok(seat);
game=act(game,seat.id,{type:'truthPass'});
}
const windowSource=JSON.stringify(['karamaShipment',game.turn,'emperor','original']);
game=act(game,'richese',{type:'truthAsk',question:{kind:'turnActions',target:'harkonnen',turn:game.turn,
mixed:{kind:'action',action:{kind:'windowCardPlay',effect:'karamaShipment',source:windowSource}}}});
assert.ok(viewGame(game,'harkonnen').truthTurnActionAnswers?.includes('yes'));
game=act(game,'harkonnen',{type:'truthAnswer',answer:'yes'});
const promise=game.turnActionPromises!.at(-1); assert.ok(promise);
if (game.pendingTreacheryDiscard) game=passDiscard(game);
game=act(game,'emperor',{type:'card',mode:'shipment',card:f.karama,target:'emperor'});
const opportunity=viewGame(game,'choam').kullReaction; assert.ok(opportunity);
game=act(game,'choam',{type:'kullDecision',event:opportunity.event,source:'printed',card:f.kull});
assert.equal(game.pendingKull?.stage,'counter');
assert.ok(viewGame(game,'richese').responseControls?.cancelCards.includes('richese-karama'));
assert.ok(!viewGame(game,'harkonnen').responseControls?.cancelCards.includes(f.counter));
const before=structuredClone(game);
assert.throws(()=>act(game,'harkonnen',{type:'card',mode:'cancel',card:f.counter}));
assert.deepEqual(game,before);
assert.ok(!game.turnActionPromises!.find(row=>row.event===promise.event)?.released);
game=advance(game,state=>!!state.pendingTreacheryDiscard);
game=passDiscard(game);
game=advance(game,clean);
assert.ok(!game.turnActionPromises!.find(row=>row.event===promise.event)?.released);
game=act(game,'harkonnen',{type:'card',mode:'shipment',card:f.counter,target:'emperor'});
const second=viewGame(game,'choam').kullReaction;
if (second) game=act(game,'choam',{type:'kullDecision',event:second.event,decline:true});
if (game.pendingTreacheryDiscard) game=passDiscard(game);
game=advance(game,clean);
assert.equal(game.turnActionPromises!.find(row=>row.event===promise.event)?.fulfilled,true);
assert.deepEqual(inventory(game),f.originalCards);
});