Seu primeiro Bot
Comece com um Bot mínimo de 10 linhas, depois adicione treino de camponeses, construções para comida, exército, heróis e ataques — e, por fim, aprenda a ler os recibos.
Um Bot é uma classe que herda de openwar3.Bot. Você só sobrescreve os hooks de que precisa; g (Game) cuida de “observar” e de “agir”.
O Bot mínimo
from openwar3 import Bot
class MyBot(Bot):
def on_start(self, g): # chamado uma vez depois que a partida começa
g.message("Cheguei")
def on_tick(self, g): # cerca de 5 vezes por segundo
for w in g.idle_workers():
g.gather(w, g.nearest(g.gold_mines(), w))
python tools/play.py --bot my_bot.py
Os camponeses ociosos vão para a mina de ouro mais próxima. Os quatro hooks:
| Hook | Quando é chamado |
|---|---|
on_start(g) | Uma vez depois que a partida começa, antes do primeiro tick |
on_tick(g) | A cada tick (5 vezes por segundo por padrão). Se um tick passar do tempo, o próximo é adiado automaticamente — eles nunca se acumulam |
on_event(g, ev) | Antes de cada on_tick; entrega a você, um por um, todos os eventos desde o último tick |
on_end(g, reason) | Uma vez quando a partida termina (o processo do jogo sumiu / não temos mais unidades / parada manual) |
Uma exceção no on_tick não encerra a partida: o executor imprime o stack trace e segue para o próximo tick; ele só para depois de 20 ticks seguidos com erro.
Adicionando economia: treinar camponeses, construir para ter comida
from openwar3 import Bot
class Economy(Bot):
def on_tick(self, g):
res = g.resources() # None se não der para ler, não 0
halls = g.my_buildings({"htow", "hkee", "hcas"})
if res is None or not halls:
return
home = halls[0]
# 1. Camponeses ociosos vão minerar ouro
for w in g.idle_workers():
mine = g.nearest(g.gold_mines(), w)
if mine:
g.gather(w, mine)
# 2. Treinar camponeses: só 1 na fila por vez (fila cheia prende o ouro nela)
if len(g.my_workers()) < 15 and not g.queue(home):
g.train(home, "hpea")
# 3. Comida quase no limite: ache um camponês que não esteja construindo e faça uma Fazenda perto da sede
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)
Três detalhes que valem atenção:
- Só treine quando
g.queue(home)estiver vazia. Dar ordem de treino a cada tick enche a fila de 7 espaços e prende seu ouro (medido: a sede enfileirou 4 camponeses, com 300 de ouro presos na fila, e a abertura ficou muito mais lenta). build_nearem vez de coordenadas fixas. Ele procura, do mais perto para o mais longe, um lugar que caiba e acompanha o resultado ao longo dos ticks; não faz nada quando falta dinheiro. Coordenadas fixas podem muito bem cair no meio de uma floresta.- Não escolha camponeses que já estão construindo. Uma Fazenda humana leva 35 segundos para ficar pronta; se você tirar o trabalhador no meio, a obra para.
A versão completa, que funciona para as quatro raças, é brains/examples/hello_bot.py: 5 por mina, os trabalhadores extras cortam árvores quando a mina está cheia, e ele retoma obras paradas.
Adicionando Quartel, herói e ataques
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]
# Herói: tem altar mas não tem herói -> reviva primeiro, só treine se não der para reviver (heróis são únicos; treinar de novo depois da morte é rejeitado)
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") # Luz Sagrada
# O Quartel produz Soldados sem parar (só 1 na fila por vez)
for b in g.my_buildings({"hbar"}):
if not g.queue(b):
g.train(b, "hfoo")
# Ataca quando uma leva estiver pronta; volta para casa depois de perdas pesadas
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)] # só dá ordens às unidades ociosas
g.attack_move(idle, target.x, target.y)
Para a versão completa, veja brains/examples/rush_bot.py (ele herda de hello_bot e constrói o Quartel / altar se não existirem).
Lendo os recibos
Todo comando retorna um recibo. if r: significa “o engine aceitou”; quando não foi aceito, r.reason diz o motivo:
r = g.train(barracks, "hfoo")
if not r:
print(r.reason) # rejected(人口不够) (= falta comida)
print(r.verdict) # 3
Códigos de motivo comuns: 3 falta comida, 8 falta ouro, 9 falta madeira, 32 fila cheia, 183 falta pré-requisito, 221 item inexistente / em construção / você já tem este herói, 1001 alvo não visível. Para a tabela completa, veja Recibos e códigos de motivo.
Aceito ≠ feito
O recibo só diz que “o engine aceitou este comando”. O engine também aceita na hora um ponto de construção dentro de uma floresta, e o trabalhador só falha quando chega lá; magias podem ser interrompidas. Para ver o efeito real, olhe os snapshots e os eventos: para construções, use build_near (ele acompanha se a fundação aparece), e para magias, veja se g.cooldown() mostra a recarga.