文件 開始
第一個 Bot
從 10 行的最小 Bot 開始,加上生產農民、蓋農場補人口、出兵、英雄和出擊,最後看懂回執。
一個 Bot 就是一個繼承 openwar3.Bot 的類別。你只需要覆寫需要的掛鉤,g(Game)負責「看」和「做」。
最小的 Bot
from openwar3 import Bot
class MyBot(Bot):
def on_start(self, g): # 進入對局後呼叫一次
g.message("我來了")
def on_tick(self, g): # 每秒約 5 次
for w in g.idle_workers():
g.gather(w, g.nearest(g.gold_mines(), w))
python tools/play.py --bot my_bot.py
閒著的農民會去最近的金礦。四個掛鉤:
| 掛鉤 | 什麼時候呼叫 |
|---|---|
on_start(g) | 進入對局後、第一拍之前呼叫一次 |
on_tick(g) | 每一拍(預設每秒 5 次)。一拍逾時會自動順延,不會越積越多 |
on_event(g, ev) | 每拍的 on_tick 之前,把上一拍以來的事件逐一交給你 |
on_end(g, reason) | 這一局結束(遊戲行程不見了 / 我方沒有單位了 / 手動停止)時呼叫一次 |
on_tick 裡拋出例外不會中斷整局:執行器會印出堆疊、下一拍繼續;連續出錯 20 拍才會停下。
加上經濟:生產農民、蓋農場補人口
from openwar3 import Bot
class Economy(Bot):
def on_tick(self, g):
res = g.resources() # 讀不到時是 None,不是 0
halls = g.my_buildings({"htow", "hkee", "hcas"})
if res is None or not halls:
return
home = halls[0]
# 1. 閒著的農民去採金
for w in g.idle_workers():
mine = g.nearest(g.gold_mines(), w)
if mine:
g.gather(w, mine)
# 2. 生產農民:佇列裡只排 1 個(排滿會把錢鎖在佇列裡)
if len(g.my_workers()) < 15 and not g.queue(home):
g.train(home, "hpea")
# 3. 人口快滿:找一個沒在蓋房子的農民,在大廳附近蓋農場
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)
三個值得注意的寫法:
g.queue(home)為空才訓練。 每拍都下訓練命令會把 7 格佇列排滿、把錢鎖住(實測大廳排了 4 個農民、300 金鎖在佇列裡,開局慢了一大截)。- 用
build_near,而不是寫死座標。 它會自己由近到遠找放得下的位置、跨拍追蹤結果;錢不夠時什麼也不做。寫死的座標很可能剛好是樹林。 - 不挑正在蓋房子的農民。 人類的農場要蓋 35 秒,中途把工人派走,地基就停工了。
完整、四個種族都能跑的版本是 brains/examples/hello_bot.py:一座礦 5 人、礦滿了就去伐木、續建停工的地基。
加上兵營、英雄和出擊
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]
# 英雄:有祭壇沒英雄 -> 先復活,復活不了再訓練(英雄唯一,陣亡後再訓練會被拒)
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") # 聖光術
# 兵營持續生產步兵(佇列只排 1 個)
for b in g.my_buildings({"hbar"}):
if not g.queue(b):
g.train(b, "hfoo")
# 存夠一波就出擊;打殘了就回家
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)] # 只對閒著的下令
g.attack_move(idle, target.x, target.y)
完整版見 brains/examples/rush_bot.py(以 hello_bot 為基礎繼承,沒有兵營 / 祭壇就蓋)。
看懂回執
每條命令都會回傳一張回執。if r: 就代表「引擎接下了」;沒接下時 r.reason 寫著原因:
r = g.train(barracks, "hfoo")
if not r:
print(r.reason) # rejected(人口不够)(= 人口不夠)
print(r.verdict) # 3
常見的原因碼:3 人口不夠、8 金不夠、9 木不夠、32 佇列已滿、183 缺少前置條件、221 沒有這一項 / 正在建造 / 英雄已經有了、1001 目標看不見。完整列表見 回執與原因碼。
接下 ≠ 做成
回執只說明「引擎接下了這條命令」。樹林裡的建造點引擎也會當場接下,工人走到才失敗;技能可能被打斷。效果要看快照和事件:蓋房子用 build_near(它會追蹤地基是否出現),放技能看 g.cooldown() 有沒有進入冷卻。