Nation build order improvements 🤖 (#2833)

## Description:

My first PR about the nation build order.
This one is a bit important for HumansVsNations.

- Nations build more SAMs in team games
- Nations build less factories if they have access to the ocean (instead
of focusing ports and factories with the same priority)
- Nations no longer place defense posts "without reason" - only when
they share a border with someone they haven't allied

I'm planning to make the build order a bit different based on
difficulty.

## 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

## Please put your Discord username so you can be contacted if a bug or
regression is found:

FloPinguin
This commit is contained in:
FloPinguin
2026-01-10 05:40:15 +01:00
committed by GitHub
parent a26590473c
commit 5955f89fe8
2 changed files with 37 additions and 4 deletions
+18 -2
View File
@@ -2,6 +2,7 @@ import {
Difficulty,
Execution,
Game,
GameMode,
Gold,
Nation,
Player,
@@ -250,17 +251,31 @@ export class NationExecution implements Execution {
private handleUnits() {
if (this.warshipBehavior === null) throw new Error("not initialized");
const hasCoastalTiles = this.hasCoastalTiles();
const isTeamGame = this.mg.config().gameConfig().gameMode === GameMode.Team;
return (
this.maybeSpawnStructure(UnitType.City, (num) => num) ||
this.maybeSpawnStructure(UnitType.Port, (num) => num) ||
this.warshipBehavior.maybeSpawnWarship() ||
this.maybeSpawnStructure(UnitType.Factory, (num) => num) ||
this.maybeSpawnStructure(UnitType.Factory, (num) =>
hasCoastalTiles ? num * 3 : num,
) ||
this.maybeSpawnStructure(UnitType.DefensePost, (num) => (num + 2) ** 2) ||
this.maybeSpawnStructure(UnitType.SAMLauncher, (num) => num ** 2) ||
this.maybeSpawnStructure(UnitType.SAMLauncher, (num) =>
isTeamGame ? num : num ** 2,
) ||
this.maybeSpawnStructure(UnitType.MissileSilo, (num) => num ** 2)
);
}
private hasCoastalTiles(): boolean {
if (this.player === null) throw new Error("not initialized");
for (const tile of this.player.borderTiles()) {
if (this.mg.isOceanShore(tile)) return true;
}
return false;
}
private maybeSpawnStructure(
type: UnitType,
multiplier: (num: number) => number,
@@ -294,6 +309,7 @@ export class NationExecution implements Execution {
: randTerritoryTileArray(this.random, this.mg, this.player, 25);
if (tiles.length === 0) return null;
const valueFunction = structureSpawnTileValue(this.mg, this.player, type);
if (valueFunction === null) return null;
let bestTile: TileRef | null = null;
let bestValue = 0;
for (const t of tiles) {
@@ -1,4 +1,4 @@
import { Game, Player, Relation, UnitType } from "../../game/Game";
import { Game, Player, PlayerType, Relation, UnitType } from "../../game/Game";
import { TileRef } from "../../game/GameMap";
import { closestTile, closestTwoTiles } from "../Util";
@@ -6,7 +6,7 @@ export function structureSpawnTileValue(
mg: Game,
player: Player,
type: UnitType,
): (tile: TileRef) => number {
): ((tile: TileRef) => number) | null {
const borderTiles = player.borderTiles();
const otherUnits = player.units(type);
// Prefer spacing structures out of atom bomb range
@@ -57,6 +57,22 @@ export function structureSpawnTileValue(
};
}
case UnitType.DefensePost: {
// Check if we have any non-friendly non-bot neighbors
const hasHostileNeighbor =
player
.neighbors()
.filter(
(n): n is Player =>
n.isPlayer() &&
player.isFriendly(n) === false &&
n.type() !== PlayerType.Bot,
).length > 0;
// Don't build defense posts if there is no danger
if (!hasHostileNeighbor) {
return null;
}
return (tile) => {
let w = 0;
@@ -79,6 +95,7 @@ export function structureSpawnTileValue(
if (id === player.smallID()) continue;
const neighbor = mg.playerBySmallID(id);
if (!neighbor.isPlayer()) continue;
if (neighbor.type() === PlayerType.Bot) continue;
neighbors.add(neighbor);
}
for (const neighbor of neighbors) {