Files
Dune/tests/emperor-extortion-bot-priority.test.ts
T
Quintine 7c1f4b4e09
Verify and publish checkpoint container / container (push) Successful in 10m54s
Preserve Emperor gift opportunity before bot Extortion
2026-10-30 17:44:00 +11:00

90 lines
5.6 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { viewGame } from '../game/engine';
import { botActions, runBots } from '../game/bots';
import { DIFFICULTIES } from '../game/bot-profiles';
import { createExtortionBotPriority, extortionAct, extortionEvent, extortionPayment, extortionSeat,
finishExtortionGiftResponse } from './emperor-extortion-bot-priority-fixture';
for (const advanced of [false, true]) for (const autopilot of [false, true]) void test(
`${advanced ? 'Advanced' : 'Basic'} human Emperor gets Extortion gift priority before ${autopilot ? 'autopilot' : 'bot'} ally`, () => {
const {game: initial} = createExtortionBotPriority(advanced, {autopilot});
let game = initial;
const event = extortionEvent(game);
assert.deepEqual(viewGame(game, 'e').emperorExtortionPriority, {event, recipient:'a', canContinue:true});
for (const id of ['a','h','m'])
assert.deepEqual(viewGame(game, id).emperorExtortionPriority, {event, recipient:'a', canContinue:false});
const e = extortionSeat(game, 'e').spice, a = extortionSeat(game, 'a').spice;
for (const difficulty of DIFFICULTIES) {
const view = viewGame(game, 'a');
const actor = view.players.find(player => player.id === 'a')!;
if (autopilot) actor.autopilot = difficulty; else actor.bot = difficulty;
assert.ok(!botActions(view).some(action => action.type === 'decision' && action.event === event), difficulty);
}
const waiting = runBots(game, 5);
assert.equal(waiting.decision?.kind, 'moritaniExtortion');
assert.equal(extortionEvent(waiting), event);
assert.equal(extortionSeat(waiting, 'e').spice, e);
assert.equal(extortionSeat(waiting, 'a').spice, a);
const original = structuredClone(game);
assert.throws(() => extortionAct(game, 'a', extortionPayment(game, false)));
assert.throws(() => extortionAct(game, 'a', {type:'emperorExtortionContinue', event}));
assert.throws(() => extortionAct(game, 'e', {type:'emperorExtortionContinue', event:'stale'}));
assert.deepEqual(game, original);
game = extortionAct(game, 'e', {type:'emperorExtortionContinue', event});
assert.ok(!viewGame(game, 'e').emperorExtortionPriority);
assert.deepEqual(game.emperorExtortionGiftRelease, {event, emperor:'e', recipient:'a'});
const action = botActions(viewGame(game, 'a')).find(row => row.type === 'decision' && row.event === event);
assert.ok(action); assert.equal(action.pay, false);
game = runBots(game, 1);
assert.ok(game.decision?.kind !== 'moritaniExtortion' || game.decision.event !== event);
});
for (const advanced of [false, true]) for (const canceled of [false, true]) void test(
`${advanced ? 'Advanced' : 'Basic'} ${canceled ? 'canceled' : 'successful'} Emperor gift preserves priority until Continue`, () => {
const fixture = createExtortionBotPriority(advanced);
let game = fixture.game;
const event = extortionEvent(game), emperor = extortionSeat(game, 'e').spice;
game = extortionAct(game, 'e', {type:'emperorGift', amount:1});
assert.equal(game.response?.kind, 'emperorGift');
game = canceled ? extortionAct(game, 'h', {type:'card', card:fixture.karama, mode:'cancel'}) :
finishExtortionGiftResponse(game);
assert.equal(game.response, null);
assert.equal(game.decision?.kind, 'moritaniExtortion');
assert.equal(extortionEvent(game), event);
assert.deepEqual(viewGame(game, 'e').emperorExtortionPriority, {event, recipient:'a', canContinue:true});
assert.equal(extortionSeat(game, 'a').spice, canceled ? 2 : 3);
assert.equal(extortionSeat(game, 'e').spice, emperor - (canceled ? 0 : 1));
const before = runBots(game, 5);
assert.equal(extortionEvent(before), event);
game = extortionAct(game, 'e', {type:'emperorExtortionContinue', event});
const action = botActions(viewGame(game, 'a')).find(row => row.type === 'decision' && row.event === event);
// The current minimal bot policy declines even when it can afford to pay.
assert.ok(action); assert.equal(action.pay, false);
game = runBots(game, 1);
assert.ok(game.decision?.kind !== 'moritaniExtortion' || game.decision.event !== event);
assert.equal(game.moritaniTerror!.tokens.find(row => row.id === fixture.token)?.status, 'extortion');
});
for (const advanced of [false, true]) void test(
`${advanced ? 'Advanced' : 'Basic'} no-spice Emperor still has neutral Continue priority`, () => {
const {game} = createExtortionBotPriority(advanced, {emperorSpice:0});
const event = extortionEvent(game);
assert.deepEqual(viewGame(game, 'e').emperorExtortionPriority, {event, recipient:'a', canContinue:true});
assert.deepEqual(viewGame(game, 'a').emperorExtortionPriority, {event, recipient:'a', canContinue:false});
assert.ok(!botActions(viewGame(game, 'a')).some(action => action.type === 'decision'));
const continued = extortionAct(game, 'e', {type:'emperorExtortionContinue', event});
assert.ok(botActions(viewGame(continued, 'a')).some(action => action.type === 'decision' && action.pay === false));
});
for (const advanced of [false, true]) void test(
`${advanced ? 'Advanced' : 'Basic'} unaffiliated bot pays or declines without Emperor priority`, () => {
const {game} = createExtortionBotPriority(advanced, {unallied:true});
const event = extortionEvent(game);
assert.ok(!viewGame(game, 'e').emperorExtortionPriority);
const action = botActions(viewGame(game, 'a')).find(row => row.type === 'decision' && row.event === event);
assert.ok(action); assert.equal(action.pay, false);
const next = runBots(game, 1);
assert.ok(next.decision?.kind !== 'moritaniExtortion' || next.decision.event !== event);
});