refactor bot attacking

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