mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 16:40:16 +00:00
Smarter nation structure placement (#1851)
## Description: Smarter nation structure placement. Fixes #881 <img width="2464" height="1235" alt="image" src="https://github.com/user-attachments/assets/b8ec0041-6f12-4ff3-9279-f5e8529521e5" /> ## 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:
@@ -458,7 +458,91 @@ export class FakeHumanExecution implements Execution {
|
||||
)
|
||||
: Array.from(this.player.tiles());
|
||||
if (tiles.length === 0) return null;
|
||||
return this.random.randElement(tiles);
|
||||
const valueFunction = this.structureSpawnTileValue(type);
|
||||
let bestTile: TileRef | null = null;
|
||||
let bestValue = 0;
|
||||
const sampledTiles = this.arraySampler(tiles);
|
||||
for (const t of sampledTiles) {
|
||||
const v = valueFunction(t);
|
||||
if (v <= bestValue && bestTile !== null) continue;
|
||||
if (!this.player.canBuild(type, t)) continue;
|
||||
// Found a better tile
|
||||
bestTile = t;
|
||||
bestValue = v;
|
||||
}
|
||||
return bestTile;
|
||||
}
|
||||
|
||||
private * arraySampler<T>(a: T[], sampleSize = 50): Generator<T> {
|
||||
if (a.length <= sampleSize) {
|
||||
// Return all elements
|
||||
yield* a;
|
||||
} else {
|
||||
// Sample `sampleSize` elements
|
||||
const remaining = new Set<T>(a);
|
||||
while (sampleSize--) {
|
||||
const t = this.random.randFromSet(remaining);
|
||||
remaining.delete(t);
|
||||
yield t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<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 {
|
||||
|
||||
Reference in New Issue
Block a user