文件 指南

職業打法食譜

高手的優勢大多來自幾十個「小習慣」。這一頁把常見的職業打法逐條落實到 SDK 程式碼上,每段都能直接貼進 on_tick。

約定:g 是 Game,home 是我方主基地(g.my_buildings({"htow", "hkee", "hcas"})[0]),now = g.clock()。API 細節見 API 目錄,完整可執行的範例在 範例 Bot。

要讓 LLM 加入某種打法時,把對應的那一招連同程式碼一起貼給它,比描述「打得職業一點」有效得多。

一、運營

1. 農民永不閒置、一座礦 5 人

for w in g.idle_workers():                                   # 只派閒置的(有工作的重新下令會打斷採集)
    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])

自己記錄每座礦派了幾個人(crew):進了金礦的工人不在快照裡。完整範例見 hello_bot.py。

2. 佇列只排 1 個,錢不鎖死

for b in g.my_buildings({"hbar"}):
    if not g.queue(b):                                       # 空了才排下一個
        g.train(b, "hfoo")

3. 人口永遠不卡

stuck = any(p.blocked for _b, p in g.all_production("me"))  # 有排隊但沒開始 = 人口不夠
res = g.resources()
if stuck or res["food_cap"] - res["food_used"] <= 6:
    g.build_near(builder, "hhou", home.x, home.y)

blocked 比「快滿了」早一步:打仗損失一片兵、再補兵時佇列一卡住就知道。

4. 建造順序 + 蓋完自己回礦(Shift 回礦)

spot = g.build_near(w, "hbar", home.x, home.y)
if spot:
    g.gather(w, mine, queue="after")                         # 蓋完回去採礦,不用下一拍再找它

一個農民連蓋幾座:g.build_queue(w, [("hhou", x1, y1), ("hhou", x2, y2)])。錢在開工時才扣。

5. 升級主城的時機、攻防升級

if not g.queue(hall) and g.can_do(hall, "hkee") in (0, 220):   # 大廳佇列空了才能升(否則 185)
    g.upgrade(hall, "hkee")
p = g.production(hall)                                          # 升級主城的進度
if p and p.kind == "upgrade":
    print(f"主城還要 {p.remaining:.0f} 秒")
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. 開分礦:依步行距離選最近的礦

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)   # 島上的礦回傳 None -> 排到最後

二、偵察與資訊

7. 看對手在做什麼

for b, p in g.all_production("enemy"):          # 看得見的對手建築在訓練什麼 / 研究什麼 / 升級什麼
    print(b.type, p.kind, p.queue, f"{p.progress:.0%}" if p.progress is not None else "卡住")

搭配事件:ev.kind == "production.done" and ev.owner != g.me() —— 對手剛生產了什麼。

8. 記住看到過的東西(戰爭迷霧)

for u, t, age in g.last_seen(max_age=60):       # 60 遊戲秒內看到過的敵人(最後的位置和血量)
    ...
hero_seen = [r for r in g.last_seen() if r[0].is_hero]   # 對面英雄上次出現在哪

公平模式下這是你唯一的對手資訊來源,和人類玩家一樣。

9. 電腦對手要打哪裡(只對電腦 AI 有效)

plan = g.enemy_ai_plan(some_enemy_soldier)      # 它的電腦隊長要去的地方

電腦出門前就決定好了目標點 —— 提前把兵帶回那裡。

三、打野

10. 夜裡打野

if g.is_night():                                 # 18 點 ~ 6 點:野怪睡覺(先手不被包圍)、所有人視野變短
    ...
wait = g.seconds_until(18)                       # 離天黑還有幾遊戲秒(一天 480 秒)

11. 只打打得過的點

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)                                 # 清光這個點要多久(粗估)
theirs = g.time_to_kill(camp, weakest_of_mine) or 1e9            # 它們打死我方最弱的要多久
if ours < theirs and g.reachable(center, camp[0]):
    g.attack_move(army, camp[0].x, camp[0].y)

完整範例:micro_bot.py 的 _maybe_creep。

四、微操

12. 集火:打「最快能打死的」,不是最近的

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)

只對「沒在打它」的單位下令(current_target),別打斷已經在打的。

13. 拉殘血

for u in army:
    if u.hp < u.hp_max * 0.35:
        g.move(u, *toward(home, u, 500))       # 往家的方向退 500;3 秒內別重複拉

判斷是否被集火:damage 事件裡同一個單位在短時間內受到多個來源攻擊 = 被包圍了。

14. 英雄保命、別送經驗

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"))       # 治療藥水:用 inventory(h) 找格位編號

15. 克制:讓對的兵打對的目標

s = g.stats(u)
best = max(enemies, key=lambda e: s.dps_vs(g.stats(e)))   # 火槍兵打獅鷲(穿刺打小甲 ×2)、獅鷲打步兵(魔法打大甲 ×2)

克制表來自遊戲資料:combat.damage_multiplier("pierce", "small") == 2.0。

16. 包夾與路徑:繞過塔走

route = g.walk_path(army_center, target)        # 地面最短路徑的轉折點
g.path(army, route, attack=True)                # 依序攻擊移動經過每個點

要繞開塔:在尋路網格上把塔周圍標成不可通行再計算:

grid = g.grid().copy()
for t in towers:
    grid.block_area(t.x, t.y, 800)             # 塔射程 700 + 餘裕
route = grid.path((army_x, army_y), (target.x, target.y))

17. 一拍的命令一批送出

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

幾十條命令只等一次遊戲執行緒(實測 8 條移動 68 ms → 6.5 ms)。

18. 攻城:用砲攻擊地面

g.attack_ground(mortars, tower.x, tower.y)       # 迫擊砲 / 投石車對一塊地方開火(樹林後面、隱形單位)

五、英雄

19. 技能加點表

SKILLS = {"Hamg": ["AHwe", "AHbz", "AHwe", "AHbz", "AHwe", "AHmt"]}   # 水元素、暴風雪……6 級終極技能群體傳送
info = g.hero_info(h)
if info and info["skill_points"]:
    g.learn(h, SKILLS[h.type][learned_count])  # 被拒(等級不夠學終極技能)就等下一級

20. 技能到底放出來沒有

r = g.cast(h, "thunderbolt", target=enemy_hero)
# 下一拍:
if g.cooldown(h, "AHtb"):                         # 進入冷卻 = 真的放出去了;接下 ≠ 放出
    ...

21. 買藥、回城

g.buy(shop, "phea")                               # 英雄站在商店旁邊
g.use_item(hero, slot, x=home.x, y=home.y)        # 回城卷軸(對點使用物品)

六、復盤

  • 每拍把決策寫進日誌(print 會輸出到執行視窗),搭配 g.say(單位, "撤") 在遊戲裡查看;
  • production.done 事件帶有「花了多少秒」—— 統計自己的建造時間軸(第幾秒出第一個英雄、第幾秒升級主城),和高手的比較;
  • 讓 Agent 自己復盤:見 Agent 自主迭代 裡的對局報告。