Tu primer Bot
Empieza con un Bot mínimo de 10 líneas, añade campesinos, comida, tropas, un héroe y un ataque, y aprende a leer los recibos.
Un Bot es una clase que hereda de openwar3.Bot. Solo tienes que sobrescribir los hooks que necesites; g (Game) se encarga de «observar» y «actuar».
El Bot mínimo
from openwar3 import Bot
class MyBot(Bot):
def on_start(self, g): # se llama una vez al entrar en la partida
g.message("¡Ya estoy aquí!")
def on_tick(self, g): # unas 5 veces 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
Los campesinos ociosos irán a la mina de oro más cercana. Estos son los cuatro hooks:
| Hook | Cuándo se llama |
|---|---|
on_start(g) | Una vez, al entrar en la partida y antes del primer tick |
on_tick(g) | En cada tick (por defecto, 5 veces por segundo). Si un tick se pasa de tiempo, el siguiente se aplaza automáticamente; no se acumulan |
on_event(g, ev) | Antes de cada on_tick; te entrega uno a uno los eventos ocurridos desde el tick anterior |
on_end(g, reason) | Una vez, cuando termina la partida (el proceso del juego desaparece / no nos quedan unidades / parada manual) |
Una excepción dentro de on_tick no interrumpe la partida: el ejecutor imprime el stack trace y sigue en el siguiente tick; solo se detiene tras 20 ticks seguidos con error.
Añade la economía: campesinos y comida
from openwar3 import Bot
class Economy(Bot):
def on_tick(self, g):
res = g.resources() # si no se puede leer es None, no 0
halls = g.my_buildings({"htow", "hkee", "hcas"})
if res is None or not halls:
return
home = halls[0]
# 1. Los campesinos ociosos van a por oro
for w in g.idle_workers():
mine = g.nearest(g.gold_mines(), w)
if mine:
g.gather(w, mine)
# 2. Entrenar campesinos: solo 1 en la cola (llenarla deja el dinero bloqueado en la cola)
if len(g.my_workers()) < 15 and not g.queue(home):
g.train(home, "hpea")
# 3. Comida casi al límite: busca un campesino que no esté construyendo y levanta una granja cerca del ayuntamiento
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)
Tres detalles que vale la pena destacar:
- Entrena solo cuando
g.queue(home)está vacía. Dar la orden de entrenar en cada tick llena la cola de 7 casillas y bloquea el dinero (medido: el ayuntamiento acabó con 4 campesinos en cola y 300 de oro bloqueados, y la apertura se retrasó muchísimo). build_nearen lugar de coordenadas fijas. Busca por sí solo, de cerca a lejos, un sitio donde quepa el edificio y sigue el resultado entre ticks; si falta dinero, no hace nada. Unas coordenadas fijas pueden caer justo en un bosque.- No elijas a un campesino que ya esté construyendo. Una granja humana tarda 35 segundos en construirse; si te llevas al trabajador a mitad de obra, los cimientos se quedan parados.
La versión completa, que funciona con las cuatro razas, es brains/examples/hello_bot.py: 5 por mina, a talar cuando la mina está llena y retomar los cimientos abandonados.
Añade cuartel, héroe y ataque
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]
# Héroe: si hay altar y no hay héroe -> primero intenta revivir; si no se puede, entrena (el héroe es único; entrenar otro tras su muerte se rechaza)
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
# El cuartel saca soldados sin parar (solo 1 en la cola)
for b in g.my_buildings({"hbar"}):
if not g.queue(b):
g.train(b, "hfoo")
# Con una oleada completa, al ataque; si queda destrozada, a casa
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)] # órdenes solo a las ociosas
g.attack_move(idle, target.x, target.y)
La versión completa está en brains/examples/rush_bot.py (hereda de hello_bot y construye el cuartel / altar si no existen).
Aprende a leer los recibos
Cada comando devuelve un recibo. if r: significa «el motor lo aceptó»; si no lo aceptó, r.reason dice por qué:
r = g.train(barracks, "hfoo")
if not r:
print(r.reason) # rejected(人口不够) = "falta comida"
print(r.verdict) # 3
Códigos de motivo frecuentes: 3 falta comida, 8 falta oro, 9 falta madera, 32 cola llena, 183 falta un requisito previo, 221 no existe esa opción / en construcción / ese héroe ya existe, 1001 el objetivo no es visible. La tabla completa está en Recibos y códigos de motivo.
Aceptado ≠ conseguido
El recibo solo indica que «el motor aceptó el comando». El motor también acepta en el acto un punto de construcción dentro de un bosque, y el trabajador solo falla al llegar; un hechizo puede ser interrumpido. Para ver el efecto, mira la instantánea y los eventos: para construir usa build_near (sigue si aparecen los cimientos) y, para los hechizos, comprueba si g.cooldown() entró en enfriamiento.