refactor: rename Bot to Tribe in internal execution code (#3372)

## 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 <PGrayCS@users.noreply.github.com>
This commit is contained in:
PGray
2026-03-10 04:31:18 +00:00
committed by GitHub
parent 13a965a9c5
commit be9ea14fe9
10 changed files with 77 additions and 86 deletions
+1 -1
View File
@@ -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()) {
+7 -6
View File
@@ -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;
}
-43
View File
@@ -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]}`;
}
}
+4 -4
View File
@@ -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[] {
+2 -2
View File
@@ -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));
}
}
@@ -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;
}
+40
View File
@@ -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]}`;
}
}
@@ -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
) {
@@ -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",
+1 -1
View File
@@ -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,