diff --git a/src/core/configuration/Config.ts b/src/core/configuration/Config.ts index 1f0b50bc8..1f12f454b 100644 --- a/src/core/configuration/Config.ts +++ b/src/core/configuration/Config.ts @@ -85,6 +85,10 @@ export interface Config { tilesPerTickUsed: number; }; attackAmount(attacker: Player, defender: Player | TerraNullius): number; + radiusPortSpawn(): number; + // When computing likelihood of trading for any given port, the X closest port + // are twice more likely to be selected. X is determined below. + proximityBonusPortsNb(totalPorts: number): number; maxPopulation(player: Player | PlayerView): number; cityPopulationIncrease(): number; boatAttackAmount(attacker: Player, defender: Player | TerraNullius): number; diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts index 56d6c4bd5..e8dd7cdfd 100644 --- a/src/core/configuration/DefaultConfig.ts +++ b/src/core/configuration/DefaultConfig.ts @@ -474,6 +474,14 @@ export class DefaultConfig implements Config { return Math.floor(attacker.troops() / 5); } + radiusPortSpawn() { + return 20; + } + + proximityBonusPortsNb(totalPorts: number) { + return within(totalPorts / 3, 4, totalPorts); + } + attackAmount(attacker: Player, defender: Player | TerraNullius) { if (attacker.type() == PlayerType.Bot) { return attacker.troops() / 20; diff --git a/src/core/execution/PortExecution.ts b/src/core/execution/PortExecution.ts index 547a05229..e6ff3962c 100644 --- a/src/core/execution/PortExecution.ts +++ b/src/core/execution/PortExecution.ts @@ -38,7 +38,6 @@ export class PortExecution implements Execution { tick(ticks: number): void { if (this.port == null) { - // TODO: use canBuild const tile = this.tile; const player = this.mg.player(this._owner); if (!player.canBuild(UnitType.Port, tile)) { @@ -46,12 +45,15 @@ export class PortExecution implements Execution { this.active = false; return; } - const spawns = Array.from(this.mg.bfs(tile, manhattanDistFN(tile, 20))) - .filter((t) => this.mg.isOceanShore(t) && this.mg.owner(t) == player) - .sort( - (a, b) => - this.mg.manhattanDist(a, tile) - this.mg.manhattanDist(b, tile), - ); + const spawns = Array.from( + this.mg.bfs( + tile, + manhattanDistFN(tile, this.mg.config().radiusPortSpawn()), + ), + ).sort( + (a, b) => + this.mg.manhattanDist(a, tile) - this.mg.manhattanDist(b, tile), + ); if (spawns.length == 0) { consolex.warn(`cannot find spawn for port`); @@ -77,10 +79,8 @@ export class PortExecution implements Execution { return; } - const ports = this.mg - .players() - .filter((p) => p != this.port.owner() && p.canTrade(this.port.owner())) - .flatMap((p) => p.units(UnitType.Port)); + const ports = this.owner().tradingPorts(this.port); + if (ports.length == 0) { return; } diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index 496cc3496..201ede85a 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -377,6 +377,7 @@ export interface Player { toUpdate(): PlayerUpdate; playerProfile(): PlayerProfile; canBoat(tile: TileRef): boolean; + tradingPorts(port: Unit): Unit[]; } export interface Game extends GameMap { diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts index 1501b5e1b..b5f7edcd4 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -953,4 +953,38 @@ export class PlayerImpl implements Player { return false; } } + + // It's a probability list, so if an element appears twice it's because it's + // twice more likely to be picked later. + tradingPorts(port: Unit): Unit[] { + let ports = this.mg + .players() + .filter((p) => p != port.owner() && p.canTrade(port.owner())) + .flatMap((p) => p.units(UnitType.Port)) + .sort((p1, p2) => { + return ( + this.mg.manhattanDist(port.tile(), p1.tile()) - + this.mg.manhattanDist(port.tile(), p2.tile()) + ); + }); + + // Make close ports twice more likely by putting them again + for ( + let i = 0; + i < this.mg.config().proximityBonusPortsNb(ports.length); + i++ + ) { + ports.push(ports[i]); + } + + // Make ally ports twice more likely by putting them again + this.mg + .players() + .filter((p) => p != port.owner() && p.canTrade(port.owner())) + .filter((p) => p.isAlliedWith(port.owner())) + .flatMap((p) => p.units(UnitType.Port)) + .forEach((p) => ports.push(p)); + + return ports; + } } diff --git a/tests/Warship.test.ts b/tests/Warship.test.ts index aa6d23b6e..b0ce7f98f 100644 --- a/tests/Warship.test.ts +++ b/tests/Warship.test.ts @@ -9,6 +9,7 @@ import { SpawnExecution } from "../src/core/execution/SpawnExecution"; import { setup } from "./util/Setup"; import { constructionExecution } from "./util/utils"; +const coastX = 7; let game: Game; let player1: Player; let player2: Player; @@ -36,10 +37,15 @@ describe("Warship", () => { ); game.addPlayer(player_2_info, 1000); - const spawnTile = game.map().ref(0, 0); game.addExecution( - new SpawnExecution(game.player(player_1_info.id).info(), spawnTile), - new SpawnExecution(game.player(player_2_info.id).info(), spawnTile), + new SpawnExecution( + game.player(player_1_info.id).info(), + game.ref(coastX, 10), + ), + new SpawnExecution( + game.player(player_2_info.id).info(), + game.ref(coastX, 15), + ), ); while (game.inSpawnPhase()) { @@ -53,8 +59,12 @@ describe("Warship", () => { test("Warship heals only if player has port", async () => { const maxHealth = game.config().unitInfo(UnitType.Warship).maxHealth; - const port = player1.buildUnit(UnitType.Port, 0, game.ref(0, 0)); - const warship = player1.buildUnit(UnitType.Warship, 0, game.ref(7, 7)); + const port = player1.buildUnit(UnitType.Port, 0, game.ref(coastX, 10)); + const warship = player1.buildUnit( + UnitType.Warship, + 0, + game.ref(coastX + 1, 10), + ); game.executeNextTick(); @@ -71,15 +81,19 @@ describe("Warship", () => { }); test("Warship captures trade if player has port", async () => { - constructionExecution(game, player1.id(), 0, 0, UnitType.Port); - constructionExecution(game, player1.id(), 7, 7, UnitType.Warship); + constructionExecution(game, player1.id(), coastX, 10, UnitType.Port); + constructionExecution(game, player1.id(), coastX + 1, 10, UnitType.Warship); // Warship need one more tick (for warship exec to actually build warship) game.executeNextTick(); expect(player1.units(UnitType.Warship)).toHaveLength(1); // Cannot buildExec with trade ship as it's not buildable (but // we can obviously directly add it to the player) - const tradeShip = player2.buildUnit(UnitType.TradeShip, 0, game.ref(6, 6)); + const tradeShip = player2.buildUnit( + UnitType.TradeShip, + 0, + game.ref(coastX + 1, 7), + ); expect(tradeShip.owner().id()).toBe(player2.id()); // Let plenty of time for A* to execute @@ -90,14 +104,18 @@ describe("Warship", () => { }); test("Warship do not capture trade if player has no port", async () => { - constructionExecution(game, player1.id(), 0, 0, UnitType.Port); - constructionExecution(game, player1.id(), 7, 7, UnitType.Warship); + constructionExecution(game, player1.id(), coastX, 10, UnitType.Port); + constructionExecution(game, player1.id(), coastX + 1, 10, UnitType.Warship); expect(player1.units(UnitType.Warship)).toHaveLength(1); player1.units(UnitType.Port)[0].delete(); // Cannot buildExec with trade ship as it's not buildable (but // we can obviously directly add it to the player) - const tradeShip = player2.buildUnit(UnitType.TradeShip, 0, game.ref(6, 6)); + const tradeShip = player2.buildUnit( + UnitType.TradeShip, + 0, + game.ref(coastX + 1, 11), + ); expect(tradeShip.owner().id()).toBe(player2.id()); // Let plenty of time for A* to execute diff --git a/tests/testdata/half_land_half_ocean.png b/tests/testdata/half_land_half_ocean.png index 059596dfa..67f40bfaa 100755 Binary files a/tests/testdata/half_land_half_ocean.png and b/tests/testdata/half_land_half_ocean.png differ diff --git a/tests/util/TestConfig.ts b/tests/util/TestConfig.ts index 1e52658c0..5a3493440 100644 --- a/tests/util/TestConfig.ts +++ b/tests/util/TestConfig.ts @@ -1,7 +1,22 @@ import { DefaultConfig } from "../../src/core/configuration/DefaultConfig"; export class TestConfig extends DefaultConfig { + _proximityBonusPortsNb: number = 0; + samHittingChance(): number { return 1; } + + radiusPortSpawn(): number { + return 1; + } + + proximityBonusPortsNb(totalPorts: number): number { + return this._proximityBonusPortsNb; + } + + // Specific to TestConfig + setProximityBonusPortsNb(nb: number): void { + this._proximityBonusPortsNb = nb; + } }