Files
OpenFrontIO/tests/NationAllianceBehavior.test.ts
evanpelle 33e1ad9897 spawn
2026-05-01 16:20:34 -06:00

195 lines
5.4 KiB
TypeScript

import { NationAllianceBehavior } from "../src/core/execution/nation/NationAllianceBehavior";
import { NationEmojiBehavior } from "../src/core/execution/nation/NationEmojiBehavior";
import {
AllianceRequest,
Game,
Player,
PlayerInfo,
PlayerType,
Tick,
} from "../src/core/game/Game";
import { PseudoRandom } from "../src/core/PseudoRandom";
import { setup } from "./util/Setup";
let game: Game;
let player: Player;
let requestor: Player;
let allianceBehavior: NationAllianceBehavior;
describe("AllianceBehavior.handleAllianceRequests", () => {
beforeEach(async () => {
game = await setup("big_plains", {
infiniteGold: true,
instantBuild: true,
});
const playerInfo = new PlayerInfo(
"player_id",
PlayerType.Bot,
null,
"player_id",
);
const requestorInfo = new PlayerInfo(
"requestor_id",
PlayerType.Human,
null,
"requestor_id",
);
game.addPlayer(playerInfo);
game.addPlayer(requestorInfo);
player = game.player("player_id");
requestor = game.player("requestor_id");
// Use a fixed random seed for deterministic behavior
const random = new PseudoRandom(46);
allianceBehavior = new NationAllianceBehavior(
random,
game,
player,
new NationEmojiBehavior(random, game, player),
);
});
function setupAllianceRequest({
isTraitor = false,
relationDelta = 2,
numTilesPlayer = 10,
numTilesRequestor = 10,
alliancesCount = 0,
createdAtTick = game.config().numSpawnPhaseTurns() + 2,
} = {}) {
if (isTraitor) requestor.markTraitor();
player.updateRelation(requestor, relationDelta);
requestor.updateRelation(player, relationDelta);
game.map().forEachTile((tile) => {
if (game.map().isLand(tile)) {
if (numTilesPlayer > 0) {
player.conquer(tile);
numTilesPlayer--;
} else if (numTilesRequestor > 0) {
requestor.conquer(tile);
numTilesRequestor--;
}
}
});
vi.spyOn(player, "alliances").mockReturnValue(new Array(alliancesCount));
const mockRequest = {
requestor: () => requestor,
recipient: () => player,
createdAt: () => createdAtTick as unknown as Tick,
accept: vi.fn(),
reject: vi.fn(),
} as unknown as AllianceRequest;
vi.spyOn(player, "incomingAllianceRequests").mockReturnValue([mockRequest]);
return mockRequest;
}
test("should reject alliance created on first post-spawn tick", () => {
const cutoff = game.config().numSpawnPhaseTurns() + 1;
const request = setupAllianceRequest({ createdAtTick: cutoff });
allianceBehavior.handleAllianceRequests();
expect(request.accept).not.toHaveBeenCalled();
expect(request.reject).toHaveBeenCalled();
});
test("should accept alliance when all conditions are met", () => {
const request = setupAllianceRequest({});
allianceBehavior.handleAllianceRequests();
expect(request.accept).toHaveBeenCalled();
expect(request.reject).not.toHaveBeenCalled();
});
test("should reject alliance if requestor is a traitor", () => {
const request = setupAllianceRequest({ isTraitor: true });
allianceBehavior.handleAllianceRequests();
expect(request.accept).not.toHaveBeenCalled();
expect(request.reject).toHaveBeenCalled();
});
test("should reject alliance if relation is hostile", () => {
const request = setupAllianceRequest({ relationDelta: -2 });
allianceBehavior.handleAllianceRequests();
expect(request.accept).not.toHaveBeenCalled();
expect(request.reject).toHaveBeenCalled();
});
test("should accept alliance if requestor is much larger (> 3 times size of recipient)", () => {
const request = setupAllianceRequest({
numTilesRequestor: 40,
});
allianceBehavior.handleAllianceRequests();
expect(request.accept).toHaveBeenCalled();
expect(request.reject).not.toHaveBeenCalled();
});
test("should reject alliance if player has too many alliances", () => {
const request = setupAllianceRequest({ alliancesCount: 10 });
allianceBehavior.handleAllianceRequests();
expect(request.accept).not.toHaveBeenCalled();
expect(request.reject).toHaveBeenCalled();
});
});
describe("AllianceBehavior.handleAllianceExtensionRequests", () => {
let mockGame: any;
let mockPlayer: any;
let mockAlliance: any;
let mockHuman: any;
let mockRandom: any;
let allianceBehavior: NationAllianceBehavior;
beforeEach(() => {
mockGame = {
addExecution: vi.fn(),
config: vi.fn(() => ({ disableAlliances: vi.fn(() => false) })),
};
mockHuman = { id: vi.fn(() => "human_id") };
mockAlliance = {
onlyOneAgreedToExtend: vi.fn(() => true),
other: vi.fn(() => mockHuman),
};
mockRandom = { chance: vi.fn() };
mockPlayer = {
alliances: vi.fn(() => [mockAlliance]),
relation: vi.fn(),
id: vi.fn(() => "bot_id"),
type: vi.fn(() => PlayerType.Nation),
};
allianceBehavior = new NationAllianceBehavior(
mockRandom,
mockGame,
mockPlayer,
new NationEmojiBehavior(mockRandom, mockGame, mockPlayer),
);
});
it("should NOT request extension if onlyOneAgreedToExtend is false (no expiration yet or both already agreed)", () => {
mockAlliance.onlyOneAgreedToExtend.mockReturnValue(false);
allianceBehavior.handleAllianceExtensionRequests();
expect(mockGame.addExecution).not.toHaveBeenCalled();
});
});