diff --git a/src/core/execution/FakeHumanExecution.ts b/src/core/execution/FakeHumanExecution.ts index dac000a3d..d55161654 100644 --- a/src/core/execution/FakeHumanExecution.ts +++ b/src/core/execution/FakeHumanExecution.ts @@ -25,6 +25,7 @@ import { PseudoRandom } from "../PseudoRandom"; import { SpawnExecution } from "./SpawnExecution"; import { TransportShipExecution } from "./TransportShipExecution"; import { closestTwoTiles } from "./Util"; +import { structureSpawnTileValue } from "./nation/structureSpawnTileValue"; export class FakeHumanExecution implements Execution { private active = true; @@ -463,6 +464,7 @@ export class FakeHumanExecution implements Execution { } private structureSpawnTile(type: UnitType): TileRef | null { + if (this.mg === undefined) throw new Error("Not initialized"); if (this.player === null) throw new Error("Not initialized"); const tiles = type === UnitType.Port @@ -471,7 +473,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); @@ -501,64 +503,6 @@ export class FakeHumanExecution implements Execution { } } - private structureSpawnTileValue(type: UnitType): (tile: TileRef) => number { - if (this.mg === undefined) throw new Error("Not initialized"); - if (this.player === null) throw new Error("Not initialized"); - const borderTiles = this.player.borderTiles(); - const { mg } = this; - 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.mg === undefined) throw new Error("Not initialized"); if (this.player === null) throw new Error("Not initialized"); 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}`); + } +}