Skip to content

Conversation

MantasPtr
Copy link

@MantasPtr MantasPtr commented Sep 5, 2025

@MantasPtr MantasPtr requested a review from a team as a code owner September 5, 2025 14:29
Copy link
Contributor

coderabbitai bot commented Sep 5, 2025

Walkthrough

Adds a full client-side bot system for singleplayer: core bot logic, analysis, strategies, action execution, and a control panel UI. Wires bot integration into ClientGameRunner, provides a global console helper, and documents how to run/use the bot. Increases singleplayer bot slider max and default. Minor renderer and HTML updates.

Changes

Cohort / File(s) Summary
Documentation
HOW_TO_RUN_THE_BOT.md
New doc explaining how to run, control (UI/console), configure, and troubleshoot the bot with examples.
Singleplayer bot count tweak
src/client/SinglePlayerModal.ts
Raise max bots to 4000; default to 2500; input/slider clamp now uses maxBots.
Bot core (analysis, strategy, execution)
src/client/bot/PlayerBot.ts, src/client/bot/analysis/GameStateAnalyzer.ts, src/client/bot/strategy/SpawnStrategy.ts, src/client/bot/strategy/BuildStrategy.ts, src/client/bot/execution/ActionExecutor.ts
Introduces PlayerBot with tick loop and status; adds analyzers for territory/resources/threat; spawn and build strategies; executor that emits transport events for actions. Public interfaces for status, analyses, and decisions.
Bot integration and client hook
src/client/bot/integration/BotIntegration.ts, src/client/ClientGameRunner.ts
Global BotIntegration singleton with start/stop/status and interval-driven ticks; exposes console helper; ClientGameRunner initializes integration for singleplayer and exposes getBotIntegration().
UI panel and renderer glue
src/client/bot/ui/BotControlPanel.ts, src/client/index.html, src/client/graphics/GameRenderer.ts
Adds bot-control-panel web component with start/stop and status; inserted into top-right controls; renderer looks up panel and logs if absent.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant ClientGameRunner
  participant BotIntegration
  participant PlayerBot
  participant EventBus
  note over ClientGameRunner,BotIntegration: Singleplayer game start
  User->>ClientGameRunner: Launch singleplayer
  ClientGameRunner->>BotIntegration: initialize(gameView, eventBus, {autoStart:false})
  User->>BotIntegration: start() (via UI or console)
  BotIntegration->>PlayerBot: enable()
  loop every 100ms
    BotIntegration->>PlayerBot: tick()
    alt Spawn phase
      PlayerBot->>EventBus: Spawn event
    else Main game
      PlayerBot->>EventBus: Build/Attack/Alliance/etc.
    end
  end
Loading
sequenceDiagram
  autonumber
  actor User
  participant BotControlPanel
  participant BotIntegration
  note over BotControlPanel: UI polling every 1s
  BotControlPanel->>BotIntegration: getStatus()
  BotIntegration-->>BotControlPanel: BotStatus
  User->>BotControlPanel: Click Start/Stop
  BotControlPanel->>BotIntegration: start()/stop()
  note over User,BotIntegration: Console helper (openFrontBot)<br/>start | stop | status | analysis | forceTick | isRunning
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

Feature - Frontend, Feature - New, UI/UX

Suggested reviewers

  • scottanderson
  • evanpelle

Poem

A whispering bot wakes on the plain,
Counting tiles, reading strain.
Tick by tick, it thinks and goes,
Spawns, builds, shifts its rows.
A tiny panel blinks “all green” —
Press start, watch unseen minds convene.
GG, machine. 🧠⚔️


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 29

🧹 Nitpick comments (27)
src/client/SinglePlayerModal.ts (2)

27-27: 4k cap and 2.5k default bots: verify engine limits and UX

4k players is extreme; 2.5k default may stall low/mid machines and blow up map/player limits. Consider an adaptive cap (map-size/tiles-based) and a safer default (e.g., 200–400).

Would you like a quick patch to compute maxBots from expected player capacity per map?

Also applies to: 35-35


358-362: Clamp instead of ignoring out-of-range input

Return-early can leave the UI value unchanged without feedback. Clamp to [0, maxBots] to be robust for keyboard edits and programmatic changes.

-    if (isNaN(value) || value < 0 || value > this.maxBots) {
-      return;
-    }
-    this.bots = value;
+    if (isNaN(value)) return;
+    const clamped = Math.min(this.maxBots, Math.max(0, value));
+    this.bots = clamped;
HOW_TO_RUN_THE_BOT.md (3)

117-124: Add language to fenced block

Annotate the block as plain text to satisfy markdownlint.

-```
+```text
 1. Game starts → Bot icon (🤖) appears in top-right
 2. Click icon → Control panel opens
 3. Click "Start Bot" → Bot begins analysis
 4. Spawn phase → Bot automatically selects best spawn location
 5. Early game → Bot manages resources and looks for expansion
 6. Mid game → Bot makes strategic decisions based on threats/opportunities

---

`139-142`: **Add language to fenced block**

Same here—mark as text/log.


```diff
-```
+```text
 PlayerBot: Spawning at (45, 32) with confidence 87%
 Spawn reasons: Large land area (167 tiles), Isolated position, Good water access

---

`146-146`: **Tone tweak**

One exclamation point is enough; consider trimming for a more neutral voice.

</blockquote></details>
<details>
<summary>src/client/bot/analysis/GameStateAnalyzer.ts (2)</summary><blockquote>

`128-132`: **Guard against empty cluster arrays**

Avoid `Math.max()` on an empty list.


```diff
   const clusters = this.analyzeTerritorialClusters(player);
-  const largestClusterSize = Math.max(...clusters.map((c) => c.size));
+  const largestClusterSize =
+    clusters.length > 0 ? Math.max(...clusters.map((c) => c.size)) : 0;
   const islandCount = clusters.length;

196-227: Add helper methods used above (outside this hunk)

New helpers to estimate missing data.

I can push this patch for you.

// Place inside GameStateAnalyzer class, near other helpers:

private estimateCurrentPopulation(player: PlayerView): number {
  const max = this.estimateMaxPopulation(player);
  // 60% of capacity, at least current troops
  return Math.max(player.troops(), Math.floor(max * 0.6));
}

private estimateCurrentTroopRatio(player: PlayerView, population: number): number {
  if (population <= 0) return 0.8;
  return Math.min(0.95, player.troops() / population);
}
src/client/index.html (1)

391-394: Mount only for singleplayer or hide via existing hideable pattern.

Avoid rendering the panel in multiplayer to reduce DOM noise and logs. Add the hideable class or gate creation on gameType.

Apply one of:

  • Add component-hideable wrapper and toggle "parent-hidden" in MP.
  • Or render the element only when gameType === Singleplayer.

Please check overlap on small screens (<= 640px) with replay-panel and right sidebar.

