KIPR · Botball Explorer
Activity Sections

Unit 5 · Big Idea 4

Every Second Counts

Student Lab · Measuring, Storing, and Making Sense of Performance

Unit Guiding Question
How can a machine operate reliably in an imperfect world?
Big Idea
Performance Can Be Measured
AI Literacy Thread
A system that never measures its own performance can’t tell whether a change actually helped.
CS1 Concepts
systime() & Elapsed Time · & Floor Division · Lists for Metrics · Flags · Linear Search · Sorting · Efficiency
Game Context
Mission 12 + Mission 16 — recover 2 spilled cubes, stack them on the Large Green Cube
What You Need
Explorer robot · full · 2 spilled cubes · Large Green Cube · the game field · this lab sheet
Before you start: type your PIN in the box at the top of the page. Your teacher gave you this number. When you finish, press Submit & Download to turn in your work and save a copy.

Overview

Every lab so far has asked “did it work?” Today’s question is different: “did it get better?” You can’t answer that without a number. Today you’ll time a real mission run, store repeated timing results the same way you stored pose data in Big Idea 1, and then write code that actually studies that stored data — finding the best and worst runs, and putting them in order.

The Big Idea of This Unit

A system that never measures its own performance can’t tell whether a change actually helped. Feeling faster isn’t the same as being faster — you need a number, and you need to keep it.

By the end of this activity you will be able to:

  • Use k.systime() to measure how long a routine actually took.
  • Use the modulo operator (%) to turn a raw time value into something readable.
  • Store repeated measurements in a list, and use a boolean to track a running yes/no answer inside a loop.
  • Write a linear search to find the best and worst value in a data set.
  • Write a simple sort, and reason about how its cost changes as the data set grows.

Phase 1 — The Mission: Restack & Shelve

This single stacking action scores three things at once: Mission 12’s Base Mission (two spilled cubes forming a valid stack), and Mission 16’s Base and Bonus Mission (one, then a second, spilled cube ON TOP OF the Large Green Cube) — confirmed directly by the rules’ own scoring examples. One clean action, most of the points on the board for these two missions.

Watch Mission 12 video

Base Mission

11 points Live judged

Two spilled cubes form a scoring stack — one spilled cube is another spilled cube. Both cubes must originate from the spilled cube area.

Bonus Mission

5 points Final judged

The scoring stack remains intact and the lower cube of the stack is touching the black line.

Scores

  • One spilled cube is another spilled cube.
  • A robot is supporting one or both cubes while the relationship exists.
  • The scoring stack exists and the lower cube is touching the black line.

Does Not Score

  • Two spilled cubes touching side-by-side.
  • A spilled cube stacked on a cube that did not originate from the spilled cube area.
  • The stack no longer exists at final scoring.
  • The upper cube is touching black line but the lower cube is not.
Watch Mission 16 video

Base Mission

9 points Final judged

At least one cube originating from the Spilled Cube area is the Large Green Cube.

Bonus Mission

11 points Final judged

Two or more cubes originating from the Spilled Cube area are the Large Green Cube.

Scores

  • One spilled cube is the Large Green Cube.
  • One spilled cube is the Large Green Cube while supported by a robot.
  • One spilled cube another spilled cube that is the Large Green Cube.
  • Multiple spilled cubes the Large Green Cube.

Does Not Score

  • A spilled cube touching only the side of the Large Green Cube.
  • A spilled cube not originating from the Spilled Cube area.
  • Only one spilled cube the Large Green Cube for the Bonus Mission.

Say the mission back in your own words. Why does stacking the cubes on the Large Green Cube, instead of just on each other, matter for how many points you earn?

Phase 2 — Concept: Measuring and Reading Elapsed Time

What does systime() actually return?

k.systime() returns a number that keeps climbing the whole time your robot is powered on — but the documentation doesn’t spell out the exact unit, so don’t guess. Find out for yourself, the same way you found ticks_per_inch by measuring instead of assuming:

start = k.systime()
k.msleep(5000)   # wait exactly 5 real seconds
stop = k.systime()
print(f"Difference: {stop - start}")

Run that. A 5-second wait should make the unit obvious from the size of the number you get back.

What number did you get, and what does that tell you about the unit k.systime() counts in?

The init_time / elapsed pattern

Once you know the unit, timing any routine follows the same two-line pattern: capture the time before, capture it again after, subtract.

init_time = k.systime()
# ...the routine you're timing goes here...
elapsed = k.systime() - init_time
// and %: splitting a total into readable parts

