Enable autonomous Nexus alliance participation
This commit is contained in:
1 parent
23b6c09ae8
commit
8045c9e3eb
3 files changed
+48
-6
No files matched your search
@@ -114,6 +114,13 @@ that these are the only rules-permitted player-count configurations. The
|
||||
and Advanced using the actual optional-module setup seams. They do not open
|
||||
public module starts or assert complete module interaction coverage.
|
||||
|
||||
All-AI Nexus samples now make public reciprocal offers as well as accepting
|
||||
them, so an actual alliance can form without a human proposer. At the
|
||||
20260927 seed, each six-seat sample made six alliance actions and completed;
|
||||
all six seats paired, leaving no unallied Nexus Card recipient. The focused
|
||||
Nexus alliance regression exercises the subsequent draw when a seat remains
|
||||
unallied. These samples do not establish complete card-effect coverage.
|
||||
|
||||
`--profile moritani-skills` adds five Basic samples with Moritani, then Emperor,
|
||||
Guild, Harkonnen, Fremen and Bene Gesserit. `--players` also narrows this profile;
|
||||
Advanced requests fail before running. Its seed offsets are 16–20, leaving
|
||||
|
||||
+12
-3
@@ -2802,10 +2802,19 @@ function policyActions(g: GameView): Action[] {
|
||||
if (card) return [{ type: 'card', card: card.id }];
|
||||
}
|
||||
if (g.nexus && !g.spiceWindow && !me.ally) {
|
||||
const offer = Object.entries(g.allianceOffers).find(
|
||||
([id, target]) => id !== me.id && target === me.id && !g.homeworldAllianceBlocks?.[id],
|
||||
const offer = g.players.find(
|
||||
(player) => player.id !== me.id &&
|
||||
g.allianceOffers[player.id] === me.id &&
|
||||
!g.homeworldAllianceBlocks?.[player.id],
|
||||
);
|
||||
if (offer && level > 0) return [{ type: 'alliance', target: offer[0] }];
|
||||
if (offer) return [{ type: 'alliance', target: offer.id }];
|
||||
if (level > 0 && !g.ready.includes(me.id) && !g.allianceOffers[me.id]) {
|
||||
const target = g.players.find(
|
||||
(player) => player.id !== me.id &&
|
||||
!player.ally && !g.homeworldAllianceBlocks?.[player.id],
|
||||
);
|
||||
if (target) return [{ type: 'alliance', target: target.id }];
|
||||
}
|
||||
}
|
||||
return g.ready.includes(me.id) ? [] : [{ type: 'ready' }];
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { applyAction } from '../game/engine';
|
||||
import { applyAction, viewGame } from '../game/engine';
|
||||
import { botActions } from '../game/bots';
|
||||
import {
|
||||
quoteNexusAlliance,
|
||||
type NexusAllianceSeat,
|
||||
} from '../game/nexus-alliance';
|
||||
import {
|
||||
nexusInventory,
|
||||
nexusPlayer,
|
||||
nexusReady,
|
||||
nexusTurnTwo,
|
||||
@@ -123,6 +125,32 @@ function negotiations() {
|
||||
return game;
|
||||
}
|
||||
|
||||
void test('AI initiates a reciprocal Nexus alliance without retrying its outstanding offer', () => {
|
||||
let game = negotiations();
|
||||
nexusPlayer(game, 'f').bot = 'Easy';
|
||||
nexusPlayer(game, 'a').bot = 'Medium';
|
||||
nexusPlayer(game, 'h').bot = 'Hard';
|
||||
|
||||
const proposal = botActions(viewGame(game, 'a'))[0];
|
||||
assert.deepEqual(proposal, { type: 'alliance', target: 'f' });
|
||||
game = applyAction(game, 'a', proposal);
|
||||
assert.deepEqual(game.allianceOffers, { a: 'f' });
|
||||
assert.deepEqual(botActions(viewGame(game, 'a'))[0], { type: 'ready' });
|
||||
const acceptance = botActions(viewGame(game, 'f'))[0];
|
||||
assert.deepEqual(acceptance, { type: 'alliance', target: 'a' });
|
||||
game = applyAction(game, 'f', acceptance);
|
||||
assert.equal(nexusPlayer(game, 'a').ally, 'f');
|
||||
assert.equal(nexusPlayer(game, 'f').ally, 'a');
|
||||
assert.deepEqual(game.allianceOffers, {});
|
||||
game = nexusReady(game);
|
||||
assert.equal(game.nexusCards?.phase?.stage, 'drawing');
|
||||
const draw = botActions(viewGame(game, 'h'))[0];
|
||||
assert.equal(draw.type, 'nexusCardChoice');
|
||||
game = applyAction(game, 'h', draw);
|
||||
assert.equal(game.nexusCards?.phase?.stage, 'complete');
|
||||
nexusInventory(game);
|
||||
});
|
||||
|
||||
void test('the actual engine preserves ordinary offer, formation and withdrawal side effects', () => {
|
||||
let game = negotiations();
|
||||
game.ready = ['h'];
|
||||
@@ -139,12 +167,10 @@ void test('the actual engine preserves ordinary offer, formation and withdrawal
|
||||
assert.equal(nexusPlayer(game, 'a').allySinceTurn, game.turn);
|
||||
assert.deepEqual(game.allianceOffers, {});
|
||||
assert.deepEqual(game.ready, []);
|
||||
assert.match(game.log.at(-1)!.text, /formed an alliance/);
|
||||
|
||||
game.ready = ['h'];
|
||||
game = applyAction(game, 'f', { type: 'alliance', target: null });
|
||||
assert.equal(nexusPlayer(game, 'f').ally, null);
|
||||
assert.equal(nexusPlayer(game, 'a').ally, null);
|
||||
assert.deepEqual(game.ready, []);
|
||||
assert.match(game.log.at(-1)!.text, /unallied/);
|
||||
});
|
||||
Reference in new issue
Block a user