108 lines
5.4 KiB
TypeScript
108 lines
5.4 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { applyAction, viewGame } from '../game/engine';
|
|
import type { Game } from '../game/engine';
|
|
import { botActions } from '../game/bots';
|
|
import { DIFFICULTIES } from '../game/bot-profiles';
|
|
import { createRicheseBetrayalFixture } from './fixture-richese-betrayal';
|
|
|
|
function saved(game: Game): Game {
|
|
return JSON.parse(JSON.stringify(game));
|
|
}
|
|
function payOriginalSeller(game: Game): Game {
|
|
const purchase = game.pendingNexusEmperorSellerPurchase;
|
|
return purchase ? applyAction(game, purchase.winner,
|
|
{ type: 'nexusEmperorSellerPurchase', event: purchase.event, use: false }) : game;
|
|
}
|
|
|
|
void test('all four profiles commit the saved purchase veto and sale diversion from their private projection', () => {
|
|
for (const profile of DIFFICULTIES) {
|
|
for (const kind of ['purchase', 'sale'] as const) {
|
|
const fixture = createRicheseBetrayalFixture(kind);
|
|
fixture.game.players.find(player => player.id === fixture.holder)!.bot = profile;
|
|
const game = saved(fixture.game);
|
|
const own = viewGame(game, fixture.holder);
|
|
// No rival hand or resources may be needed to choose the response.
|
|
for (const rival of own.players) {
|
|
if (rival.id !== own.me) {
|
|
delete rival.hand;
|
|
delete rival.spice;
|
|
}
|
|
}
|
|
const reaction = own.richeseBetrayalReaction!;
|
|
assert.equal(reaction.canUse, true);
|
|
const actions = botActions(own);
|
|
assert.deepEqual(actions, [{ type: 'richeseBetrayalUse', event: reaction.event }]);
|
|
const balances = game.players.map(player => player.spice);
|
|
const buyerCount = game.players.find(player => player.id === fixture.buyer)!.hand.length;
|
|
const result = saved(payOriginalSeller(applyAction(game, fixture.holder, actions[0])));
|
|
assert.equal(viewGame(result, fixture.holder).richeseBetrayalReaction, null);
|
|
assert.ok(result.nexusCards?.cards?.discard.includes('richese'));
|
|
if (kind === 'purchase') {
|
|
assert.deepEqual(result.players.map(player => player.spice), balances);
|
|
assert.equal(result.players.find(player => player.id === fixture.buyer)!.hand.length, buyerCount);
|
|
assert.ok(result.discard.some(card => card.id === fixture.card));
|
|
} else {
|
|
assert.equal(result.players.find(player => player.id === fixture.target)!.spice,
|
|
game.players.find(player => player.id === fixture.target)!.spice);
|
|
assert.equal(result.players.find(player => player.id === fixture.buyer)!.spice,
|
|
game.players.find(player => player.id === fixture.buyer)!.spice - reaction.price);
|
|
assert.ok(result.players.find(player => player.id === fixture.buyer)!.hand.some(card => card.id === fixture.card));
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
void test('all profiles pass publicly possible nonholder boundaries through saved original settlement', () => {
|
|
for (const profile of DIFFICULTIES) {
|
|
for (const kind of ['purchase', 'sale'] as const) {
|
|
const fixture = createRicheseBetrayalFixture(kind, { holderNexus: 'choam' });
|
|
for (const player of fixture.game.players) player.bot = profile;
|
|
let game = saved(fixture.game);
|
|
const original = viewGame(game, fixture.holder).richeseBetrayalReaction!;
|
|
assert.equal(original.canUse, false);
|
|
for (let acknowledgements = 0; acknowledgements < game.players.length; acknowledgements++) {
|
|
const responder = game.players.find(player => {
|
|
const reaction = viewGame(game, player.id).richeseBetrayalReaction;
|
|
return reaction?.canPass && !reaction.hasPassed;
|
|
});
|
|
if (!responder) break;
|
|
const view = viewGame(game, responder.id);
|
|
const actions = botActions(view);
|
|
assert.deepEqual(actions, [{ type: 'richeseBetrayalPass', event: original.event }]);
|
|
game = saved(applyAction(game, responder.id, actions[0]));
|
|
if (viewGame(game, responder.id).richeseBetrayalReaction) {
|
|
assert.deepEqual(botActions(viewGame(game, responder.id)), []);
|
|
}
|
|
}
|
|
assert.equal(viewGame(game, fixture.holder).richeseBetrayalReaction, null);
|
|
game = saved(payOriginalSeller(game));
|
|
assert.ok(game.players.find(player => player.id === fixture.buyer)!.hand.some(card => card.id === fixture.card));
|
|
assert.equal(game.players.find(player => player.id === fixture.buyer)!.spice,
|
|
fixture.game.players.find(player => player.id === fixture.buyer)!.spice - original.price);
|
|
assert.equal(game.nexusCards?.cards?.hands[fixture.holder], 'choam');
|
|
}
|
|
}
|
|
});
|
|
|
|
void test('projected restrictions defeat physical Richese Nexus hints without exposing underlying bot actions', () => {
|
|
for (const profile of DIFFICULTIES) {
|
|
const fixture = createRicheseBetrayalFixture('purchase');
|
|
fixture.game.players.find(player => player.id === fixture.holder)!.bot = profile;
|
|
const view = viewGame(saved(fixture.game), fixture.holder);
|
|
assert.equal(view.nexusCards?.card, 'richese');
|
|
const reaction = view.richeseBetrayalReaction!;
|
|
reaction.canUse = false;
|
|
reaction.blocked = 'The committed promise prevents spending this Nexus card.';
|
|
assert.deepEqual(botActions(view), [{ type: 'richeseBetrayalPass', event: reaction.event }]);
|
|
reaction.hasPassed = true;
|
|
assert.deepEqual(botActions(view), []);
|
|
reaction.hasPassed = false;
|
|
reaction.canPass = false;
|
|
assert.deepEqual(botActions(view), []);
|
|
reaction.canPass = true;
|
|
view.status = 'finished';
|
|
assert.deepEqual(botActions(view), []);
|
|
}
|
|
});
|