diff --git a/src/core/execution/BotExecution.ts b/src/core/execution/BotExecution.ts index 8535a9b81..69f1f4f29 100644 --- a/src/core/execution/BotExecution.ts +++ b/src/core/execution/BotExecution.ts @@ -20,8 +20,8 @@ export class BotExecution implements Execution { this.random = new PseudoRandom(simpleHash(bot.id())); this.attackRate = this.random.nextInt(40, 80); this.attackTick = this.random.nextInt(0, this.attackRate); - this.triggerRatio = this.random.nextInt(60, 90) / 100; - this.reserveRatio = this.random.nextInt(20, 30) / 100; + this.triggerRatio = this.random.nextInt(50, 60) / 100; + this.reserveRatio = this.random.nextInt(30, 40) / 100; this.expandRatio = this.random.nextInt(10, 20) / 100; } diff --git a/src/core/execution/FakeHumanExecution.ts b/src/core/execution/FakeHumanExecution.ts index 344ec0866..73c56c1ec 100644 --- a/src/core/execution/FakeHumanExecution.ts +++ b/src/core/execution/FakeHumanExecution.ts @@ -53,9 +53,9 @@ 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(60, 90) / 100; - this.reserveRatio = this.random.nextInt(30, 60) / 100; - this.expandRatio = this.random.nextInt(15, 25) / 100; + this.triggerRatio = this.random.nextInt(50, 60) / 100; + this.reserveRatio = this.random.nextInt(30, 40) / 100; + this.expandRatio = this.random.nextInt(10, 20) / 100; } init(mg: Game) { @@ -223,23 +223,12 @@ export class FakeHumanExecution implements Execution { const toAlly = this.random.randElement(enemies); if (this.player.canSendAllianceRequest(toAlly)) { this.player.createAllianceRequest(toAlly); - return; } } - // 50-50 attack weakest player vs random player - const toAttack = this.random.chance(2) - ? enemies[0] - : this.random.randElement(enemies); - - if (this.shouldAttack(toAttack)) { - this.behavior.sendAttack(toAttack); - return; - } - this.behavior.forgetOldEnemies(); this.behavior.assistAllies(); - const enemy = this.behavior.selectEnemy(); + const enemy = this.behavior.selectEnemy(enemies); if (!enemy) return; this.maybeSendEmoji(enemy); this.maybeSendNuke(enemy); diff --git a/src/core/execution/utils/BotBehavior.ts b/src/core/execution/utils/BotBehavior.ts index b3b9378f7..3cff57227 100644 --- a/src/core/execution/utils/BotBehavior.ts +++ b/src/core/execution/utils/BotBehavior.ts @@ -1,5 +1,6 @@ import { AllianceRequest, + Difficulty, Game, Player, PlayerType, @@ -71,11 +72,48 @@ export class BotBehavior { this.game.addExecution(new EmojiExecution(this.player, player.id(), emoji)); } - private setNewEnemy(newEnemy: Player | null) { + private setNewEnemy(newEnemy: Player | null, force = false) { + if (newEnemy !== null && !force && !this.shouldAttack(newEnemy)) return; this.enemy = newEnemy; this.enemyUpdated = this.game.ticks(); } + private shouldAttack(other: Player): boolean { + if (this.player === null) throw new Error("not initialized"); + if (this.player.isOnSameTeam(other)) { + return false; + } + if (this.player.isFriendly(other)) { + if (this.shouldDiscourageAttack(other)) { + return this.random.chance(200); + } + return this.random.chance(50); + } else { + if (this.shouldDiscourageAttack(other)) { + return this.random.chance(4); + } + return true; + } + } + + private shouldDiscourageAttack(other: Player) { + if (other.isTraitor()) { + return false; + } + const { difficulty } = this.game.config().gameConfig(); + if ( + difficulty === Difficulty.Hard || + difficulty === Difficulty.Impossible + ) { + return false; + } + if (other.type() !== PlayerType.Human) { + return false; + } + // Only discourage attacks on Humans who are not traitors on easy or medium difficulty. + return true; + } + private clearEnemy() { this.enemy = null; } @@ -87,7 +125,13 @@ export class BotBehavior { } } - private hasSufficientTroops(): boolean { + private hasReserveRatioTroops(): boolean { + const maxTroops = this.game.config().maxTroops(this.player); + const ratio = this.player.troops() / maxTroops; + return ratio >= this.reserveRatio; + } + + private hasTriggerRatioTroops(): boolean { const maxTroops = this.game.config().maxTroops(this.player); const ratio = this.player.troops() / maxTroops; return ratio >= this.triggerRatio; @@ -104,7 +148,7 @@ export class BotBehavior { largestAttacker = attack.attacker(); } if (largestAttacker !== undefined) { - this.setNewEnemy(largestAttacker); + this.setNewEnemy(largestAttacker, true); } } @@ -140,10 +184,13 @@ export class BotBehavior { } } - selectEnemy(): Player | null { + selectEnemy(enemies: Player[]): Player | null { if (this.enemy === null) { - // Save up troops until we reach the trigger ratio - if (!this.hasSufficientTroops()) return null; + // Save up troops until we reach the reserve ratio + if (!this.hasReserveRatioTroops()) return null; + + // Maybe save up troops until we reach the trigger ratio + if (!this.hasTriggerRatioTroops() && !this.random.chance(10)) return null; // Prefer neighboring bots const bots = this.player @@ -171,11 +218,13 @@ export class BotBehavior { // Retaliate against incoming attacks if (this.enemy === null) { + // Only after clearing bots this.checkIncomingAttacks(); } // Select the most hated player - if (this.enemy === null) { + if (this.enemy === null && this.random.chance(2)) { + // 50% chance const mostHated = this.player.allRelationsSorted()[0]; if ( mostHated !== undefined && @@ -184,6 +233,16 @@ export class BotBehavior { this.setNewEnemy(mostHated.player); } } + + // Select the weakest player + if (this.enemy === null && enemies.length > 0) { + this.setNewEnemy(enemies[0]); + } + + // Select a random player + if (this.enemy === null && enemies.length > 0) { + this.setNewEnemy(this.random.randElement(enemies)); + } } // Sanity check, don't attack our allies or teammates @@ -193,7 +252,7 @@ export class BotBehavior { selectRandomEnemy(): Player | TerraNullius | null { if (this.enemy === null) { // Save up troops until we reach the trigger ratio - if (!this.hasSufficientTroops()) return null; + if (!this.hasTriggerRatioTroops()) return null; // Choose a new enemy randomly const neighbors = this.player.neighbors();