Skip to content
6 changes: 1 addition & 5 deletions src/core/execution/BotExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,7 @@ export class BotExecution implements Execution {
this.neighborsTerraNullius = false;
}

this.behavior.forgetOldEnemies();
const enemy = this.behavior.selectRandomEnemy();
if (!enemy) return;
if (!this.bot.sharesBorderWith(enemy)) return;
this.behavior.sendAttack(enemy);
this.behavior.attackRandomTarget();
}

isActive(): boolean {
Expand Down
97 changes: 30 additions & 67 deletions src/core/execution/FakeHumanExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,13 @@ import { GameID } from "../Schemas";
import { boundingBoxTiles, calculateBoundingBox, simpleHash } from "../Util";
import { AllianceRequestExecution } from "./alliance/AllianceRequestExecution";
import { ConstructionExecution } from "./ConstructionExecution";
import { EmojiExecution } from "./EmojiExecution";
import { MirvExecution } from "./MIRVExecution";
import { structureSpawnTileValue } from "./nation/structureSpawnTileValue";
import { NukeExecution } from "./NukeExecution";
import { SpawnExecution } from "./SpawnExecution";
import { TransportShipExecution } from "./TransportShipExecution";
import { calculateTerritoryCenter, closestTwoTiles } from "./Util";
import { BotBehavior, EMOJI_HECKLE } from "./utils/BotBehavior";
import { BotBehavior } from "./utils/BotBehavior";

export class FakeHumanExecution implements Execution {
private active = true;
Expand All @@ -42,7 +41,6 @@ export class FakeHumanExecution implements Execution {
private reserveRatio: number;
private expandRatio: number;

private readonly lastEmojiSent = new Map<Player, Tick>();
private readonly lastNukeSent: [Tick, TileRef][] = [];
private readonly lastMIRVSent: [Tick, TileRef][] = [];
private readonly embargoMalusApplied = new Set<PlayerID>();
Expand Down Expand Up @@ -207,34 +205,42 @@ export class FakeHumanExecution implements Execution {
throw new Error("not initialized");
}

const enemyborder = Array.from(this.player.borderTiles())
const border = Array.from(this.player.borderTiles())
.flatMap((t) => this.mg.neighbors(t))
.filter(
(t) =>
this.mg.isLand(t) && this.mg.ownerID(t) !== this.player?.smallID(),
);
const borderPlayers = enemyborder.map((t) =>
this.mg.playerBySmallID(this.mg.ownerID(t)),
);
const borderingEnemies = borderPlayers
const borderingPlayers = border
.map((t) => this.mg.playerBySmallID(this.mg.ownerID(t)))
.filter((o) => o.isPlayer())
.sort((a, b) => a.troops() - b.troops());
const borderingFriends = borderingPlayers.filter(
(o) => this.player?.isFriendly(o) === true,
);
const borderingEnemies = borderingPlayers.filter(
(o) => this.player?.isFriendly(o) === false,
);

if (enemyborder.length === 0) {
// Attack TerraNullius but not nuked territory
const hasNonNukedTerraNullius = border.some(
(t) => !this.mg.hasOwner(t) && !this.mg.hasFallout(t),
);
if (hasNonNukedTerraNullius) {
this.behavior.sendAttack(this.mg.terraNullius());
return;
}

if (borderingEnemies.length === 0) {
if (this.random.chance(5)) {
this.sendBoatRandomly(borderingEnemies);
this.sendBoatRandomly();
}
} else {
if (this.random.chance(10)) {
this.sendBoatRandomly(borderingEnemies);
return;
}

if (borderPlayers.some((o) => !o.isPlayer())) {
this.behavior.sendAttack(this.mg.terraNullius());
return;
}

// 5% chance to send a random alliance request
if (this.random.chance(20)) {
const toAlly = this.random.randElement(borderingEnemies);
Expand All @@ -246,41 +252,20 @@ export class FakeHumanExecution implements Execution {
}
}

this.behavior.forgetOldEnemies();
this.behavior.assistAllies();

const enemy = this.behavior.selectEnemy(borderingEnemies);
if (!enemy) return;
this.maybeSendEmoji(enemy);
this.maybeSendNuke(enemy);
if (this.player.sharesBorderWith(enemy)) {
this.behavior.sendAttack(enemy);
} else {
this.maybeSendBoatAttack(enemy);
}
}
this.behavior.attackBestTarget(borderingFriends, borderingEnemies);

private maybeSendEmoji(enemy: Player) {
if (this.player === null) throw new Error("not initialized");
if (enemy.type() !== PlayerType.Human) return;
const lastSent = this.lastEmojiSent.get(enemy) ?? -300;
if (this.mg.ticks() - lastSent <= 300) return;
this.lastEmojiSent.set(enemy, this.mg.ticks());
this.mg.addExecution(
new EmojiExecution(
this.player,
enemy.id(),
this.random.randElement(EMOJI_HECKLE),
),
);
this.maybeSendNuke(this.behavior.findBestNukeTarget(borderingEnemies));
}

private maybeSendNuke(other: Player) {
private maybeSendNuke(other: Player | null) {
if (this.player === null) throw new Error("not initialized");
const silos = this.player.units(UnitType.MissileSilo);
if (
silos.length === 0 ||
this.player.gold() < this.cost(UnitType.AtomBomb) ||
other === null ||
other.type() === PlayerType.Bot || // Don't nuke bots (as opposed to fakehumans and humans)
this.player.isOnSameTeam(other)
) {
Expand Down Expand Up @@ -326,7 +311,7 @@ export class FakeHumanExecution implements Execution {
}
}
if (bestTile !== null) {
this.sendNuke(bestTile, nukeType);
this.sendNuke(bestTile, nukeType, other);
}
}

Expand All @@ -344,11 +329,13 @@ export class FakeHumanExecution implements Execution {
private sendNuke(
tile: TileRef,
nukeType: UnitType.AtomBomb | UnitType.HydrogenBomb,
targetPlayer: Player,
) {
if (this.player === null) throw new Error("not initialized");
const tick = this.mg.ticks();
this.lastNukeSent.push([tick, tile]);
this.mg.addExecution(new NukeExecution(nukeType, this.player, tile));
this.behavior?.maybeSendEmoji(targetPlayer);
}

private nukeTileScore(tile: TileRef, silos: Unit[], targets: Unit[]): number {
Expand Down Expand Up @@ -399,30 +386,6 @@ export class FakeHumanExecution implements Execution {
return tileValue;
}

private maybeSendBoatAttack(other: Player) {
if (this.player === null) throw new Error("not initialized");
if (this.player.isFriendly(other)) return;
const closest = closestTwoTiles(
this.mg,
Array.from(this.player.borderTiles()).filter((t) =>
this.mg.isOceanShore(t),
),
Array.from(other.borderTiles()).filter((t) => this.mg.isOceanShore(t)),
);
if (closest === null) {
return;
}
this.mg.addExecution(
new TransportShipExecution(
this.player,
other.id(),
closest.y,
this.player.troops() / 5,
null,
),
);
}

private handleUnits() {
return (
this.maybeSpawnStructure(UnitType.City, (num) => num) ||
Expand Down Expand Up @@ -597,7 +560,7 @@ export class FakeHumanExecution implements Execution {
return this.mg.unitInfo(type).cost(this.player);
}

sendBoatRandomly(borderingEnemies: Player[]) {
sendBoatRandomly(borderingEnemies: Player[] = []) {
if (this.player === null) throw new Error("not initialized");
const oceanShore = Array.from(this.player.borderTiles()).filter((t) =>
this.mg.isOceanShore(t),
Expand Down Expand Up @@ -884,7 +847,7 @@ export class FakeHumanExecution implements Execution {
private maybeSendMIRV(enemy: Player): void {
if (this.player === null) throw new Error("not initialized");

this.maybeSendEmoji(enemy);
this.behavior?.maybeSendEmoji(enemy);

const centerTile = this.calculateTerritoryCenter(enemy);
if (centerTile && this.player.canBuild(UnitType.MIRV, centerTile)) {
Expand Down
Loading
Loading