A raw elapsed value is hard to read at a glance. You’ve already met /, which always keeps the decimal part. Here you want the opposite — a whole number of seconds, with nothing left over. // (floor division) divides and throws away the remainder in one step:

total_sec = elapsed // 1000   # whole seconds (adjust if your unit differs)
minutes = total_sec // 60
seconds = total_sec % 60      # <-- % here is the modulo operator: the remainder
print(f"Elapsed: {minutes}:{seconds:02d}")

% is the modulo operator — it gives you the remainder of division, the part // just threw away. Together, // and % split one number into two readable pieces: how many whole groups fit (//), and what’s left over after removing them (%).

If total_sec is 197, what do 197 // 60 and 197 % 60 each give you, and what do those two numbers mean together?

Phase 3 — Build: Time the Restack Run

Wrap your Restack & Shelve routine in the timing pattern from Phase 2, using your own library calls.

main.py
#!/usr/bin/python3

# Unit 5, Big Idea 4: Restack & Shelve, Timed

# Name: _______________________   Date: ___________

import os, sys
sys.path.append("/usr/lib")
import _kipr as k
from yourname import *

def main():
    k.enable_servo(0)
    k.enable_servo(1)

    init_time = k.systime()

    # ===== the mission itself, using your own library =====
    # Drive(...) / Turn(...) to spilled cube 1, pick it up
    # Drive(...) / Turn(...) to the Large Green Cube, place it
    # Drive(...) / Turn(...) to spilled cube 2, pick it up
    # Drive(...) / Turn(...) back to the Large Green Cube, stack it

    elapsed = k.systime() - init_time
    total_sec = elapsed // 1000   # use what Phase 2 taught you about the unit
    print(f"Elapsed: {total_sec // 60}:{total_sec % 60:02d}")

main()

Why does init_time get captured before the mission code runs, and elapsed get calculated after it finishes — what would go wrong if you calculated elapsed too early?

Phase 4 — Run It 4 Times

Reset the 2 spilled cubes to their starting positions between attempts, and run the full mission 4 separate times. Record each printed elapsed time (in seconds — convert from the MM:SS your program prints).

Record your 4 trial times
TrialPrinted MM:SSElapsed in seconds
1
2
3
4

Phase 5 — Concept: Studying Your Own Data

A list of results, not just one

Back in Big Idea 1, pose grouped three related values under one name. Today’s list groups four results from the same measurement, repeated:

times = [t1, t2, t3, t4]  # your 4 recorded seconds from Phase 4
: walking through a list by number

range(4) counts 0, 1, 2, 3 for you — one number per trip through the loop:

for i in range(4):
    # this block runs once for i=0, once for i=1, once for i=2, once for i=3

The new part today isn’t the loop itself — it’s what you do with i inside it. Since slots are numbered, i can walk straight into your list as an : times[i] means “whichever slot i currently points to.” As i counts 0, 1, 2, 3, times[i] visits every slot in the list in order.

Linear search: finding the best and worst

A search uses that same walk to check each value against the best one found so far:

fastest = times[0]
slowest = times[0]
for i in range(1, 4):
    if times[i] < fastest: fastest = times[i]
    if times[i] > slowest: slowest = times[i]

This checks every element exactly once — that’s why it’s called linear: the work grows in a straight line with the size of the list.

Swapping two values

Sorting means trading two values’ positions. That takes three lines, not two — if you just wrote times[i] = times[i+1] then times[i+1] = times[i], the first line would already overwrite times[i]’s original value before you had a chance to move it into times[i+1]. You need a temporary holding spot:

temp = times[i]           # 1. set the first value aside, so it isn't lost
times[i] = times[i + 1]   # 2. copy the second value into the first spot
times[i + 1] = temp       # 3. copy the saved-aside value into the second spot

Understanding why a temporary spot is necessary is the real idea here — it’s true in every language. (Python happens to also offer a one-line shortcut, times[i], times[i+1] = times[i+1], times[i], but it’s doing the exact same three-step swap underneath — worth knowing it exists, but the three-line version is what makes the underlying idea visible.)

Sorting: the simple version

Picture 4 people standing in a line, out of height order. Walk down the line one time: compare each pair of neighbors, and swap them if the left person is taller than the right person. After one full walk, the tallest person has “bubbled” to the top like bubbles rising through water. Do that same walk a few more times, and eventually everyone’s in order. That “bubbling” to the top gives this its name: bubble sort.

for pass_num in range(3):              # walk down the line a few times
    for i in range(3):                  # one full walk, comparing neighbors
        if times[i] > times[i + 1]:     # out of order?
            temp = times[i]              # swap them (see above)
            times[i] = times[i + 1]
            times[i + 1] = temp

