# Agent 自主迭代

> 讓 Coding Agent 自己跑對局、讀結果、改程式碼、再跑：它需要一條能無人值守執行的命令、一份結構化的對局報告，以及一個明確的目標。

來源: https://war3ai.com/zh-tw/docs/agent-loop/

在 [用 LLM 寫一個 Bot](https://war3ai.com/zh-tw/docs/ai-bot/) 裡，「跑一局 → 看現象 → 告訴模型」這一步由你來做。能執行命令的 Coding Agent（Claude Code、Codex、Cursor 的 Agent 模式等）可以把這一步也接手，形成閉環：

```text
  改程式碼 ──► 跑一局（無人值守）──► 讀對局報告 ──► 找出最影響結果的一處 ──┐
    ▲                                                                     │
    └─────────────────────────────────────────────────────────────────────┘
```

要讓這個閉環真正收斂，Agent 需要三樣東西。

## 1. 一條無人值守的命令

```bash
python tools/play.py --bot brains/my_bot.py --speed 200 --minutes 10 --fair
```

- `--minutes` 讓這一局一定會結束（以實際時間的分鐘計），Agent 不會卡在一局裡；
- `--speed 200` 用 2 倍速節省時間 —— 但 Bot 裡要**依遊戲時鐘等待**（`g.clock()`），別依實際時間 `sleep`；
- `--fair` 讓它從第一天起就照擂台規則寫，只看得見視野內的東西；
- 執行結束時終端機會印出結束原因，例如 `我方没有单位了`、`到时间了`；Bot 自己 `print` 的內容也在終端機裡。

> **注意**
>
> 視窗最小化時，遊戲模擬是停住的。讓 Agent 用預設的視窗模式啟動遊戲，並且別和你正在使用的實例撞號（`--inst`）。

## 2. 一份結構化的對局報告

終端機輸出是給人看的。給 Agent 看的應該是一份 JSON：發生了什麼、什麼沒做成、為什麼。SDK 已經把原料都給你了 —— 回執帶原因碼，事件流帶生產完成和傷亡。把它們收集起來即可：

```python title="recorder.py"
import collections, json, time
from openwar3 import Bot

class Recorder(Bot):
    """幫 Bot 加上一份對局報告。繼承它，再在自己的 on_start / on_event 裡呼叫 super()。"""

    def on_start(self, g):
        self.rejects = collections.Counter()   # "train hfoo: rejected（人口不够）" -> 次數（= 人口不夠）
        self.timeline = []                     # [遊戲秒, 類別, 四字碼]：訓練 / 研究 / 建造 / 升級完成
        self.lost = collections.Counter()      # 我方死了什麼
        self.killed = collections.Counter()    # 我方打死了什麼

    def check(self, r, what):
        """包住命令，記錄被拒原因：self.check(g.train(b, "hfoo"), "train hfoo")"""
        if r is not None and not r:
            self.rejects[f"{what}: {r.reason}"] += 1
        return r

    def on_event(self, g, ev):
        me = g.me()
        if ev.kind == "production.done" and ev.owner == me:
            self.timeline.append([round(ev.clock), ev.done_kind, ev.done_code])
        elif ev.kind == "unit.died":
            (self.lost if ev.owner == me else self.killed)[ev.type] += 1

    def on_end(self, g, reason):
        report = {"reason": reason, "timeline": self.timeline, "lost": self.lost,
                  "killed": self.killed, "rejects": self.rejects.most_common(10)}
        try:                                   # 遊戲可能已經結束，讀不到就算了
            report |= {"clock": g.clock(), "resources": g.resources(),
                       "army": len(g.my_army()), "workers": len(g.my_workers())}
        except Exception:
            pass
        with open(f"run_{int(time.time())}.json", "w", encoding="utf-8") as f:
            json.dump(report, f, ensure_ascii=False, indent=1)
```

這份報告能回答的問題：

| 訊號 | 從哪裡來 | 能看出什麼 |
|---|---|---|
| 被拒最多的原因 | 回執 `reason` / `verdict` | 人口一直卡住（3）、錢不夠卻一直下令（8 / 9）、攻擊迷霧裡的目標（1001）、英雄陣亡了還在訓練（221） |
| 生產時間軸 | `production.done` 事件（帶有花了多少遊戲秒） | 第幾秒出第一個英雄、第幾秒升級主城、兵營有沒有一直在出兵；可以和職業選手的開局比較 |
| 雙方傷亡 | `unit.died` 事件 | 是不是一直在送兵、英雄陣亡幾次、打野有沒有賺 |
| 結束原因 | `on_end(g, reason)` | `我方没有单位了`（我方沒有單位了）= 輸了；`到时间了`（時間到了）= 還沒分出勝負 |
| 最終兵力和資源 | `on_end` 時讀一次快照 | 錢存著沒花 = 生產跟不上；工人太少 = 經濟沒起來 |

> **說明**
>
> 以程式判定勝負是 [對戰平台](https://war3ai.com/zh-tw/arena/) 的地基實驗之一，還在路線圖上。目前可以用 `我方没有单位了`（我方沒有單位了）判定輸，用「看得見的敵方建築全滅」近似判定贏。

## 3. 一個明確的目標和幾條約束

把下面這段交給 Agent，依你的目標修改：

```text
目標：讓 brains/my_bot.py 在 Echo Isles 上穩定打贏「簡單」難度的電腦（人類對隨機種族）。

每一輪：
1. 執行 python tools/play.py --bot brains/my_bot.py --speed 200 --minutes 10 --fair
2. 讀終端機輸出和最新的 run_*.json：結束原因、生產時間軸、被拒最多的原因、雙方傷亡
3. 找出最影響結果的「一個」問題，只改這一處；在程式碼註解裡寫下修改理由和依據的資料
4. 回到第 1 步。連續 3 局沒有進步就停下來，把報告和你的判斷告訴我

約束：
- 只能用 docs/api.json 裡的方法，不要編造 API
- 不要每拍對同一個單位重複下同一條命令；只對閒著的單位下令
- 保持 --fair（只用視野內看得見的敵人）
- 改程式碼前先執行 python tools/run_tests.py，確認沒有把範例改壞
```

## 讓閉環更快收斂的幾個習慣

- **一次只改一處。** 同時改三處，贏了不知道是哪一處起作用，輸了也不知道是哪一處改壞的。
- **比較時要打夠局數。** 同一個局面的隨機性很大；兩局只看得出很大的差距。判斷「是否進步」至少要看幾局的趨勢。
- **先修「被拒」再調策略。** 回執裡被拒最多的原因，往往就是 Bot 最大的 bug。
- **把判斷寫進註解。** 下一輪的 Agent（或下一次對話）能從註解裡知道為什麼這樣寫，不會把修好的地方改回去。
- **用離線測試兜底。** 為關鍵邏輯寫不需要開遊戲的單元測試（範例 Bot 的測試在 `brains/examples/tests/`），Agent 每次改完先跑一遍。
