Автономные итерации агента
Пусть Coding Agent сам играет партии, читает результаты, правит код и запускает снова. Для этого ему нужны команда, работающая без присмотра, структурированный отчёт о партии и чёткая цель.
В разделе Пишем бота с помощью LLM шаг «сыграть партию → посмотреть, что происходит → рассказать модели» выполняете вы. Coding Agent, умеющий запускать команды (Claude Code, Codex, режим Agent в Cursor и т. п.), может взять на себя и этот шаг, и цикл замкнётся:
правка кода ──► партия (без присмотра) ──► чтение отчёта ──► поиск главной проблемы ──┐
▲ │
└──────────────────────────────────────────────────────────────────────────────────┘
Чтобы такой цикл действительно сходился, агенту нужны три вещи.
1. Команда, работающая без присмотра
python tools/play.py --bot brains/my_bot.py --speed 200 --minutes 10 --fair
--minutesгарантирует, что партия закончится (в минутах реального времени), и агент не застрянет в ней;--speed 200экономит время за счёт скорости 2× — но в боте ждите по игровым часам (g.clock()), а не черезsleepпо реальному времени;--fairс первого дня заставляет писать по правилам Арены: видно только то, что в зоне обзора;- по окончании запуска терминал печатает причину завершения, например
我方没有单位了(«у нас не осталось юнитов») или到时间了(«время вышло»); всё, что бот выводит черезprint, тоже попадает в терминал.
Когда окно свёрнуто, симуляция игры стоит. Пусть агент запускает игру в оконном режиме по умолчанию и не занимает номер экземпляра, которым пользуетесь вы (--inst).
2. Структурированный отчёт о партии
Вывод терминала рассчитан на человека. Агенту нужен JSON: что произошло, что не получилось и почему. SDK уже даёт всё сырьё — у квитанций есть коды причин, в потоке событий есть завершение производства и потери. Остаётся только это собрать:
import collections, json, time
from openwar3 import Bot
class Recorder(Bot):
"""Добавляет Bot отчёт о партии. Унаследуйтесь от него и вызывайте super() в своих on_start / on_event."""
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. Чёткая цель и несколько ограничений
Передайте агенту следующий текст, подправив цель под себя:
Цель: добиться, чтобы 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 и убедись, что примеры не сломаны
Привычки, которые ускоряют сходимость цикла
- Меняйте что-то одно за раз. Если поменять три вещи сразу, то при победе неизвестно, что сработало, а при поражении — что сломалось.
- Сравнивайте на достаточном числе партий. В одной и той же ситуации случайность велика; по двум партиям видна только очень большая разница. Чтобы судить о прогрессе, смотрите на тренд хотя бы по нескольким партиям.
- Сначала чините отказы, потом настраивайте стратегию. Самая частая причина отказа в квитанциях — зачастую и есть главный баг бота.
- Записывайте выводы в комментарии. Агент в следующем раунде (или в следующем диалоге) поймёт из комментариев, почему код написан именно так, и не откатит уже исправленное.
- Страхуйтесь офлайн-тестами. Напишите для ключевой логики модульные тесты, которым не нужна запущенная игра (тесты примеров ботов лежат в
brains/examples/tests/), и пусть агент прогоняет их после каждой правки.