文档 让 AI 写 Bot
Agent 自主迭代
让 Coding Agent 自己跑局、读结果、改代码、再跑:它需要一条能无人值守的命令、一份结构化的对局报告和一个明确的目标。
用大模型写一个 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 里的方法,不要编造接口
- 不要每拍给同一个单位重复下同一条命令;只给闲着的单位下令
- 保持 --fair(只用视野里看得见的敌人)
- 改代码前先跑 python tools/run_tests.py,确认没有把示例改坏
让闭环更快收敛的几个习惯
- 一次只改一处。 同时改三处,赢了不知道是哪处起作用,输了也不知道是哪处搞坏的。
- 对比要打够局数。 同一局面随机性很大;两局只能看出很大的差距。判断「是否进步」至少看几局的趋势。
- 先修「被拒」再调策略。 回执里被拒最多的原因,往往就是 Bot 最大的 bug。
- 把判断写进注释。 下一轮的 Agent(或者下一次对话)能从注释里知道为什么这么写,不会把修好的地方改回去。
- 离线测试兜底。 给关键逻辑写不需要开游戏的单元测试(示例 Bot 的测试在
brains/examples/tests/),Agent 每次改完先跑一遍。