Docs Guides

Recettes des joueurs pros

L'avantage des meilleurs joueurs tient surtout à des dizaines de « petites habitudes ». Cette page traduit une à une les techniques pro courantes en code SDK ; chaque extrait peut être copié tel quel dans on_tick.

Conventions : g est le Game, home est votre base principale (g.my_buildings({"htow", "hkee", "hcas"})[0]), now = g.clock(). Le détail des méthodes se trouve dans le catalogue de l’API, et des exemples complets et exécutables dans Bots d’exemple.

Quand vous demandez à un LLM d’ajouter une technique, collez-lui la recette correspondante avec son code : c’est bien plus efficace que de lui demander de « jouer un peu plus pro ».

I. Économie

1. Jamais de paysan inactif, 5 par mine

for w in g.idle_workers():                                   # n'affecter que les inactifs (redonner un ordre à un ouvrier occupé interrompt sa récolte)
    mine = g.nearest([m for m in g.gold_mines() if crew[m.addr] < 5], w)
    g.gather(w, mine) if mine else g.gather(w, g.trees(w.x, w.y, limit=1)[0])

Tenez vous-même le compte des ouvriers envoyés à chaque mine (crew) : un ouvrier entré dans une mine d’or n’apparaît pas dans l’instantané. Exemple complet : hello_bot.py.

2. Une seule unité en file, l’argent n’est pas bloqué

for b in g.my_buildings({"hbar"}):
    if not g.queue(b):                                       # n'en lancer une autre que quand la file est vide
        g.train(b, "hfoo")

3. Ne jamais être bloqué par la nourriture

stuck = any(p.blocked for _b, p in g.all_production("me"))  # en file mais pas démarré = nourriture insuffisante
res = g.resources()
if stuck or res["food_cap"] - res["food_used"] <= 6:
    g.build_near(builder, "hhou", home.x, home.y)

blocked réagit un temps avant « presque plein » : après avoir perdu une bonne partie de l’armée en combat, vous le savez dès que la file se bloque au moment de reconstruire.

4. Ordre de construction + retour à la mine une fois le bâtiment fini (Shift + mine)

spot = g.build_near(w, "hbar", home.x, home.y)
if spot:
    g.gather(w, mine, queue="after")                         # retourner récolter une fois le bâtiment terminé, inutile de le rechercher au tick suivant

Un paysan qui enchaîne plusieurs bâtiments : g.build_queue(w, [("hhou", x1, y1), ("hhou", x2, y2)]). L’argent n’est prélevé qu’au début de chaque construction.

5. Moment du passage de tier, améliorations d’attaque et d’armure

if not g.queue(hall) and g.can_do(hall, "hkee") in (0, 220):   # l'amélioration n'est possible que si la file du hall est vide (sinon 185)
    g.upgrade(hall, "hkee")
p = g.production(hall)                                          # progression du passage de tier
if p and p.kind == "upgrade":
    print(f"Hall principal : encore {p.remaining:.0f} s")
for sm in g.my_buildings({"hbla"}):
    if not g.queue(sm):
        ok = [u for u, v in zip(UPS, g.can_do_many([(sm, u) for u in UPS])) if v in (0, 220)]
        if ok:
            g.research(sm, ok[0])

6. Prendre une expansion : choisir la mine la plus proche à pied

mines = [m for m in g.gold_mines() if g.dist(m, home) > 1500 and not taken(m)]
best = min(mines, key=lambda m: g.path_distance(home, m) or 1e9)   # une mine sur une île renvoie None -> classée en dernier

II. Éclaireurs et renseignement

7. Voir ce que fait l’adversaire

for b, p in g.all_production("enemy"):          # ce que les bâtiments adverses visibles entraînent / recherchent / améliorent
    print(b.type, p.kind, p.queue, f"{p.progress:.0%}" if p.progress is not None else "bloqué")

Avec les événements : ev.kind == "production.done" and ev.owner != g.me() — ce que l’adversaire vient de produire.

8. Se souvenir de ce qu’on a vu (brouillard de guerre)

for u, t, age in g.last_seen(max_age=60):       # ennemis vus dans les 60 dernières secondes de jeu (dernière position et derniers PV)
    ...
hero_seen = [r for r in g.last_seen() if r[0].is_hero]   # où se trouvait le héros adverse la dernière fois

En mode équitable, c’est votre seule source d’information sur l’adversaire, exactement comme pour un joueur humain.

9. Où l’ordinateur adverse va attaquer (IA de l’ordinateur uniquement)

plan = g.enemy_ai_plan(some_enemy_soldier)      # la destination de son capitaine d'IA

L’ordinateur fixe son point cible avant même de sortir : ramenez vos troupes là-bas à l’avance.

III. Creeping

10. Creeper la nuit

if g.is_night():                                 # de 18 h à 6 h : les creeps dorment (vous frappez en premier sans être encerclé), la vision de tous diminue
    ...
