Most people learn to program by printing words to a black screen. You're going to learn by writing the mind of a microscopic swarm that has to save a patient without you.
if and elsefor loopsYou need Python 3.10 or newer. Then, in a terminal, from the project folder:
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
python main.py # opens the app
Make yourself a file to work in — strategies/my_strategy.py. Everything you
write in these lessons goes in that one file.
To see it run: in the app, press Run Match, pick a map and pick
my_strategy for Player 1, then press Run Match again.
You'll watch the whole thing play back.
A program is a list of instructions. In Python you give an instruction by writing its name followed by brackets. That's called calling a function:
bot.stop()
stop is the instruction. The empty brackets () mean "do it now."
Some instructions need extra information to make sense — "move" is meaningless without
where. Information you hand a function is called an argument,
and it goes inside the brackets:
bot.move_to((10, 4)) # go to column 10, row 4
Here is the smallest complete program that does something. Type it into your file:
from nanobot.api.nano_strategy import NanoStrategy
class MyStrategy(NanoStrategy):
def choose_injection_point(self, map_info):
return (0, 0)
def what_to_do_next(self, map_info, my_bots):
for bot in my_bots:
bot.move_to((10, 10))
Don't worry about every word yet. The important part: what_to_do_next is
your instruction list, and the game runs it once every turn,
1500 times. You are not describing one moment — you are describing what to do at
any moment.
(0, 0) in the top-left corner.
Writing (10, 10) everywhere is fragile — change your mind and you must find
every copy. Instead give the value a name. That's a variable:
destination = (10, 10)
bot.move_to(destination)
The = means "store this under this name" (it is not the maths
"equals"). Now the value lives in one place.
That (10, 10) with brackets and a comma is a tuple — an
ordered pair. Every position in this game is one: (x, y), column then row.
You read the pieces out by position, counting from zero:
spot = (10, 4)
x = spot[0] # 10
y = spot[1] # 4
Your bots carry information you can read the same way, using a dot:
bot.position # where it is, e.g. (3, 7)
bot.hp # how much health it has left
bot.azn # how much medicine it's carrying
bot.type # what kind of bot it is, e.g. "NanoCollector"
x, y = bot.position then bot.move_to((x + 4, y)).
Real programs make decisions. In Python that's if: do this only
when something is true.
if map_info.azn_bank >= 20:
# we can afford a collector (they cost 20)
ai.build("NanoCollector", (1, 2))
Two things matter here. The colon at the end of the line, and the
indentation underneath it. In Python, indented lines are the ones that
belong to the if. Indentation isn't decoration — it's how Python knows what
goes with what. Four spaces per level, consistently.
You can add alternatives with elif ("otherwise, if…") and else:
if bot.azn >= 10:
bot.move_to(needle.position) # carrying enough — deliver it
elif bot.hp < 20:
bot.stop() # hurt — sit still
else:
bot.move_to(node.position) # otherwise go mine
Comparisons you'll use constantly:
| Written | Means |
|---|---|
== | is equal to (two equals signs — one means "store") |
!= | is not equal to |
>= <= | at least / at most |
and or | both must be true / either will do |
is None | there's nothing here at all |
= stores a value. == compares two. Writing if x = 5
is an error; you want if x == 5.
You don't have one bot, you have a swarm — and it grows. You can't write a line per bot. A loop repeats the same instructions for each item in a group:
for bot in my_bots:
bot.stop()
Read it as an English sentence: "for each bot in my bots, stop it." The name
bot is yours to choose — it just holds one item at a time while the indented
lines run.
Loops combine with if to act on only some of them:
for bot in my_bots:
if bot.type == "NanoCollector":
bot.move_to((20, 20)) # only the collectors move
move_to and then stop on the
same bot in the same turn means it just stops. This surprises everyone once.
my_bots is a list — several things in order. The map hands
you more of them:
map_info.habitas_points # the treatment sites
map_info.azn_nodes # where the medicine is
map_info.visible_enemies # rival bots you can currently see
You can check how many there are, or grab one:
len(map_info.azn_nodes) # how many nodes exist
map_info.azn_nodes[0] # the first one (counting starts at 0)
The items inside are objects — bundles of related facts you read with a dot, like bots:
node = map_info.azn_nodes[0]
node.position # (x, y)
node.quantity # how much medicine is left in it
point = map_info.habitas_points[0]
point.position
point.owner_id # -1 means nobody has claimed it yet
One exception worth memorising, because it trips people up: visible enemies and hazards are dictionaries, not objects. A dictionary looks things up by name in square brackets with quotes:
enemy = map_info.visible_enemies[0]
enemy["position"] # square brackets + quotes, NOT enemy.position
enemy["hp"]
Finally, you can build a smaller list from a bigger one by filtering it:
free = [p for p in map_info.habitas_points if p.owner_id == -1]
That reads: "every point p in the list, but only where nobody owns it."
When you catch yourself writing the same few lines twice, give them a name of their own.
That's def — defining a function:
def distance(self, a, b):
return abs(a[0] - b[0]) + abs(a[1] - b[1])
return hands an answer back to whoever asked. Now you can use it anywhere:
self.distance(bot.position, node.position). (Inside a class every function
takes self first, and you call your own with self. in front —
for now, just follow the pattern.)
Here's a genuinely useful one — find the closest thing in a list:
def nearest_node(self, map_info, from_pos):
best = None
for node in map_info.azn_nodes:
if node.quantity == 0:
continue # empty — skip to the next one
if best is None or self.distance(from_pos, node.position) < self.distance(from_pos, best.position):
best = node
return best
Follow the idea: remember the best one seen so far, look at each in turn, replace the champion whenever you find a closer one. That "keep the best so far" pattern is everywhere in programming.
what_to_do_next runs from scratch every turn. Ordinary variables inside it
vanish when it ends — so how do you remember something between turns?
You store it on self, which is your strategy object and sticks around for the
whole match. Set up what you want to remember in __init__:
def __init__(self):
self.home = None
def what_to_do_next(self, map_info, my_bots):
ai = my_bots[0]
if self.home is None:
self.home = ai.position # first turn only: remember where we started
This matters more than it looks. Deciding something once and sticking to it is often the difference between a swarm that works and one that dithers: if you re-pick the "nearest" node every single turn, two nodes at almost equal distance will swap places as your bot moves, and it will walk back and forth between them forever without ever arriving. Real strategies hit this bug constantly. Remembering your choice fixes it.
Everything so far, in one working strategy. It builds a collector, claims a treatment site, and ferries medicine into it — which is the entire core loop of the game.
from nanobot.api.nano_strategy import NanoStrategy
class MyStrategy(NanoStrategy):
def choose_injection_point(self, map_info):
return (0, 0)
def distance(self, a, b):
return abs(a[0] - b[0]) + abs(a[1] - b[1])
def find(self, my_bots, kind):
for bot in my_bots:
if bot.type == kind and bot.is_alive:
return bot
return None
def what_to_do_next(self, map_info, my_bots):
ai = self.find(my_bots, "NanoAI")
worker = self.find(my_bots, "NanoCollector")
implant = self.find(my_bots, "NanoNeedle")
if ai is None:
return
# 1. No worker yet? Build one next to the commander.
if worker is None and map_info.azn_bank >= 20:
x, y = ai.position
for spot in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)):
cell = map_info.get_cell(spot[0], spot[1])
if cell is not None and not cell.is_bone:
ai.build("NanoCollector", spot)
break
# 2. No implant yet? Walk to a free site and plant one ON it.
elif implant is None:
free = [p for p in map_info.habitas_points if p.owner_id == -1]
if free:
site = free[0].position
for p in free:
if self.distance(ai.position, p.position) < self.distance(ai.position, site):
site = p.position
if self.distance(ai.position, site) == 1:
if map_info.azn_bank >= 40:
ai.build("NanoNeedle", site)
else:
ai.move_to(site)
# 3. The worker: carry medicine in, or go fetch more.
if worker is not None:
node = None
for n in map_info.azn_nodes:
if n.quantity > 0:
if node is None or self.distance(worker.position, n.position) < self.distance(worker.position, node.position):
node = n
if implant is not None and worker.azn >= 10:
if worker.position == implant.position:
worker.transfer_to(implant.position)
else:
worker.move_to(implant.position)
elif node is not None:
if worker.position == node.position:
worker.collect_from(node.position)
else:
worker.move_to(node.position)
collect_from() and transfer_to() only work while your bot is
standing on that exact cell. There is no picking things up from a distance —
move_to there first, then act.
Not "nano-bot syntax." These are the load-bearing ideas in every programming language:
| You used | It's called | You'll meet it in |
|---|---|---|
bot.move_to((10, 4)) | calling a function with arguments | everything, forever |
x = spot[0] | variables and indexing | every language |
if / elif / else | conditionals | every language |
for bot in my_bots: | iteration | every language |
[p for p in … if …] | filtering a collection | SQL, spreadsheets, data science |
def nearest_node(…) | defining functions, "best so far" | every algorithm you'll write |
self.home | object state | all object-oriented code |
And one lesson most beginners take years to meet: your program has to keep working when you aren't watching. Your swarm runs 1500 turns without you. It will face situations you never imagined, and it has to do something sensible anyway. That is the actual job of a programmer, and you've now done it.
strategies/ — read the shipped examples. They're commented, and they
each demonstrate one idea properly.