Write a Bot with an LLM
No coding required: you explain how you want it to play, and the LLM writes the code. Copy the prompt template, describe your strategy, run it, then have the model revise it.
This is for people who play Warcraft but don’t code, and for developers who want to save time. The whole process is a conversation: you describe the strategy → the model writes code → you play a game → you tell the model what happened → it revises.
First set up your environment with Quickstart and get hello_bot running (you’ll see peasants go mine gold). That way, when something goes wrong, you can tell whether it’s the environment or the Bot.
1. Prepare materials for the model
How well the model writes depends mostly on whether it has read the right materials. Pick the option that matches your tool:
| What you’re using | How to give it the materials |
|---|---|
| A coding agent that can read your repo (Claude Code, Cursor, Codex, etc.) | Open it in the repo directory and have it read docs/BOT_HANDBOOK_ZH.md, docs/api.json and one example first (brains/examples/macro_bot.py for economy, micro_bot.py for fighting) |
| A chat model with web access | Have it read https://war3ai.com/llms-full.txt first — the entire site’s docs are in that one file |
| Web chat without web access | Paste the handbook, api.json and one example file after your prompt |
| A local model (LM Studio, Ollama) | Same as above. A context window of 32K tokens or more is recommended; otherwise the handbook and the API reference won’t fit |
If you want a particular pro technique, also paste the matching recipe from the Pro Playbook Cookbook.
2. Copy this prompt
Replace “The strategy I want” at the end with your own words — the more specific, the better:
You are writing an AI (in Python) for Warcraft III 1.27. Use only the Game methods listed in api.json;
don't invent methods that don't exist. Follow the style of rush_bot.py: subclass openwar3.Bot and implement on_start(g) and on_tick(g).
Rules:
- on_tick is called about 5 times per second and must be fast (don't sleep inside it).
- Values that can't be read are None, not 0 — check before using them.
- Commands return a receipt (Receipt); `if r:` means "the engine accepted it"; when it wasn't accepted, `r.reason` says why
(not enough food, not enough gold, target not visible, you already have this hero…) — try again next tick or try something else.
- To attack a specific enemy, use g.attack(units, enemy); the enemy must be in vision — ones you can't see are rejected.
- A dead hero must be revived with g.revive(altar); you can't train another one.
- Build with g.build_near(worker, building_code, x, y): it finds a spot that fits, tracks the result, and does nothing when you can't afford it.
- To know "what just happened" (who died, who took damage, a hero leveled up, an item dropped), implement on_event(g, ev).
- Don't re-issue the same command to the same unit every tick (it interrupts what the unit is doing); give orders to "idle" units.
- Only assign workers from idle_workers() to harvesting. At most 5 workers per gold mine.
- Queue only 1 unit at a time in training (queue the next when g.queue(building) is empty); to detect being food blocked, check g.production(building).blocked.
- When a tick issues many commands, wrap them in with g.batch(): (it waits on the game thread only once).
- To choose what to attack, use g.time_to_kill(my_group, enemy) (accounts for counters and armor); to choose where to go, use g.path_distance (returns None if unreachable).
- In fair mode you can only see what's in vision; for enemies you saw earlier, use g.last_seen().
- Units are four-character codes (Human Peasant hpea, Footman hfoo, Barracks hbar…), spells are order strings (thunderbolt Storm Bolt,
blizzard Blizzard, holybolt Holy Light…, full table in data/order-ids.txt), and learning skills uses four-character codes (AHtb, AHbz…).
The strategy I want:
<Write it here in plain words, for example:
"Human. At the start, 5 peasants on gold and 1 on lumber; Archmage first; two Barracks making Footmen and Riflemen;
once I have 12 units, take the hero and attack the enemy expansion; retreat home when the hero drops below 30% HP;
when creeping, go for the camps closest to home first.">
How to describe your strategy clearly
Models struggle most with vague requests. Instead of “play more aggressively”, this kind of information is much more useful:
- Race and heroes: which hero first, and the skill order (e.g. Archmage: Water Elemental, Blizzard, Water Elemental…).
- Build order: at which peasant count to build Barracks, when to tier up, how many Barracks.
- Army composition: Footmen + Riflemen? At what count do you move out?
- Attack and retreat conditions: how many units before attacking, what hero HP triggers a retreat, go home and rebuild after taking heavy losses.
- Creeping: creep or not, when (after nightfall?), only camps you can beat?
- Fair or not: if you plan to enter the Arena later, say “only use enemies visible in vision”.
3. Run it
Save the model’s code as brains/my_bot.py, then:
python tools/play.py --bot brains/my_bot.py --race 1 --difficulty 2
To see results faster, add --speed 200 (2× game speed).
4. Have it revise
- It errors out: paste the entire error back to the model verbatim and say “fix this”.
- It plays badly: describe what you saw in the game, not what you guess the cause is. For example: “the hero just stands at home”, “units trickle in one at a time”, “peasants crowd onto one mine”.
- You want to add a new tactic: add one thing at a time, play a game to confirm nothing broke, then add the next.
A coding agent that can run commands itself can take over steps 3 and 4 as well: play a game, read the logs and receipts, change the code, run again. For how to give it enough information, see Agent self-iteration.
5. Common problems
| Symptom | Most likely cause |
|---|---|
| Nothing moves | Wrong instance number (--inst), or the game hasn’t started yet |
| Peasants don’t mine | Orders were given to peasants who were already working; only assign from idle_workers() |
| Houses never get built | Use build_near instead of hard-coded coordinates; check whether the receipt’s reason says you can’t afford it |
| The hero never comes out | Check the train receipt: not enough food? Or did the hero die (use revive)? |
| The hero doesn’t cast spells | The skill wasn’t learned (learn) or there’s no mana; after casting, check whether cooldown() shows a cooldown |
| Units twitch tick after tick | Commands are being re-issued every tick; only order idle units |
| No units come out and gold keeps rising | You’re food blocked: check g.production(barracks).blocked |
| The model used methods that don’t exist | Stress “only use methods in api.json” again in the prompt, and paste the complete api.json |
Going further
- Every API and the underlying mechanism behind each one: API reference;
- The reference brain (
brains/xwar3/strategy) is a complete AI that expands, creeps and attacks. You can have the model read it for ideas, but it uses lower-level APIs, so copying it directly isn’t recommended; - Once you’re on the Arena, you’ll only be able to see enemies in vision — add
--fairnow to hold yourself to that, and you won’t need changes later.