Docs Guides

Example bots

Four examples, from simple to complex. Each one runs as-is, and every piece of logic maps to an SDK capability. Plus a complete reference brain.

The examples live in brains/examples/. Each one builds on the previous one and only adds what’s new. Read them in order:

ExampleWhat it teachesRun it
hello_bot.pyGathering (5 per mine; when a mine is full, chop lumber), training workers (only 1 in the queue), building food structures, resuming stalled foundations; works for all four racespython tools/play.py --bot brains/examples/hello_bot.py
rush_bot.pyBarracks and altar (built with build_near if missing), hero first (revived when it dies), learning skills whenever points are available, gathering a wave and attack-moving… --bot brains/examples/rush_bot.py
macro_bot.pyBuild order + returning to the mine after building (Shift), adding food the moment production is blocked, 1 unit in the barracks queue, attack/armor upgrades, tiering up and advanced units, picking targets by actual ground distance and following the path… --bot brains/examples/macro_bot.py --speed 200
micro_bot.pyTakes over combat on top of the macro: focus fire on whatever dies fastest, pull back wounded units, keep the hero alive, pick creep camps it can win at night, defend when enemies reach the base; sends each tick’s commands as one batch… --bot brains/examples/micro_bot.py --fair

The comments in hello_bot and rush_bot record pitfalls hit in live games, such as “it always picked the first worker to build, so all 3 farms ended up as half-built foundations” and “the hardcoded barracks coordinates were right in a forest, and not a single barracks got built in 3 minutes.” The comments teach more than the code.

hello_bot: economy

# race -> (worker, town halls, food building)
RACES = {
    "h": ("hpea", {"htow", "hkee", "hcas"}, "hhou"),
    "o": ("opeo", {"ogre", "ostr", "ofrt"}, "otrb"),
    "u": ("uaco", {"unpl", "unp1", "unp2"}, "uzig"),
    "e": ("ewsp", {"etol", "etoa", "etoe"}, "emow"),
}
MINE_CAP = 5            # at most 5 peasants per mine (more doesn't raise income)
LUMBER_CREW = 5         # lumber crew size: 5 on gold per mine + this many on lumber = worker target

Three things: idle workers go mine gold (it keeps its own count of workers per mine; when a mine is full, they chop lumber); train workers when short (only 1 in the queue); when food is nearly capped, find a worker that isn’t already building and put up a food building next to the town hall (for Human and Orc, it also sends workers to resume stalled foundations).

rush_bot: army and attacks

Adds three things on top of hello_bot: build a barracks and an altar if missing; train a hero at the altar (revive it first if it’s dead, since heroes are unique) and learn skills whenever points are available; once 8 soldiers are gathered, attack-move them all to the enemy town hall, and come home to regroup when they’re badly beaten. Only idle soldiers get orders, so fights aren’t interrupted tick after tick.

macro_bot: macro fundamentals

TECH = {
    "h": dict(order=["halt", "hbar", "hbla", "hlum"], altar="halt", hero="Hamg", skills=["AHwe", "AHbz", "AHab"],
              barracks="hbar", soldiers=["hfoo", "hrif", "hkni"], smith="hbla", upgrades=["Rhme", "Rhar", "Rhra", "Rhla"],
              tiers=["hkee", "hcas"]),
    ...
}

The things pro players do every game, each mapped to an SDK capability: a build-order table + gather(..., queue="after") to return to the mine after building; production().blocked to spot food-blocked production; g.queue to keep only 1 unit in the barracks queue; can_do to ask the engine whether the next attack/armor level can be researched; tiering up and advanced units (a lesson from a live game: it stayed at tier 1 the whole time and got steamrolled at 23 minutes by tier-3 knights and gryphons); picking targets by path_distance and following waypoints with path().

micro_bot: once the fighting starts

def _fight(self, g, army, foes, home, now):
    ...
    visible = [e for e in foes if e.visible_to(me)]              # targets you can't see get rejected (1001)
    atk = [s for s in (g.stats(u) for u in fighters) if s]
    target = min(visible, key=lambda e: _ttk(g, atk, e))         # the one that dies fastest, not the nearest
    idle_or_other = [u for u in fighters if g.current_target(u) is None
                     or g.current_target(u).handle != target.handle]
    if idle_or_other:
        g.attack(idle_or_other, target)

In a live game: 1497 ticks, 3023 commands and 0 errors over 5 minutes.

Reference brain: a complete AI

brains/xwar3/ is a complete AI that expands, creeps and attacks, organized in three layers:

LayerLocationCadenceWhat it does
Strategy layerstrategy/SecondsAMAI-style selection and switching among multiple strategies, build tables, counter units, hero picks; optional LLM strategy coach
Reflex layerreflex/ (4 separate processes)~100 msStaying alive, casting, focus fire, picking up items
Win-rate modelworldmodel/—Can we win this fight (inference subset)

Multiple processes share units through a claim table, and priority decides who’s in charge: manual 95 > survival 90 > dodging spells 85 > casting 80 > item pickup 70 > … > strategy 50 > worker assignment 45. Your own bot appears in the table as bot, with a default priority of 50.

The reference brain uses the SDK’s low-level layer directly (w3cmd / act) and relies heavily on full-map information. Treat it as a source of ideas; don’t have an LLM copy it directly. It needs AMAI data: the first time start.bat deploys, it pulls it from AMAI’s public repository and generates it (AMAI has a custom license, so the generated files are not committed to git; if that fails, retry with start.bat setup).

The easiest way to start the reference brain is the Farsight console: on the Instances & Setup page, check the instance numbers and click Start test.