refactor bot attacking

This commit is contained in:
Scott Anderson
2025-04-03 19:45:18 -04:00
parent 52542baa63
commit 8e9d6ce07e
2 changed files with 102 additions and 67 deletions
+55 -31
View File
@@ -13,16 +13,23 @@ import { AttackExecution } from "./AttackExecution";
export class BotExecution implements Execution {
private active = true;
private random: PseudoRandom;
private updateRate: number;
private updateTick: number;
private mg: Game;
private neighborsTerraNullius = true;
private enemy: Player;
private attackRate: number;
private attackTick: number;
private triggerRatio: number;
private reserveRatio: number;
constructor(private bot: Player) {
this.random = new PseudoRandom(simpleHash(bot.id()));
this.updateRate = this.random.nextInt(10, 50);
this.updateTick = this.random.nextInt(0, this.updateRate);
this.attackRate = this.random.nextInt(10, 50);
this.attackTick = this.random.nextInt(0, this.attackRate);
this.triggerRatio = this.random.nextInt(20, 30);
this.reserveRatio = this.random.nextInt(10, 20);
}
activeDuringSpawnPhase(): boolean {
return false;
}
@@ -30,11 +37,10 @@ export class BotExecution implements Execution {
init(mg: Game, ticks: number) {
this.mg = mg;
this.bot.setTargetTroopRatio(0.7);
// this.neighborsTerra = this.bot.neighbors().filter(n => n == this.gs.terraNullius()).length > 0
}
tick(ticks: number) {
if (ticks % this.updateRate != this.updateTick) return;
if (ticks % this.attackRate != this.attackTick) return;
if (!this.bot.isAlive()) {
this.active = false;
@@ -59,18 +65,6 @@ export class BotExecution implements Execution {
}
private maybeAttack() {
const traitors = this.bot
.neighbors()
.filter((n) => n.isPlayer() && n.isTraitor()) as Player[];
if (traitors.length > 0) {
const toAttack = this.random.randElement(traitors);
const odds = this.bot.isFriendly(toAttack) ? 6 : 3;
if (this.random.chance(odds)) {
this.sendAttack(toAttack);
return;
}
}
if (this.neighborsTerraNullius) {
for (const b of this.bot.borderTiles()) {
for (const n of this.mg.neighbors(b)) {
@@ -83,35 +77,65 @@ export class BotExecution implements Execution {
this.neighborsTerraNullius = false;
}
const border = Array.from(this.bot.borderTiles())
.flatMap((t) => this.mg.neighbors(t))
.filter((t) => this.mg.hasOwner(t) && this.mg.owner(t) != this.bot);
if (this.enemy === null) {
// Save up troops until we reach the trigger ratio
const ratio =
this.bot.population() / this.mg.config().maxPopulation(this.bot);
if (ratio * 100 < this.triggerRatio) return;
if (border.length == 0) {
return;
}
// Select a new enemy
const traitors = this.bot
.neighbors()
.filter((n) => n.isPlayer() && n.isTraitor()) as Player[];
if (traitors.length > 0) {
const toAttack = this.random.randElement(traitors);
const odds = this.bot.isFriendly(toAttack) ? 6 : 3;
if (this.random.chance(odds)) {
this.sendAttack(toAttack);
return;
}
}
const toAttack = border[this.random.nextInt(0, border.length)];
const owner = this.mg.owner(toAttack);
const border = Array.from(this.bot.borderTiles())
.flatMap((t) => this.mg.neighbors(t))
.filter((t) => this.mg.hasOwner(t) && this.mg.owner(t) != this.bot);
if (owner.isPlayer()) {
if (this.bot.isFriendly(owner)) {
if (border.length == 0) {
return;
}
if (owner.type() == PlayerType.FakeHuman) {
const toAttack = border[this.random.nextInt(0, border.length)];
const owner = this.mg.owner(toAttack);
if (!owner.isPlayer()) {
this.neighborsTerraNullius = true;
return;
}
this.enemy = owner;
}
if (this.enemy) {
if (this.bot.isFriendly(this.enemy)) {
this.enemy = null;
return;
}
if (this.enemy.type() == PlayerType.FakeHuman) {
if (!this.random.chance(2)) {
return;
}
}
this.sendAttack(this.enemy);
}
this.sendAttack(owner);
}
sendAttack(toAttack: Player | TerraNullius) {
if (toAttack.isPlayer() && this.bot.isOnSameTeam(toAttack)) return;
const max = this.mg.config().maxPopulation(this.bot);
const target = (max * this.reserveRatio) / 100;
const troops = this.bot.population() - target;
if (troops < 1) return;
this.mg.addExecution(
new AttackExecution(
this.bot.troops() / 20,
troops,
this.bot.id(),
toAttack.isPlayer() ? toAttack.id() : null,
),
+47 -36
View File
@@ -38,15 +38,16 @@ export class FakeHumanExecution implements Execution {
private player: Player = null;
private enemy: Player | null = null;
private attackRate: number;
private attackTick: number;
private triggerRatio: number;
private reserveRatio: number;
private lastEnemyUpdateTick: number = 0;
private lastEmojiSent = new Map<Player, Tick>();
private lastNukeSent: [Tick, TileRef][] = [];
private embargoMalusApplied = new Set<PlayerID>();
private attackRate: number;
private attackTick: number;
constructor(
gameID: GameID,
private playerInfo: PlayerInfo,
@@ -56,6 +57,8 @@ export class FakeHumanExecution implements Execution {
);
this.attackRate = this.random.nextInt(40, 80);
this.attackTick = this.random.nextInt(0, this.attackRate);
this.triggerRatio = this.random.nextInt(45, 80);
this.reserveRatio = this.random.nextInt(20, 40);
}
init(mg: Game, ticks: number) {
@@ -156,6 +159,14 @@ export class FakeHumanExecution implements Execution {
(t) => this.mg.isLand(t) && this.mg.ownerID(t) != this.player.smallID(),
);
const enemiesWithTN = enemyborder.map((t) =>
this.mg.playerBySmallID(this.mg.ownerID(t)),
);
if (enemiesWithTN.filter((o) => !o.isPlayer()).length > 0) {
this.sendAttack(this.mg.terraNullius());
return;
}
if (enemyborder.length == 0) {
if (this.random.chance(5)) {
this.sendBoat();
@@ -167,23 +178,17 @@ export class FakeHumanExecution implements Execution {
return;
}
const enemiesWithTN = enemyborder.map((t) =>
this.mg.playerBySmallID(this.mg.ownerID(t)),
);
if (enemiesWithTN.filter((o) => !o.isPlayer()).length > 0) {
this.sendAttack(this.mg.terraNullius());
return;
}
const enemies = enemiesWithTN
.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.player.createAllianceRequest(toAlly);
return;
for (const toAlly of enemies) {
if (this.player.canSendAllianceRequest(toAlly)) {
this.player.createAllianceRequest(toAlly);
return;
}
}
}
@@ -213,7 +218,7 @@ export class FakeHumanExecution implements Execution {
}
}
shouldDiscourageAttack(other: Player) {
private shouldDiscourageAttack(other: Player) {
if (other.isTraitor()) {
return false;
}
@@ -233,29 +238,31 @@ export class FakeHumanExecution implements Execution {
this.enemy = null;
}
const target =
this.player
.allies()
.filter((ally) => this.player.relation(ally) == Relation.Friendly)
.filter((ally) => ally.targets().length > 0)
.map((ally) => ({ ally: ally, t: ally.targets()[0] }))[0] ?? null;
outer: for (const ally of this.player.allies()) {
if (this.player.relation(ally) < Relation.Friendly) continue;
for (const target of ally.targets()) {
if (target === this.player) continue;
if (this.player.isAlliedWith(target)) continue;
if (
target != null &&
target.t != this.player &&
!this.player.isAlliedWith(target.t)
) {
this.player.updateRelation(target.ally, -20);
this.enemy = target.t;
this.lastEnemyUpdateTick = this.mg.ticks();
if (target.ally.type() == PlayerType.Human) {
this.mg.addExecution(
new EmojiExecution(this.player.id(), target.ally.id(), "👍"),
);
this.player.updateRelation(ally, -20);
this.enemy = target;
this.lastEnemyUpdateTick = this.mg.ticks();
if (ally.type() == PlayerType.Human) {
this.mg.addExecution(
new EmojiExecution(this.player.id(), ally.id(), "👍"),
);
}
break outer;
}
}
if (this.enemy == null) {
if (this.enemy === null) {
// Save up troops until we reach the trigger ratio
const ratio =
this.player.population() / this.mg.config().maxPopulation(this.player);
if (ratio * 100 < this.triggerRatio) return;
// Choose a new enemy
const mostHated = this.player.allRelationsSorted()[0] ?? null;
if (mostHated != null && mostHated.relation == Relation.Hostile) {
this.enemy = mostHated.player;
@@ -652,9 +659,13 @@ export class FakeHumanExecution implements Execution {
sendAttack(toAttack: Player | TerraNullius) {
if (toAttack.isPlayer() && this.player.isOnSameTeam(toAttack)) return;
const max = this.mg.config().maxPopulation(this.player);
const target = (max * this.reserveRatio) / 100;
const troops = this.player.population() - target;
if (troops < 1) return;
this.mg.addExecution(
new AttackExecution(
this.player.troops() / 5,
troops,
this.player.id(),
toAttack.isPlayer() ? toAttack.id() : null,
),