From be9ea14fe9505e1a708b98044c098109af126e5e Mon Sep 17 00:00:00 2001 From: PGray <77597544+PGrayCS@users.noreply.github.com> Date: Tue, 10 Mar 2026 04:31:18 +0000 Subject: [PATCH] refactor: rename Bot to Tribe in internal execution code (#3372) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description Follows up on #3290 which renamed the user-facing "Bots" to "Tribes". This renames the internal implementation to match: - `BotExecution` → `TribeExecution` - `BotSpawner` → `TribeSpawner` - `BotNames` → `TribeNames` (`BOT_NAME_*` → `TRIBE_NAME_*`) All callers updated accordingly. `PlayerType.Bot` and `ColoredTeams.Bot` are intentionally left unchanged as they are serialised wire-format values. Closes #3335 ## Please complete the following: - [x] My changes do not break existing functionality - [x] I have tested my changes ## Please put your Discord username so you can be contacted if a bug or regression is found: Just reply here --------- Co-authored-by: PGray --- src/core/GameRunner.ts | 2 +- src/core/Util.ts | 13 +++--- src/core/execution/BotSpawner.ts | 43 ------------------- src/core/execution/ExecutionManager.ts | 8 ++-- src/core/execution/SpawnExecution.ts | 4 +- .../{BotExecution.ts => TribeExecution.ts} | 38 ++++++++-------- src/core/execution/TribeSpawner.ts | 40 +++++++++++++++++ .../execution/nation/NationNukeBehavior.ts | 2 +- .../utils/{BotNames.ts => TribeNames.ts} | 11 +---- src/core/game/Game.ts | 2 +- 10 files changed, 77 insertions(+), 86 deletions(-) delete mode 100644 src/core/execution/BotSpawner.ts rename src/core/execution/{BotExecution.ts => TribeExecution.ts} (73%) create mode 100644 src/core/execution/TribeSpawner.ts rename src/core/execution/utils/{BotNames.ts => TribeNames.ts} (95%) diff --git a/src/core/GameRunner.ts b/src/core/GameRunner.ts index 427a32781..ddf4da20d 100644 --- a/src/core/GameRunner.ts +++ b/src/core/GameRunner.ts @@ -99,7 +99,7 @@ export class GameRunner { } if (this.game.config().bots() > 0) { this.game.addExecution( - ...this.execManager.spawnBots(this.game.config().numBots()), + ...this.execManager.spawnTribes(this.game.config().bots()), ); } if (this.game.config().spawnNations()) { diff --git a/src/core/Util.ts b/src/core/Util.ts index 1c8a07088..6273824ad 100644 --- a/src/core/Util.ts +++ b/src/core/Util.ts @@ -13,9 +13,9 @@ import { } from "./Schemas"; import { - BOT_NAME_PREFIXES, - BOT_NAME_SUFFIXES, -} from "./execution/utils/BotNames"; + TRIBE_NAME_PREFIXES, + TRIBE_NAME_SUFFIXES, +} from "./execution/utils/TribeNames"; export function manhattanDistWrapped( c1: Cell, @@ -296,11 +296,12 @@ export function createRandomName( let randomName: string | null = null; if (playerType === PlayerType.Human) { const hash = simpleHash(name); - const prefixIndex = hash % BOT_NAME_PREFIXES.length; + const prefixIndex = hash % TRIBE_NAME_PREFIXES.length; const suffixIndex = - Math.floor(hash / BOT_NAME_PREFIXES.length) % BOT_NAME_SUFFIXES.length; + Math.floor(hash / TRIBE_NAME_PREFIXES.length) % + TRIBE_NAME_SUFFIXES.length; - randomName = `👤 ${BOT_NAME_PREFIXES[prefixIndex]} ${BOT_NAME_SUFFIXES[suffixIndex]}`; + randomName = `👤 ${TRIBE_NAME_PREFIXES[prefixIndex]} ${TRIBE_NAME_SUFFIXES[suffixIndex]}`; } return randomName; } diff --git a/src/core/execution/BotSpawner.ts b/src/core/execution/BotSpawner.ts deleted file mode 100644 index 3ccea6a35..000000000 --- a/src/core/execution/BotSpawner.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Game, PlayerInfo, PlayerType } from "../game/Game"; -import { PseudoRandom } from "../PseudoRandom"; -import { GameID } from "../Schemas"; -import { simpleHash } from "../Util"; -import { SpawnExecution } from "./SpawnExecution"; -import { BOT_NAME_PREFIXES, BOT_NAME_SUFFIXES } from "./utils/BotNames"; - -export class BotSpawner { - private random: PseudoRandom; - private bots: SpawnExecution[] = []; - - constructor( - private gs: Game, - private gameID: GameID, - ) { - // Use a different seed than createGameRunner (which uses simpleHash(gameID)) - // to avoid bot IDs colliding with nation/human IDs from the same PRNG sequence. - this.random = new PseudoRandom(simpleHash(gameID) + 2); - } - - spawnBots(numBots: number): SpawnExecution[] { - for (let i = 0; i < numBots; i++) { - const name = this.randomBotName(); - const spawn = this.spawnBot(name); - this.bots.push(spawn); - } - - return this.bots; - } - - spawnBot(botName: string): SpawnExecution { - return new SpawnExecution( - this.gameID, - new PlayerInfo(botName, PlayerType.Bot, null, this.random.nextID()), - ); - } - - private randomBotName(): string { - const prefixIndex = this.random.nextInt(0, BOT_NAME_PREFIXES.length); - const suffixIndex = this.random.nextInt(0, BOT_NAME_SUFFIXES.length); - return `${BOT_NAME_PREFIXES[prefixIndex]} ${BOT_NAME_SUFFIXES[suffixIndex]}`; - } -} diff --git a/src/core/execution/ExecutionManager.ts b/src/core/execution/ExecutionManager.ts index 9b61629d9..009780d3b 100644 --- a/src/core/execution/ExecutionManager.ts +++ b/src/core/execution/ExecutionManager.ts @@ -8,7 +8,6 @@ import { AllianceRequestExecution } from "./alliance/AllianceRequestExecution"; import { BreakAllianceExecution } from "./alliance/BreakAllianceExecution"; import { AttackExecution } from "./AttackExecution"; import { BoatRetreatExecution } from "./BoatRetreatExecution"; -import { BotSpawner } from "./BotSpawner"; import { ConstructionExecution } from "./ConstructionExecution"; import { DeleteUnitExecution } from "./DeleteUnitExecution"; import { DonateGoldExecution } from "./DonateGoldExecution"; @@ -26,6 +25,7 @@ import { RetreatExecution } from "./RetreatExecution"; import { SpawnExecution } from "./SpawnExecution"; import { TargetPlayerExecution } from "./TargetPlayerExecution"; import { TransportShipExecution } from "./TransportShipExecution"; +import { TribeSpawner } from "./TribeSpawner"; import { UpgradeStructureExecution } from "./UpgradeStructureExecution"; import { PlayerSpawner } from "./utils/PlayerSpawner"; @@ -38,7 +38,7 @@ export class Executor { private gameID: GameID, private clientID: ClientID | undefined, ) { - // Add one to avoid id collisions with bots. + // Add one to avoid id collisions with tribes. this.random = new PseudoRandom(simpleHash(gameID) + 1); } @@ -126,8 +126,8 @@ export class Executor { } } - spawnBots(numBots: number): SpawnExecution[] { - return new BotSpawner(this.mg, this.gameID).spawnBots(numBots); + spawnTribes(numTribes: number): SpawnExecution[] { + return new TribeSpawner(this.mg, this.gameID).spawnTribes(numTribes); } spawnPlayers(): SpawnExecution[] { diff --git a/src/core/execution/SpawnExecution.ts b/src/core/execution/SpawnExecution.ts index 0e3e8ef46..2f0fc753e 100644 --- a/src/core/execution/SpawnExecution.ts +++ b/src/core/execution/SpawnExecution.ts @@ -10,8 +10,8 @@ import { TileRef } from "../game/GameMap"; import { PseudoRandom } from "../PseudoRandom"; import { GameID } from "../Schemas"; import { simpleHash } from "../Util"; -import { BotExecution } from "./BotExecution"; import { PlayerExecution } from "./PlayerExecution"; +import { TribeExecution } from "./TribeExecution"; import { getSpawnTiles } from "./Util"; type Spawn = { center: TileRef; tiles: TileRef[] }; @@ -71,7 +71,7 @@ export class SpawnExecution implements Execution { if (!player.hasSpawned()) { this.mg.addExecution(new PlayerExecution(player)); if (player.type() === PlayerType.Bot) { - this.mg.addExecution(new BotExecution(player)); + this.mg.addExecution(new TribeExecution(player)); } } diff --git a/src/core/execution/BotExecution.ts b/src/core/execution/TribeExecution.ts similarity index 73% rename from src/core/execution/BotExecution.ts rename to src/core/execution/TribeExecution.ts index 11b74cdeb..16ee48aef 100644 --- a/src/core/execution/BotExecution.ts +++ b/src/core/execution/TribeExecution.ts @@ -1,11 +1,11 @@ -import { Execution, Game, Player, Structures } from "../game/Game"; +import { Execution, Game, Player, Structures } from "../game/Game"; import { PseudoRandom } from "../PseudoRandom"; import { simpleHash } from "../Util"; import { AllianceExtensionExecution } from "./alliance/AllianceExtensionExecution"; import { DeleteUnitExecution } from "./DeleteUnitExecution"; import { AiAttackBehavior } from "./utils/AiAttackBehavior"; -export class BotExecution implements Execution { +export class TribeExecution implements Execution { private active = true; private random: PseudoRandom; private mg: Game; @@ -18,8 +18,8 @@ export class BotExecution implements Execution { private reserveRatio: number; private expandRatio: number; - constructor(private bot: Player) { - this.random = new PseudoRandom(simpleHash(bot.id())); + constructor(private tribe: Player) { + this.random = new PseudoRandom(simpleHash(tribe.id())); this.attackRate = this.random.nextInt(40, 80); this.attackTick = this.random.nextInt(0, this.attackRate); this.triggerRatio = this.random.nextInt(50, 60) / 100; @@ -38,8 +38,8 @@ export class BotExecution implements Execution { tick(ticks: number) { if (ticks % this.attackRate !== this.attackTick) return; - if (!this.bot.isAlive()) { - //removeOnDeath is called from bot's PlayerExecution + if (!this.tribe.isAlive()) { + //removeOnDeath is called from tribe's PlayerExecution this.active = false; return; } @@ -48,7 +48,7 @@ export class BotExecution implements Execution { this.attackBehavior = new AiAttackBehavior( this.random, this.mg, - this.bot, + this.tribe, this.triggerRatio, this.reserveRatio, this.expandRatio, @@ -66,27 +66,27 @@ export class BotExecution implements Execution { private acceptAllAllianceRequests() { // Accept all alliance requests - for (const req of this.bot.incomingAllianceRequests()) { + for (const req of this.tribe.incomingAllianceRequests()) { req.accept(); } // Accept all alliance extension requests - for (const alliance of this.bot.alliances()) { + for (const alliance of this.tribe.alliances()) { // Alliance expiration tracked by Events Panel, only human ally can click Request to Renew - // Skip if no expiration yet/ ally didn't request extension yet / bot already agreed to extend + // Skip if no expiration yet/ ally didn't request extension yet / tribe already agreed to extend if (!alliance.onlyOneAgreedToExtend()) continue; - const human = alliance.other(this.bot); + const human = alliance.other(this.tribe); this.mg.addExecution( - new AllianceExtensionExecution(this.bot, human.id()), + new AllianceExtensionExecution(this.tribe, human.id()), ); } } private deleteAllStructures() { - for (const unit of this.bot.units()) { - if (Structures.has(unit.type()) && this.bot.canDeleteUnit()) { - this.mg.addExecution(new DeleteUnitExecution(this.bot, unit.id())); + for (const unit of this.tribe.units()) { + if (Structures.has(unit.type()) && this.tribe.canDeleteUnit()) { + this.mg.addExecution(new DeleteUnitExecution(this.tribe, unit.id())); } } } @@ -97,13 +97,13 @@ export class BotExecution implements Execution { } const toAttack = this.attackBehavior.getNeighborTraitorToAttack(); if (toAttack !== null) { - const odds = this.bot.isFriendly(toAttack) ? 6 : 3; + const odds = this.tribe.isFriendly(toAttack) ? 6 : 3; if (this.random.chance(odds)) { // Check and break alliance before attacking if needed - const alliance = this.bot.allianceWith(toAttack); + const alliance = this.tribe.allianceWith(toAttack); if (alliance !== null) { - this.bot.breakAlliance(alliance); + this.tribe.breakAlliance(alliance); } this.attackBehavior.sendAttack(toAttack); @@ -112,7 +112,7 @@ export class BotExecution implements Execution { } if (this.neighborsTerraNullius) { - if (this.bot.neighbors().some((n) => !n.isPlayer())) { + if (this.tribe.neighbors().some((n) => !n.isPlayer())) { this.attackBehavior.sendAttack(this.mg.terraNullius()); return; } diff --git a/src/core/execution/TribeSpawner.ts b/src/core/execution/TribeSpawner.ts new file mode 100644 index 000000000..18529bccd --- /dev/null +++ b/src/core/execution/TribeSpawner.ts @@ -0,0 +1,40 @@ +import { Game, PlayerInfo, PlayerType } from "../game/Game"; +import { PseudoRandom } from "../PseudoRandom"; +import { GameID } from "../Schemas"; +import { simpleHash } from "../Util"; +import { SpawnExecution } from "./SpawnExecution"; +import { TRIBE_NAME_PREFIXES, TRIBE_NAME_SUFFIXES } from "./utils/TribeNames"; + +export class TribeSpawner { + private random: PseudoRandom; + + constructor( + private gs: Game, + private gameID: GameID, + ) { + // Use a different seed than createGameRunner (which uses simpleHash(gameID)) + // to avoid tribe IDs colliding with nation/human IDs from the same PRNG sequence. + this.random = new PseudoRandom(simpleHash(gameID) + 2); + } + + spawnTribes(numTribes: number): SpawnExecution[] { + const tribes: SpawnExecution[] = []; + for (let i = 0; i < numTribes; i++) { + tribes.push(this.spawnTribe(this.randomTribeName())); + } + return tribes; + } + + spawnTribe(tribeName: string): SpawnExecution { + return new SpawnExecution( + this.gameID, + new PlayerInfo(tribeName, PlayerType.Bot, null, this.random.nextID()), + ); + } + + private randomTribeName(): string { + const prefixIndex = this.random.nextInt(0, TRIBE_NAME_PREFIXES.length); + const suffixIndex = this.random.nextInt(0, TRIBE_NAME_SUFFIXES.length); + return `${TRIBE_NAME_PREFIXES[prefixIndex]} ${TRIBE_NAME_SUFFIXES[suffixIndex]}`; + } +} diff --git a/src/core/execution/nation/NationNukeBehavior.ts b/src/core/execution/nation/NationNukeBehavior.ts index 1b49214c0..9ba0aa8d6 100644 --- a/src/core/execution/nation/NationNukeBehavior.ts +++ b/src/core/execution/nation/NationNukeBehavior.ts @@ -51,7 +51,7 @@ export class NationNukeBehavior { const silos = this.player.units(UnitType.MissileSilo); if ( silos.length === 0 || - nukeTarget.type() === PlayerType.Bot || // Don't nuke bots (as opposed to nations and humans) + nukeTarget.type() === PlayerType.Bot || // Don't nuke tribes (as opposed to nations and humans) this.player.isOnSameTeam(nukeTarget) || this.attackBehavior.shouldAttack(nukeTarget) === false ) { diff --git a/src/core/execution/utils/BotNames.ts b/src/core/execution/utils/TribeNames.ts similarity index 95% rename from src/core/execution/utils/BotNames.ts rename to src/core/execution/utils/TribeNames.ts index f75ff10bc..8f0786dbf 100644 --- a/src/core/execution/utils/BotNames.ts +++ b/src/core/execution/utils/TribeNames.ts @@ -1,4 +1,4 @@ -export const BOT_NAME_PREFIXES = [ +export const TRIBE_NAME_PREFIXES = [ "Akkadian", "Babylonian", "Sumerian", @@ -159,8 +159,6 @@ export const BOT_NAME_PREFIXES = [ "Armenian", "Circassian", "Georgian", - "Phoenician", - "Chaldean", "Kurdish", "Turkic", "Kazakh", @@ -171,7 +169,6 @@ export const BOT_NAME_PREFIXES = [ "Pashtun", "Baloch", "Afghan", - "Persian", "Kenyan", "Ugandan", "Bhutanese", @@ -180,7 +177,7 @@ export const BOT_NAME_PREFIXES = [ "Militant", "Spartan", ]; -export const BOT_NAME_SUFFIXES = [ +export const TRIBE_NAME_SUFFIXES = [ "Empire", "Dynasty", "Kingdom", @@ -218,7 +215,6 @@ export const BOT_NAME_SUFFIXES = [ "Confederacy", "Order", "Regime", - "Dominion", "Syndicate", "Guild", "Corporation", @@ -231,10 +227,7 @@ export const BOT_NAME_SUFFIXES = [ "Sisterhood", "Ascendancy", "Supremacy", - "Province", "Tribe", - "Dominion", - "Assembly", "Republics", "Army", "Dictatorship", diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index 475d0e4a6..d77d46069 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -508,7 +508,7 @@ export class PlayerInfo { constructor( public readonly name: string, public readonly playerType: PlayerType, - // null if bot. + // null if tribe. public readonly clientID: ClientID | null, // TODO: make player id the small id public readonly id: PlayerID,