文件 讓 AI 寫 Bot
Agent 自主迭代
讓 Coding Agent 自己跑對局、讀結果、改程式碼、再跑:它需要一條能無人值守執行的命令、一份結構化的對局報告,以及一個明確的目標。
在 用 LLM 寫一個 Bot 裡,「跑一局 → 看現象 → 告訴模型」這一步由你來做。能執行命令的 Coding Agent(Claude Code、Codex、Cursor 的 Agent 模式等)可以把這一步也接手,形成閉環:
改程式碼 ──► 跑一局(無人值守)──► 讀對局報告 ──► 找出最影響結果的一處 ──┐
▲ │
└─────────────────────────────────────────────────────────────────────┘
要讓這個閉環真正收斂,Agent 需要三樣東西。
1. 一條無人值守的命令
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 已經把原料都給你了 —— 回執帶原因碼,事件流帶生產完成和傷亡。把它們收集起來即可:
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 時讀一次快照 | 錢存著沒花 = 生產跟不上;工人太少 = 經濟沒起來 |
以程式判定勝負是 對戰平台 的地基實驗之一,還在路線圖上。目前可以用 我方没有单位了(我方沒有單位了)判定輸,用「看得見的敵方建築全滅」近似判定贏。
3. 一個明確的目標和幾條約束
把下面這段交給 Agent,依你的目標修改:
目標:讓 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 每次改完先跑一遍。