wait = g.seconds_until(18)                       # secondes de jeu avant la tombée de la nuit (une journée = 480 s)

11. N’attaquer que les camps à votre portée

from openwar3 import combat
mine = [g.stats(u) for u in army]
def ttk(target): return combat.time_to_kill(mine, g.stats(target), target_hp=target.hp) or 1e9
camp = [c for c in g.creeps() if g.dist(c, center) < 600]
ours = max(ttk(c) for c in camp)                                 # temps pour vider ce camp (estimation grossière)
theirs = g.time_to_kill(camp, weakest_of_mine) or 1e9            # temps qu'il leur faut pour tuer notre unité la plus faible
if ours < theirs and g.reachable(center, camp[0]):
    g.attack_move(army, camp[0].x, camp[0].y)

Exemple complet : _maybe_creep dans micro_bot.py.

IV. Micro

12. Tir concentré : viser « ce qui meurt le plus vite », pas le plus proche

target = min(visible_enemies, key=lambda e: g.time_to_kill(fighters, e) or 1e9)
g.attack([u for u in fighters if (g.current_target(u) or target).handle != target.handle], target)

Ne donnez l’ordre qu’aux unités qui « ne l’attaquent pas déjà » (current_target) ; n’interrompez pas celles qui le frappent.

13. Retirer les unités blessées

for u in army:
    if u.hp < u.hp_max * 0.35:
        g.move(u, *toward(home, u, 500))       # reculer de 500 vers la base ; ne pas la retirer à nouveau dans les 3 s

Pour détecter un focus : dans les événements damage, une même unité touchée par plusieurs sources en peu de temps = elle est encerclée.

14. Garder le héros en vie, ne pas offrir d’expérience

for h in g.my_heroes():
    if h.hp < h.hp_max * 0.4:
        g.move(h, home.x, home.y)
        g.use_item(h, slot_of(h, "phea"))       # potion de soins : trouver l'emplacement avec inventory(h)

15. Contres : les bonnes unités sur les bonnes cibles

s = g.stats(u)
best = max(enemies, key=lambda e: s.dps_vs(g.stats(e)))   # fusiliers contre chevaucheurs de griffon (perforant contre armure légère ×2), griffons contre fantassins (magique contre armure lourde ×2)

La table des contres vient des données du jeu : combat.damage_multiplier("pierce", "small") == 2.0.

16. Prise en tenaille et itinéraires : contourner les tours

route = g.walk_path(army_center, target)        # points d'inflexion du plus court chemin au sol
g.path(army, route, attack=True)                # attaque-déplacement par chaque point, dans l'ordre

Pour contourner les tours, marquez leurs abords comme infranchissables sur la grille de pathfinding avant de calculer :

grid = g.grid().copy()
for t in towers:
    grid.block_area(t.x, t.y, 800)             # portée de la tour 700 + marge
route = grid.path((army_x, army_y), (target.x, target.y))

17. Envoyer toutes les commandes d’un tick en un lot

with g.batch():
    g.attack(melee, target_a)
    g.attack(ranged, target_b)
    g.move(wounded, *home_xy)
    g.cast(hero, "thunderclap")

Des dizaines de commandes pour une seule attente du thread du jeu (mesuré : 8 déplacements, 68 ms → 6.5 ms).

18. Siège : l’artillerie tire au sol

g.attack_ground(mortars, tower.x, tower.y)       # mortiers / engins de siège tirant sur une zone (derrière des arbres, unités invisibles)

V. Héros

19. Ordre des compétences

SKILLS = {"Hamg": ["AHwe", "AHbz", "AHwe", "AHbz", "AHwe", "AHmt"]}   # Élémentaire d'eau, Blizzard… ultime au niveau 6 : Téléportation de masse
info = g.hero_info(h)
if info and info["skill_points"]:
    g.learn(h, SKILLS[h.type][learned_count])  # si rejeté (niveau insuffisant pour l'ultime), attendre le niveau suivant

20. Le sort est-il vraiment parti ?

r = g.cast(h, "thunderbolt", target=enemy_hero)
# au tick suivant :
if g.cooldown(h, "AHtb"):                         # en recharge = vraiment lancé ; accepté ≠ lancé
    ...

21. Acheter des potions, rentrer en ville

g.buy(shop, "phea")                               # le héros se tient à côté de la boutique
g.use_item(hero, slot, x=home.x, y=home.y)        # parchemin de téléportation (objet utilisé sur un point)

VI. Analyse d’après-partie

  • À chaque tick, écrivez vos décisions dans le journal (print s’affiche dans la fenêtre d’exécution), et utilisez g.say(unité, "Repli !") pour les voir en jeu ;
  • L’événement production.done indique « combien de secondes cela a pris » : établissez votre propre chronologie de construction (à quelle seconde sort le premier héros, à quelle seconde vous passez de tier) et comparez-la à celle des meilleurs joueurs ;
  • Laissez l’agent analyser lui-même ses parties : voir le rapport de partie dans Itération autonome d’un agent.