From c7f7fb0ee46711a1dc24adc0aebc6e97a2a44b9c Mon Sep 17 00:00:00 2001 From: Scott Anderson <662325+scottanderson@users.noreply.github.com> Date: Mon, 25 Aug 2025 03:34:52 -0400 Subject: [PATCH] Refactor `structureSpawnTileValue()` (#1927) ## Description: Move `structureSpawnTileValue()` into its own file, as `FakeHumanExecution.ts` was getting quite large. ## Please complete the following: - [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 --- src/core/execution/FakeHumanExecution.ts | 69 ++----------------- .../nation/structureSpawnTileValue.ts | 62 +++++++++++++++++ 2 files changed, 66 insertions(+), 65 deletions(-) create mode 100644 src/core/execution/nation/structureSpawnTileValue.ts diff --git a/src/core/execution/FakeHumanExecution.ts b/src/core/execution/FakeHumanExecution.ts index 7326375f1..e498d63c2 100644 --- a/src/core/execution/FakeHumanExecution.ts +++ b/src/core/execution/FakeHumanExecution.ts @@ -20,6 +20,7 @@ import { GameID } from "../Schemas"; import { calculateBoundingBox, flattenedEmojiTable, simpleHash } from "../Util"; import { ConstructionExecution } from "./ConstructionExecution"; import { EmojiExecution } from "./EmojiExecution"; +import { structureSpawnTileValue } from "./nation/structureSpawnTileValue"; import { NukeExecution } from "./NukeExecution"; import { SpawnExecution } from "./SpawnExecution"; import { TransportShipExecution } from "./TransportShipExecution"; @@ -486,7 +487,8 @@ export class FakeHumanExecution implements Execution { } private structureSpawnTile(type: UnitType): TileRef | null { - if (this.player === null) throw new Error("not initialized"); + if (this.mg === undefined) throw new Error("Not initialized"); + if (this.player === null) throw new Error("Not initialized"); const tiles = type === UnitType.Port ? Array.from(this.player.borderTiles()).filter((t) => @@ -494,7 +496,7 @@ export class FakeHumanExecution implements Execution { ) : Array.from(this.player.tiles()); if (tiles.length === 0) return null; - const valueFunction = this.structureSpawnTileValue(type); + const valueFunction = structureSpawnTileValue(this.mg, this.player, type); let bestTile: TileRef | null = null; let bestValue = 0; const sampledTiles = this.arraySampler(tiles); @@ -524,69 +526,6 @@ export class FakeHumanExecution implements Execution { } } - private structureSpawnTileValue(type: UnitType): (tile: TileRef) => number { - if (this.player === null) throw new Error("not initialized"); - const borderTiles = this.player.borderTiles(); - const mg = this.mg; - const otherUnits = this.player.units(type); - // Prefer spacing structures out of atom bomb range - const borderSpacing = this.mg - .config() - .nukeMagnitudes(UnitType.AtomBomb).outer; - const structureSpacing = borderSpacing * 2; - switch (type) { - case UnitType.Port: - return (tile) => { - let w = 0; - - // Prefer to be far away from other structures of the same type - const otherTiles: Set = new Set( - otherUnits.map((u) => u.tile()), - ); - otherTiles.delete(tile); - const closestOther = closestTwoTiles(mg, otherTiles, [tile]); - if (closestOther !== null) { - const d = mg.manhattanDist(closestOther.x, tile); - w += Math.min(d, structureSpacing); - } - - return w; - }; - case UnitType.City: - case UnitType.Factory: - case UnitType.MissileSilo: - return (tile) => { - let w = 0; - - // Prefer higher elevations - w += mg.magnitude(tile); - - // Prefer to be away from the border - const closestBorder = closestTwoTiles(mg, borderTiles, [tile]); - if (closestBorder !== null) { - const d = mg.manhattanDist(closestBorder.x, tile); - w += Math.min(d, borderSpacing); - } - - // Prefer to be away from other structures of the same type - const otherTiles: Set = new Set( - otherUnits.map((u) => u.tile()), - ); - otherTiles.delete(tile); - const closestOther = closestTwoTiles(mg, otherTiles, [tile]); - if (closestOther !== null) { - const d = mg.manhattanDist(closestOther.x, tile); - w += Math.min(d, structureSpacing); - } - - // TODO: Cities and factories should consider train range limits - return w; - }; - default: - throw new Error(`Value function not implemented for ${type}`); - } - } - private maybeSpawnWarship(): boolean { if (this.player === null) throw new Error("not initialized"); if (!this.random.chance(50)) { diff --git a/src/core/execution/nation/structureSpawnTileValue.ts b/src/core/execution/nation/structureSpawnTileValue.ts new file mode 100644 index 000000000..563f42f80 --- /dev/null +++ b/src/core/execution/nation/structureSpawnTileValue.ts @@ -0,0 +1,62 @@ +import { Game, Player, UnitType } from "../../game/Game"; +import { TileRef } from "../../game/GameMap"; +import { closestTwoTiles } from "../Util"; + +export function structureSpawnTileValue( + mg: Game, + player: Player, + type: UnitType, +): (tile: TileRef) => number { + const borderTiles = player.borderTiles(); + const otherUnits = player.units(type); + // Prefer spacing structures out of atom bomb range + const borderSpacing = mg.config().nukeMagnitudes(UnitType.AtomBomb).outer; + const structureSpacing = borderSpacing * 2; + switch (type) { + case UnitType.Port: + return (tile) => { + let w = 0; + + // Prefer to be far away from other structures of the same type + const otherTiles: Set = new Set(otherUnits.map((u) => u.tile())); + otherTiles.delete(tile); + const closestOther = closestTwoTiles(mg, otherTiles, [tile]); + if (closestOther !== null) { + const d = mg.manhattanDist(closestOther.x, tile); + w += Math.min(d, structureSpacing); + } + + return w; + }; + case UnitType.City: + case UnitType.Factory: + case UnitType.MissileSilo: + return (tile) => { + let w = 0; + + // Prefer higher elevations + w += mg.magnitude(tile); + + // Prefer to be away from the border + const closestBorder = closestTwoTiles(mg, borderTiles, [tile]); + if (closestBorder !== null) { + const d = mg.manhattanDist(closestBorder.x, tile); + w += Math.min(d, borderSpacing); + } + + // Prefer to be away from other structures of the same type + const otherTiles: Set = new Set(otherUnits.map((u) => u.tile())); + otherTiles.delete(tile); + const closestOther = closestTwoTiles(mg, otherTiles, [tile]); + if (closestOther !== null) { + const d = mg.manhattanDist(closestOther.x, tile); + w += Math.min(d, structureSpacing); + } + + // TODO: Cities and factories should consider train range limits + return w; + }; + default: + throw new Error(`Value function not implemented for ${type}`); + } +}