src/client/ClientGameRunner.ts (2)

48-53: Avoid double registration/import side effects for the web component.

This file imports the BotControlPanel for side effects, and GameRenderer also imports the class. Keep registration in one place to prevent accidental double evaluation.

Option A: Keep this side-effect import here; remove the runtime import from GameRenderer and use customElements.get() there. Option B: Register in GameRenderer only.


675-680: Public getter seems unnecessary; prefer composition over globals.

Since UI queries the global accessor, this method adds little value. Either remove it, or have BotControlPanel receive the integration via property for better composition and testability.

src/client/graphics/GameRenderer.ts (2)

6-6: Use a type-only import or remove it to avoid side effects.

You don’t use the class value at runtime; importing it can re-execute module code and re-register the element.

-import { BotControlPanel } from "../bot/ui/BotControlPanel";
+// import type to avoid runtime side effects
+import type { BotControlPanel } from "../bot/ui/BotControlPanel";

156-165: Avoid instanceof on a type-only class; rely on the Custom Elements registry.

Instanceof will fail if the class isn’t the same constructor reference. Use customElements.get() and HTMLElement guards.

-  const botControlPanel = document.querySelector(
-    "bot-control-panel",
-  ) as BotControlPanel;
-  if (!(botControlPanel instanceof BotControlPanel)) {
-    console.log(
-      "Bot control panel not found - this is normal for multiplayer games",
-    );
-  }
+  const botControlPanel = document.querySelector("bot-control-panel") as BotControlPanel | null;
+  const isDefined = !!customElements.get("bot-control-panel");
+  if (!botControlPanel || !isDefined) {
+    console.log("Bot control panel not found - this is normal for multiplayer games");
+  }
src/client/bot/ui/BotControlPanel.ts (2)

243-249: Construct via tag name to use the registered custom element constructor.

new BotControlPanel() bypasses the registry. Prefer document.createElement.

-export function addBotControlPanel(
-  container: HTMLElement = document.body,
-): BotControlPanel {
-  const panel = new BotControlPanel();
-  container.appendChild(panel);
-  return panel;
-}
+export function addBotControlPanel(
+  container: HTMLElement = document.body,
+): BotControlPanel {
+  const panel = document.createElement("bot-control-panel") as BotControlPanel;
+  container.appendChild(panel);
+  return panel;
+}

129-139: Prefer composition over globals.

Expose a @Property({attribute:false}) integration?: BotIntegration and read status from it when provided; fallback to getBotIntegration() if absent. Easier to test and avoids globals.

Happy to draft this refactor.

src/client/bot/strategy/SpawnStrategy.ts (2)

203-209: BFS explores water too; push only land neighbors to reduce work.

-      // Add neighbors
-      const neighbors = this.gameView.neighbors(tile);
-      for (const neighbor of neighbors) {
-        if (!visited.has(neighbor)) {
-          toVisit.push(neighbor);
-        }
-      }
+      // Add only land neighbors
+      for (const n of this.gameView.neighbors(tile)) {
+        if (!visited.has(n) && this.gameView.isLand(n)) {
+          toVisit.push(n);
+        }
+      }

327-347: Avoid recomputing heavy analysis on every call. Cache per tick.

Store the last SpawnDecision and the tick it was computed. Reuse until tick changes.

I can add a small cache with an optional invalidate() API and tests.

src/client/bot/PlayerBot.ts (2)

86-99: Reduce noisy logs on disabled ticks

The info log on every disabled tick can spam the console. Consider downgrading to debug or gating with a verbose flag.


39-41: Make tickRate configurable

Expose tickRate via constructor param or setter for easier tuning across maps/speeds.

src/client/bot/integration/BotIntegration.ts (1)

159-171: Gate global console helper in production

Exposing openFrontBot is handy for dev, but should be disabled in prod builds.

Example:

-  (window as any).openFrontBot = { ... };
+  if (process.env.NODE_ENV !== "production") {
+    (window as any).openFrontBot = { ... };
+  }
src/client/bot/strategy/BuildStrategy.ts (5)

36-57: Avoid N× sequential awaits for player.actions

This loop awaits per tile and can be slow on large borders. Batch with limited concurrency to keep the worker busy but safe.

One simple approach without extra libs:

-    for (const tile of borderTiles) {
-      const actions = await player.actions(tile);
-      const buildableUnits = actions.buildableUnits;
-      for (const buildable of buildableUnits) {
-        const decision = this.evaluateBuildingOption(player, tile, buildable, analysis);
-        if (decision) {
-          recommendations.push(decision);
-        }
-      }
-    }
+    const concurrency = 8;
+    for (let i = 0; i < borderTiles.length; i += concurrency) {
+      const batch = borderTiles.slice(i, i + concurrency);
+      const actionsBatch = await Promise.all(
+        batch.map((tile) => player.actions(tile).then((a) => ({ tile, a })))
+      );
+      for (const { tile, a } of actionsBatch) {
+        for (const buildable of a.buildableUnits) {
+          const decision = this.evaluateBuildingOption(player, tile, buildable, analysis);
+          if (decision) recommendations.push(decision);
+        }
+      }
+    }

100-133: Narrow types and rename to avoid interface shadowing

BuildDecision here conflicts in name with ActionExecutor’s BuildDecision (different shape). Rename to BotBuildDecision to make intent clear.

Apply this diff:

