Votre premier Bot
Partez d'un Bot minimal de 10 lignes, ajoutez la production de paysans, la nourriture, l'armée, le héros et l'attaque, puis apprenez à lire les reçus.
Un Bot est simplement une classe qui hérite de openwar3.Bot. Vous ne surchargez que les hooks dont vous avez besoin ; g (Game) se charge de « voir » et d’« agir ».
Le Bot minimal
from openwar3 import Bot
class MyBot(Bot):
def on_start(self, g): # appelé une fois au début de la partie
g.message("Me voilà !")
def on_tick(self, g): # environ 5 fois par seconde
for w in g.idle_workers():
g.gather(w, g.nearest(g.gold_mines(), w))
python tools/play.py --bot my_bot.py
Les paysans inactifs partent vers la mine d’or la plus proche. Les quatre hooks :
| Hook | Quand il est appelé |
|---|---|
on_start(g) | Une fois, au début de la partie, avant le premier tick |
on_tick(g) | À chaque tick (5 fois par seconde par défaut). Un tick qui déborde décale automatiquement le suivant, sans accumulation |
on_event(g, ev) | Avant chaque on_tick, vous transmet un par un les événements survenus depuis le tick précédent |
on_end(g, reason) | Une fois, à la fin de la partie (processus du jeu disparu / plus aucune unité de notre côté / arrêt manuel) |
Une exception levée dans on_tick n’interrompt pas la partie : l’exécuteur affiche la pile d’appels et continue au tick suivant ; il ne s’arrête qu’après 20 ticks d’erreurs consécutives.
Ajouter l’économie : paysans et nourriture
from openwar3 import Bot
class Economy(Bot):
def on_tick(self, g):
res = g.resources() # une valeur illisible vaut None, pas 0
halls = g.my_buildings({"htow", "hkee", "hcas"})
if res is None or not halls:
return
home = halls[0]
# 1. les paysans inactifs vont récolter de l'or
for w in g.idle_workers():
mine = g.nearest(g.gold_mines(), w)
if mine:
g.gather(w, mine)
# 2. produire des paysans : un seul à la fois dans la file (une file pleine immobilise l'argent)
if len(g.my_workers()) < 15 and not g.queue(home):
g.train(home, "hpea")
# 3. nourriture presque au plafond : prendre un paysan qui ne construit pas et bâtir une ferme près du hall
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)
Trois points à noter :
- N’entraîner que si
g.queue(home)est vide. Donner l’ordre d’entraînement à chaque tick remplit la file de 7 emplacements et immobilise l’argent (mesuré : 4 paysans en file dans le hall, 300 d’or bloqués, un début de partie nettement plus lent). build_nearplutôt que des coordonnées en dur. Il cherche lui-même un emplacement libre, du plus proche au plus éloigné, et suit le résultat d’un tick à l’autre ; si l’argent manque, il ne fait rien. Des coordonnées en dur tombent facilement en pleine forêt.- Ne pas choisir un paysan en train de construire. Une ferme humaine demande 35 secondes ; si vous retirez l’ouvrier en cours de route, le chantier s’arrête.
La version complète, qui fonctionne pour les quatre races, est brains/examples/hello_bot.py : 5 ouvriers par mine, les suivants partent couper du bois une fois la mine pleine, et les chantiers arrêtés sont repris.
Ajouter la caserne, le héros et l’attaque
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éros : un autel mais pas de héros -> ressusciter d'abord, sinon entraîner (un héros est unique : en entraîner un autre après sa mort est rejeté)
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") # Lumière sacrée
# la caserne produit des fantassins en continu (un seul à la fois dans la file)
for b in g.my_buildings({"hbar"}):
if not g.queue(b):
g.train(b, "hfoo")
# attaquer quand une vague est prête ; rentrer après de lourdes pertes
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)] # ne donner des ordres qu'aux unités inactives
g.attack_move(idle, target.x, target.y)
Version complète : brains/examples/rush_bot.py (elle hérite de hello_bot et construit la caserne / l’autel s’ils manquent).
Lire les reçus
Chaque commande renvoie un reçu. if r: signifie « le moteur l’a acceptée » ; sinon, r.reason en donne la raison :
r = g.train(barracks, "hfoo")
if not r:
print(r.reason) # rejected(人口不够) (= nourriture insuffisante)
print(r.verdict) # 3
Codes de motif courants : 3 nourriture insuffisante, 8 or insuffisant, 9 bois insuffisant, 32 file pleine, 183 prérequis manquant, 221 élément absent / en construction / héros déjà présent, 1001 cible invisible. Liste complète dans Reçus et codes de motif.
Accepté ≠ réussi
Un reçu indique seulement que « le moteur a accepté cette commande ». Le moteur accepte aussi sur le moment un emplacement de construction en pleine forêt ; l’ouvrier n’échoue qu’une fois sur place. Un sort peut être interrompu. Pour connaître l’effet réel, regardez l’instantané et les événements : pour construire, utilisez build_near (qui vérifie que les fondations apparaissent) ; pour un sort, vérifiez avec g.cooldown() qu’il est bien en recharge.