From 5bf012385b3fee2a22f2f9d036a2e0f8f40d5aaa Mon Sep 17 00:00:00 2001 From: 1brucben <1benjbruce@gmail.com> Date: Sat, 26 Apr 2025 18:41:04 +0200 Subject: [PATCH] ai updates. more balance --- src/client/graphics/layers/WinModal.ts | 2 +- src/core/configuration/DefaultConfig.ts | 4 +- src/core/execution/FakeHumanExecution.ts | 52 ++++++++++++++++++++++-- src/core/execution/utils/BotBehavior.ts | 15 ++++--- 4 files changed, 60 insertions(+), 13 deletions(-) diff --git a/src/client/graphics/layers/WinModal.ts b/src/client/graphics/layers/WinModal.ts index e983bca6c..d8a89e6aa 100644 --- a/src/client/graphics/layers/WinModal.ts +++ b/src/client/graphics/layers/WinModal.ts @@ -221,7 +221,7 @@ export class WinModal extends LitElement implements Layer { this._title = "You Won!"; this.won = true; } else { - this._title = `${wu.winner} has won!`; + this._title = `${winner.name()} has won!`; this.won = false; } this.show(); diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts index ac50b9024..4ba88a741 100644 --- a/src/core/configuration/DefaultConfig.ts +++ b/src/core/configuration/DefaultConfig.ts @@ -577,9 +577,9 @@ export class DefaultConfig implements Config { case Difficulty.Easy: return maxPop * 0.4; case Difficulty.Medium: - return maxPop * 0.7; + return maxPop * 0.6; case Difficulty.Hard: - return maxPop * 1.2; + return maxPop * 1; case Difficulty.Impossible: return maxPop * 2; } diff --git a/src/core/execution/FakeHumanExecution.ts b/src/core/execution/FakeHumanExecution.ts index 7a9cfda16..0a4be4237 100644 --- a/src/core/execution/FakeHumanExecution.ts +++ b/src/core/execution/FakeHumanExecution.ts @@ -17,7 +17,7 @@ import { import { euclDistFN, manhattanDistFN, TileRef } from "../game/GameMap"; import { PseudoRandom } from "../PseudoRandom"; import { GameID } from "../Schemas"; -import { calculateBoundingBox, simpleHash } from "../Util"; +import { calculateBoundingBox, simpleHash, within } from "../Util"; import { ConstructionExecution } from "./ConstructionExecution"; import { EmojiExecution } from "./EmojiExecution"; import { NukeExecution } from "./NukeExecution"; @@ -53,6 +53,7 @@ export class FakeHumanExecution implements Execution { private dogpileTarget: Player | null = null; private dogpileLastChecked: number = -1; + private attackedThisTick: boolean = false; constructor( gameID: GameID, @@ -116,6 +117,7 @@ export class FakeHumanExecution implements Execution { tick(ticks: number) { if (ticks % this.attackRate != this.attackTick) return; + this.attackedThisTick = false; // new tick, reset this.updateDogpile(); @@ -170,6 +172,9 @@ export class FakeHumanExecution implements Execution { this.handleEnemies(); this.handleUnits(); this.handleEmbargoesToHostileNations(); + if (this.attackedThisTick) { + return; // ⛔ Stop if already attacked this tick + } this.maybeAttack(); } @@ -278,6 +283,9 @@ export class FakeHumanExecution implements Execution { this.maybeSendNuke(enemy); if (this.player.sharesBorderWith(enemy)) { this.behavior.sendAttack(enemy); + if (this.behavior.sendAttack(enemy)) { + this.attackedThisTick = true; + } } else { this.maybeSendBoatAttack(enemy); } @@ -405,6 +413,7 @@ export class FakeHumanExecution implements Execution { private maybeSendBoatAttack(other: Player) { if (this.player.isOnSameTeam(other)) return; + const closest = closestTwoTiles( this.mg, Array.from(this.player.borderTiles()).filter((t) => @@ -415,12 +424,29 @@ export class FakeHumanExecution implements Execution { if (closest == null) { return; } + + const maxPop = this.mg.config().maxPopulation(this.player); + const maxTroops = maxPop * this.player.targetTroopRatio(); + const targetTroops = maxTroops * this.reserveRatio; + + const surplusTroops = this.player.troops() - targetTroops; + + if (surplusTroops <= 0) return; // ❗ Not enough spare troops to send + + const troopsToSend = within( + surplusTroops, + 0.1 * this.player.troops(), + 0.2 * this.player.troops(), + ); + + if (troopsToSend < 1) return; // ❗ Don't send if too little + this.mg.addExecution( new TransportShipExecution( this.player.id(), other.id(), closest.y, - this.player.troops() / 5, + troopsToSend, null, ), ); @@ -735,16 +761,30 @@ export class FakeHumanExecution implements Execution { return; } + const maxPop = this.mg.config().maxPopulation(this.player); + const maxTroops = maxPop * this.player.targetTroopRatio(); + const targetTroops = maxTroops * this.reserveRatio; + + const surplusTroops = this.player.troops() - targetTroops; + if (surplusTroops <= 0) return; // ❗ Not enough troops to send a boat + + const troopsToSend = within( + surplusTroops, + 0.1 * this.player.troops(), // a little smaller range for random boats + 0.2 * this.player.troops(), + ); + + if (troopsToSend < 1) return; // ❗ Avoid sending tiny attacks + this.mg.addExecution( new TransportShipExecution( this.player.id(), this.mg.owner(dst).id(), dst, - this.player.troops() / 5, + troopsToSend, null, ), ); - return; } randomLand(): TileRef | null { @@ -809,6 +849,10 @@ export class FakeHumanExecution implements Execution { } private updateDogpile() { + if (this.mg.ticks() < 1200) { + this.dogpileTarget = null; + return; + } const CHECK_INTERVAL = 50; // only check every 50 ticks if (this.mg.ticks() - this.dogpileLastChecked < CHECK_INTERVAL) return; diff --git a/src/core/execution/utils/BotBehavior.ts b/src/core/execution/utils/BotBehavior.ts index 67a61b4d3..88407016e 100644 --- a/src/core/execution/utils/BotBehavior.ts +++ b/src/core/execution/utils/BotBehavior.ts @@ -164,25 +164,26 @@ export class BotBehavior { return this.enemy; } - sendAttack(target: Player | TerraNullius, force: boolean = false) { - if (target.isPlayer() && this.player.isOnSameTeam(target)) return; + sendAttack(target: Player | TerraNullius, force: boolean = false): boolean { + if (target.isPlayer() && this.player.isOnSameTeam(target)) return false; const maxPop = this.game.config().maxPopulation(this.player); const maxTroops = maxPop * this.player.targetTroopRatio(); const targetTroops = maxTroops * this.reserveRatio; + if (!force && this.player.troops() < targetTroops) { + return false; + } + let troops: number; if (force) { - // send exactly 40% of current troops - troops = this.player.troops() * 0.4; - if (troops < 1) return; + troops = this.player.troops() * 0.2; } else { troops = within( this.player.troops() - targetTroops, 0.2 * this.player.troops(), 0.4 * this.player.troops(), ); - if (troops < 1) return; } this.game.addExecution( @@ -192,6 +193,8 @@ export class BotBehavior { target.isPlayer() ? target.id() : null, ), ); + + return true; // ✅ Attack was actually sent } }