mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-22 13:39:46 +00:00
refactor bot attacking
This commit is contained in:
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user