Integrate full funded Guild Nexus Betrayal
This commit is contained in:
1 parent
d3bf12fcd8
commit
8a56299155
28 files changed
+3005
-58
No files matched your search
@@ -0,0 +1,136 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { applyAction, viewGame } from '../game/engine';
|
||||
import { botActions } from '../game/bots';
|
||||
import { DIFFICULTIES } from '../game/bot-profiles';
|
||||
import { createGuildBetrayalFixture } from './fixture-nexus-guild-betrayal';
|
||||
|
||||
void test('all four legal bot profiles replace the original payment for every supported shipment adapter', () => {
|
||||
for (const profile of DIFFICULTIES) {
|
||||
const scenarios = [
|
||||
{ source: 'reserve' },
|
||||
{ source: 'guildTransport' },
|
||||
{ source: 'homeworld' },
|
||||
{ source: 'junction' },
|
||||
{ source: 'reserve', homeworlds: true, occupiedJunction: true },
|
||||
] as const;
|
||||
for (const options of scenarios) {
|
||||
const fixture = createGuildBetrayalFixture(options);
|
||||
const game = structuredClone(fixture.game);
|
||||
const own = viewGame(game, fixture.holder);
|
||||
own.players.find(player => player.id === fixture.holder)!.bot = profile;
|
||||
// Bots must not reconstruct eligibility or price from hidden opponents.
|
||||
for (const rival of own.players) {
|
||||
if (rival.id !== own.me) {
|
||||
delete rival.hand;
|
||||
delete rival.spice;
|
||||
}
|
||||
}
|
||||
const reaction = own.guildBetrayalReaction!;
|
||||
assert.equal(reaction.canUse, true);
|
||||
const actions = botActions(own);
|
||||
assert.deepEqual(actions, [{ type: 'guildBetrayalUse', event: fixture.event }]);
|
||||
const result = applyAction(game, fixture.holder, actions[0]);
|
||||
assert.equal(viewGame(result, fixture.holder).guildBetrayalReaction, null);
|
||||
assert.equal(result.players.find(player => player.id === fixture.holder)!.spice,
|
||||
game.players.find(player => player.id === fixture.holder)!.spice + fixture.price);
|
||||
assert.equal(result.players.find(player => player.id === fixture.shipper)!.spice,
|
||||
game.players.find(player => player.id === fixture.shipper)!.spice - fixture.price);
|
||||
assert.equal(result.nexusCards?.cards?.hands[fixture.holder], null);
|
||||
assert.ok(result.nexusCards?.cards?.discard.includes('guild'));
|
||||
assert.deepEqual(result.players.map(player => ({ id: player.id, forces: player.forces, reserves: player.reserves, elites: player.elites, shipped: player.shipped })),
|
||||
fixture.original.players.map(player => ({ id: player.id, forces: player.forces, reserves: player.reserves, elites: player.elites, shipped: player.shipped })));
|
||||
assert.deepEqual(result.homeworlds?.custody, fixture.original.homeworlds?.custody);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
void test('all profiles legally take their own fully funded invoice without treating a refund as payment funding', () => {
|
||||
for (const profile of DIFFICULTIES) {
|
||||
const fixture = createGuildBetrayalFixture({ payer: 'holder' });
|
||||
const own = viewGame(fixture.game, fixture.holder);
|
||||
own.players.find(player => player.id === fixture.holder)!.bot = profile;
|
||||
const actions = botActions(own);
|
||||
assert.deepEqual(actions, [{ type: 'guildBetrayalUse', event: fixture.event }]);
|
||||
const result = applyAction(fixture.game, fixture.holder, actions[0]);
|
||||
assert.equal(result.players.find(player => player.id === fixture.holder)!.spice,
|
||||
fixture.game.players.find(player => player.id === fixture.holder)!.spice);
|
||||
assert.equal(result.players.find(player => player.id === fixture.holder)!.reserves,
|
||||
fixture.game.players.find(player => player.id === fixture.holder)!.reserves - 2);
|
||||
assert.equal(result.players.find(player => player.id === fixture.holder)!.shipped, true);
|
||||
assert.equal(result.nexusCards?.cards?.hands[fixture.holder], null);
|
||||
assert.equal(viewGame(result, fixture.holder).guildBetrayalReaction, null);
|
||||
}
|
||||
});
|
||||
|
||||
void test('every profile acknowledges irrelevant faces until the original settlement completes, without fallthrough', () => {
|
||||
for (const profile of DIFFICULTIES) {
|
||||
for (const payer of ['holder', 'other'] as const) {
|
||||
const fixture = createGuildBetrayalFixture({ payer, nexusFace: 'choam' });
|
||||
let game = structuredClone(fixture.game);
|
||||
for (const player of game.players) {
|
||||
const own = viewGame(game, player.id);
|
||||
own.players.find(seat => seat.id === player.id)!.bot = profile;
|
||||
const reaction = own.guildBetrayalReaction;
|
||||
if (!reaction) break;
|
||||
const actions = botActions(own);
|
||||
if (!reaction.canPass || reaction.hasPassed) {
|
||||
assert.deepEqual(actions, []);
|
||||
continue;
|
||||
}
|
||||
assert.deepEqual(actions, [{ type: 'guildBetrayalPass', event: fixture.event }]);
|
||||
game = applyAction(game, player.id, actions[0]);
|
||||
const after = viewGame(game, player.id);
|
||||
after.players.find(seat => seat.id === player.id)!.bot = profile;
|
||||
if (after.guildBetrayalReaction) assert.deepEqual(botActions(after), []);
|
||||
}
|
||||
assert.equal(viewGame(game, fixture.holder).guildBetrayalReaction, null);
|
||||
assert.deepEqual(game.players.map(player => ({ id: player.id, spice: player.spice, forces: player.forces, reserves: player.reserves, shipped: player.shipped })),
|
||||
fixture.original.players.map(player => ({ id: player.id, spice: player.spice, forces: player.forces, reserves: player.reserves, shipped: player.shipped })));
|
||||
assert.equal(game.nexusCards?.cards?.hands[fixture.holder], 'choam');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
void test('seat-private blocked and response ownership gates override visible held-card hints for all profiles', () => {
|
||||
for (const profile of DIFFICULTIES) {
|
||||
const fixture = createGuildBetrayalFixture();
|
||||
const own = viewGame(fixture.game, fixture.holder);
|
||||
own.players.find(player => player.id === fixture.holder)!.bot = profile;
|
||||
assert.equal(own.nexusCards?.card, 'guild');
|
||||
const reaction = own.guildBetrayalReaction!;
|
||||
reaction.blocked = 'The committed promise prevents spending this Nexus card.';
|
||||
assert.deepEqual(botActions(own), [{ type: 'guildBetrayalPass', event: fixture.event }]);
|
||||
reaction.blocked = null;
|
||||
reaction.canUse = false;
|
||||
assert.deepEqual(botActions(own), [{ type: 'guildBetrayalPass', event: fixture.event }]);
|
||||
reaction.canUse = true;
|
||||
reaction.hasPassed = true;
|
||||
assert.deepEqual(botActions(own), []);
|
||||
reaction.hasPassed = false;
|
||||
reaction.canPass = false;
|
||||
assert.deepEqual(botActions(own), []);
|
||||
reaction.canPass = true;
|
||||
own.status = 'finished';
|
||||
assert.deepEqual(botActions(own), []);
|
||||
own.status = 'playing';
|
||||
reaction.event = '';
|
||||
assert.deepEqual(botActions(own), []);
|
||||
}
|
||||
});
|
||||
|
||||
void test('human seats require delegation while autopilot obeys the same private response offer', () => {
|
||||
const fixture = createGuildBetrayalFixture({ payer: 'holder' });
|
||||
const own = viewGame(fixture.game, fixture.holder);
|
||||
const seat = own.players.find(player => player.id === fixture.holder)!;
|
||||
delete seat.bot;
|
||||
delete seat.autopilot;
|
||||
assert.deepEqual(botActions(own), []);
|
||||
seat.autopilot = 'Easy';
|
||||
const actions = botActions(own);
|
||||
assert.deepEqual(actions, [{ type: 'guildBetrayalUse', event: fixture.event }]);
|
||||
const result = applyAction(fixture.game, fixture.holder, actions[0]);
|
||||
assert.equal(result.nexusCards?.cards?.hands[fixture.holder], null);
|
||||
assert.equal(result.players.find(player => player.id === fixture.holder)!.spice,
|
||||
fixture.game.players.find(player => player.id === fixture.holder)!.spice);
|
||||
});
|
||||
Reference in new issue
Block a user