diff --git a/src/core/execution/BotExecution.ts b/src/core/execution/BotExecution.ts index 75d843632..ce6b63998 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 7e79a9bf3..dea580dc1 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) { @@ -205,22 +205,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 b93903aed..f46703095 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, @@ -87,11 +88,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; } @@ -104,7 +142,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; @@ -121,7 +165,7 @@ export class BotBehavior { largestAttacker = attack.attacker(); } if (largestAttacker !== undefined) { - this.setNewEnemy(largestAttacker); + this.setNewEnemy(largestAttacker, true); } } @@ -157,10 +201,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 @@ -188,11 +235,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 && @@ -201,6 +250,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 @@ -210,7 +269,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();