Unit 4 · Big Idea 3
The Turn Model
Student Lab · Turn Any Angle, in Either Direction
Student PIN:
Overview
Your turns right now are stuck at 90°. But missions need all kinds of angles — 45°, 180°, whatever the field demands. Today you’ll build one flexible function, Turn, that takes a direction and an angle and handles them all. Along the way you’ll meet four new tools: a new type for letters, a new kind of loop, functions that take more than one input, and functions that hand a value back. And you’ll discover something real engineers live with every day: a model is never perfect — it’s the best fit you can find.
Core Insight
A model like “ per degree” lets one function turn any angle. But the real world fights back — and friction mean no single number is perfect. You find the one that fits best.
By the end of this activity you will be able to:
- Use
len()to confirm a string is exactly one letter long. - Use a
forloop to repeat an action a set number of times. - Write a function that takes two parameters and returns a value.
- Build a
ticks_per_degreemodel and a flexibleTurnfunction.
Phase 1 — New Tool: Checking Length With len()
Python doesn’t have a separate type for “one letter” — a single character is just a string that happens to be one letter long:
direction = 'R' # a string, one letter longThis is perfect for telling the robot which way to turn: 'R' for right, 'L' for left. But because Python doesn’t enforce a length for you, nothing stops someone from typing 'Right' by mistake — so you need a way to check.
len() returns the number of characters in a string. Use it to confirm you actually got a single letter before you trust it:
print(len('R')) # 1
print(len('Right')) # 5
print(len('R') == 1) # True confirms that this is a single letter.And remember: 'R' and 'r' are still different strings to the computer — capitalization matters.
What does len() tell you about a string? Why is checking len(direction) == 1 a useful safeguard when Python doesn’t have a separate type just for single letters?
Phase 2 — New Tool: The for Loop
You’ve used while loops that run until something changes. A for loop with range() is for when you know exactly how many times to repeat. It counts for you:
for i in range(4): # run 4 times: i = 0, 1, 2, 3
# ...do this each time...range(4) counts 0, 1, 2, 3 — four values, even though it stops before reaching 4. Each time through, i holds the next number, and the loop keeps going until range() runs out.
How is a for loop different from the while loops you’ve used? When would you reach for a for loop instead?
Phase 3 — Calibrate: Find ticks_per_degree
Just like ticks_per_inch told you ticks-to-inches, you now need ticks_per_degree: how many ticks make one degree of turning. The clever way to measure it: make the robot spin all the way around — a full 360° — counting ticks, then divide.
The turning model
Here’s where the for loop shines. A full 360° spin can be built three different ways — and they should all equal 360°:
| The for loop | Total turn | |
|---|---|---|
| A | for i in range(4) → 90° each | 4 × 90° = 360° |
| B | for i in range(8) → 45° each | 8 × 45° = 360° |
| C | for i in range(2) → 180° each | 2 × 180° = 360° |
This example pivots in chunks using a for loop. Notice it uses mav, not motor — control is smoother for turning. Use a slow speed so the robot doesn’t from its own momentum.
k.cmpc(0) # clear the counter once, before the spin
for i in range(4): # Four chunks make one full 360-degree spin.
target = (i + 1) * CHUNK_TICKS # how far we should be after this chunk
while k.gmpc(0) < target:
k.mav(0, 300) # Use a SLOW velocity while the left wheel moves forward.
k.mav(1, -300) # right wheel backward (pivot right)
k.motor(0,0); k.motor(3,0); k.msleep(50) # brake-settle
print(f"total ticks = {k.gmpc(0)}")Run all three versions (A, B, C). After each full spin, read the total ticks and compute ticks_per_degree. Mark the robot’s start so you can see how close it lands to a true 360°.
Data
| Version | Total ticks for 360° | ticks ÷ 360 = ticks_per_degree |
|---|---|---|
| A — four 90° turns | ||
| B — eight 45° turns | ||
| C — two 180° turns |
They won't perfectly agree --- and that's the lesson
You’ll find it’s incredibly hard to make all three land on a perfect 360°. Every time the robot starts and stops a chunk, inertia carries it a little extra, and friction varies. More chunks (eight 45s) means more start-stops and more error pile-up. There is no single perfect ticks_per_degree — your job is to find the value that fits your robot best across the cases you care about.
Did your three ticks_per_degree values come out the same? Why might the eight-turn version (B) drift more than the two-turn version (C)?
Which ticks_per_degree value will you use as your model, and why did you pick it?
Phase 4 — Build: The Turn Function
Now build Turn — and it introduces two more new ideas at once: it takes two parameters (a direction and an angle), and it returns a value to report whether it worked.
Until now your functions took one input (or none). Turn takes two, separated by a comma — a direction and an angle:
Turn('R', 90.0) # turn right 90 degrees
Turn('L', 45.0) # turn left 45 degreesEvery function you’ve built so far hasn’t handed anything back. Turn is different: it returns a value that reports what happened. Python has a real type for exactly this — , which are just True and False. We’ll return True for success and False for failure (a bad direction). return also immediately exits the function — so a bad input never reaches the turning code.
A good function is easy to use and hard to break. Instead of demanding a capital 'R', accept either case with the OR keyword or — true if either side is true. That way a user who types 'r' still succeeds — one less thing to remember.
if direction == 'R' or direction == 'r': # either capital or lowercase#!/usr/bin/python3
# Unit 4, Big Idea 3: The Turn Model
# Name: _______________________ Date: ___________
import os, sys
sys.path.append("/usr/lib")
import _kipr as k
from yourname import *
ticks_per_degree = ____ # YOUR best value from Phase 3
def main():
Turn('R', 90.0) # right 90
Turn('l', 45.0) # Turn left 45 degrees. Lowercase works too!
def Turn(direction, angle):
if len(direction) != 1: # defensive: must be a single letter
print("Invalid direction! Use 'R' or 'L'.")
return False # report FAILURE and stop here
ticks = round(angle * ticks_per_degree) # PREDICT ticks from the model
if direction == 'R' or direction == 'r': # RIGHT (either case)
k.cmpc(0) # right pivot watches left wheel
while k.gmpc(0) < ticks:
k.mav(0, 300) # slow velocity, left forward
k.mav(1, -300) # right backward
elif direction == 'L' or direction == 'l': # LEFT (either case)
k.cmpc(1) # left pivot watches right wheel
while k.gmpc(1) < ticks:
k.mav(0, -300)
k.mav(1, 300)
else: # Any value other than R/r or L/l is invalid input.
print("Invalid direction! Use 'R' or 'L'.")
return False # report FAILURE and stop here
k.motor(0, 0); k.motor(3, 0); k.msleep(50) # brake-settle (your usual stop)
return True # report SUCCESS
main()Test Turn('R', 90.0) and Turn('l', 90.0). Did both work, even with the lowercase L? Why does accepting both cases make your function easier for someone else to use?
Now try a bad input like Turn('X', 90.0). What did the robot do, what got printed, and what did the function return?
Phase 5 — Test Your Model on Real Angles
Your model should now turn any angle. Test a range, both directions, and measure how close each lands. Remember: it’s a best-fit, so expect small errors — especially on bigger angles.
| Try | Turn call | Actual angle turned (degrees) |
|---|---|---|
| 1 | Turn(‘R’, 90.0) | |
| 2 | Turn(‘L’, 45.0) | |
| 3 | ||
| 4 |
How close were your turns to the angles you asked for? Were small angles or big angles more accurate? Why might that be?
Phase 6 — Add to & Connect
Add ticks_per_degree and your Turn function to your library. Now any mission can turn any angle, either direction, with one readable call — and you can retire the old fixed 90° turns.
AI Literacy Thread
Models are best-fit approximations — never perfect, but good enough to act on.
Your three calibration runs disagreed, and no single ticks_per_degree was perfect. That’s not failure — that’s how models work everywhere in AI. A weather model, a self-driving car’s physics, a language model’s predictions: none are exactly right. They’re the best fit to messy real-world data, good enough to act on while never being flawless. The skill isn’t finding a perfect model — it’s finding one that fits well enough and knowing its limits.
Read each scenario. Think it through, then write your answer.
Why is it impossible to find one ticks_per_degree that turns every angle perfectly? Connect this to why real AI models are never 100% accurate.
Your Turn function returns True for success and False for failure. Why is it useful for a function to report back whether it worked?
Phase 7 — Individual Reflection
Complete this section on your own.
1. Why doesn’t Python need a separate type just for single letters, and what does len(direction) == 1 check for that Python won’t guarantee on its own?
2. Explain the three parts of a for loop, using your calibration spin as the example.
3. Your Turn takes two parameters and returns a value. What are the two inputs, and what does the return value tell you?
4. Complete in 2–3 sentences: “Models are best-fit approximations, never perfect. This means that when my robot turns, I should expect…”
Extension Challenges
Finished early? Try one or more of these.
Extension A — Slow vs. Fast
- Recalibrate at a faster
mavspeed. Does the robot overshoot more from inertia? How does that change your bestticks_per_degree?
Extension B — A Full Circle Test
- Use a
forloop to callTurn('R', 90.0)four times. Does the robot return to its start? Compare to your old fixed turns.
Extension C — Check the Return Value
- Store the return:
ok = Turn('X', 90.0)thenprint()whether it succeeded. How could a mission use that to react to a failed turn?
Extension D — Retire the Old Turns
- Find an old program that used
turn_left()/turn_right()and replace them withTurn. Is the new version easier to read and change?
Extension E — The Recursive Version
- Extension B used a
forloop to callTurn('R', 90.0)four times. A recursive function could do the same thing by calling itself: a function that turns once, then calls itself again with one fewer turn remaining, until it hits zero. - Sketch (in words or ) what that recursive version would look like. Why might a loop be the more natural choice than recursion for this particular task?
When you are finished, press the button to turn in your work and save a copy.
KIPR · Botball Explorer · Unit 4 Big Idea 3 — Student Lab