From 080cf8f3f83ba87d6339891bf3e4a5363b7dc874 Mon Sep 17 00:00:00 2001 From: Arnaud Moreau <72945143+Nephty@users.noreply.github.com> Date: Fri, 6 Jun 2025 20:57:02 +0200 Subject: [PATCH] Improve readability of alliance acceptation logic for bots and add tests (#1049) ## Description: The method deciding whether bots should accept an alliance used to use multiple variables with negated names (notTraitor, notMalice, notTooManyAlliances). For readability, I have negated their value in order to given them a positive name (isTraitor, hasMalice, tooManyAlliances). I have also added tests for the alliances acceptation/rejection behavior of bots. - [x] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced - [X] I understand that submitting code with bugs that could have been caught through manual testing blocks releases and new features for all contributors Discord: nephty --- src/core/execution/utils/BotBehavior.ts | 11 +- tests/BotBehavior.test.ts | 150 ++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 5 deletions(-) create mode 100644 tests/BotBehavior.test.ts diff --git a/src/core/execution/utils/BotBehavior.ts b/src/core/execution/utils/BotBehavior.ts index e68a0c775..111e0baa6 100644 --- a/src/core/execution/utils/BotBehavior.ts +++ b/src/core/execution/utils/BotBehavior.ts @@ -202,11 +202,12 @@ export class BotBehavior { } function shouldAcceptAllianceRequest(player: Player, request: AllianceRequest) { - const notTraitor = !request.requestor().isTraitor(); - const noMalice = player.relation(request.requestor()) >= Relation.Neutral; + const isTraitor = request.requestor().isTraitor(); + const hasMalice = player.relation(request.requestor()) < Relation.Neutral; const requestorIsMuchLarger = request.requestor().numTilesOwned() > player.numTilesOwned() * 3; - const notTooManyAlliances = - requestorIsMuchLarger || request.requestor().alliances().length < 3; - return notTraitor && noMalice && notTooManyAlliances; + const tooManyAlliances = request.requestor().alliances().length >= 3; + return ( + !isTraitor && !hasMalice && (requestorIsMuchLarger || !tooManyAlliances) + ); } diff --git a/tests/BotBehavior.test.ts b/tests/BotBehavior.test.ts new file mode 100644 index 000000000..99df34764 --- /dev/null +++ b/tests/BotBehavior.test.ts @@ -0,0 +1,150 @@ +import { BotBehavior } from "../src/core/execution/utils/BotBehavior"; +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 botBehavior: BotBehavior; + +describe("BotBehavior.handleAllianceRequests", () => { + beforeEach(async () => { + game = await setup("BigPlains", { infiniteGold: true, instantBuild: true }); + + const playerInfo = new PlayerInfo( + "us", + "player_id", + PlayerType.Bot, + null, + "player_id", + ); + const requestorInfo = new PlayerInfo( + "fr", + "requestor_id", + PlayerType.Human, + null, + "requestor_id", + ); + + game.addPlayer(playerInfo); + game.addPlayer(requestorInfo); + + player = game.player("player_id"); + requestor = game.player("requestor_id"); + + const random = new PseudoRandom(42); + + botBehavior = new BotBehavior(random, game, player, 0.5, 0.5); + }); + + function setupAllianceRequest({ + isTraitor = false, + relationDelta = 2, + numTilesPlayer = 10, + numTilesRequestor = 10, + alliancesCount = 0, + } = {}) { + 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--; + } + } + }); + + jest + .spyOn(requestor, "alliances") + .mockReturnValue(new Array(alliancesCount)); + + const mockRequest = { + requestor: () => requestor, + recipient: () => player, + createdAt: () => 0 as unknown as Tick, + accept: jest.fn(), + reject: jest.fn(), + } as unknown as AllianceRequest; + + jest + .spyOn(player, "incomingAllianceRequests") + .mockReturnValue([mockRequest]); + + return mockRequest; + } + + test("should accept alliance when all conditions are met", () => { + const request = setupAllianceRequest({}); + + botBehavior.handleAllianceRequests(); + + expect(request.accept).toHaveBeenCalled(); + expect(request.reject).not.toHaveBeenCalled(); + }); + + test("should reject alliance if requestor is a traitor", () => { + const request = setupAllianceRequest({ isTraitor: true }); + + botBehavior.handleAllianceRequests(); + + expect(request.accept).not.toHaveBeenCalled(); + expect(request.reject).toHaveBeenCalled(); + }); + + test("should reject alliance if relation is malicious", () => { + const request = setupAllianceRequest({ relationDelta: -2 }); + + botBehavior.handleAllianceRequests(); + + expect(request.accept).not.toHaveBeenCalled(); + expect(request.reject).toHaveBeenCalled(); + }); + + test("should accept alliance if requestor is much larger (> 3 times size of recipient) and has too many alliances (>= 3)", () => { + const request = setupAllianceRequest({ + numTilesRequestor: 40, + alliancesCount: 4, + }); + + botBehavior.handleAllianceRequests(); + + expect(request.accept).toHaveBeenCalled(); + expect(request.reject).not.toHaveBeenCalled(); + }); + + test("should accept alliance if requestor is much larger (> 3 times size of recipient) and does not have too many alliances (< 3)", () => { + const request = setupAllianceRequest({ + numTilesRequestor: 40, + alliancesCount: 2, + }); + + botBehavior.handleAllianceRequests(); + + expect(request.accept).toHaveBeenCalled(); + expect(request.reject).not.toHaveBeenCalled(); + }); + + test("should reject alliance if requestor is acceptably small (<= 3 times size of recipient) and has too many alliances (>= 3)", () => { + const request = setupAllianceRequest({ alliancesCount: 3 }); + + botBehavior.handleAllianceRequests(); + + expect(request.accept).not.toHaveBeenCalled(); + expect(request.reject).toHaveBeenCalled(); + }); +});