Lesson 8 of 8 in the Pre-COSMOS series, 39 slides, and the capstone. It snaps the whole series together into a dry run of camp's signature task: a Rover class from Lesson 2, driven by a control loop from Lesson 3 whose state is steered by an FSM next_state dictionary, also from Lesson 3, fed by a NumPy camera grid from Lesson 4 and a red color mask from Lesson 5, and paced by time.sleep, all to follow colored markers through a maze. The second half is explicit traceback practice: the LAST line names the error type and message, the line just above it points at the offending code, and the five errors you met across the series - a TypeError saying the object is not callable, a KeyError, an IndexError, an AttributeError, and a NameError - each map to one specific mistake. Three debugging traps show real tracebacks copied from CPython - a KeyError from a typo in a state name, a TypeError from an int that is not callable, and an out-of-bounds IndexError - and you find the culprit line and the fix. There are five checks and a scaffolded your-turn Maze Rover Simulator build that ends on "you're ready for camp." Every snippet was run on CPython 3.12 with numpy 2.3, and the printed output and traceback text were copied verbatim.
Subject: Python · 68 slides · code lesson
Open the interactive version of this deck · Homework for this lesson
Title
Pre-COSMOS · Lesson 8 of 8
Everything you learned - the class, the loop, the FSM, the NumPy camera, the red mask - assembled into camp's signature task. Plus the skill you'll use most: reading the error and finding the line.
Objectives
This is the last lesson before camp. We assemble all eight lessons into one running robot, then practice the thing you'll spend half your camp time on: figuring out why won't this run. By the end you can:
Warm-up
Discussion prompt
Before we open Capstone: Maze Rover + Reading Tracebacks: without looking back, what was the main idea of while Loops as Robot Control Loops, and what could you do by the end of it that you could not do before?
Hint: One sentence for the idea, one for the skill. If the second one is blank, that is the part to revisit.
Answer:
Day 7 maze upgrade (34 slides), build-it-yourself: while loops as a robot control loop - sense, decide, act, update, stop - around 'what makes this robot stop?'. Students BUILD the controller themselves in four 'your turn' levels (skeleton + target behavior, no answer); the teacher demo shows only the target behavior; debug challenges hide the body; and the complete solution is revealed on one slide at the very end.
Concept
Figure (svg): Six labeled boxes feeding into one rover: a Rover class, a control loop, an FSM dict, a NumPy camera grid, a red mask, and time.sleep, all arrows pointing into a central rover box.
Every lesson built one part of the same machine. None of them was the whole robot - until now.
Camp's signature task: send a rover through a maze, following colored markers. That needs all six pieces working together.
Counterexample
Discussion prompt
Every lesson built one part of the same machine. None of them was the whole robot - until now.
That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.
Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.
Answer:
Camp's signature task: send a rover through a maze, following colored markers. That needs all six pieces working together.
Concept
Two halves: assemble it, then debug it.
Matching
Match the pairs
From Today's roadmap — match each one to what it actually does. The descriptions have been shuffled.
Why: Assemble, The FSM, Detect & steer, Tracebacks are easy to tell apart while they are sitting next to their descriptions and much harder afterwards, which is what this checks.
Section
Section 1
Concept
Here's the parts list. You've met every one - the capstone is just wiring, not new ideas.
energy and .move().for loop that steps the mission forward.next_state dict (L3) - decides what the rover does each step.time.sleep - a pause so a human can watch it run.Intuition
Think of the loop as the rover's heartbeat - one beat per step. On each beat it asks one question: what state am I in, and what do I do?
The FSM dict is the tiny brain answering 'what next.' The camera + red mask are the eyes. The Rover object is the body that actually moves. Same loop you've written a dozen times - it just has senses now.
Analogy
Discussion prompt
Explain A loop with a tiny brain by analogy to something with no Python in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.
Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.
Answer:
Think of the loop as the rover's heartbeat - one beat per step. On each beat it asks one question: what state am I in, and what do I do?
Worked example
The whole 'brain' is one dict. Each state maps to the state that follows it. Cycle it and you get the rover's rhythm.
next_state = {"scan": "steer", "steer": "drive", "drive": "scan"}
state = "scan"
for i in range(6):
print(state)
state = next_state[state]state = next_state[state] looks up where to go next, then the loop repeats. Watch the state walk the cycle.
| i | state (printed) | next_state[state] |
|---|---|---|
| 0 | scan | steer |
| 1 | steer | drive |
| 2 | drive | scan |
| 3 | scan | steer |
| 4 | steer | drive |
| 5 | drive | scan |
Real output (6 prints): scan steer drive scan steer drive - the cycle repeats forever, which is exactly what a control loop should do.
Comparison
Comparison matrix
From The FSM brain: next_state: refill the state (printed) column from what you know. The rest of the table is as it appeared.
| i | state (printed) | next_state[state] |
|---|---|---|
| 0 | scan | steer |
| 1 | steer | drive |
| 2 | drive | scan |
| 3 | scan | steer |
| 4 | steer | drive |
| 5 | drive | scan |
Worked example
The camera is a NumPy grid: 1 where the marker is red, 0 elsewhere. frame == 1 builds the red mask; .sum() counts the red pixels.
import numpy as np
frame = np.array([[0, 0, 1, 0],
[0, 1, 1, 0],
[0, 0, 0, 0],
[1, 0, 0, 0]])
def red_count(frame):
return int((frame == 1).sum())
print(red_count(frame))(frame == 1) is a True/False mask; summing it counts the Trues - one per red pixel.
| row | values | reds in row |
|---|---|---|
| 0 | 0 0 1 0 | 1 |
| 1 | 0 1 1 0 | 2 |
| 2 | 0 0 0 0 | 0 |
| 3 | 1 0 0 0 | 1 |
Total reds = 1 + 2 + 0 + 1 = 4. Real output: 4.
Trade off
Comparison matrix
From The eyes: a camera frame + red count: every row here is a choice with a cost. Fill the values column, then say which row you would actually pick and what you give up for it.
| row | values | reds in row |
|---|---|---|
| 0 | 0 0 1 0 | 1 |
| 1 | 0 1 1 0 | 2 |
| 2 | 0 0 0 0 | 0 |
| 3 | 1 0 0 0 | 1 |
Worked example
A real robot loop would spin too fast for a human to watch. time.sleep(seconds) pauses each step so you can see the rover think. (We use 0.0 here so the trace is instant.)
import time
for tick in range(3):
print(f"tick {tick}")
time.sleep(0.0)
print("done")time.sleep just waits, then the loop continues - it changes the pace, never the result.
| tick | real output |
|---|---|
| 0 | tick 0 |
| 1 | tick 1 |
| 2 | tick 2 |
| after loop | done |
Pattern
Step through it
Step through The pacing: time.sleep one row at a time. What is driving the change, and what would the row after the last one be?
Concept
The connection that makes it a maze follower: when the scan state sees red, the steer state turns the rover toward the marker. No red -> hold course.
perception-action loop — The core robot pattern: sense the world (camera), decide (FSM + red detection), act (move the rover) - then repeat. Every autonomous robot is some version of this.
Explain it
Discussion prompt
Explain Wiring it: detect red -> steer toward it to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.
Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.
Answer:
The connection that makes it a maze follower: when the scan state sees red, the steer state turns the rover toward the marker. No red -> hold course.
Worked example
All six pieces in one program: the Rover, the loop, the next_state FSM, a sequence of camera frames, the red_count mask. It runs the perception-action loop for 6 steps.
rex = Rover("Rex")
state = "scan"
for i in range(6):
frame = frames[i % len(frames)]
if state == "scan":
print(f"[scan] red pixels seen: {red_count(frame)}")
elif state == "steer":
if red_count(frame) > 0:
print("[steer] red detected - turning toward it")
else:
print("[steer] no red - holding course")
elif state == "drive":
rex.move()
state = next_state[state]Each pass: read the frame, act on the state, then advance the FSM. drive calls rex.move(), which spends 10 energy.
| i | state | real printed output |
|---|---|---|
| 0 | scan | [scan] red pixels seen: 0 |
| 1 | steer | [steer] red detected - turning toward it |
| 2 | drive | Rex rolls forward. Energy: 90 |
| 3 | scan | [scan] red pixels seen: 0 |
| 4 | steer | [steer] red detected - turning toward it |
| 5 | drive | Rex rolls forward. Energy: 80 |
That's the capstone running. Same six lines of output every time - copied verbatim from CPython 3.12.
Intuition
Stretch this 6-step run across a real maze: every scan is a glance at the camera, every steer is a turn toward the red marker the glance found, every drive is a roll forward. Repeat, and the rover threads the maze marker by marker.
Nothing here is new - it's the same six pieces looping. A longer maze is just more turns of the same loop, with different camera frames coming in.
Ranking
Put in order
These are the steps of The capstone recipe, scrambled. Put them back in order before the next slide shows you.
rover = Rover(name).next_state = {...}.red_count(frame) over the frame.state (steer toward red, drive, scan).state = next_state[state], then repeat.Why: This is the order the recipe itself gives. Recalling the sequence without the slide in front of you is the difference between recognising the method and being able to run it — most of what goes wrong in practice is a step done out of turn.
Pattern
Any sense-decide-act robot you build at camp follows this shape:
rover = Rover(name).next_state = {...}.red_count(frame) over the frame.state (steer toward red, drive, scan).state = next_state[state], then repeat.Edge cases
Discussion prompt
The capstone recipe works on the cases you have just seen. Push it to the edge: what is the most degenerate input it still handles — empty, zero, one item, everything equal — and what is the first case where it stops being true? Name the case, not just "it breaks".
Hint: Try the smallest legal input, then the largest, then the one where two things collide. Methods are specified at their edges; the middle takes care of itself.
Answer:
Any sense-decide-act robot you build at camp follows this shape:
Elimination
Eliminate the wrong options
The capstone maze rover combines which set of pieces?
3 of these 4 are wrong. Strike them one at a time, and say what rules each one out before you strike the next. The survivor is the answer.
Survives elimination: A
Why: The capstone wires together all six pieces from the series: the Rover object (L2), the control loop (L3), the FSM next_state dict (L3), the NumPy camera grid (L4), the red color mask (L5), and time.sleep for pacing.
Check
Recall the parts list before answering.
Check your understanding
The capstone maze rover combines which set of pieces?
Answer: A
Why: The capstone wires together all six pieces from the series: the Rover object (L2), the control loop (L3), the FSM next_state dict (L3), the NumPy camera grid (L4), the red color mask (L5), and time.sleep for pacing.
Prediction
Predict first
Starting at "scan", what are the first four states the loop visits?
Answer it in your own words, now, with nothing to choose from. The options are on the next slide — and picking the right one off a list is an easier skill than producing it.
Correct: scan, steer, drive, scan
Why: Each step looks up state = next_state[state]: scan -> steer -> drive -> scan. After three steps the cycle wraps back to scan, so the fourth state is scan again.
Check
next_state = {"scan": "steer", "steer": "drive", "drive": "scan"}. Start at scan.
Check your understanding
Starting at "scan", what are the first four states the loop visits?
Answer: A
Why: Each step looks up state = next_state[state]: scan -> steer -> drive -> scan. After three steps the cycle wraps back to scan, so the fourth state is scan again.
Check
In the steer state the rover checks red_count(frame).
Check your understanding
In the steer state, what makes the rover decide to turn toward the marker?
Answer: A
Why: The steer branch turns toward the marker when red_count(frame) > 0 - meaning the red mask found at least one red pixel. No red pixels means hold course instead.
Section
Section 2
Concept
At camp you'll hit errors constantly - everyone does. The skill that separates 'stuck for an hour' from 'fixed in a minute' is reading the traceback instead of panicking at the red text.
A traceback is not noise. It's Python telling you exactly what broke and exactly where. You just have to know which lines to read.
Picture it
Figure (svg): A traceback block with an arrow pointing at the second-to-last line labeled the culprit line, and an arrow at the last line labeled what went wrong.
Discussion prompt
Read the picture before the words. What is this showing, and what is the one thing it is built to make obvious? Commit to an answer, then read on.
Hint: Name the parts, then say what changes between them — and if nothing changes, say what is being held still.
Answer:
Tracebacks read like a receipt printed in reverse. Start at the bottom: the last line is the headline - what went wrong (the error type and message).
Intuition
Tracebacks read like a receipt printed in reverse. Start at the bottom: the last line is the headline - what went wrong (the error type and message).
Then look one line up from there: that's the actual line of your code that triggered it - where it went wrong. Those two lines answer 95% of 'why won't this run.'
Figure (svg): A traceback block with an arrow pointing at the second-to-last line labeled the culprit line, and an arrow at the last line labeled what went wrong.
Explain it
Discussion prompt
Explain Read a traceback bottom-up to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.
Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.
Answer:
Tracebacks read like a receipt printed in reverse. Start at the bottom: the last line is the headline - what went wrong (the error type and message).
Worked example
Here is a real traceback from CPython 3.12, copied verbatim. Let's label every part.
Traceback (most recent call last):
File "trap_a.py", line 5, in <module>
state = next_state["drve"]
~~~~~~~~~~^^^^^^^^
KeyError: 'drve'Line 5 (the ^^^ carets) marks the exact spot; the last line names the error. Read those two and you know the bug.
| line in traceback | what it tells you |
|---|---|
| Traceback (most recent...) | an error happened; details follow |
| File "...", line 5 | the file and line number |
| state = next_state["drve"] | the offending code (the culprit line) |
| ~~~^^^ carets | points under the exact bad part |
| KeyError: 'drve' | the LAST line: the error type + message |
Anomaly
Predict first
A student writes this, and it looks reasonable:
You ran the loop and got red text. The program prints scan once, then dies.
It is wrong. Say what breaks — and say it before you turn the page.
Correct: You meant drive but typed drve.
Read the last line, then look up one line, then fix the key.
Why: You meant drive but typed drve. The dict has no key 'drve', so the lookup fails.
Trap
You ran the loop and got red text. The program prints scan once, then dies.
The code: state = next_state["drve"]
Why: You meant drive but typed drve. The dict has no key 'drve', so the lookup fails.
Real traceback ends in KeyError: 'drve', pointing at line 5
Why: The last line names the error (KeyError) and the missing key ('drve'); the line above it is the lookup that failed.
Read the last line, then look up one line, then fix the key.
KeyError: 'drve' -> a key that isn't in the dict
Why: A KeyError always means: you asked a dict for a key it doesn't have. The message even quotes the bad key.
Fix: state = next_state[state] (no typo'd literal)
Why: Use the real state variable - or spell drive correctly. The keys are exactly scan, steer, drive.
Anomaly
Predict first
A student writes this, and it looks reasonable:
You wanted the rover's battery level, so you wrote rex.energy().
It is wrong. Say what breaks — and say it before you turn the page.
Correct: energy is a number (an int), not a method.
Drop the parentheses - data is read with no ().
Why: energy is a number (an int), not a method. The () tells Python to call the number 90 - which makes no sense.
Trap
You wanted the rover's battery level, so you wrote rex.energy().
The code: print(rex.energy())
Why: energy is a number (an int), not a method. The () tells Python to call the number 90 - which makes no sense.
Real traceback ends in TypeError: 'int' object is not callable, pointing at the rex.energy() line
Why: The carets sit under rex.energy(). 'object is not callable' = you put () on something that isn't a function.
Drop the parentheses - data is read with no ().
Fix: print(rex.energy)
Why: energy is an attribute, so you read it straight, no parentheses. Actions get (); data does not.
Rule of thumb for 'X object is not callable'
Why: You added () to a value (an int, list, str...). Remove the (), or you meant a different name that really is a function.
Break the constraint
Discussion prompt
The rule this trap just fixed:energy is an attribute, so you read it straight, no parentheses. Actions get (); data does not.
Now break it on purpose. Build a case that violates it and follow the consequences until something visibly fails. Where does the failure first show up — and would you have noticed it if you had not been looking?
Hint: The dangerous rules are the ones whose violation still produces an answer. If yours fails loudly, try to find one that fails quietly.
Answer:
energy is a number (an int), not a method. The () tells Python to call the number 90 - which makes no sense.
Anomaly
Predict first
A student writes this, and it looks reasonable:
Your camera frame has 2 rows, but you reach for frame[5].
It is wrong. Say what breaks — and say it before you turn the page.
Correct: Valid row indexes are 0 and 1. Index 5 is past the end of the array's first axis.
Index within range - check the shape first.
Why: Valid row indexes are 0 and 1. Index 5 is past the end of the array's first axis.
Trap
Your camera frame has 2 rows, but you reach for frame[5].
The code: print(frame[5]) on a 2-row grid
Why: Valid row indexes are 0 and 1. Index 5 is past the end of the array's first axis.
Real traceback: IndexError: index 5 is out of bounds for axis 0 with size 2
Why: The last line spells it out: axis 0 (rows) only has size 2, so index 5 doesn't exist. NumPy tells you the size.
Index within range - check the shape first.
Fix: use a valid index like frame[0] or frame[1]
Why: An IndexError means the position is past the end. Print frame.shape to see how big it actually is.
Rule of thumb for IndexError
Why: You asked for an item beyond the last one. Indexes start at 0 and stop at length minus 1.
Two truths and a lie
Sort into buckets
Some of these hold up and some are the exact mistakes this lesson is built to prevent. Sort them.
scan once, then dies.; You wanted the rover's battery level, so you wrote rex.energy().Worked example
The last two errors of the series, both real CPython 3.12 tracebacks. rex.faceing is a typo for facing; using Rover before it's defined or imported is a NameError.
rex = Rover("Rex")
print(rex.faceing)
# ---
# (in a fresh file, before defining Rover)
# rex = Rover("Rex")Both last lines name the error AND hint the fix - 3.12 even suggests 'Did you mean: ...'.
| mistake | last line of the real traceback |
|---|---|
| rex.faceing (typo) | AttributeError: 'Rover' object has no attribute 'faceing'. Did you mean: 'facing'? |
| Rover(...) before it exists | NameError: name 'Rover' is not defined |
AttributeError -> check the .name spelling and that the object really has it. NameError -> you forgot to define it or import it (or typed it wrong).
Concept
Across this whole series you met exactly five errors. Each one maps to one kind of mistake. Memorize this table and you can fix most camp bugs on sight.
| error (last line) | what it means | the mistake |
|---|---|---|
| TypeError: 'int' object is not callable | you called a non-function | added () to data: rover.energy() |
| KeyError: 'drve' | dict has no such key | wrong/typo'd dict key: next_state["drve"] |
| IndexError: ... out of bounds | position past the end | index too big: frame[5] on 2 rows |
| AttributeError: ... no attribute 'speed' | object lacks that name | wrong attribute: rover.speed |
| NameError: name 'Rover' is not defined | name was never created | used a name before defining/importing it |
Analogy
Discussion prompt
Explain The five errors, each a specific mistake by analogy to something with no Python in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.
Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.
Answer:
Across this whole series you met exactly five errors. Each one maps to one kind of mistake. Memorize this table and you can fix most camp bugs on sight.
Intuition
You don't have to memorize fixes - the error type points you at the kind of thing to check:
.name the object doesn't have.Counterexample
Discussion prompt
You don't have to memorize fixes - the error type points you at the kind of thing to check:
That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.
Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.
Commit first
Predict first
In a Python traceback, which line tells you the error type and message?
Commit to an answer, then rate it — certain, fairly sure, or guessing — and write the rating down before you turn the page.
Correct: The very last line
Why: The last line of a traceback names the error type and message, like KeyError: 'drve' or TypeError: 'int' object is not callable. Read it first, then look one line up for the culprit code.
The rating matters as much as the answer: confident-and-wrong is the combination that survives revision, because nothing about it feels like it needs revisiting.
Check
Picture a traceback on your screen. Where do you look first?
Check your understanding
In a Python traceback, which line tells you the error type and message?
Answer: A
Why: The last line of a traceback names the error type and message, like KeyError: 'drve' or TypeError: 'int' object is not callable. Read it first, then look one line up for the culprit code.
Prediction
Predict first
next_state = {"scan":"steer"} and rover.energy is 100. Which line raises a KeyError?
Answer it in your own words, now, with nothing to choose from. The options are on the next slide — and picking the right one off a list is an easier skill than producing it.
Correct: next_state["drive"]
Why: next_state only has the key "scan". Asking for "drive" - a key that isn't in the dict - raises KeyError: 'drive'. KeyError is always a missing dict key.
Check
One of these lines runs cleanly; the rest each raise a different error. Read carefully.
Check your understanding
next_state = {"scan":"steer"} and rover.energy is 100.
Which line raises a KeyError?
Answer: A
Why: next_state only has the key "scan". Asking for "drive" - a key that isn't in the dict - raises KeyError: 'drive'. KeyError is always a missing dict key.
Elimination
Eliminate the wrong options
print(rex.speed) blows up. What is the last line of the traceback?
3 of these 4 are wrong. Strike them one at a time, and say what rules each one out before you strike the next. The survivor is the answer.
Survives elimination: A
Why: rex is a real object but has no attribute named speed, so reading rex.speed raises AttributeError: 'Rover' object has no attribute 'speed'. AttributeError = a .name the object doesn't have.
Check
rex is a Rover with name, energy, and facing - but no speed.
Check your understanding
print(rex.speed) blows up. What is the last line of the traceback?
Answer: A
Why: rex is a real object but has no attribute named speed, so reading rex.speed raises AttributeError: 'Rover' object has no attribute 'speed'. AttributeError = a .name the object doesn't have.
Section
Section 3 · build it yourself
Concept
Build the whole thing yourself: a robot that uses a class + control loop + FSM dict + NumPy camera + color detection + time.sleep to follow markers. Type every line, run after each one, and read your errors out loud - don't erase them.
| # | do this | tools you'll use |
|---|---|---|
| 1 | a camera frame + the red mask | np.array, frame == 1, .sum() |
| 2 | the FSM dict + the loop | next_state dict, for, state = next_state[state] |
| 3 | wire detection -> steer decision | red_count(frame) > 0 |
| 4 | run it, then read one traceback you trigger | time.sleep, the last line of the error |
Comparison
Comparison matrix
From The build: a maze rover dry run: refill the tools you'll use column from what you know. The rest of the table is as it appeared.
| # | do this | tools you'll use |
|---|---|---|
| 1 | a camera frame + the red mask | np.array, frame == 1, .sum() |
| 2 | the FSM dict + the loop | next_state dict, for, state = next_state[state] |
| 3 | wire detection -> steer decision | red_count(frame) > 0 |
| 4 | run it, then read one traceback you trigger | time.sleep, the last line of the error |
Worked example
Your turn: make a small NumPy camera frame and count its red pixels. Predict the count before you run it.
Hint: build the mask with frame == 1, then .sum() it; wrap in int(...). You don't need a loop.
import numpy as np
frame = np.array([[0, 0, 1],
[0, 1, 0],
[0, 0, 0]])
red = (frame == 1)
print(red)
print(int(red.sum()))| line | real output |
|---|---|
| print(red) | [[False False True] |
| [False True False] | |
| [False False False]] | |
| print(int(red.sum())) | 2 |
Worked example
Your turn: define next_state and step it 6 times from "scan". Predict the six states before running.
Hint: the engine is state = next_state[state] at the bottom of the loop; print state at the top.
next_state = {"scan": "steer", "steer": "drive", "drive": "scan"}
state = "scan"
for i in range(6):
print(state)
state = next_state[state]| i | real output |
|---|---|
| 0 | scan |
| 1 | steer |
| 2 | drive |
| 3 | scan |
| 4 | steer |
| 5 | drive |
Pattern
Step through it
Step through Milestone 2 — the FSM dict + loop one row at a time. What is driving the change, and what would the row after the last one be?
Worked example
Your turn: in the steer state, turn toward the marker only when there's red. Predict what an all-zero frame does before running.
Hint: the deciding test is red_count(frame) > 0; the else branch holds course.
def red_count(frame):
return int((frame == 1).sum())
def steer(frame):
if red_count(frame) > 0:
print("[steer] red -> turn toward it")
else:
print("[steer] none -> hold")
steer(np.array([[0, 0, 1], [0, 1, 0]]))
steer(np.array([[0, 0, 0], [0, 0, 0]]))| call | red_count | real output |
|---|---|---|
| steer(red frame) | 2 | [steer] red -> turn toward it |
| steer(empty frame) | 0 | [steer] none -> hold |
Worked example
Your turn: trigger one error on purpose, then read it. Set state = "scann" (a typo) and look it up. Predict the error type before running.
Hint: a missing dict key gives one specific error - look at the last line to name it, the line above for the culprit.
next_state = {"scan": "steer", "steer": "drive", "drive": "scan"}
state = "scann"
print(next_state[state])| traceback line | real text |
|---|---|
| File "...", line 3 | print(next_state[state]) |
| last line | KeyError: 'scann' |
| the fix | spell it "scan" - the key that exists |
You read the error, named it (KeyError), found the line, and fixed it. That's the whole debugging loop.
Trade off
Comparison matrix
From Milestone 4 — run it & read a traceback: every row here is a choice with a cost. Fill the real text column, then say which row you would actually pick and what you give up for it.
| traceback line | real text |
|---|---|
| File "...", line 3 | print(next_state[state]) |
| last line | KeyError: 'scann' |
| the fix | spell it "scan" - the key that exists |
Worked example
Your turn: assemble everything - Rover, the FSM, a list of camera frames, red_count, time.sleep, the loop. Predict the final energy before running.
import numpy as np, time
rex = Rover("Rex")
next_state = {"scan": "steer", "steer": "drive", "drive": "scan"}
frames = [np.array([[0,0,0],[0,0,0]]),
np.array([[0,0,1],[0,1,0]]),
np.array([[1,0,0],[0,0,0]])]
state = "scan"
for i in range(6):
frame = frames[i % len(frames)]
if state == "scan":
print(f"[scan] saw {red_count(frame)} red")
elif state == "steer":
print("[steer] red -> turn" if red_count(frame) > 0 else "[steer] none -> hold")
elif state == "drive":
rex.move()
time.sleep(0.0)
state = next_state[state]| i | state | real output |
|---|---|---|
| 0 | scan | [scan] saw 0 red |
| 1 | steer | [steer] red -> turn |
| 2 | drive | Rex rolls forward. Energy: 90 |
| 3 | scan | [scan] saw 0 red |
| 4 | steer | [steer] red -> turn |
| 5 | drive | Rex rolls forward. Energy: 80 |
If yours ends with Rex rolls forward. Energy: 80 - you built the whole maze rover: class, loop, FSM, camera, detection, pacing, all working together.
Comparison
Comparison matrix
From Milestone 5 — the full simulator: refill the state column from what you know. The rest of the table is as it appeared.
| i | state | real output |
|---|---|---|
| 0 | scan | [scan] saw 0 red |
| 1 | steer | [steer] red -> turn |
| 2 | drive | Rex rolls forward. Energy: 90 |
| 3 | scan | [scan] saw 0 red |
| 4 | steer | [steer] red -> turn |
| 5 | drive | Rex rolls forward. Energy: 80 |
Worked example
Explain your simulator out loud, piece by piece: point to the class, the FSM dict, the camera frame, the red mask, the loop, and the steer decision. Name what each one does.
Break it on purpose, then fix it from the traceback: typo a state (KeyError), put () on energy (TypeError), index off the grid (IndexError). Each time, read the last line, find the line above, fix it.
You can now assemble a robot from six independent pieces and debug it when it won't run. That's exactly what camp asks of you.
Eight lessons done. The dot, the class, the loop, the FSM, NumPy, color, decisions, and debugging are all yours now. You're ready for camp.
Connect it up
Draw it
One page, no notation unless you need it: draw how these connect — Assembling the Pieces · Reading Tracebacks · Your Turn: Maze Rover Simulator. Put an arrow wherever one of them is what makes another possible, and label the arrow with why.
Recap
| error you see | what to check |
|---|---|
| TypeError: ... not callable | you put () on data (rover.energy()) |
| KeyError: 'x' | a dict key that doesn't exist |
| IndexError: out of bounds | a list/array position past the end |
| AttributeError: no attribute | a .name the object doesn't have |
| NameError: not defined | a name you never imported or defined |
That's the whole Pre-COSMOS series. You went from 'what does the dot mean' to a debugged, sensing, deciding robot. Bring questions to camp - and have fun out there.
Want this taught 1-on-1? Alexander tutors Python — $55/session, free consultation.