# 첫 번째 Bot

> 10줄짜리 최소 Bot에서 시작해 농부 생산, 인구 확보, 병력 생산, 영웅, 공격을 추가하고 마지막으로 회신을 읽는 법을 익힙니다.

출처: https://war3ai.com/ko/docs/first-bot/

Bot은 `openwar3.Bot`을 상속한 클래스입니다. 필요한 훅만 오버라이드하면 되고, `g`(`Game`)가 "보기"와 "하기"를 맡습니다.

## 최소 Bot

```python title="my_bot.py"
from openwar3 import Bot

class MyBot(Bot):
    def on_start(self, g):          # 게임에 들어간 뒤 한 번 호출
        g.message("내가 왔다")

    def on_tick(self, g):           # 초당 약 5번
        for w in g.idle_workers():
            g.gather(w, g.nearest(g.gold_mines(), w))
```

```bash
python tools/play.py --bot my_bot.py
```

놀고 있는 농부가 가장 가까운 금광으로 갑니다. 훅은 네 가지입니다:

| 훅 | 호출 시점 |
|---|---|
| `on_start(g)` | 게임에 들어간 뒤, 첫 틱 전에 한 번 |
| `on_tick(g)` | 매 틱(기본 초당 5번). 한 틱이 시간을 넘기면 자동으로 뒤로 밀리며, 밀린 틱이 쌓이지는 않습니다 |
| `on_event(g, ev)` | 매 틱의 `on_tick` 직전에, 이전 틱 이후의 이벤트를 하나씩 전달 |
| `on_end(g, reason)` | 이번 게임이 끝날 때(게임 프로세스 종료 / 아군 유닛 전멸 / 수동 중지) 한 번 |

> **팁**
>
> `on_tick`에서 예외가 발생해도 게임 전체가 중단되지는 않습니다. 러너가 스택 트레이스를 출력하고 다음 틱을 계속 진행하며, **20틱 연속으로 오류가 나야** 멈춥니다.

## 경제 추가: 농부 생산, 인구 확보

```python
from openwar3 import Bot

class Economy(Bot):
    def on_tick(self, g):
        res = g.resources()                       # 읽지 못하면 0이 아니라 None
        halls = g.my_buildings({"htow", "hkee", "hcas"})
        if res is None or not halls:
            return
        home = halls[0]

        # 1. 놀고 있는 농부는 금 채집
        for w in g.idle_workers():
            mine = g.nearest(g.gold_mines(), w)
            if mine:
                g.gather(w, mine)

        # 2. 농부 생산: 대기열에는 1개만(가득 채우면 돈이 대기열에 묶임)
        if len(g.my_workers()) < 15 and not g.queue(home):
            g.train(home, "hpea")

        # 3. 인구가 거의 찼으면: 건물을 짓고 있지 않은 농부를 골라 본진 건물 근처에 농장 건설
        if res["food_cap"] - res["food_used"] <= 6:
            builder = next((w for w in g.my_workers() if not g.is_constructing(w)), None)
            if builder:
                g.build_near(builder, "hhou", home.x, home.y)
```

눈여겨볼 작성법 세 가지:

- **`g.queue(home)`이 비어 있을 때만 훈련합니다.** 매 틱 훈련 명령을 내리면 7칸 대기열이 가득 차고 돈이 묶입니다(실측: 본진 건물에 농부 4명이 대기하면서 금 300이 대기열에 묶여 초반이 크게 늦어졌습니다).
- **좌표를 하드코딩하지 말고 `build_near`를 씁니다.** 가까운 곳부터 지을 수 있는 지점을 스스로 찾고, 여러 틱에 걸쳐 결과를 추적하며, 돈이 부족하면 아무것도 하지 않습니다. 하드코딩한 좌표는 숲 한가운데일 가능성이 큽니다.
- **건물을 짓고 있는 농부는 고르지 않습니다.** 휴먼 농장은 짓는 데 35초가 걸리는데, 도중에 일꾼을 빼면 기초 공사가 멈춥니다.

네 종족 모두에서 동작하는 완전한 버전은 `brains/examples/hello_bot.py`입니다. 금광당 5명, 금광이 차면 벌목, 멈춘 기초 공사 이어서 짓기까지 들어 있습니다.

## 병영, 영웅, 공격 추가

```python
from openwar3 import Bot

WAVE = 8

class Rush(Bot):
    def on_start(self, g):
        self.attacking = False

    def on_tick(self, g):
        halls = g.my_buildings({"htow", "hkee", "hcas"})
        if not halls:
            return
        home = halls[0]

        # 영웅: 제단은 있는데 영웅이 없으면 -> 먼저 부활, 부활이 안 되면 훈련(영웅은 유일해서, 죽은 뒤 다시 훈련하면 거부됨)
        altars = g.my_buildings({"halt"})
        if altars and not g.my_heroes():
            if not g.revive(altars[0]):
                g.train(altars[0], "Hpal")
        for h in g.my_heroes():
            info = g.hero_info(h)
            if info and info["skill_points"]:
                g.learn(h, "AHhb")                # Holy Light

        # 병영은 계속 보병 생산(대기열에는 1개만)
        for b in g.my_buildings({"hbar"}):
            if not g.queue(b):
                g.train(b, "hfoo")

        # 한 웨이브가 모이면 공격, 많이 잃으면 귀환
        army = g.my_army()
        if len(army) >= WAVE:
            self.attacking = True
        elif len(army) < WAVE // 2:
            self.attacking = False
        if self.attacking:
            target = g.nearest([e for e in g.enemies() if g.is_building(e)], home)
            if target:
                idle = [u for u in army if not g.order_of(u)]      # 놀고 있는 유닛에게만 명령
                g.attack_move(idle, target.x, target.y)
```

완전한 버전은 `brains/examples/rush_bot.py`를 참고하세요(`hello_bot`을 상속하며, 병영 / 제단이 없으면 짓습니다).

## 회신 읽기

모든 명령은 회신 하나를 반환합니다. `if r:`은 "엔진이 수락했다"는 뜻이고, 수락되지 않았으면 `r.reason`에 이유가 적혀 있습니다:

```python
r = g.train(barracks, "hfoo")
if not r:
    print(r.reason)        # rejected（人口不够） = 인구 부족
    print(r.verdict)       # 3
```

자주 보는 원인 코드: `3` 인구 부족, `8` 금 부족, `9` 목재 부족, `32` 대기열 가득 참, `183` 선행 조건 부족, `221` 해당 항목 없음 / 건설 중 / 이미 있는 영웅, `1001` 대상이 보이지 않음. 전체 표는 [회신과 원인 코드](https://war3ai.com/ko/docs/reason-codes/)를 참고하세요.

> **수락 ≠ 성공**
>
> 회신은 "엔진이 이 명령을 받았다"는 것만 알려 줍니다. 숲 속 건설 지점도 엔진은 즉시 수락하고, 일꾼이 도착해서야 실패합니다. 스킬은 끊길 수도 있습니다. 결과는 스냅샷과 이벤트로 확인하세요. 건물은 `build_near`로 짓고(기초가 생겼는지 추적합니다), 스킬은 `g.cooldown()`이 쿨다운에 들어갔는지 보세요.

## 다음 단계

  - [멘탈 모델](https://war3ai.com/ko/docs/concepts/): 스냅샷, 명령, 이벤트, 틱, 배치 — 왜 이렇게 설계했는가.
  - [프로 운영 레시피](https://war3ai.com/ko/docs/cookbook/): 21가지 기술: 채집 포화, 인구 막힘 방지, 점사, 체력 낮은 유닛 빼기, 밤 사냥……
