# Agent 자율 반복

> Coding Agent가 스스로 게임을 돌리고, 결과를 읽고, 코드를 고치고, 다시 돌리게 합니다. 이를 위해 무인 실행 명령 하나, 구조화된 게임 리포트 하나, 명확한 목표 하나가 필요합니다.

출처: https://war3ai.com/ko/docs/agent-loop/

[LLM으로 Bot 만들기](https://war3ai.com/ko/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 = []                     # [게임 초, 종류, 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` 이벤트(걸린 게임 초 포함) | 첫 영웅이 몇 초에 나왔는지, 몇 초에 테크 업을 했는지, 병영이 쉬지 않고 유닛을 뽑았는지. 프로 선수의 초반 빌드와 비교할 수 있습니다 |
| 양측 사상자 | `unit.died` 이벤트 | 병력을 계속 헛되이 잃고 있는지, 영웅이 몇 번 죽었는지, 사냥이 이득이었는지 |
| 종료 원인 | `on_end(g, reason)` | `我方没有单位了`(아군 유닛 전멸) = 패배, `到时间了`(시간 종료) = 아직 승부가 나지 않음 |
| 최종 병력과 자원 | `on_end` 시점에 스냅샷을 한 번 읽음 | 돈이 쌓여 있음 = 생산이 못 따라감, 일꾼이 너무 적음 = 경제가 성장하지 못함 |

> **참고**
>
> 프로그램으로 승패를 판정하는 것은 [아레나](https://war3ai.com/ko/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의 가장 큰 버그입니다.
- **판단을 주석에 남기세요.** 다음 라운드의 Agent(또는 다음 대화)가 주석을 보고 왜 이렇게 작성했는지 알 수 있어서, 고쳐 둔 곳을 되돌리지 않습니다.
- **오프라인 테스트로 안전망을 치세요.** 핵심 로직에는 게임을 띄우지 않아도 되는 단위 테스트를 작성하고(예제 Bot의 테스트는 `brains/examples/tests/`에 있습니다), Agent가 고칠 때마다 먼저 실행하게 하세요.
