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
This commit is contained in:
Scott Anderson
2025-08-25 03:34:52 -04:00
committed by GitHub
parent 9a9979fa6b
commit e079bc772f
2 changed files with 65 additions and 59 deletions
+3 -59
View File
@@ -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<TileRef> = 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<TileRef> = 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");
@@ -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<TileRef> = 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<TileRef> = 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}`);
}
}