Brains
From a loop to think(me) and me.memory, with a practice arena.
Do this lesson in the simulatorThe built-in bots in the games are written as a brain: def think(me), a function the arena calls ten times a second. (Your own game programs can be plain scripts like every lesson, or brains; both run in the arena.) You have been writing the same thing all module with a for tick loop around it. This lesson turns a state machine into a brain, and puts it in an arena against other robots.
The loop is outside
A behaviour loop:
state = "look"
for tick in range(300):
if state == "look":
...
wait(0.1)
The same as a brain:
def think(me):
state = me.memory.get("state", "look")
if state == "look":
...
me.memory["state"] = state
The arena owns the loop and the waiting. Your function is the body of the loop. Anything that has to survive from one tick to the next goes in me.memory, because local variables are forgotten when the function returns.
Escape, as a loop
The robot starts inside three walls. Look around, find the gap, drive out:
# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()
def wrapped(h):
return (h + 180) % 360 - 180
state = "look"
samples = []
# do this 290 times (tick counts from 0)
for tick in range(290):
if state == "look":
# a full turn of readings
samples.append((distance(), heading()))
# spin clockwise on the spot at 50
turn_right(50)
if len(samples) >= 65:
# headings with room: the gap
opens = [h for d, h in samples if d > 40]
# the middle of the gap, not the longest ray
target = opens[len(opens) // 2]
state = "turn"
print("gap is around heading", round(target))
elif state == "turn":
error = wrapped(target - heading())
if abs(error) < 4:
state = "go"
else:
# forward, sideways, rotation: -100 to 100 each, until the next command
drive(0, 0, max(-40, min(40, error * 3)))
elif state == "go":
# where am I? (cm from where I started)
x, y = position()
if y < -32:
# leave the loop
break
# forward, sideways, rotation: -100 to 100 each, until the next command
drive(60, 0, wrapped(target - heading()) * 3)
# pause 0.1 s (the robot keeps doing what it was told)
wait(0.1)
# all motors off
stop()
print("out, at", position())
Note the gap logic: the longest single reading points at a corner of the opening, so the robot aims at the middle of all the headings that had room.
The same thing as a brain
The Control Points game below has no walls, so the brain is simpler: go to a zone, then hold it, stepping away from anyone who comes close (driving into an opponent is a foul) and going back in if you drift out. A state machine with two states and me.memory:
def think(me):
state = me.memory.get("state", "go")
zone = me.info["points"][1]
cx, cy = zone["x"] + zone["w"] / 2, zone["y"] + zone["h"] / 2
if state == "go":
...drive towards (cx, cy)...
if close enough:
state = "hold"
elif state == "hold":
if not in the zone any more:
state = "go"
...else step away from the nearest robot, or drift back to the middle...
me.memory["state"] = state
Try it
This is the real game arena with the built-in bots: you are on the red team. Your brain (a plain script would work here too), no room:
# maths: atan2, hypot, sin, cos, radians
import math
def wrapped(h):
return (h + 180) % 360 - 180
def go_towards(me, x, y, speed):
bearing = math.degrees(math.atan2(x - me.x, y - me.y))
a = math.radians(wrapped(bearing - me.heading))
me.drive(speed * math.cos(a), speed * math.sin(a), 0)
def think(me):
state = me.memory.get("state", "go")
# the middle one of the three zones
zone = me.info["points"][1]
cx, cy = zone["x"] + zone["w"] / 2, zone["y"] + zone["h"] / 2
in_zone = zone["x"] <= me.x <= zone["x"] + zone["w"] and zone["y"] <= me.y <= zone["y"] + zone["h"]
near = [o for o in me.others if math.hypot(o['x'] - me.x, o['y'] - me.y) < 12]
if state == "go":
go_towards(me, cx, cy, 100)
if math.hypot(me.x - cx, me.y - cy) < 4:
state = "hold"
elif state == "hold":
if not in_zone:
# drifted out: get back in
state = "go"
elif near:
o = near[0]
# too close: step straight away from them (driving into an opponent freezes you for 3 s)
go_towards(me, 2 * me.x - o['x'], 2 * me.y - o['y'], 60)
elif math.hypot(me.x - cx, me.y - cy) > 3:
# drift back to the middle
go_towards(me, cx, cy, 60)
else:
me.stop()
me.memory["state"] = state
Task: escape the box
Three walls around you. Find the gap with the distance sensor, drive out and into the green zone, touching nothing.
# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()
# as long as the thing ahead is further than 15 cm
while distance() > 15:
# drive forward at 50 (keeps going until the next command)
forward(50)
# pause 0.1 s (the robot keeps doing what it was told)
wait(0.1)
# all motors off
stop()
print("wall at", distance())
Challenges
- Escape faster: stop looking as soon as you have seen a reading over 40 and the readings are falling again.
- In the arena, add a third state: "shove", entered when another robot is within 10 cm, that drives at it for five ticks.
- Write the escape as a brain (
think(me)withme.memory) on paper. What changes?