Mission 7  ·  Solution

The Grand Maze

There is more than one right answer. Yours may look different — that is fine if it works.

The route
1234GREENFINISH START
Dashed line = the car’s path. Red dots = the dead-end detour. Numbered circles are the decision points.

What the car senses

  1. T-junction: wall ahead, right and left open → right first: into the dead end.
  2. Dead end: everything blocked → turn around.
  3. Corner: wall ahead, right open → turn right along the exit corridor.
  4. Doorway: no wall ahead, so the car drives on until the belly sensor sees the black start line → switch to line following.
  5. Follow the tape to the green tile. Stop. Sound.
First

Make these My Blocks

The program

Word Blocks

Same thing in Python
from hub import port, sound, light_matrix
import runloop, motor, motor_pair, distance_sensor, color_sensor, color

DRIVE      = motor_pair.PAIR_1
STEER      = port.D            # large motor, steers the back
TURRET     = port.C            # large motor that swings the distance sensor
EYES       = port.B            # distance sensor (rides on the turret)
BELLY      = port.F            # color sensor pointing down
STOP_MM    = 200               # wall closer than 20 cm ahead = stop and look
OPEN_MM    = 300               # farther than this = OPEN (about one lane width - measure yours!)
LOOK_RIGHT = 90                # turret position that looks right (tune for your gearing)
LOOK_LEFT  = -90               # turret position that looks left
TARGET_REFLECT = 50          # halfway between white and black readings
K_LINE     = 0.6               # line-following strength
DEG_PER_CM = 20                # wheel degrees per cm: tune
TURN_CM    = 20                # roll while steering: tune

def wall_closer_than(mm):
    d = distance_sensor.distance(EYES)
    return 0 < d < mm

def on_color(c):
    return color_sensor.color(BELLY) == c

async def drive_cm(cm, speed=300):
    await motor_pair.move_for_degrees(DRIVE, int(cm * DEG_PER_CM), 0, velocity=speed)

async def turn_right():
    await motor.run_to_absolute_position(STEER, 60, 500)
    await drive_cm(TURN_CM)
    await motor.run_to_absolute_position(STEER, 0, 500)

async def turn_left():
    await motor.run_to_absolute_position(STEER, -60, 500)
    await drive_cm(TURN_CM)
    await motor.run_to_absolute_position(STEER, 0, 500)

async def turn_around():
    await turn_left()          # two 90-degree turns = a U-turn inside the lane
    await turn_left()

async def celebrate():
    light_matrix.show_image(light_matrix.IMAGE_HAPPY)
    await sound.beep(880, 300)
    await sound.beep(1175, 500)

async def look_open(angle):
    """Swing the turret to 'angle' and say whether that way is OPEN."""
    await motor.run_to_absolute_position(TURRET, angle, 700)
    await runloop.sleep_ms(200)                 # let the sensor settle
    d = distance_sensor.distance(EYES)          # -1 = nothing seen = wide open
    return d == -1 or d > OPEN_MM

async def look_around():
    right_open = await look_open(LOOK_RIGHT)
    left_open = await look_open(LOOK_LEFT)
    await motor.run_to_absolute_position(TURRET, 0, 700)   # face ahead again
    return right_open, left_open

async def drive_until_wall_or(stop_color):
    """Drive until a wall is close ahead, or stop_color is under the car."""
    motor_pair.move(DRIVE, 0, velocity=300)
    await runloop.until(lambda: wall_closer_than(STOP_MM) or on_color(stop_color))
    motor_pair.stop(DRIVE)

async def hug_right_wall_until(stop_color):
    """Stop-and-look maze solver (right-hand rule). Returns (route, dead_ends)."""
    route = []
    dead_ends = 0
    while not on_color(stop_color):
        await drive_until_wall_or(stop_color)
        if on_color(stop_color):
            break
        right_open, left_open = await look_around()
        if right_open:
            await turn_right()
            route.append("R")
        elif left_open:
            await turn_left()
            route.append("L")
        else:                               # all sides blocked: a dead end
            await turn_around()
            dead_ends += 1
            route.append("U")
    return route, dead_ends

async def follow_line_until_green():
    motor_pair.move(DRIVE, 0, velocity=250)
    while not on_color(color.GREEN):
        error = color_sensor.reflection(BELLY) - TARGET_REFLECT
        angle = max(-60, min(60, int(error * K_LINE)))
        await motor.run_to_absolute_position(STEER, angle, 1000)
    motor_pair.stop(DRIVE)
    await motor.run_to_absolute_position(STEER, 0, 800)

async def main():
    motor_pair.pair(DRIVE, port.A, port.E)      # your drive motor ports
    await hug_right_wall_until(color.BLACK)
    await follow_line_until_green()
    await celebrate()

runloop.run(main())

Starting values only. Steering angle, roll distances, the OPEN limit and the line target depend on your car and maze. Measure, then tune one at a time. I have not run these on a car — expect to adjust.

Level up

Ready for more?

← Hints All missions

Printable version: mission-7-solution.pdf