# 第一个 Bot

> 从 10 行的最小 Bot 开始，加上造农民、盖人口、出兵、英雄和出击，最后读懂回执。

来源: https://war3ai.com/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()                       # 读不到是 None，不是 0
        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")                # 圣光术

        # 兵营一直出步兵（队列只排 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/docs/reason-codes/)。

> **接下 ≠ 做成**
>
> 回执只说明「引擎接了这条命令」。树林里的建造点引擎也会当场接下，工人走到才失败；技能可能被打断。效果要看快照和事件：盖房子用 `build_near`（它会跟踪地基是否出现），放技能看 `g.cooldown()` 有没有进冷却。

## 下一步

  - [心智模型](https://war3ai.com/docs/concepts/): 快照、命令、事件、一拍、批量 —— 为什么这样设计。
  - [职业打法食谱](https://war3ai.com/docs/cookbook/): 21 招：饱和采集、人口不卡、集火、拉残血、夜里打野……
