Lesson 8 · Implementing the Navigator Class
Lesson 8 · Implementing the Navigator Class
Teacher mode is on. Toggle it off (bottom-right) to preview the student view.
Time to make the robot move. You'll package the turning logic from Lesson 7 into a
Navigator class that drives any Manhattan path on the grid. The best part: it does
the actual driving by reusing the LineTrack class you built in Module 2 — no motor
code to rewrite. That's the payoff of building reusable classes.
Learning Objectives
By the end of this lesson you will be able to:
- Design a
Navigatorwithposition,heading, and aline_trackobject - Implement
desired_heading(),turn_to(), anddrive_path()as methods - Explain delegation: the Navigator asks
LineTrackto turn and follow lines - Explain why the robot must "clear the intersection" when driving straight
- Integrate
ManhattanandNavigatorto drive a computed path on the robot
What the Navigator needs
Manhattan plans; Navigator drives. When you create one, it needs to know its
starting position, its starting heading, and it builds a LineTrack to do the physical
work:
HEADING_NAMES = ["N", "E", "S", "W"]
class Navigator:
def __init__(self, start, heading):
self.position = start
self.heading = heading # 0=N, 1=E, 2=S, 3=W
self.line_track = LineTrack()
Notice the Navigator does not create a DifferentialDrive — LineTrack already has
one inside it. The Navigator talks to LineTrack; LineTrack talks to the motors. Each
class has one job. That handing-off is called delegation.
How does the Navigator control the robot's motors?
The two logic methods
desired_heading() is the Lesson 7 logic, now a method that reads self.position
instead of taking a current parameter — the object already knows where it is:
def desired_heading(self, next_pos):
row_diff = next_pos[0] - self.position[0]
col_diff = next_pos[1] - self.position[1]
if row_diff == -1:
return 0 # North
elif col_diff == 1:
return 1 # East
elif row_diff == 1:
return 2 # South
elif col_diff == -1:
return 3 # West
And turn_to() is the while loop from Lesson 7 — but now each turn is a real turn,
self.line_track.turn_right(), which physically spins the robot:
def turn_to(self, desired):
while self.heading != desired:
self.line_track.turn_right()
self.heading = self.heading + 1
if self.heading == 4:
self.heading = 0
Why does desired_heading() use self.position instead of a 'current' parameter?
drive_path and clearing the intersection
drive_path loops the path: work out the heading, turn to it, follow the line to the
next intersection, update position. There's one subtle wrinkle:
def drive_path(self, path):
for next_pos in path:
needed = self.desired_heading(next_pos)
if self.heading == needed:
self.line_track.drivetrain.straight(8) # clear the intersection
self.turn_to(needed)
self.line_track.track_until_cross()
self.position = next_pos
When the robot needs to turn, turn_right() already drives it off the current
intersection as part of the turn. But when it's going straight (no turn needed), it's
still sitting on the cross it just arrived at — and track_until_cross() would instantly
detect that cross and stop. So for the straight-ahead case only, we drive forward 8 cm
first to clear it. track_until_cross() then line-follows until the next intersection —
no distance measurement, the sensors say when it's arrived.
Why drive straight(8) before track_until_cross() only when going straight ahead?
Activity · Trace it, then integrate
Before running anything, trace [(1,0), (1,1)] from (0,0) heading 0 (N) on paper.
For each step write down: the next intersection, the heading the robot needs, and
how many right turns get it there. Watch what happens when the count passes 3.
Now connect the two classes — Manhattan computes, Navigator drives:
manhattan = Manhattan((0, 0))
navigator = Navigator((0, 0), 0) # start heading North
path = manhattan.compute_path((2, 3))
print("Path:", path)
navigator.drive_path(path)
print("Arrived at:", navigator.position)
print("Heading:", HEADING_NAMES[navigator.heading])
Test it in stages: Manhattan alone (prints), then one short leg on the robot, then a
longer path. Watch the straight-through cells especially — does the robot clear each
intersection cleanly?
The path from Manhattan is [(1,0),(2,0),(2,1)]. How many times does drive_path call track_until_cross()?
Real-world connections
Delegating hard work to a well-built component is how real systems scale:
Libraries
You call a graphics or networking library instead of rewriting it — exactly how Navigator calls LineTrack.
Motor controllers
High-level planners send "go to X"; a lower-level controller handles the motors, just like this split.
Drive-by-wire
The steering software sets a target; a separate actuator system does the physical turning.
Wrap-up
- What three things does the Navigator store, and what does each do? (
position,heading,line_track— where it is, which way it faces, and the driver.) - What is delegation here? (Navigator asks LineTrack to move; it never touches motors itself.)
- Why the
straight(8)clear? (To leave the current cross so the sensors find the next one.)