# Первый бот

> Начинаем с минимального бота на 10 строк, добавляем найм крестьян, постройку ферм, армию, героя и атаку, а в конце разбираемся с квитанциями.

Источник: https://war3ai.com/ru/docs/first-bot/

Бот — это класс, унаследованный от `openwar3.Bot`. Вам нужно переопределить только нужные хуки, а `g` (`Game`) отвечает за то, чтобы «видеть» и «делать».

## Минимальный бот

```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/ru/docs/reason-codes/).

> **«Принята» ≠ «выполнена»**
>
> Квитанция сообщает лишь, что «движок принял команду». Точку постройки в лесу движок тоже примет сразу, а неудача случится, только когда рабочий дойдёт до места; заклинание могут прервать. Результат смотрите по снимку и событиям: для построек используйте `build_near` (он следит, появился ли фундамент), для заклинаний проверяйте, ушло ли оно на перезарядку, через `g.cooldown()`.

## Что дальше

  - [Ментальная модель](https://war3ai.com/ru/docs/concepts/): Снимок, команды, события, тик, пачка — почему всё устроено именно так.
  - [Книга рецептов: приёмы профи](https://war3ai.com/ru/docs/cookbook/): 21 приём: полная загрузка добычи, без упора в лимит пищи, фокус огня, отвод раненых, крипинг ночью…
