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