-export interface BuildDecision {
+export interface BotBuildDecision {
   tile: TileRef;
   unitType: UnitType;
   priority: number;
   reasoning: string;
   canUpgrade: number | false;
   existingUnitId?: number;
   cost: bigint;
 }
 
-export interface BuildAnalysis {
-  recommendations: BuildDecision[];
+export interface BuildAnalysis {
+  recommendations: BotBuildDecision[];
   totalCost: bigint;
-  immediateBuilds: BuildDecision[];
-  futureBuilds: BuildDecision[];
+  immediateBuilds: BotBuildDecision[];
+  futureBuilds: BotBuildDecision[];
 }

And update references in this file accordingly. No runtime change; just clarity.


169-173: Prefer integers for priorities

Multiplying by 1.2 turns priorities into floats. Consider using integer math (e.g., priority = Math.round(priority * 12 / 10)) to keep types simple.


371-387: Replace stubbed SAM “important asset” check or mark TODO

This block always returns false. Add a TODO with a small plan, or wire a simple check for own City/Factory neighbors.

Example:

-      // Check if neighbor has important buildings
-      // This would require additional game state analysis
-      return false; // Simplified for now
+      // TODO: consider own Cities/Factories nearby as “important”
+      const unit = this.gameView.unit(this.gameView.cell(neighbor).unitId ?? -1);
+      return unit?.owner().smallID() === player.smallID() &&
+             (unit.type() === UnitType.City || unit.type() === UnitType.Factory);

122-133: Reason strings: make them specific

“Building X for strategic value” is vague. Include one or two top reasons (e.g., “border tile + low gold income”).

src/client/bot/execution/ActionExecutor.ts (3)

229-267: Prefer a discriminated union for batch items; avoid any

Gives compile-time safety and simpler switches.

Minimal change:

-  public executeBatch(
-    decisions: Array<{
-      type: "attack" | "build" | "alliance" | "boat" | "spawn";
-      decision: any;
-    }>,
-  ): void {
+  public executeBatch(decisions: DecisionBatchItem[]): void {

Add near the interfaces section:

type DecisionBatchItem =
  | { type: "attack"; decision: AttackDecision }
  | { type: "build"; decision: BuildDecision }
  | { type: "alliance"; decision: AllianceDecision }
  | { type: "boat"; decision: BoatAttackDecision }
  | { type: "spawn"; decision: TileRef };

27-33: Reuse the shared BuildDecision type to avoid drift

There’s already a richer BuildDecision in strategy/BuildStrategy.ts. Import and reuse it instead of redefining.

-export interface BuildDecision {
-  unitType: UnitType;
-  tile: TileRef;
-  priority: number;
-  reasoning: string;
-}
+// At top of file (imports)
+// import type { BuildDecision } from "../strategy/BuildStrategy";
+// Then remove this local interface.

172-183: Optional: validate emoji id

Prevent out-of-range or NaN values, if the protocol defines a range.

   public executeEmoji(
     recipient: PlayerView,
     emoji: number,
     reasoning: string,
   ): void {
+    if (!Number.isInteger(emoji) || emoji < 0) {
+      console.error(`ActionExecutor: Invalid emoji id: ${emoji}`);
+      return;
+    }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3927db9 and 538646e.

📒 Files selected for processing (12)
  • HOW_TO_RUN_THE_BOT.md (1 hunks)
  • src/client/ClientGameRunner.ts (4 hunks)
  • src/client/SinglePlayerModal.ts (3 hunks)
  • src/client/bot/PlayerBot.ts (1 hunks)
  • src/client/bot/analysis/GameStateAnalyzer.ts (1 hunks)
  • src/client/bot/execution/ActionExecutor.ts (1 hunks)
  • src/client/bot/integration/BotIntegration.ts (1 hunks)
  • src/client/bot/strategy/BuildStrategy.ts (1 hunks)
  • src/client/bot/strategy/SpawnStrategy.ts (1 hunks)
  • src/client/bot/ui/BotControlPanel.ts (1 hunks)
  • src/client/graphics/GameRenderer.ts (2 hunks)
  • src/client/index.html (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-09T02:20:43.637Z
Learnt from: VariableVince
PR: openfrontio/OpenFrontIO#1110
File: src/client/Main.ts:293-295
Timestamp: 2025-06-09T02:20:43.637Z
Learning: In src/client/Main.ts, during game start in the handleJoinLobby callback, UI elements are hidden using direct DOM manipulation with classList.add("hidden") for consistency. This includes modals, buttons, and error divs. The codebase follows this pattern rather than using component APIs for hiding elements during game transitions.

Applied to files:

  • src/client/graphics/GameRenderer.ts
  • src/client/bot/ui/BotControlPanel.ts
🧬 Code graph analysis (8)
src/client/bot/ui/BotControlPanel.ts (3)
src/client/bot/PlayerBot.ts (1)
  • BotStatus (12-19)
src/client/ClientGameRunner.ts (1)
  • getBotIntegration (678-680)
src/client/bot/integration/BotIntegration.ts (1)
  • getBotIntegration (179-181)
src/client/bot/execution/ActionExecutor.ts (5)
src/core/game/Game.ts (1)
  • PlayerID (14-14)
src/client/bot/strategy/BuildStrategy.ts (1)
  • BuildDecision (6-14)
src/core/game/GameMap.ts (1)
  • TileRef (3-3)
src/core/EventBus.ts (1)
  • EventBus (7-44)
src/client/Transport.ts (11)
  • SendSpawnIntentEvent (68-70)
  • SendAttackIntentEvent (72-77)
  • SendBoatAttackIntentEvent (79-86)
  • BuildUnitIntentEvent (88-93)
  • SendAllianceRequestIntentEvent (34-39)
  • SendAllianceReplyIntentEvent (55-62)
  • SendBreakAllianceIntentEvent (41-46)
  • SendTargetPlayerIntentEvent (95-97)
  • SendEmojiIntentEvent (99-104)
  • SendDonateGoldIntentEvent (106-111)
  • SendDonateTroopsIntentEvent (113-118)
src/client/bot/PlayerBot.ts (6)
src/client/bot/analysis/GameStateAnalyzer.ts (2)
  • GameStateAnalyzer (82-618)
  • GameStateAnalysis (71-80)
src/client/bot/strategy/SpawnStrategy.ts (1)
  • SpawnStrategy (24-348)
src/client/bot/strategy/BuildStrategy.ts (1)
  • BuildStrategy (23-435)
src/client/bot/execution/ActionExecutor.ts (1)
  • ActionExecutor (48-268)
src/core/game/GameView.ts (3)
  • GameView (364-696)
  • myPlayer (509-512)
  • PlayerView (180-362)
src/core/EventBus.ts (1)
  • EventBus (7-44)
src/client/bot/strategy/BuildStrategy.ts (6)
src/client/bot/execution/ActionExecutor.ts (1)
  • BuildDecision (27-32)
src/core/game/GameMap.ts (1)
  • TileRef (3-3)
src/client/bot/analysis/GameStateAnalyzer.ts (1)
  • GameStateAnalysis (71-80)
src/core/game/PlayerImpl.ts (2)
  • buildableUnits (875-894)
  • canBuild (896-943)
src/core/game/Game.ts (1)
  • BuildableUnit (723-729)
src/client/bot/strategy/SpawnStrategy.ts (1)
  • hasWaterAccess (250-269)
src/client/ClientGameRunner.ts (1)
src/client/bot/integration/BotIntegration.ts (2)
  • initializeBotIntegration (144-174)
  • getBotIntegration (179-181)
src/client/bot/strategy/SpawnStrategy.ts (3)
src/core/game/GameMap.ts (1)
  • TileRef (3-3)
src/core/game/GameView.ts (1)
  • GameView (364-696)
src/client/bot/analysis/GameStateAnalyzer.ts (1)
  • GameStateAnalyzer (82-618)
src/client/bot/integration/BotIntegration.ts (4)
src/client/bot/PlayerBot.ts (2)
  • PlayerBot (30-426)
  • BotStatus (12-19)
src/core/game/GameView.ts (1)
  • GameView (364-696)
src/core/EventBus.ts (1)
  • EventBus (7-44)
src/client/ClientGameRunner.ts (1)
  • getBotIntegration (678-680)
src/client/bot/analysis/GameStateAnalyzer.ts (3)
src/core/game/GameMap.ts (1)
  • TileRef (3-3)
src/core/game/GameView.ts (2)
  • PlayerView (180-362)
  • GameView (364-696)
src/core/game/Game.ts (1)
  • Gold (16-16)
🪛 LanguageTool
HOW_TO_RUN_THE_BOT.md

[grammar] ~1-~1: There might be a mistake here.
Context: # 🤖 How to Run the OpenFront AI Bot The AI bot has been fully integrated...

(QB_NEW_EN)


[grammar] ~5-~5: There might be a mistake here.
Context: ... to run it: ## 🚀 Quick Start (2 steps) ### 1. Start OpenFrontIO ```bash npm start ...

(QB_NEW_EN)


[grammar] ~19-~19: There might be a mistake here.
Context: ...e game The bot is now available! 🎉 ## 🎮 Control the Bot ### Option A: Visual...

(QB_NEW_EN)


[grammar] ~21-~21: There might be a mistake here.
Context: ...w available!** 🎉 ## 🎮 Control the Bot ### Option A: Visual UI (Easiest) 1. **Look...

(QB_NEW_EN)


[grammar] ~25-~25: There might be a mistake here.
Context: ...on** in the top-right corner of the game 2. Click the icon to open the bot control...

(QB_NEW_EN)


[grammar] ~28-~28: There might be a mistake here.
Context: ...ance 4. Watch the bot play! It will: - Automatically select optimal spawn locat...

(QB_NEW_EN)


[grammar] ~29-~29: There might be a mistake here.
Context: ...matically select optimal spawn locations - Manage resources and troops - Make st...

(QB_NEW_EN)


[grammar] ~32-~32: There might be a mistake here.
Context: ...c decisions - Show real-time analysis ### Option B: Browser Console 1. **Open bro...

(QB_NEW_EN)


[grammar] ~56-~56: There might be a mistake here.
Context: ...ess: 80 }); ``` ## 🎯 What the Bot Does ### ✅ Fully Working Features - **Smart Spaw...

(QB_NEW_EN)


[grammar] ~65-~65: There might be a mistake here.
Context: ...xplanations ### 🔄 Basic Implementation - Threat Detection: Identifies incoming ...

(QB_NEW_EN)


[grammar] ~71-~71: There might be a mistake here.
Context: ...uture Enhancements (Not Yet Implemented) - Full attack execution - Unit constructio...

(QB_NEW_EN)


[grammar] ~73-~73: There might be a mistake here.
Context: ...et Implemented) - Full attack execution - Unit construction - Naval operations - N...

(QB_NEW_EN)


[grammar] ~74-~74: There might be a mistake here.
Context: ...ull attack execution - Unit construction - Naval operations - Nuclear weapons - Adv...

(QB_NEW_EN)


[grammar] ~75-~75: There might be a mistake here.
Context: ...n - Unit construction - Naval operations - Nuclear weapons - Advanced diplomacy ##...

(QB_NEW_EN)


[grammar] ~76-~76: There might be a mistake here.
Context: ...ion - Naval operations - Nuclear weapons - Advanced diplomacy ## 🔧 Configuration ...

(QB_NEW_EN)


[grammar] ~79-~79: There might be a mistake here.
Context: ... Advanced diplomacy ## 🔧 Configuration Customize bot behavior from the console:...

(QB_NEW_EN)


[grammar] ~92-~92: There might be a mistake here.
Context: ...ggressive }); ``` ## 🐛 Troubleshooting ### Bot Icon Not Showing? - Make sure you'r...

(QB_NEW_EN)


[grammar] ~96-~96: There might be a mistake here.
Context: ... sure you're in a singleplayer game (bot doesn't work in multiplayer) - Check...

(QB_NEW_EN)


[grammar] ~103-~103: There might be a mistake here.
Context: ...force a decision - Check if the game is in spawn phase (bot waits for spawn to com...

(QB_NEW_EN)


[grammar] ~103-~103: There might be a mistake here.
Context: ...n - Check if the game is in spawn phase (bot waits for spawn to complete) ### Wa...

(QB_NEW_EN)


[grammar] ~103-~103: There might be a mistake here.
Context: ...f the game is in spawn phase (bot waits for spawn to complete) ### Want More Debug...

(QB_NEW_EN)


[grammar] ~115-~115: There might be a mistake here.
Context: ...status(); ## 🎭 Example Bot Session 1. Game starts → Bot icon (🤖) appears in top-right 2. Click icon → Control panel opens 3. Click "Start Bot" → Bot begins analysis 4. Spawn phase → Bot automatically selects best spawn location 5. Early game → Bot manages resources and looks for expansion 6. Mid game → Bot makes strategic decisions based on threats/opportunities ``` ## 🏆 Advanced Usage ### Multiple Difficul...

(QB_NEW_EN)


[grammar] ~126-~126: There might be a mistake here.
Context: .../opportunities ``` ## 🏆 Advanced Usage ### Multiple Difficulty Levels - Easy: ...

(QB_NEW_EN)


[grammar] ~130-~130: There might be a mistake here.
Context: ...eaceful, slow, high confidence threshold - Medium: Balanced approach (default) - ...

(QB_NEW_EN)


[grammar] ~131-~131: There might be a mistake here.
Context: ... Medium: Balanced approach (default) - Hard: Aggressive, fast decisions, nucl...

(QB_NEW_EN)


[grammar] ~132-~132: There might be a mistake here.
Context: ... fast decisions, nuclear weapons enabled - Expert: Very aggressive, risky plays, ...

(QB_NEW_EN)


[style] ~146-~146: Using many exclamation marks might seem excessive (in this case: 5 exclamation marks for a text that’s 2491 characters long)
Context: ...ayer game and click the 🤖 icon to begin! 🎮

(EN_EXCESSIVE_EXCLAMATION)


[grammar] ~146-~146: There might be a mistake here.
Context: ... game and click the 🤖 icon to begin! 🎮

(QB_NEW_EN)

🪛 markdownlint-cli2 (0.17.2)
HOW_TO_RUN_THE_BOT.md

117-117: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


139-139: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🪛 GitHub Actions: 🧪 CI
src/client/bot/execution/ActionExecutor.ts

[error] 15-15: TS2305: Module '../../Transport' has no exported member 'SendSetTargetTroopRatioEvent'.


[warning] 107-107: WARNING in ./src/client/bot/execution/ActionExecutor.ts 107:31-59 export 'SendSetTargetTroopRatioEvent' (imported as 'SendSetTargetTroopRatioEvent') was not found in '../../Transport' (possible exports: BuildUnitIntentEvent, CancelAttackIntentEvent, CancelBoatIntentEvent, MoveWarshipIntentEvent, PauseGameEvent, SendAllianceExtensionIntentEvent, SendAllianceReplyIntentEvent, SendAllianceRequestIntentEvent, SendAttackIntentEvent, SendBoatAttackIntentEvent, SendBreakAllianceIntentEvent, SendDeleteUnitIntentEvent, SendDonateGoldIntentEvent, SendDonateTroopsIntentEvent, SendEmbargoIntentEvent, SendEmojiIntentEvent, SendHashEvent, SendKickPlayerIntentEvent, SendQuickChatEvent, SendSpawnIntentEvent, SendTargetPlayerIntentEvent, SendUpgradeStructureIntentEvent, SendWinnerEvent, Transport).

src/client/bot/strategy/BuildStrategy.ts

[error] 242-242: TS2339: Property 'workers' does not exist on type 'PlayerView'.

src/client/bot/analysis/GameStateAnalyzer.ts

[error] 198-198: TS2339: Property 'population' does not exist on type 'PlayerView'.


[error] 199-199: TS2339: Property 'workers' does not exist on type 'PlayerView'.


[error] 201-201: TS2339: Property 'targetTroopRatio' does not exist on type 'PlayerView'.


[error] 361-361: TS2339: Property 'workers' does not exist on type 'PlayerView'.


[error] 388-388: TS2339: Property 'population' does not exist on type 'PlayerView'.


[error] 403-403: TS2339: Property 'population' does not exist on type 'PlayerView'.


[error] 405-405: TS2339: Property 'workers' does not exist on type 'PlayerView'.


[error] 450-450: TS2339: Property 'targetTroopRatio' does not exist on type 'PlayerView'.


[error] 495-495: TS2339: Property 'targetTroopRatio' does not exist on type 'PlayerView'.

🔇 Additional comments (4)
src/client/SinglePlayerModal.ts (1)

224-229: LGTM: slider bound uses the new max

Using max=${this.maxBots} keeps UI and validation consistent.

src/client/bot/analysis/GameStateAnalyzer.ts (2)

175-186: Double-check Relation enum ordering

The threshold checks assume ordinal ordering (e.g., Friendly > Neutral > Hostile). Confirm enum values or switch to explicit sets to avoid misclassification.

Would you like a quick refactor to use explicit sets (e.g., isHostile = relation === Relation.Hostile || relation === Relation.War)?

Also applies to: 337-342


550-553: LGTM: phase detection

Using inSpawnPhase() is the right source of truth.

src/client/bot/execution/ActionExecutor.ts (1)

54-57: Spawn looks good

Event emission and logging are straightforward.

Comment on lines +71 to +79
### 🚧 Future Enhancements (Not Yet Implemented)

- Full attack execution
- Unit construction
- Naval operations
- Nuclear weapons
- Advanced diplomacy

## 🔧 Configuration
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Doc conflict: “Nuclear weapons” listed as future work but also “enabled” on Hard

These two sections contradict each other. Please align them (either move “nuclear weapons” out of Hard or out of “Future Enhancements”).

- **Hard**: Aggressive, fast decisions, nuclear weapons enabled
+ **Hard**: Aggressive, fast decisions

Also applies to: 130-134

🧰 Tools
🪛 LanguageTool

[grammar] ~71-~71: There might be a mistake here.
Context: ...uture Enhancements (Not Yet Implemented) - Full attack execution - Unit constructio...

(QB_NEW_EN)


[grammar] ~73-~73: There might be a mistake here.
Context: ...et Implemented) - Full attack execution - Unit construction - Naval operations - N...

(QB_NEW_EN)


[grammar] ~74-~74: There might be a mistake here.
Context: ...ull attack execution - Unit construction - Naval operations - Nuclear weapons - Adv...

(QB_NEW_EN)


[grammar] ~75-~75: There might be a mistake here.
Context: ...n - Unit construction - Naval operations - Nuclear weapons - Advanced diplomacy ##...

(QB_NEW_EN)


[grammar] ~76-~76: There might be a mistake here.
Context: ...ion - Naval operations - Nuclear weapons - Advanced diplomacy ## 🔧 Configuration ...

(QB_NEW_EN)


[grammar] ~79-~79: There might be a mistake here.
Context: ... Advanced diplomacy ## 🔧 Configuration Customize bot behavior from the console:...

(QB_NEW_EN)

🤖 Prompt for AI Agents
In HOW_TO_RUN_THE_BOT.md around lines 71-79 (and also lines 130-134), the
document lists "Nuclear weapons" both as a future enhancement and as enabled in
the Hard configuration; remove the contradiction by deleting "Nuclear weapons"
from the "Future Enhancements" list and ensure the Hard configuration section
clearly documents that nuclear weapons are enabled (or, if the intended state is
disabled, instead remove/mark it disabled in the Hard config). Update both
locations (71-79 and 130-134) so only one place asserts the feature's status and
add a short note clarifying its current availability.

Comment on lines +196 to +227
public analyzeResources(player: PlayerView): ResourceAnalysis {
const gold = player.gold();
const population = player.population();
const workers = player.workers();
const troops = player.troops();
const targetTroopRatio = player.targetTroopRatio();

// Estimate rates (would need historical data for accuracy)
const goldPerTick = this.estimateGoldPerTick(player);
const maxPopulation = this.estimateMaxPopulation(player);
const populationGrowthRate = this.estimatePopulationGrowthRate(player);

const isResourceConstrained = this.isResourceConstrained(player);
// Calculate troop ratio without circular dependencies
const recommendedTroopRatio = this.calculateRecommendedTroopRatioSafe(
player,
isResourceConstrained,
);

return {
gold,
goldPerTick,
population,
workers,
troops,
targetTroopRatio,
maxPopulation,
populationGrowthRate,
isResourceConstrained,
recommendedTroopRatio,
};
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix TS errors: PlayerView has no population/workers/targetTroopRatio

Replace nonexistent calls with safe estimates so CI passes.

   public analyzeResources(player: PlayerView): ResourceAnalysis {
     const gold = player.gold();
-    const population = player.population();
-    const workers = player.workers();
-    const troops = player.troops();
-    const targetTroopRatio = player.targetTroopRatio();
+    const population = this.estimateCurrentPopulation(player);
+    const troops = player.troops();
+    const workers = Math.max(0, population - troops);
+    const targetTroopRatio = this.estimateCurrentTroopRatio(player, population);

     // Estimate rates (would need historical data for accuracy)
     const goldPerTick = this.estimateGoldPerTick(player);
     const maxPopulation = this.estimateMaxPopulation(player);
-    const populationGrowthRate = this.estimatePopulationGrowthRate(player);
+    const populationGrowthRate = this.estimatePopulationGrowthRate(player);

     const isResourceConstrained = this.isResourceConstrained(player);
     // Calculate troop ratio without circular dependencies
     const recommendedTroopRatio = this.calculateRecommendedTroopRatioSafe(
       player,
       isResourceConstrained,
     );

     return {
       gold,
       goldPerTick,
       population,
       workers,
       troops,
       targetTroopRatio,
       maxPopulation,
       populationGrowthRate,
       isResourceConstrained,
       recommendedTroopRatio,
     };
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public analyzeResources(player: PlayerView): ResourceAnalysis {
const gold = player.gold();
const population = player.population();
const workers = player.workers();
const troops = player.troops();
const targetTroopRatio = player.targetTroopRatio();
// Estimate rates (would need historical data for accuracy)
const goldPerTick = this.estimateGoldPerTick(player);
const maxPopulation = this.estimateMaxPopulation(player);
const populationGrowthRate = this.estimatePopulationGrowthRate(player);
const isResourceConstrained = this.isResourceConstrained(player);
// Calculate troop ratio without circular dependencies
const recommendedTroopRatio = this.calculateRecommendedTroopRatioSafe(
player,
isResourceConstrained,
);
return {
gold,
goldPerTick,
population,
workers,
troops,
targetTroopRatio,
maxPopulation,
populationGrowthRate,
isResourceConstrained,
recommendedTroopRatio,
};
}
public analyzeResources(player: PlayerView): ResourceAnalysis {
const gold = player.gold();
const population = this.estimateCurrentPopulation(player);
const troops = player.troops();
const workers = Math.max(0, population - troops);
const targetTroopRatio = this.estimateCurrentTroopRatio(player, population);
// Estimate rates (would need historical data for accuracy)
const goldPerTick = this.estimateGoldPerTick(player);
const maxPopulation = this.estimateMaxPopulation(player);
const populationGrowthRate = this.estimatePopulationGrowthRate(player);
const isResourceConstrained = this.isResourceConstrained(player);
// Calculate troop ratio without circular dependencies
const recommendedTroopRatio = this.calculateRecommendedTroopRatioSafe(
player,
isResourceConstrained,
);
return {
gold,
goldPerTick,
population,
workers,
troops,
targetTroopRatio,
maxPopulation,
populationGrowthRate,
isResourceConstrained,
recommendedTroopRatio,
};
}
🧰 Tools
🪛 GitHub Actions: 🧪 CI

[error] 198-198: TS2339: Property 'population' does not exist on type 'PlayerView'.


[error] 199-199: TS2339: Property 'workers' does not exist on type 'PlayerView'.


[error] 201-201: TS2339: Property 'targetTroopRatio' does not exist on type 'PlayerView'.

Comment on lines +295 to +302
private getNeighboringPlayers(player: PlayerView): PlayerView[] {
return this.gameView
.players()
.filter(
(p) =>
p.id() !== player.id() && p.isAlive() && p.type() !== PlayerType.Bot,
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Do not exclude bots from neighbor analysis

In singleplayer, all neighbors are bots; this filter empties analysis and weakens decisions.

   private getNeighboringPlayers(player: PlayerView): PlayerView[] {
     return this.gameView
       .players()
       .filter(
         (p) =>
-          p.id() !== player.id() && p.isAlive() && p.type() !== PlayerType.Bot,
+          p.id() !== player.id() && p.isAlive(),
       );
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private getNeighboringPlayers(player: PlayerView): PlayerView[] {
return this.gameView
.players()
.filter(
(p) =>
p.id() !== player.id() && p.isAlive() && p.type() !== PlayerType.Bot,
);
}
private getNeighboringPlayers(player: PlayerView): PlayerView[] {
return this.gameView
.players()
.filter(
(p) =>
p.id() !== player.id() && p.isAlive(),
);
}
🤖 Prompt for AI Agents
In src/client/bot/analysis/GameStateAnalyzer.ts around lines 295 to 302, the
neighbor-selection currently filters out bots which causes an empty neighbor
list in singleplayer; remove the restriction that excludes PlayerType.Bot so
neighbors include all alive players except the subject. Update the filter to
only exclude the same player and require isAlive() (remove the p.type() !==
PlayerType.Bot check) so bot opponents are included in neighbor analysis.

Comment on lines +359 to +374
private estimateGoldPerTick(player: PlayerView): number {
// More accurate gold calculation
const workers = player.workers();
const territories = player.numTilesOwned();
const cities = player.units(UnitType.City).length;
const factories = player.units(UnitType.Factory).length;
const tradePorts = player.units(UnitType.Port).length;

// Base gold per worker (affected by buildings)
const goldPerWorker = 2 + factories * 0.5; // Factories boost worker efficiency
const territoryBonus = territories * 0.3; // Small gold per territory
const cityBonus = cities * 5; // Cities provide significant gold
const tradeBonus = tradePorts * 3; // Ports enable trade

return workers * goldPerWorker + territoryBonus + cityBonus + tradeBonus;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove nonexistent workers() usage in gold-per-tick calc

Compute income from territory/buildings only.

   private estimateGoldPerTick(player: PlayerView): number {
-    // More accurate gold calculation
-    const workers = player.workers();
     const territories = player.numTilesOwned();
     const cities = player.units(UnitType.City).length;
     const factories = player.units(UnitType.Factory).length;
     const tradePorts = player.units(UnitType.Port).length;

-    // Base gold per worker (affected by buildings)
-    const goldPerWorker = 2 + factories * 0.5; // Factories boost worker efficiency
-    const territoryBonus = territories * 0.3; // Small gold per territory
-    const cityBonus = cities * 5; // Cities provide significant gold
-    const tradeBonus = tradePorts * 3; // Ports enable trade
-
-    return workers * goldPerWorker + territoryBonus + cityBonus + tradeBonus;
+    const territoryBonus = territories * 0.5;
+    const cityBonus = cities * 5;
+    const factoryBonus = factories * 2;
+    const tradeBonus = tradePorts * 3;
+    return territoryBonus + cityBonus + factoryBonus + tradeBonus;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private estimateGoldPerTick(player: PlayerView): number {
// More accurate gold calculation
const workers = player.workers();
const territories = player.numTilesOwned();
const cities = player.units(UnitType.City).length;
const factories = player.units(UnitType.Factory).length;
const tradePorts = player.units(UnitType.Port).length;
// Base gold per worker (affected by buildings)
const goldPerWorker = 2 + factories * 0.5; // Factories boost worker efficiency
const territoryBonus = territories * 0.3; // Small gold per territory
const cityBonus = cities * 5; // Cities provide significant gold
const tradeBonus = tradePorts * 3; // Ports enable trade
return workers * goldPerWorker + territoryBonus + cityBonus + tradeBonus;
}
private estimateGoldPerTick(player: PlayerView): number {
const territories = player.numTilesOwned();
const cities = player.units(UnitType.City).length;
const factories = player.units(UnitType.Factory).length;
const tradePorts = player.units(UnitType.Port).length;
const territoryBonus = territories * 0.5;
const cityBonus = cities * 5;
const factoryBonus = factories * 2;
const tradeBonus = tradePorts * 3;
return territoryBonus + cityBonus + factoryBonus + tradeBonus;
}
🧰 Tools
🪛 GitHub Actions: 🧪 CI

[error] 361-361: TS2339: Property 'workers' does not exist on type 'PlayerView'.

🤖 Prompt for AI Agents
In src/client/bot/analysis/GameStateAnalyzer.ts around lines 359 to 374, remove
the nonexistent workers() usage and change the gold-per-tick calculation to only
use territory and building income: delete the workers variable and goldPerWorker
computation and return only territoryBonus + cityBonus + tradeBonus (keeping
factories factored into any building-related bonuses if needed) so the function
no longer references player.workers().

Comment on lines +387 to +399
private estimatePopulationGrowthRate(player: PlayerView): number {
const current = player.population();
const max = this.estimateMaxPopulation(player);
const cities = player.units(UnitType.City).length;

if (current >= max) return 0;

const growthRatio = (max - current) / max;
const cityBonus = cities * 2; // Cities boost growth rate
const baseGrowth = 8;

return Math.min(20, baseGrowth * growthRatio + cityBonus);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Replace population() with an estimate

Avoid calling a missing API.

   private estimatePopulationGrowthRate(player: PlayerView): number {
-    const current = player.population();
+    const current = this.estimateCurrentPopulation(player);
     const max = this.estimateMaxPopulation(player);
     const cities = player.units(UnitType.City).length;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private estimatePopulationGrowthRate(player: PlayerView): number {
const current = player.population();
const max = this.estimateMaxPopulation(player);
const cities = player.units(UnitType.City).length;
if (current >= max) return 0;
const growthRatio = (max - current) / max;
const cityBonus = cities * 2; // Cities boost growth rate
const baseGrowth = 8;
return Math.min(20, baseGrowth * growthRatio + cityBonus);
}
private estimatePopulationGrowthRate(player: PlayerView): number {
const current = this.estimateCurrentPopulation(player);
const max = this.estimateMaxPopulation(player);
const cities = player.units(UnitType.City).length;
if (current >= max) return 0;
const growthRatio = (max - current) / max;
const cityBonus = cities * 2; // Cities boost growth rate
const baseGrowth = 8;
return Math.min(20, baseGrowth * growthRatio + cityBonus);
}
🧰 Tools
🪛 GitHub Actions: 🧪 CI

[error] 388-388: TS2339: Property 'population' does not exist on type 'PlayerView'.

🤖 Prompt for AI Agents
In src/client/bot/analysis/GameStateAnalyzer.ts around lines 387 to 399, the
code calls a non-existent player.population() API; replace that call with a
local estimate method (e.g., this.estimatePopulation(player)) or directly
compute the population from known player data (sum unit population values and
city populations / sizes), ensure estimatePopulation(player) is implemented on
this class (or available on PlayerView), and use that estimated value in the
existing growth-rate calculation so the function no longer depends on a missing
API.

Comment on lines +167 to +176
if (!this.isVisible) {
return html`
<div
class="show-btn"
@click=${this.toggleVisibility}
title="Show Bot Control Panel"
>
🤖 ${this.botStatus.isEnabled ? "✅" : "⏸️"}
</div>
`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Localize the “show” button title and make it accessible.

-        <div
-          class="show-btn"
-          @click=${this.toggleVisibility}
-          title="Show Bot Control Panel"
-        >
+        <div
+          class="show-btn"
+          @click=${this.toggleVisibility}
+          title=${translateText("bot.show_panel")}
+          aria-label=${translateText("bot.show_panel")}
+          role="button"
+          tabindex="0"
+          @keydown=${(e: KeyboardEvent) => (e.key === "Enter" || e.key === " ") && this.toggleVisibility()}
+        >
           🤖 ${this.botStatus.isEnabled ? "✅" : "⏸️"}
         </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!this.isVisible) {
return html`
<div
class="show-btn"
@click=${this.toggleVisibility}
title="Show Bot Control Panel"
>
🤖 ${this.botStatus.isEnabled ? "✅" : "⏸️"}
</div>
`;
if (!this.isVisible) {
return html`
<div
class="show-btn"
@click=${this.toggleVisibility}
title=${translateText("bot.show_panel")}
aria-label=${translateText("bot.show_panel")}
role="button"
tabindex="0"
@keydown=${(e: KeyboardEvent) =>
(e.key === "Enter" || e.key === " ") && this.toggleVisibility()}
>
🤖 ${this.botStatus.isEnabled ? "✅" : "⏸️"}
</div>
`;
🤖 Prompt for AI Agents
In src/client/bot/ui/BotControlPanel.ts around lines 167–176, the "show" button
title is hardcoded and uses a non-semantic div; replace it with a semantic,
accessible control and use the app's i18n/localization function for the
title/label. Specifically, change the div to a <button> (or add role="button"
and tabindex if you must keep an element) and set its title and aria-label to a
localized string (e.g., t('botControl.showPanel')), add an accessible state
attribute like aria-pressed={this.botStatus.isEnabled} (or aria-expanded if
appropriate), keep the @click handler, and ensure the element is
keyboard-focusable so screen readers and keyboard users can operate it.

Comment on lines +190 to +216
<h3 style="margin: 0 0 8px 0; font-size: 14px;">🤖 AI Assistant</h3>
<button
class="toggle-btn ${this.botStatus.isEnabled ? "stop" : ""}"
@click=${this.toggleBot}
>
${this.botStatus.isEnabled ? "Stop Bot" : "Start Bot"}
</button>
<div class="status-line">
<strong>Phase:</strong> ${this.botStatus.currentPhase}
</div>
<div class="status-line">
<strong>Status:</strong> ${this.botStatus.isActive
? "Active"
: "Idle"}
</div>
<div class="status-line">
<strong>Decision:</strong> ${this.botStatus.lastDecision}
</div>
<div class="status-line">
<strong>Confidence:</strong> ${this.botStatus.confidence}%
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Localize static labels.

Replace hard-coded strings.

-        <h3 style="margin: 0 0 8px 0; font-size: 14px;">🤖 AI Assistant</h3>
+        <h3 style="margin: 0 0 8px 0; font-size: 14px;">🤖 ${translateText("bot.panel.title")}</h3>
...
-        <div class="status-line">
-          <strong>Phase:</strong> ${this.botStatus.currentPhase}
+        <div class="status-line">
+          <strong>${translateText("bot.phase")}:</strong> ${this.botStatus.currentPhase}
         </div>
...
-        <div class="status-line">
-          <strong>Status:</strong> ${this.botStatus.isActive ? "Active" : "Idle"}
+        <div class="status-line">
+          <strong>${translateText("bot.status")}:</strong> ${this.botStatus.isActive ? translateText("bot.active") : translateText("bot.idle")}
         </div>
...
-        <div class="status-line">
-          <strong>Decision:</strong> ${this.botStatus.lastDecision}
+        <div class="status-line">
+          <strong>${translateText("bot.decision")}:</strong> ${this.botStatus.lastDecision}
         </div>
...
-        <div class="status-line">
-          <strong>Confidence:</strong> ${this.botStatus.confidence}%
+        <div class="status-line">
+          <strong>${translateText("bot.confidence")}:</strong> ${this.botStatus.confidence}%
         </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<h3 style="margin: 0 0 8px 0; font-size: 14px;">🤖 AI Assistant</h3>
<button
class="toggle-btn ${this.botStatus.isEnabled ? "stop" : ""}"
@click=${this.toggleBot}
>
${this.botStatus.isEnabled ? "Stop Bot" : "Start Bot"}
</button>
<div class="status-line">
<strong>Phase:</strong> ${this.botStatus.currentPhase}
</div>
<div class="status-line">
<strong>Status:</strong> ${this.botStatus.isActive
? "Active"
: "Idle"}
</div>
<div class="status-line">
<strong>Decision:</strong> ${this.botStatus.lastDecision}
</div>
<div class="status-line">
<strong>Confidence:</strong> ${this.botStatus.confidence}%
</div>
<h3 style="margin: 0 0 8px 0; font-size: 14px;">🤖 ${translateText("bot.panel.title")}</h3>
<button
class="toggle-btn ${this.botStatus.isEnabled ? "stop" : ""}"
@click=${this.toggleBot}
>
${this.botStatus.isEnabled ? "Stop Bot" : "Start Bot"}
</button>
<div class="status-line">
<strong>${translateText("bot.phase")}:</strong> ${this.botStatus.currentPhase}
</div>
<div class="status-line">
<strong>${translateText("bot.status")}:</strong>
${this.botStatus.isActive
? translateText("bot.active")
: translateText("bot.idle")}
</div>
<div class="status-line">
<strong>${translateText("bot.decision")}:</strong> ${this.botStatus.lastDecision}
</div>
<div class="status-line">
<strong>${translateText("bot.confidence")}:</strong> ${this.botStatus.confidence}%
</div>

Comment on lines +193 to +198
class="toggle-btn ${this.botStatus.isEnabled ? "stop" : ""}"
@click=${this.toggleBot}
>
${this.botStatus.isEnabled ? "Stop Bot" : "Start Bot"}
</button>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Start/Stop label should reflect running state, not “isEnabled”.

Use isActive (or integration.isRunning()) for consistency with the toggle behavior.

-        <button
-          class="toggle-btn ${this.botStatus.isEnabled ? "stop" : ""}"
+        <button
+          class="toggle-btn ${this.botStatus.isActive ? "stop" : ""}"
           @click=${this.toggleBot}
         >
-          ${this.botStatus.isEnabled ? "Stop Bot" : "Start Bot"}
+          ${this.botStatus.isActive ? translateText("bot.stop") : translateText("bot.start")}
         </button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class="toggle-btn ${this.botStatus.isEnabled ? "stop" : ""}"
@click=${this.toggleBot}
>
${this.botStatus.isEnabled ? "Stop Bot" : "Start Bot"}
</button>
<button
class="toggle-btn ${this.botStatus.isActive ? "stop" : ""}"
@click=${this.toggleBot}
>
${this.botStatus.isActive ? translateText("bot.stop") : translateText("bot.start")}
</button>
🤖 Prompt for AI Agents
In src/client/bot/ui/BotControlPanel.ts around lines 193 to 198, the button’s
class and label currently use this.botStatus.isEnabled but should reflect the
running state; replace uses of this.botStatus.isEnabled with
this.botStatus.isActive (or call integration.isRunning() if that API is
available) for both the class conditional and the displayed text so the toggle
reflects whether the bot is actively running; ensure the click handler still
calls toggleBot but reads/sets the active/running flag rather than the enabled
flag.

Comment on lines +225 to +236
${this.botStatus.recommendations.length > 0
? html`
<div class="recommendations">
<strong>Recommendations:</strong>
<ul style="margin: 4px 0; padding-left: 16px;">
${this.botStatus.recommendations.map(
(rec) => html`<li>${rec}</li>`,
)}
</ul>
</div>
`
: ""}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Localize “Recommendations” header.

-              <div class="recommendations">
-                <strong>Recommendations:</strong>
+              <div class="recommendations">
+                <strong>${translateText("bot.recommendations")}:</strong>
🤖 Prompt for AI Agents
In src/client/bot/ui/BotControlPanel.ts around lines 225-236 the
"Recommendations:" header is hardcoded; replace it with the project's
localization call (use the component's i18n/translation helper — e.g.
this.i18n.t('bot.recommendations') or translate('bot.recommendations')) and
render that value in the template (add the ":" either inside the localized
string or append it after the translated text) so the header is localizable and
falls back to an English default if missing.

Comment on lines +214 to +220
// Initialize bot integration for singleplayer games
if (this.lobby.gameStartInfo?.config.gameType === GameType.Singleplayer) {
console.log("Initializing bot for singleplayer game");
initializeBotIntegration(this.gameView, this.eventBus, {
autoStart: false, // Don't auto-start, let user control via console
});
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Init looks good; add cleanup on stop() to prevent leaks.

When the game ends, the global bot instance persists. Call cleanup to stop timers and detach listeners.

Example change (outside this hunk, in stop()):

public stop(saveFullGame: boolean = false) {
  if (!this.isActive) return;
  this.isActive = false;
  // NEW: clean up bot integration if present
  try { getBotIntegration()?.cleanup(); } catch {}
  this.worker.cleanup();
  this.transport.leaveGame(saveFullGame);
  if (this.connectionCheckInterval) {
    clearInterval(this.connectionCheckInterval);
    this.connectionCheckInterval = null;
  }
}

I can wire this and add a regression test that ensures no timers remain after stop().

🤖 Prompt for AI Agents
In src/client/ClientGameRunner.ts around lines 214-220, the bot integration is
initialized for singleplayer games but never cleaned up on stop; update the
stop() method to detect and cleanup the bot integration instance before tearing
down the worker/transport (call the global getBotIntegration()?.cleanup() or
equivalent inside a try/catch), then proceed with existing cleanup
(worker.cleanup(), transport.leaveGame, clearInterval for
connectionCheckInterval) to ensure timers and listeners are removed and prevent
leaks.

@TheGiraffe3 TheGiraffe3 closed this Sep 7, 2025
@MantasPtr MantasPtr deleted the bot branch September 9, 2025 20:09
@MantasPtr MantasPtr changed the title Bot _ Sep 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants