Files
Dune/tests/semuta-emperor-share.test.ts

70 lines
4.2 KiB
TypeScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { applyAction, viewGame } from '../game/engine';
import { botActions } from '../game/bots';
import { SEMUTA_DRUG_ID } from '../game/semuta-drug';
import {
createSemutaEmperorShareFixture, reactSemutaEmperorShare,
semutaEmperorPlayer, semutaEmperorSharePhysical, semutaEmperorShareViews,
type SemutaEmperorShareKind,
} from './fixture-semuta-emperor-share';
for (const kind of ['emperorGift', 'emperorRevival'] as SemutaEmperorShareKind[]) {
for (const claim of [false, true]) void test(
`Semuta ${claim ? 'claims' : 'passes'} the printed Karama canceling Emperor ${kind === 'emperorGift' ? 'gift' : 'revival'} before the native cancellation`, () => {
const f = createSemutaEmperorShareFixture(kind);
assert.equal(semutaEmperorPlayer(f.game, f.holder).hand.some(card => card.id === f.semuta), true);
const declared = applyAction(f.game, f.owner, f.declare);
assert.equal(declared.response?.kind, kind);
assert.equal(semutaEmperorPlayer(declared, f.owner).spice, semutaEmperorPlayer(f.game, f.owner).spice);
const pending = applyAction(declared, f.recipient, f.cancel);
const reaction = pending.pendingTreacheryDiscard!;
assert.equal(reaction.batch.cause, `karama:${kind}`);
assert.deepEqual(reaction.batch.entries, [{ card: f.karamaCard, discardedBy: f.recipient, publicFace: true }]);
assert.equal(reaction.reaction?.stage, 'offer');
semutaEmperorShareViews(pending);
const before = semutaEmperorSharePhysical(pending);
const offer = viewGame(pending, f.holder).semutaReaction!;
assert.equal(offer.canCommit, true);
assert.deepEqual(offer.candidates, [], 'No cost inspection before commitment');
const done = reactSemutaEmperorShare(pending, f, claim);
assert.equal(done.resolvedTreacheryDiscardSequence, reaction.sequence);
assert.equal(semutaEmperorPlayer(done, f.holder).hand.some(card => card.id === f.karama), claim);
assert.equal(semutaEmperorPlayer(done, f.holder).hand.some(card => card.id === SEMUTA_DRUG_ID), !claim);
assert.equal(done.discard.filter(card => card.id === f.karama).length, Number(!claim));
assert.equal(done.discard.filter(card => card.id === SEMUTA_DRUG_ID).length, Number(claim));
// The denied share moves nothing: no spice, forces, revivals or extra-revival ledger.
assert.deepEqual(semutaEmperorSharePhysical(done), before);
assert.equal(done.emperorExtra[f.recipient] ?? 0, 0);
assert.throws(() => applyAction(done, f.recipient, f.cancel));
assert.throws(() => applyAction(done, f.holder, { type: 'semutaCommit', event: reaction.batch.event }));
semutaEmperorShareViews(done);
},
);
}
void test('the native Emperor share still settles when its cost is declined', () => {
const f = createSemutaEmperorShareFixture('emperorGift');
const declared = applyAction(f.game, f.owner, f.declare);
// The neutral window still requires the Karama holder's own choice.
const settled = applyAction(declared, f.recipient, { type: 'passResponse' });
assert.equal(semutaEmperorPlayer(settled, f.owner).spice, semutaEmperorPlayer(f.game, f.owner).spice - 3);
assert.equal(semutaEmperorPlayer(settled, f.recipient).spice, semutaEmperorPlayer(f.game, f.recipient).spice + 3);
assert.equal(semutaEmperorPlayer(settled, f.holder).hand.some(card => card.id === SEMUTA_DRUG_ID), true);
});
void test('every legal Semuta profile finishes the printed Emperor-share cancellation cost', () => {
for (const difficulty of ['Easy', 'Medium', 'Hard', 'Brutal'] as const) {
const f = createSemutaEmperorShareFixture('emperorRevival');
const pending = applyAction(applyAction(f.game, f.owner, f.declare), f.recipient, f.cancel);
const own = viewGame(pending, f.holder);
own.players.find(player => player.id === f.holder)!.bot = difficulty;
const action = botActions(own)[0];
assert.ok(action);
assert.equal(action.type, 'semutaCommit');
const done = applyAction(pending, f.holder, action);
assert.equal(semutaEmperorPlayer(done, f.holder).hand.some(card => card.id === f.karama), true);
assert.equal(done.emperorExtra[f.recipient] ?? 0, 0);
}
});