# 最初の Bot

> 10 行の最小 Bot から始めて、農民の生産、人口の確保、兵の生産、ヒーロー、出撃を加え、最後にレシートの読み方を理解します。

出典: https://war3ai.com/ja/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):          # 試合に入った後に 1 回呼ばれる
        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
```

手の空いた農民が最寄りの金鉱へ向かいます。フックは 4 つです。

| フック | 呼ばれるタイミング |
|---|---|
| `on_start(g)` | 試合に入った後、最初のティックの前に 1 回 |
| `on_tick(g)` | 毎ティック（デフォルトで毎秒 5 回）。1 ティックが時間を超えると自動的に後ろにずれ、どんどん溜まっていくことはありません |
| `on_event(g, ev)` | 毎ティックの `on_tick` の前に、前回のティック以降のイベントを 1 件ずつ渡します |
| `on_end(g, reason)` | 試合が終わったとき（ゲームプロセスがなくなった / 自軍のユニットがいなくなった / 手動で停止した）に 1 回 |

> **ヒント**
>
> `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. 人口がもうすぐ上限：建設中でない農民を見つけ、本拠地の近くに Farm を建てる
        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)
```

注目すべき書き方が 3 つあります。

- **`g.queue(home)` が空のときだけ訓練する。** 毎ティック訓練命令を出すと 7 スロットのキューが埋まり、ゴールドがロックされます（実測：本拠地のキューに農民が 4 人入り、300 ゴールドがロックされて、序盤が大きく遅れました）。
- **座標を決め打ちせずに `build_near` を使う。** 近いところから順に置ける場所を探し、ティックをまたいで結果を追跡します。ゴールドが足りないときは何もしません。決め打ちの座標は、ちょうど木立の中かもしれません。
- **建設中の農民は選ばない。** ヒューマンの Farm は建設に 35 秒かかり、途中でワーカーを別の場所へ送ると基礎の工事が止まります。

4 種族すべてで動く完全版は `brains/examples/hello_bot.py` です：1 鉱山 5 人、鉱山がいっぱいなら伐採、工事が止まった基礎の建設再開まで行います。

## Barracks、ヒーロー、出撃を加える

```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

        # Barracks から Footman を出し続ける（キューには 1 つだけ）
        for b in g.my_buildings({"hbar"}):
            if not g.queue(b):
                g.train(b, "hfoo")

        # 1 波分たまったら出撃し、壊滅したら本拠地に戻る
        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` を継承し、Barracks / 祭壇がなければ建てます）。

## レシートを読む

すべてのコマンドはレシートを返します。`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/ja/docs/reason-codes/) を参照してください。

> **受理 ≠ 成功**
>
> レシートが示すのは「エンジンがこのコマンドを受け付けた」ということだけです。木立の中の建設地点もエンジンはその場で受理し、ワーカーが到着してから失敗します。スキルは中断されることもあります。結果はスナップショットとイベントで確認してください：建物は `build_near` で建て（基礎が現れたかを追跡してくれます）、スキルは `g.cooldown()` がクールダウンに入ったかを見ます。

## 次のステップ

  - [メンタルモデル](https://war3ai.com/ja/docs/concepts/): スナップショット、コマンド、イベント、ティック、バッチ —— なぜこう設計されているのか。
  - [プロの戦術レシピ集](https://war3ai.com/ja/docs/cookbook/): 21 のレシピ：採掘の飽和、人口で詰まらない、集中攻撃、瀕死ユニットを下げる、夜のクリープ狩り……