Walk through it by hand with 4 index cards showing your own 4 times, out of order. Do the swaps yourself, pass by pass, until they’re sorted. For only 4 values, 3 passes is always enough to guarantee a full sort — that’s why the outer loop stops at range(3).

Bubble sort passes bubbling the largest value to the end
Sorting: stopping early with a stored boolean

The version above always does all 3 passes, even if the line was already sorted after pass 1. You’ve used booleans inline before, inside an if with and or or. Here’s a new use: store a boolean’s answer in a variable that carries a true/false memory from one pass into the next.

swapped = True                   # start true, just to make sure the loop runs at least once
while swapped:
    swapped = False               # assume this pass finds nothing to fix...
    for i in range(3):
        if times[i] > times[i + 1]:
            temp = times[i]
            times[i] = times[i + 1]
            times[i + 1] = temp
            swapped = True        # ...unless a swap actually happened

If an entire pass finds nothing out of order, swapped never gets reset to True, the while loop’s condition goes false, and the sort stops — without wasting a pass it didn’t need.

Walk through the simple version by hand on paper first, with your own 4 numbers. Then answer: why does checking swapped let the improved version stop early on data that’s already close to sorted? What’s stored in the variable at the moment it does?

Efficiency: what happens at a bigger size?

Your search touched 4 values once each. Your sort, in the worst case, compared neighboring pairs across multiple passes over those same 4 values. Now imagine 100 recorded times instead of 4.

Would the search’s work grow by roughly 25× (matching the 25× growth in data, from 4 to 100)? Would the sort’s worst-case work grow by exactly 25×, or by something faster than that? This — how the amount of work grows as the data grows — is what computer scientists mean by algorithm efficiency.

Phase 6 — Build: Analyze Your Times

Using your 4 recorded values from Phase 4, write the full analysis: hardcode the list, search for fastest/slowest, sort it, and report all of it.

main.py
times = [____, ____, ____, ____]  # your 4 recorded seconds

# ===== SEARCH =====
fastest = times[0]
slowest = times[0]
for i in range(1, 4):
    if times[i] < fastest: fastest = times[i]
    if times[i] > slowest: slowest = times[i]
print(f"Fastest: {fastest:.2f}  Slowest: {slowest:.2f}")

# ===== SORT =====
swapped = True
while swapped:
    swapped = False
    for i in range(3):
        if times[i] > times[i + 1]:
            temp = times[i]
            times[i] = times[i + 1]
            times[i + 1] = temp
            swapped = True
print(f"Sorted: {times[0]:.2f}, {times[1]:.2f}, {times[2]:.2f}, {times[3]:.2f}")

check

4 real trial times recorded and hardcoded into times. Fastest and slowest found by search. Full list sorted using the boolean-flag loop. Both results printed.

Once your list is sorted, where do the fastest and slowest values sit in it? Could you have skipped writing a separate search entirely, if you’d sorted first?

Phase 7 — Connect & Reflect

AI Literacy Thread

A system that never measures its own performance can’t tell whether a change actually helped.

Every real engineering team does exactly what you just did: time something, run it more than once, store the results, and actually look at the numbers instead of trusting a gut feeling. A search engine times how long a query takes across millions of runs. A game studio times level-load times across every playtest. None of them trust a single run — and none of them just eyeball the numbers either; they sort them, find the extremes, and watch how those numbers change as the system scales up.

Complete the reflection on your own.

1. Walk through the init_time / elapsed pattern in your own words — what does each line actually do?

2. What does // give you that / doesn’t, and what does % give you that neither of the others does?

3. What was swapped actually tracking, and why does a stored boolean matter here in a way that an inline and/or check couldn’t?

4. Complete in 2–3 sentences: “A system that never measures its own performance can’t tell whether a change helped. This means that before I claim my robot got faster, I should…”

Extension Challenges

Finished early? Try one or more of these.

Extension A — Time a Different Metric

  • Apply the same list + search + sort pattern to a different measurement — for example, how many degrees off-target your last 4 turns landed. What changes in your code, and what stays exactly the same?

Extension B — Watch the Early Exit

  • Add a print() inside the while swapped: loop that prints the value of swapped at the end of each pass. Run your sort on data that’s already close to sorted — how many passes does it actually take before swapped stays False?

Extension C — Scaling to 8

  • If you had 8 trial times instead of 4, how would you need to change the list declaration and the loop bounds? In the worst case, roughly how many more comparisons would the sort need — twice as many, or more than that?

When you are finished, press the button to turn in your work and save a copy.

KIPR · Botball Explorer · Unit 5 Big Idea 4 — Student Lab