Your First Bot
Start with a 10-line minimal Bot, then add training peasants, building for food, producing an army, heroes and attacking — and finally learn to read receipts.
A Bot is a class that subclasses openwar3.Bot. You only override the hooks you need; g (Game) handles “observing” and “acting”.
The minimal Bot
from openwar3 import Bot
class MyBot(Bot):
def on_start(self, g): # called once after the game starts
g.message("I'm here")
def on_tick(self, g): # about 5 times per second
for w in g.idle_workers():
g.gather(w, g.nearest(g.gold_mines(), w))
python tools/play.py --bot my_bot.py
Idle peasants head to the nearest gold mine. The four hooks:
| Hook | When it’s called |
|---|---|
on_start(g) | Once after the game starts, before the first tick |
on_tick(g) | Every tick (5 times per second by default). If a tick runs over, the next one is pushed back automatically — they never pile up |
on_event(g, ev) | Before each on_tick; hands you every event since the last tick, one by one |
on_end(g, reason) | Once when the game ends (the game process is gone / we have no units left / stopped manually) |
An exception in on_tick doesn’t end the game: the runner prints the stack trace and continues with the next tick; it only stops after 20 consecutive ticks with errors.
Adding an economy: training peasants, building for food
from openwar3 import Bot
class Economy(Bot):
def on_tick(self, g):
res = g.resources() # None if it can't be read, not 0
halls = g.my_buildings({"htow", "hkee", "hcas"})
if res is None or not halls:
return
home = halls[0]
# 1. Idle peasants go mine gold
for w in g.idle_workers():
mine = g.nearest(g.gold_mines(), w)
if mine:
g.gather(w, mine)
# 2. Train peasants: only 1 in the queue at a time (a full queue locks gold up in it)
if len(g.my_workers()) < 15 and not g.queue(home):
g.train(home, "hpea")
# 3. Food almost capped: find a peasant who isn't building and build a Farm near the 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)
Three things worth noting:
- Only train when
g.queue(home)is empty. Issuing a train order every tick fills the 7-slot queue and locks up your gold (measured: the hall queued 4 peasants with 300 gold locked in the queue, and the opening was much slower). build_nearinstead of hard-coded coordinates. It searches outward from near to far for a spot that fits and tracks the result across ticks; it does nothing when you can’t afford it. Hard-coded coordinates may well land right in a forest.- Don’t pick peasants who are already building. A Human Farm takes 35 seconds to build; if you send the worker away partway through, construction stops.
The complete version that works for all four races is brains/examples/hello_bot.py: 5 per mine, extra workers chop trees once the mine is full, and it resumes stalled construction.
Adding Barracks, a hero and attacks
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]
# Hero: have an altar but no hero -> revive first, train only if revive fails (heroes are unique; training again after death is rejected)
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") # Holy Light
# Barracks keep producing Footmen (only 1 in the queue at a time)
for b in g.my_buildings({"hbar"}):
if not g.queue(b):
g.train(b, "hfoo")
# Attack once a wave is ready; go home after heavy losses
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)] # only order idle units
g.attack_move(idle, target.x, target.y)
For the full version, see brains/examples/rush_bot.py (it subclasses hello_bot and builds the Barracks / altar if missing).
Reading receipts
Every command returns a receipt. if r: means “the engine accepted it”; when it wasn’t accepted, r.reason says why:
r = g.train(barracks, "hfoo")
if not r:
print(r.reason) # rejected(人口不够) (= not enough food)
print(r.verdict) # 3
Common reason codes: 3 not enough food, 8 not enough gold, 9 not enough lumber, 32 queue full, 183 missing prerequisite, 221 no such item / under construction / you already have this hero, 1001 target not visible. For the full table, see Receipts and reason codes.
Accepted ≠ done
A receipt only tells you “the engine accepted this command”. The engine also accepts a build spot inside a forest on the spot, and the worker only fails once it gets there; spells can be interrupted. To see the actual effect, look at snapshots and events: for buildings use build_near (it tracks whether the foundation appears), and for spells check whether g.cooldown() shows a cooldown.