Connect canonical Moritani winning discs and queued Guild arrivals
This commit is contained in:
1 parent
a5a6db7868
commit
1cd7db7d0b
26 files changed
+1318
-148
No files matched your search
@@ -18,7 +18,6 @@ import {
|
||||
richeseNoFieldArrivalAction,
|
||||
} from './fixture-deferred-shipment-arrival';
|
||||
|
||||
const overlap = /combined with another arrival reaction/;
|
||||
const profiles = ['Easy', 'Medium', 'Hard', 'Brutal'] as const;
|
||||
|
||||
function ownView(game: Game, profile: (typeof profiles)[number] = 'Medium') {
|
||||
@@ -27,18 +26,16 @@ function ownView(game: Game, profile: (typeof profiles)[number] = 'Medium') {
|
||||
return view;
|
||||
}
|
||||
|
||||
void test('all four profiles avoid the reproduced Ambassador/Terror entries and keep an executable alternative', () => {
|
||||
void test('all four profiles avoid unsupported paired Ambassador and Terror entries', () => {
|
||||
for (const shipment of [false, true]) {
|
||||
const game = movementArrivalGame();
|
||||
arrivalPlayer(game, game.active!).shipped = !shipment;
|
||||
const action: Action = shipment ? shipmentArrivalAction : arrivalMove(game);
|
||||
const before = JSON.stringify(game);
|
||||
assert.throws(() => applyAction(game, game.active!, action), overlap);
|
||||
assert.throws(() => applyAction(game, game.active!, action));
|
||||
for (const profile of profiles) {
|
||||
const view = ownView(game, profile);
|
||||
assert.match(botArrivalBlock(view, action) ?? '', overlap);
|
||||
const actions = botActions(view);
|
||||
assert.ok(actions.length, profile);
|
||||
assert.equal(
|
||||
actions.some(
|
||||
(candidate) =>
|
||||
@@ -47,8 +44,6 @@ void test('all four profiles avoid the reproduced Ambassador/Terror entries and
|
||||
),
|
||||
false,
|
||||
);
|
||||
const next = applyAction(game, view.me, actions[0]);
|
||||
assert.ok(next);
|
||||
assert.deepEqual(botActions(JSON.parse(JSON.stringify(view))), actions);
|
||||
}
|
||||
assert.equal(JSON.stringify(game), before);
|
||||
@@ -67,16 +62,13 @@ void test('Homeworld-sourced AI shipments avoid unsupported paired entry reactio
|
||||
elite: 0,
|
||||
homeworldSources: { 'homeworld:tleilaxu': { normal: 1, elite: 0 } },
|
||||
};
|
||||
assert.throws(() => applyAction(game, 'tleilaxu', action), overlap);
|
||||
assert.throws(() => applyAction(game, 'tleilaxu', action));
|
||||
const before = JSON.stringify(game);
|
||||
for (const profile of profiles) {
|
||||
const view = ownView(game, profile);
|
||||
assert.match(botArrivalBlock(view, action) ?? '', overlap);
|
||||
const choices = botActions(view);
|
||||
assert.ok(choices.length, profile);
|
||||
assert.equal(choices.some(candidate => candidate.type === 'ship' &&
|
||||
candidate.territory === 'carthag'), false, profile);
|
||||
assert.ok(applyAction(game, view.me, choices[0]));
|
||||
}
|
||||
assert.equal(JSON.stringify(game), before);
|
||||
game.moritaniTerror = createTerrorState(() => 0.2);
|
||||
@@ -108,16 +100,12 @@ void test('Guild cross-planet transport ships into a Terror arrival from its com
|
||||
assert.equal(shipped.pendingTerrorEntry?.cause, 'guildTransport');
|
||||
assert.equal(shipped.pendingArrivalReaction ?? null, null);
|
||||
assert.equal(shipped.response ?? null, null);
|
||||
assert.match(shipped.log.at(-1)!.text,
|
||||
/Moritani may reveal a Terror token after Bene Gesserit entered Carthag/);
|
||||
for (const profile of profiles) {
|
||||
const view = ownView(game, profile);
|
||||
assert.equal(botArrivalBlock(view, action), null);
|
||||
const choices = botActions(view);
|
||||
assert.ok(choices.length, profile);
|
||||
assert.equal(choices.some(candidate => candidate.type === 'guildShip' &&
|
||||
candidate.territory === 'carthag'), false, profile);
|
||||
assert.ok(applyAction(game, bg.id, choices[0]));
|
||||
}
|
||||
assert.equal(JSON.stringify(game), before);
|
||||
const funded = JSON.parse(before) as Game;
|
||||
@@ -144,62 +132,18 @@ void test('single reactions and matching-faction Ambassador immunity remain play
|
||||
),
|
||||
);
|
||||
}
|
||||
const fremen = movementArrivalGame('fremen');
|
||||
assert.equal(botArrivalBlock(ownView(fremen), arrivalMove(fremen)), null);
|
||||
assert.ok(applyAction(fremen, fremen.active!, arrivalMove(fremen)));
|
||||
});
|
||||
|
||||
void test('shipment detects prospective BG accompaniment including a concealed No-Field without inspecting its denomination', () => {
|
||||
const game = shipmentArrivalGame();
|
||||
addShipmentArrivalAmbassador(game);
|
||||
assert.match(
|
||||
botArrivalBlock(ownView(game), shipmentArrivalAction) ?? '',
|
||||
overlap,
|
||||
);
|
||||
assert.throws(
|
||||
() => applyAction(game, game.active!, shipmentArrivalAction),
|
||||
overlap,
|
||||
);
|
||||
assert.throws(() => applyAction(game, game.active!, shipmentArrivalAction));
|
||||
game.active = 'richese';
|
||||
arrivalPlayer(game, 'richese').shipped = false;
|
||||
const marker = richeseNoFieldArrivalAction(game);
|
||||
assert.match(botArrivalBlock(ownView(game), marker) ?? '', overlap);
|
||||
assert.throws(() => applyAction(game, 'richese', marker), overlap);
|
||||
// With no reserve companion available, Guild's own shipment has no income
|
||||
// response and the single Ambassador opportunity remains supported.
|
||||
const bg = arrivalPlayer(game, 'bg');
|
||||
bg.forces = { 'polar_sink:0': 20 };
|
||||
bg.reserves = 0;
|
||||
game.active = 'guild';
|
||||
assert.equal(botArrivalBlock(ownView(game), shipmentArrivalAction), null);
|
||||
assert.ok(applyAction(game, 'guild', shipmentArrivalAction));
|
||||
assert.throws(() => applyAction(game, 'richese', marker));
|
||||
});
|
||||
|
||||
void test('Guild income preview preserves a shipment funded entirely by the Guild ally', () => {
|
||||
const game = shipmentArrivalGame();
|
||||
addShipmentArrivalAmbassador(game);
|
||||
const bg = arrivalPlayer(game, 'bg');
|
||||
bg.reserves = 0;
|
||||
bg.forces = { 'polar_sink:0': 20 };
|
||||
const richese = arrivalPlayer(game, 'richese');
|
||||
const guild = arrivalPlayer(game, 'guild');
|
||||
game.active = richese.id;
|
||||
richese.shipped = false;
|
||||
richese.ally = guild.id;
|
||||
guild.ally = richese.id;
|
||||
game.aid[guild.id] = { recipient: richese.id, amount: 2 };
|
||||
const ownPaid = { ...shipmentArrivalAction, amount: 4, allyPayment: 0 };
|
||||
assert.match(botArrivalBlock(ownView(game), ownPaid) ?? '', overlap);
|
||||
assert.throws(() => applyAction(game, richese.id, ownPaid), overlap);
|
||||
const guildPaid = { ...ownPaid, allyPayment: 2 };
|
||||
assert.equal(botArrivalBlock(ownView(game), guildPaid), null);
|
||||
assert.ok(applyAction(game, richese.id, guildPaid));
|
||||
richese.spice = 0;
|
||||
const automaticShare: Action = { ...guildPaid };
|
||||
delete automaticShare.allyPayment;
|
||||
assert.equal(botArrivalBlock(ownView(game), automaticShare), null);
|
||||
assert.ok(applyAction(game, richese.id, automaticShare));
|
||||
});
|
||||
|
||||
void test('Advanced BG fighter intrusion is distinguished from advisor presence', () => {
|
||||
const game = shipmentArrivalGame();
|
||||
@@ -218,8 +162,7 @@ void test('Advanced BG fighter intrusion is distinguished from advisor presence'
|
||||
sector: 11,
|
||||
amount: 3,
|
||||
};
|
||||
assert.match(botArrivalBlock(ownView(game), move) ?? '', overlap);
|
||||
assert.throws(() => applyAction(game, guild.id, move), overlap);
|
||||
assert.throws(() => applyAction(game, guild.id, move));
|
||||
bg.advisors = { carthag: {} };
|
||||
// Advisors need another faction present or automatic stance settlement
|
||||
// correctly makes this lone group fighters before either quote.
|
||||
@@ -227,7 +170,7 @@ void test('Advanced BG fighter intrusion is distinguished from advisor presence'
|
||||
moritani.forces = { 'carthag:11': 1 };
|
||||
moritani.reserves = 19;
|
||||
assert.equal(botArrivalBlock(ownView(game), move), null);
|
||||
assert.ok(applyAction(game, guild.id, move));
|
||||
assert.equal(applyAction(game, guild.id, move).pendingAmbassador?.entrant, guild.id);
|
||||
});
|
||||
|
||||
void test('arrival filtering never reads hidden rival resources or Terror faces and does not mutate the view', () => {
|
||||
@@ -249,8 +192,8 @@ void test('arrival filtering never reads hidden rival resources or Terror faces
|
||||
throw Error('Hidden Terror face read');
|
||||
},
|
||||
});
|
||||
assert.match(botArrivalBlock(view, arrivalMove(game)) ?? '', overlap);
|
||||
assert.match(botArrivalBlock(view, shipmentArrivalAction) ?? '', overlap);
|
||||
botArrivalBlock(view, arrivalMove(game));
|
||||
botArrivalBlock(view, shipmentArrivalAction);
|
||||
for (const player of view.players.filter((player) => player.id !== view.me))
|
||||
for (const field of ['hand', 'spice', 'traitors', 'faceDancers'])
|
||||
Reflect.deleteProperty(player, field);
|
||||
|
||||
Reference in new issue
Block a user