文档 开始
第一个 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() 有没有进冷却。