Skip to content
English

Navigate the maze

In this tutorial you build a complete program, step by step. The robot drives around the maze avoiding walls and holes, and reports anything that looks like a wall token. It is the program you ran in Your first run.

It uses the starter robot, and what you learned in Movement and Sensors.

The complete program: maze.py · maze.cpp

Run it on world1.wbt, which looks like this:

The world world1 in Webots

First, the program creates the robot, gets the wheel motors and lets them turn without limit. speeds holds the speed of each wheel, which the rest of the program changes. reported remembers where the robot has already reported a token.

from controller import Robot
import struct
import numpy as np
import cv2 as cv
robot = Robot()
timeStep = 32 # Milliseconds between steps
maxVelocity = 6.28 # The maximum speed of the wheels, in radians per second
leftWheel = robot.getDevice("left wheel motor")
rightWheel = robot.getDevice("right wheel motor")
leftWheel.setPosition(float("inf")) # Let the wheels turn without limit
rightWheel.setPosition(float("inf"))
speeds = [maxVelocity, maxVelocity] # [left wheel speed, right wheel speed]
reported = [] # Positions where a token has already been reported

Next, it gets and enables the sensors of the starter robot:

The sample robot with its sensors labelled

leftDist = robot.getDevice("leftDist")
frontDist = robot.getDevice("frontDist")
rightDist = robot.getDevice("rightDist")
camera = robot.getDevice("camera")
colorSensor = robot.getDevice("color")
gps = robot.getDevice("gps")
emitter = robot.getDevice("emitter") # The emitter does not need to be enabled
for sensor in [leftDist, frontDist, rightDist, camera, colorSensor, gps]:
sensor.enable(timeStep)

These functions change the speeds to turn and spin. delay keeps the robot doing the same thing for a given number of milliseconds, and getBrightness reads the colour sensor as a single value from 0 (black) to 255 (white).

def turnRight():
speeds[0] = 0.6 * maxVelocity
speeds[1] = -0.2 * maxVelocity
def turnLeft():
speeds[0] = -0.2 * maxVelocity
speeds[1] = 0.6 * maxVelocity
def spin():
speeds[0] = 0.6 * maxVelocity
speeds[1] = -0.6 * maxVelocity
def delay(ms):
start = robot.getTime()
while robot.step(timeStep) != -1:
if (robot.getTime() - start) * 1000 > ms:
break
def getBrightness():
image = colorSensor.getImage()
return colorSensor.imageGetGray(image, colorSensor.getWidth(), 0, 0) # 0 (black) to 255 (white)

This function looks for wall tokens in a camera image. Tokens are bright squares on dark walls, so it looks for bright shapes that are roughly square and do not touch the edge of the image, where the floor usually is.

def seesToken(image):
height, width = camera.getHeight(), camera.getWidth()
image = np.frombuffer(image, np.uint8).reshape((height, width, 4)) # BGRA image for OpenCV
gray = cv.cvtColor(image, cv.COLOR_BGRA2GRAY)
_, bright = cv.threshold(gray, 200, 255, cv.THRESH_BINARY) # Bright parts in white
contours, _ = cv.findContours(bright, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
for contour in contours:
x, y, w, h = cv.boundingRect(contour)
if x == 0 or y == 0 or x + w == width or y + h == height:
continue # Shapes touching the edge of the image are usually the floor
if cv.contourArea(contour) > 40 and 0.65 < w / h < 1.5:
return True # A bright, roughly square shape on a wall
return False

This is a simple start: it cannot tell the symbols apart, and it does not see cognitive targets well. The Wall Tokens tutorial shows how to do better.

To score a token, the robot must stop for more than 1 second, and send its position and the type of the token. Each token can only be scored once, and reporting it again costs 5 points, so the program remembers where it has reported. Communication explains these messages.

def report(tokenType):
leftWheel.setVelocity(0) # Stop for more than 1 second
rightWheel.setVelocity(0)
delay(1300)
x = int(gps.getValues()[0] * 100) # Meters to centimeters
z = int(gps.getValues()[2] * 100)
emitter.send(struct.pack("i i c", x, z, tokenType.encode()))
robot.step(timeStep)
reported.append((x, z))
def alreadyReported():
# Reporting the same token again is a misidentification
x = gps.getValues()[0] * 100
z = gps.getValues()[2] * 100
return any(abs(x - rx) < 12 and abs(z - rz) < 12 for rx, rz in reported) # Closer than 1 tile

Finally, the main loop puts everything together. Each step, it starts at full speed, turns away from walls, turns back from holes, and reports what the camera sees when it is close to a wall in front.

while robot.step(timeStep) != -1:
speeds[0] = maxVelocity
speeds[1] = maxVelocity
if leftDist.getValue() < 0.05: # Wall on the left: turn right
turnRight()
if rightDist.getValue() < 0.05: # Wall on the right: turn left
turnLeft()
if frontDist.getValue() < 0.05: # Wall in front: spin
spin()
if getBrightness() < 80: # Black floor: turn away from the hole
spin()
leftWheel.setVelocity(speeds[0])
rightWheel.setVelocity(speeds[1])
delay(600)
# Close to a wall, and something on it: report it
if frontDist.getValue() < 0.07 and not alreadyReported() and seesToken(camera.getImage()):
report("H") # The type cannot be told yet, so guess "H"
leftWheel.setVelocity(speeds[0])
rightWheel.setVelocity(speeds[1])

This robot wanders: it does not know where it has been, and it guesses the type of every victim. It gets stuck sometimes, and the game moves it back to a checkpoint after 20 seconds. That is fine for a first program.

  • Find the best speeds and distances for your robot.
  • Follow the wall on one side: keep the side sensor at a constant distance, and turn when the front sensor sees a wall.
  • Change the speed depending on how close the walls are.

Then, learn to score points in Communication.