Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 54 additions & 32 deletions src/core/execution/FakeHumanExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,42 +195,43 @@ export class FakeHumanExecution implements Execution {
this.mg.isLand(t) && this.mg.ownerID(t) !== this.player?.smallID(),
);

let borderingEnemies: Player[] = [];
if (enemyborder.length === 0) {
if (this.random.chance(5)) {
this.sendBoatRandomly();
}
} else {
if (this.random.chance(10)) {
this.sendBoatRandomly();
return;
}
return;
}
if (this.random.chance(20)) {
this.sendBoatRandomly();
return;
}

const borderPlayers = enemyborder.map((t) =>
this.mg.playerBySmallID(this.mg.ownerID(t)),
);
if (borderPlayers.some((o) => !o.isPlayer())) {
this.behavior.sendAttack(this.mg.terraNullius());
return;
}
const borderPlayers = enemyborder.map((t) =>
this.mg.playerBySmallID(this.mg.ownerID(t)),
);
if (borderPlayers.some((o) => !o.isPlayer())) {
this.behavior.sendAttack(this.mg.terraNullius());
return;
}

const enemies = borderPlayers
.filter((o) => o.isPlayer())
.sort((a, b) => a.troops() - b.troops());

// 5% chance to send a random alliance request
if (this.random.chance(20)) {
const toAlly = this.random.randElement(enemies);
if (this.player.canSendAllianceRequest(toAlly)) {
this.mg.addExecution(
new AllianceRequestExecution(this.player, toAlly.id()),
);
borderingEnemies = borderPlayers
.filter((o) => o.isPlayer())
.sort((a, b) => a.troops() - b.troops());

// 5% chance to send a random alliance request
if (this.random.chance(20)) {
const toAlly = this.random.randElement(borderingEnemies);
if (this.player.canSendAllianceRequest(toAlly)) {
this.mg.addExecution(
new AllianceRequestExecution(this.player, toAlly.id()),
);
}
}
}

this.behavior.forgetOldEnemies();
this.behavior.assistAllies();
const enemy = this.behavior.selectEnemy(enemies);
const enemy = this.behavior.selectEnemy(borderingEnemies);
if (!enemy) return;
this.maybeSendEmoji(enemy);
this.maybeSendNuke(enemy);
Expand Down Expand Up @@ -589,9 +590,14 @@ export class FakeHumanExecution implements Execution {

const src = this.random.randElement(oceanShore);

const dst = this.randomBoatTarget(src, 150);
// First look for high-interest targets (unowned or bot-owned). Mainly relevant for earlygame
let dst = this.randomBoatTarget(src, 150, true);
if (dst === null) {
return;
// None found? Then look for players
dst = this.randomBoatTarget(src, 150, false);
if (dst === null) {
return;
}
}

this.mg.addExecution(
Expand Down Expand Up @@ -631,7 +637,11 @@ export class FakeHumanExecution implements Execution {
return null;
}

private randomBoatTarget(tile: TileRef, dist: number): TileRef | null {
private randomBoatTarget(
tile: TileRef,
dist: number,
highInterestOnly: boolean = false,
): TileRef | null {
if (this.player === null) throw new Error("not initialized");
const x = this.mg.x(tile);
const y = this.mg.y(tile);
Expand All @@ -646,11 +656,23 @@ export class FakeHumanExecution implements Execution {
continue;
}
const owner = this.mg.owner(randTile);
if (!owner.isPlayer()) {
return randTile;
if (owner === this.player) {
continue;
}
if (!owner.isFriendly(this.player)) {
return randTile;
// Don't spam boats into players that are more than twice as large as us
if (owner.isPlayer() && owner.troops() > this.player.troops() * 2) {
continue;
}
// High-interest targeting: prioritize unowned tiles or tiles owned by bots
if (highInterestOnly) {
if (!owner.isPlayer() || owner.type() === PlayerType.Bot) {
return randTile;
}
} else {
// Normal targeting: return unowned tiles or tiles owned by non-friendly players
if (!owner.isPlayer() || !owner.isFriendly(this.player)) {
return randTile;
}
}
}
return null;
Expand Down
70 changes: 64 additions & 6 deletions src/core/execution/utils/BotBehavior.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
Tick,
} from "../../game/Game";
import { PseudoRandom } from "../../PseudoRandom";
import { flattenedEmojiTable } from "../../Util";
import { calculateBoundingBoxCenter, flattenedEmojiTable } from "../../Util";
import { AllianceExtensionExecution } from "../alliance/AllianceExtensionExecution";
import { AttackExecution } from "../AttackExecution";
import { EmojiExecution } from "../EmojiExecution";
Expand Down Expand Up @@ -198,7 +198,7 @@ export class BotBehavior {
}
}

selectEnemy(enemies: Player[]): Player | null {
selectEnemy(borderingEnemies: Player[]): Player | null {
if (this.enemy === null) {
// Save up troops until we reach the reserve ratio
if (!this.hasReserveRatioTroops()) return null;
Expand Down Expand Up @@ -249,20 +249,78 @@ export class BotBehavior {
}

// Select the weakest player
if (this.enemy === null && enemies.length > 0) {
this.setNewEnemy(enemies[0]);
if (this.enemy === null && borderingEnemies.length > 0) {
this.setNewEnemy(borderingEnemies[0]);
}

// Select a random player
if (this.enemy === null && enemies.length > 0) {
this.setNewEnemy(this.random.randElement(enemies));
if (this.enemy === null && borderingEnemies.length > 0) {
this.setNewEnemy(this.random.randElement(borderingEnemies));
}

// If we don't have bordering enemies, we are on an island. Attack someone on an island next to us
if (this.enemy === null && borderingEnemies.length === 0) {
this.selectNearestIslandEnemy();
}
}

// Sanity check, don't attack our allies or teammates
return this.enemySanityCheck();
}

selectNearestIslandEnemy() {
const allPlayers = this.game.players();
const filteredPlayers = allPlayers.filter(
(p) =>
// Don't spam boats into players that are more than twice as large as us
p.troops() <= this.player.troops() * 2 &&
!this.player.isFriendly(p) &&
p !== this.player,
);

if (filteredPlayers.length > 0) {
const playerCenter = calculateBoundingBoxCenter(
this.game,
this.player.borderTiles(),
);

const sortedPlayers = filteredPlayers
.map((filteredPlayer) => {
const filteredPlayerCenter = calculateBoundingBoxCenter(
this.game,
filteredPlayer.borderTiles(),
);
const playerCenterTile = this.game.ref(
playerCenter.x,
playerCenter.y,
);
const filteredPlayerCenterTile = this.game.ref(
filteredPlayerCenter.x,
filteredPlayerCenter.y,
);

const distance = this.game.manhattanDist(
playerCenterTile,
filteredPlayerCenterTile,
);
return { player: filteredPlayer, distance };
})
.sort((a, b) => a.distance - b.distance); // Sort by distance (ascending)

// Select the nearest or second-nearest enemy (So our boat doesn't always run into the same warship, if there is one)
let selectedEnemy: Player | null;
if (sortedPlayers.length > 1 && this.random.chance(2)) {
selectedEnemy = sortedPlayers[1].player;
} else {
selectedEnemy = sortedPlayers[0].player;
}

if (selectedEnemy !== null) {
this.setNewEnemy(selectedEnemy);
}
}
}

selectRandomEnemy(): Player | TerraNullius | null {
if (this.enemy === null) {
// Save up troops until we reach the trigger ratio
Expand Down
Loading