Первый бот
Начинаем с минимального бота на 10 строк, добавляем найм крестьян, постройку ферм, армию, героя и атаку, а в конце разбираемся с квитанциями.
Бот — это класс, унаследованный от openwar3.Bot. Вам нужно переопределить только нужные хуки, а g (Game) отвечает за то, чтобы «видеть» и «делать».
Минимальный бот
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))
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 тиков подряд с ошибкой.
Добавляем экономику: крестьяне и фермы
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 рабочих на рудник, при заполненном руднике — на лес, достройка замороженных фундаментов.
Добавляем казарму, героя и атаку
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 указано почему:
r = g.train(barracks, "hfoo")
if not r:
print(r.reason) # rejected(人口不够) (= «не хватает пищи»)
print(r.verdict) # 3
Частые коды причин: 3 не хватает пищи, 8 не хватает золота, 9 не хватает древесины, 32 очередь заполнена, 183 нет нужного здания или технологии, 221 такого нет / уже строится / герой уже есть, 1001 цель не видна. Полная таблица — в разделе Квитанции и коды причин.
«Принята» ≠ «выполнена»
Квитанция сообщает лишь, что «движок принял команду». Точку постройки в лесу движок тоже примет сразу, а неудача случится, только когда рабочий дойдёт до места; заклинание могут прервать. Результат смотрите по снимку и событиям: для построек используйте build_near (он следит, появился ли фундамент), для заклинаний проверяйте, ушло ли оно на перезарядку, через g.cooldown().