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