Learn it one match at a time
Work in pairs: one person predicts the behavior, the other changes one line. Swap roles after watching a replay. Keep a copy of the bot before each change. Start with the one-page quickstart.
Session 1: drive, turn, then search
First get robosoccer check my_bot.py working together. Use the quickstart chaser. You can leave setup out and leave the memory argument unused.
robosoccer run my_bot.py --vs chaser --perfect-sensors --half-seconds 5 --watch- Replace the action temporarily with
return {"move": (0.5, 0.0)}. Predict its path, then watch. - Try
return {"move": (0.0, 1.0)}, then a turn rate of-1.0. Which way does each turn? - Restore the chaser. Change its turn multiplier from
4.0to1.0. Does it line up more slowly? - In the no-ball branch, try
{}instead of a turning action. Why does it sometimes stay still? - Restore the search turn. Remove
--perfect-sensors; look for an empty camera list in the replay.
Keep speed low while experimenting. If a bot spins too much, inspect the ball's bearing and the action's turn sign before increasing speed. Success today: you can explain the action of one selected robot, with and without a visible ball.
Session 2: kick, then assign simple roles
Use the same short-match command. First add this near the end of the chaser's act:
kick = 0.8 if sensors["ball_in_kicker"] else 0.0
return {"move": (speed, 4.0 * bearing), "kick": kick}This replaces the final return, not the no-ball return. The ball must be close, in front, and the cooldown ready. Kicks go along the robot's facing direction. The robot can still push or kick into its own goal; watch which end it approaches from.
- Compare power
0.3and1.0. Does every kick request actually produce a kick? - Select a robot and inspect
ball_in_kickerandkick_cooldown. Find a failed kick request. - Add
if robot_id == 0: return {}at the start ofact. Which robot stops chasing? - Change that robot's action to a slow search turn. What happens to collisions between teammates?
- Give robot 0, 1 and 2 different search directions or speeds. Explain each role before adding radio.
A stationary robot is not automatically a goalkeeper. A real keeper must find and defend its own goal, and the team must obey the one-defender goal-area rule. Success today: all three robots do something intentional, and you can explain why kick range and direction matter.
Session 3: understand the two views, then add memory
Camera bearing asks where is that object compared with my nose? IMU heading asks which way is my nose pointing on the field map? They are different angles. Neither is your x/y position.
In the diagram the robot faces northeast: IMU heading is about +0.79 radians. The ball is to its right: camera bearing is about -0.79. Those signs can differ without an error. At halftime use the current attack_direction to identify the attacking end. A replay's spectator positions are not sent to your bot.
Predict this experiment: replace your whole act body with the following lines. Keep the function signature; indent this body inside it.
if sensors["phase"] == "kickoff":
return {}
memory["calls"] = memory.get("calls", 0) + 1
turn = 1.0 if memory["calls"] < 30 else 0.0
return {"move": (0.0, turn)}It turns for about one simulated second after kickoff, then brakes its turn. Each robot counts its own calls. The first two lines skip the counter while kickoff freezes movement, so you can see the turn after the freeze ends.
| Call | Supplied dictionary starts with | After changing its entry | Next call remembers |
|---|---|---|---|
| 1 | {} | {"calls": 1} | 1 |
| 2 | {"calls": 1} | {"calls": 2} | 2 |
| 3 | {"calls": 2} | {"calls": 3} | 3 |
Write memory["calls"] = ... to change the supplied dictionary. Writing memory = {"calls": 1} gives your local variable a new dictionary; the runner still holds the old one. A module-level calls = 0 also restarts each call because the bot module is recreated. Treat memory as a notebook the runner hands back to the same robot, not a notebook shared with its teammates.
- Change 30 calls to 60. Predict the duration using 30 ticks per second.
- Compare an entry update with local dictionary reassignment. Which one is remembered?
- Return to your chaser and save the last ball bearing. Use it to choose a search direction.
- Reset an old ball track when a kickoff starts. The field resets, but the notebook survives.
Success today: you can explain what is remembered, by which robot, and why camera bearing cannot be substituted for IMU heading.
Session 4A: simple radio, no saved history
Try this complete bot only after the earlier sessions. Each robot announces one number: its distance to the visible ball. A value of 1000.0 means it cannot see the ball. Among the bids it hears this tick, the closest robot chases. Equal distances prefer the lower robot ID. Others stop and let it approach.
def act(robot_id, sensors, memory):
balls = [obj for obj in sensors["camera"] if obj["kind"] == "ball"]
ball = min(balls, key=lambda obj: obj["distance"]) if balls else None
distance = ball["distance"] if ball else 1000.0
best_distance, chaser = distance, robot_id
for packet in sensors["messages"]:
if len(packet["data"]) != 1:
continue
other_distance = packet["data"][0]
sender = packet["sender"]
if other_distance < best_distance or (other_distance == best_distance and sender < chaser):
best_distance, chaser = other_distance, sender
move = (0.0, 0.0)
if ball is None:
move = (0.0, 1.0) # Search, even if a teammate is chasing.
elif chaser == robot_id:
bearing = ball["bearing"]
move = (0.8 if abs(bearing) < 0.4 else 0.0, 4.0 * bearing)
return {"move": move, "kick": 0.0, "broadcast": [distance]}This intentionally uses only this tick's inbox; it has no memory or timestamps. Radio messages take 2–5 ticks and can be lost. Different robots can hear different bids and both chase. A delivered old distance can win by mistake. This is an experiment to make those failures visible, not a promise of perfect coordination.
- Pause a replay and select each teammate. Compare their inboxes on the same tick.
- Find a moment when two robots think they should chase. Which bid is missing or delayed?
- Disable the broadcast and compare crowding. Does radio always improve this simple bot?
- Discuss a support behavior for non-chasers before adding more code.
Each number describes the sender's distance, which is comparable across robots. A sender's bearing describes its own view and cannot be used directly as another robot's steering angle.
Session 4B: robust radio, one repair at a time
Now use the robust striker_bid helper in the full handbook. Its packet is a different protocol: [1, distance, observation_tick]. All robots must use the same version.
| Problem you observed | Add one repair | Explain it before coding |
|---|---|---|
| A packet is missing this tick | Remember recent bids in memory | When should you stop trusting an unheard teammate? |
| A teammate disappeared long ago | Expire old observations | Why should a very old short distance stop winning? |
| An older packet arrives late | Keep the newest timestamp per sender | Which of observations at ticks 30 and 33 should replace the other? |
| Two bids have the same distance | Agree on a robot-ID tie-break | Which robot wins if IDs 0 and 2 have the same bid? |
| A goal resets all positions | Clear saved bids at kickoff | Why is the pre-goal distance no longer useful? |
Work a paper example before copying the helper: robot 0 last reported 0.4 m at tick 20; robot 2 reports 0.6 m at tick 33. At tick 34, a ten-tick expiry removes robot 0's bid, so robot 2 wins. If robot 0's newer observation from tick 32 is already saved, a late packet from tick 29 must not overwrite it. Even the robust helper can briefly disagree under packet loss. Compare replays, then add a margin before switching roles or a better support behavior.
Keep the experiments fair
Use short perfect-sensor matches to learn one control, then turn noise back on. Later compare several seeds and both sides in full matches. Change one behavior at a time, record your prediction and result, and judge what the robot actually did. Use the handbook and API reference to look up a detail when you need it; they are not required reading for session 1.