diff --git a/src/core/execution/BotExecution.ts b/src/core/execution/BotExecution.ts index 71141929d..8586a9653 100644 --- a/src/core/execution/BotExecution.ts +++ b/src/core/execution/BotExecution.ts @@ -14,13 +14,15 @@ export class BotExecution implements Execution { private attackTick: number; private triggerRatio: number; private reserveRatio: number; + private expandRatio: number; constructor(private bot: Player) { 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(30, 60) / 100; + this.reserveRatio = this.random.nextInt(20, 30) / 100; + this.expandRatio = this.random.nextInt(10, 20) / 100; } activeDuringSpawnPhase(): boolean { @@ -47,7 +49,12 @@ export class BotExecution implements Execution { this.bot, this.triggerRatio, this.reserveRatio, + this.expandRatio, ); + + // Send an attack on the first tick + this.behavior.sendAttack(this.mg.terraNullius()); + return; } this.behavior.handleAllianceRequests(); diff --git a/src/core/execution/FakeHumanExecution.ts b/src/core/execution/FakeHumanExecution.ts index 622c43383..3e7770644 100644 --- a/src/core/execution/FakeHumanExecution.ts +++ b/src/core/execution/FakeHumanExecution.ts @@ -1,6 +1,5 @@ import { Cell, - Difficulty, Execution, Game, Gold, @@ -27,8 +26,6 @@ import { closestTwoTiles } from "./Util"; import { BotBehavior } from "./utils/BotBehavior"; export class FakeHumanExecution implements Execution { - private firstMove = true; - private active = true; private random: PseudoRandom; private behavior: BotBehavior | null = null; @@ -39,6 +36,7 @@ export class FakeHumanExecution implements Execution { private attackTick: number; private triggerRatio: number; private reserveRatio: number; + private expandRatio: number; private lastEmojiSent = new Map(); private lastNukeSent: [Tick, TileRef][] = []; @@ -56,6 +54,7 @@ export class FakeHumanExecution implements Execution { 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.heckleEmoji = ["🤡", "😡"].map((e) => flattenedEmojiTable.indexOf(e)); } @@ -145,11 +144,10 @@ export class FakeHumanExecution implements Execution { this.player, this.triggerRatio, this.reserveRatio, + this.expandRatio, ); - } - if (this.firstMove) { - this.firstMove = false; + // Send an attack on the first tick this.behavior.sendAttack(this.mg.terraNullius()); return; } @@ -163,7 +161,6 @@ export class FakeHumanExecution implements Execution { this.updateRelationsFromEmbargos(); this.behavior.handleAllianceRequests(); - this.handleEnemies(); this.handleUnits(); this.handleEmbargoesToHostileNations(); this.maybeAttack(); @@ -191,80 +188,30 @@ export class FakeHumanExecution implements Execution { return; } - const enemiesWithTN = enemyborder.map((t) => + const borderPlayers = enemyborder.map((t) => this.mg.playerBySmallID(this.mg.ownerID(t)), ); - if (enemiesWithTN.filter((o) => !o.isPlayer()).length > 0) { + if (borderPlayers.some((o) => !o.isPlayer())) { this.behavior.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; - } - } - - // 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); - } - } - - 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.mg.config().gameConfig().difficulty; - 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; - } - - handleEnemies() { - if (this.player === null || this.behavior === null) { - throw new Error("not initialized"); - } this.behavior.forgetOldEnemies(); this.behavior.assistAllies(); const enemy = this.behavior.selectEnemy(); - if (!enemy) return; + if (!enemy) { + // 5% chance to send a random alliance request + if (this.random.chance(20)) { + const enemies = borderPlayers + .filter((o) => o.isPlayer()) + .sort((a, b) => a.troops() - b.troops()); + const toAlly = this.random.randElement(enemies); + if (this.player.canSendAllianceRequest(toAlly)) { + this.player.createAllianceRequest(toAlly); + } + } + return; + } this.maybeSendEmoji(enemy); this.maybeSendNuke(enemy); if (this.player.sharesBorderWith(enemy)) { @@ -561,7 +508,7 @@ export class FakeHumanExecution implements Execution { const src = this.random.randElement(oceanShore); - const dst = this.randOceanShoreTile(src, 150); + const dst = this.randomBoatTarget(src, 150); if (dst === null) { return; } @@ -603,7 +550,7 @@ export class FakeHumanExecution implements Execution { return null; } - private randOceanShoreTile(tile: TileRef, dist: number): TileRef | null { + private randomBoatTarget(tile: TileRef, dist: number): TileRef | null { if (this.player === null) throw new Error("not initialized"); const x = this.mg.x(tile); const y = this.mg.y(tile); @@ -614,7 +561,7 @@ export class FakeHumanExecution implements Execution { continue; } const randTile = this.mg.ref(randX, randY); - if (!this.mg.isOceanShore(randTile)) { + if (!this.mg.isLand(randTile)) { continue; } const owner = this.mg.owner(randTile); diff --git a/src/core/execution/utils/BotBehavior.ts b/src/core/execution/utils/BotBehavior.ts index 5e393d967..a91d24c55 100644 --- a/src/core/execution/utils/BotBehavior.ts +++ b/src/core/execution/utils/BotBehavior.ts @@ -18,14 +18,13 @@ export class BotBehavior { private assistAcceptEmoji = flattenedEmojiTable.indexOf("👍"); - private firstAttackSent = false; - constructor( private random: PseudoRandom, private game: Game, private player: Player, private triggerRatio: number, private reserveRatio: number, + private expandRatio: number, ) {} handleAllianceRequests() { @@ -211,14 +210,12 @@ export class BotBehavior { if (target.isPlayer() && this.player.isOnSameTeam(target)) return; const maxPop = this.game.config().maxPopulation(this.player); const maxTroops = maxPop * this.player.targetTroopRatio(); - const targetTroops = maxTroops * this.reserveRatio; - // Don't wait until it has sufficient reserves to send the first attack - // to prevent the bot from waiting too long at the start of the game. - const troops = this.firstAttackSent - ? this.player.troops() - targetTroops - : this.player.troops() / 5; + const reserveRatio = target.isPlayer() + ? this.reserveRatio + : this.expandRatio; + const targetTroops = maxTroops * reserveRatio; + const troops = this.player.troops() - targetTroops; if (troops < 1) return; - this.firstAttackSent = true; this.game.addExecution( new AttackExecution( troops, diff --git a/tests/BotBehavior.test.ts b/tests/BotBehavior.test.ts index 9710770ac..51084b509 100644 --- a/tests/BotBehavior.test.ts +++ b/tests/BotBehavior.test.ts @@ -43,7 +43,7 @@ describe("BotBehavior.handleAllianceRequests", () => { const random = new PseudoRandom(42); - botBehavior = new BotBehavior(random, game, player, 0.5, 0.5); + botBehavior = new BotBehavior(random, game, player, 0.5, 0.5, 0.2); }); function setupAllianceRequest({