# Agent による自律反復

> Coding Agent に自分で試合を回し、結果を読み、コードを直し、また回させます。そのために必要なのは、無人で実行できるコマンド、構造化された対局レポート、そして明確な目標です。

出典: https://war3ai.com/ja/docs/agent-loop/

[LLM で Bot を書く](https://war3ai.com/ja/docs/ai-bot/) では、「1 試合動かす → 現象を見る → モデルに伝える」というステップをあなたが担当していました。コマンドを実行できる Coding Agent（Claude Code、Codex、Cursor の Agent モードなど）なら、このステップも引き受けてループを閉じることができます。

```text
  コードを直す ──► 1 試合回す（無人）──► 対局レポートを読む ──► 結果に最も効く 1 か所を探す ──┐
    ▲                                                                                         │
    └─────────────────────────────────────────────────────────────────────────────────────────┘
```

このループを実際に収束させるには、Agent に 3 つのものが必要です。

## 1. 無人で実行できるコマンド

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

- `--minutes` で試合が必ず終わるようにします（実時間の分）。Agent が 1 試合に張り付いたままになりません。
- `--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 = []                     # [ゲーム秒, 種別, 4 文字コード]：訓練 / 研究 / 建設 / アップグレードの完了
        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` イベント（かかったゲーム秒数付き） | 何秒で最初のヒーローが出たか、何秒でティアアップしたか、Barracks が兵を出し続けているか。プロの序盤と比較できる |
| 双方の損害 | `unit.died` イベント | 兵を献上し続けていないか、ヒーローが何回死んだか、クリープ狩りで得をしたか |
| 終了理由 | `on_end(g, reason)` | `我方没有单位了` = 負け。`到时间了` = まだ勝敗がついていない |
| 最終的な兵力と資源 | `on_end` 時にスナップショットを 1 回読む | ゴールドを使わずに貯めている = 生産が追いついていない。ワーカーが少なすぎる = 経済が立ち上がっていない |

> **補足**
>
> プログラムによる勝敗判定は [アリーナ](https://war3ai.com/ja/arena/) の基盤実験の 1 つで、まだロードマップ上にあります。現時点では `我方没有单位了` で負けを判定し、「見えている敵の建物が全滅した」ことで勝ちを近似的に判定できます。

## 3. 明確な目標といくつかの制約

以下を Agent に渡し、目標に合わせて書き換えてください。

```text
目標：brains/my_bot.py が Echo Isles で「Easy」難易度のコンピューター（ヒューマン対ランダム種族）に安定して勝てるようにする。

各ラウンド：
1. python tools/play.py --bot brains/my_bot.py --speed 200 --minutes 10 --fair を実行する
2. ターミナル出力と最新の run_*.json を読む：終了理由、生産タイムライン、最も多い拒否理由、双方の損害
3. 結果に最も影響している問題を「1 つ」見つけ、その 1 か所だけを直す。変更理由と根拠にしたデータをコードのコメントに書く
4. ステップ 1 に戻る。3 試合続けて改善がなければ止まり、レポートとあなたの判断を私に伝える

制約：
- docs/api.json にあるメソッドだけを使い、API をでっち上げない
- 同じユニットに毎ティック同じコマンドを出し直さない。命令は手が空いているユニットにだけ出す
- --fair を維持する（視界内に見えている敵だけを使う）
- コードを変更する前に python tools/run_tests.py を実行し、サンプルを壊していないことを確認する
```

## ループを早く収束させるための習慣

- **一度に直すのは 1 か所だけ。** 3 か所同時に直すと、勝ってもどれが効いたのか、負けてもどれが原因なのか分かりません。
- **比較には十分な試合数を。** 同じ局面でもランダム性は大きく、2 試合で分かるのは大きな差だけです。「改善したか」の判断には、少なくとも数試合の傾向を見てください。
- **戦略の調整より先に「拒否」を直す。** レシートで最も多い拒否理由が、たいてい Bot の最大のバグです。
- **判断をコメントに書く。** 次のラウンドの Agent（あるいは次の会話）がコメントからなぜそう書いたかを知ることができ、直した箇所を元に戻さずに済みます。
- **オフラインテストで下支えする。** 重要なロジックには、ゲームを起動せずに動く単体テストを書きます（サンプル Bot のテストは `brains/examples/tests/` にあります）。Agent には変更のたびにまずそれを実行させます。
