Skip to main content

Lab 5 — Reactors

Date: October 13, 2026

Skills: 0, 1​

Purpose​

See how animations and interactive programs are built from ordinary functions, using PLL reactors.

Introduction​

The amount of programming we've learned so far is already enough to build some interesting programs. This lab will:

  • give you more practice writing functions and conditionals
  • show you how animations can be created from functions
  • introduce reactors, which you will use again on Project 2

You might not get through every optional feature. That's okay — the important part is understanding how a handful of functions become an animation.

We're going to animate a husky heading home to its doghouse.

A looping animation of a husky running across a field into a doghouse.

Take a minute to look at this gif with your partner:

  1. What elements do you see on the screen?
  2. What is moving? What is not moving?

Write your answers as comments in your Python file.

Copy this into your Python file (after the #level beginner line at the very top):

HUSKY_URL = (
"https://raw.githubusercontent.com/neu-pdi/cs2000-public-resources/refs/heads/main/static/img/lab5-husky.png"
)
DOGHOUSE_URL = (
"https://raw.githubusercontent.com/neu-pdi/cs2000-public-resources/refs/heads/main/static/img/lab5-doghouse.png"
)
BACKGROUND_URL = (
"https://raw.githubusercontent.com/neu-pdi/cs2000-public-resources/refs/heads/main/static/img/lab5-background.png"
)

husky = scale(0.6, load_image(HUSKY_URL))
doghouse = scale(0.6, load_image(DOGHOUSE_URL))
background = load_image(BACKGROUND_URL)

BACK = 40
HEIGHT = 400
WIDTH = 750
DOGHOUSE_X = WIDTH - 120
GROUND_Y = HEIGHT - 90

If you prefer different images, you may replace the URLs, but don't spend more than a few minutes looking.

PLL has a useful function load_image for loading pictures from the web (or from files next to your program). Once they're loaded, you can manipulate them like any other Image.

Note: You will find place_image more useful than overlay_xy. It lets you fix one image (the background) and put another (the doghouse, then the husky) on top of it at a coordinate. You can nest calls to place_image, just as you have with other image functions.

Note: The origin of the background (the point (0, 0)) is the top left corner. Increasing x moves right; increasing y moves down. place_image positions an image by its center.

Hint: If the husky or doghouse looks too large or too small, adjust the scale in the starter code.


Problem 1​

An animation is a sequence of images (usually called frames) that some tool flips through quickly, creating the illusion of motion. Here is an example sequence of frames for our husky:

Frame 1Frame 2Frame 3
Husky on the left, doghouse on the right.Husky in the middle of the field.Husky arriving at the doghouse.

We'll start by drawing one frame.

Part A​

Write an expression that generates the frame where the husky is at x-coordinate 100 and the same ground y-coordinate as the doghouse. If your code is correct, you should see something close to the first frame above. Remember to include the doghouse!

Part B​

Discuss with your partner: where specifically would the expression you wrote for one frame be edited to place the husky at a different x-coordinate?

Part C​

Create a function called draw_husky that takes one number input (the husky's x coordinate) and produces the frame Image with the husky at that x-coordinate. You do not need tests for this function (we'll come back to that later). Remember to include the doghouse!

Problem 2​

With draw_husky, we can generate a whole sequence of images:

draw_husky(0)
draw_husky(20)
draw_husky(40)
draw_husky(60)
draw_husky(80)
# ...

That creates all the frame images, but we are still writing (roughly) the same expression over and over. If we want to generate frames automatically, it would be great to have a program generate the sequence, not just the individual images.

Part A​

Look at each pair of consecutive draw_husky calls. Is there a pattern across the differences? What do you notice? Discuss with your partner.

Finish your discussion before opening!

The x coordinate is increasing by 20 from one expression to the next. A constant difference in coordinate is how we get a consistent rate of motion. We're manually doing the same computation (increase by 20) over and over — and that also sounds like a function!

Part B​

Add the following code to your file (no tests for now):

def next_x(x: float) -> float:
"""generate x coordinate for the next frame"""
return x + 20

If we have an initial x-coordinate, we can draw the first frame, use next_x to get the next coordinate, draw the second frame, and repeat. In other words, these two functions already define an animation. If we could get PLL to use them to generate the images and flip through them, we'd be done.

Part C​

Write down any questions you have at this point as comments. Discuss them with your partner or a TA.

Problem 3​

Luckily, PLL has something called a reactor that does this for us. Add the following at the bottom of your file, then run it (each name=value gives the value for one of the reactor's components):

husky_reactor = reactor(
init=0,
to_draw=draw_husky,
on_tick=next_x,
)

husky_reactor.interact()

You should see something like the animation we described: the husky runs to the right, past the doghouse, and off the screen. You'll have a chance to fix that later.

A reactor is a special value in PLL (like a str, int, Image, or bool) with components that together create the animation you see when you call .interact():

  • init=0 — the initial x-coordinate; the first value of whatever will change from frame to frame
  • to_draw=draw_husky — the function that returns an image based on the current x-coordinate
  • on_tick=next_x — the function that takes the x-coordinate for a frame and returns the coordinate for the next frame. Its first input is the init value.

Behind the scenes, .interact() runs a loop: draw, update, draw, update, …

The punchline: you could replace draw_husky and next_x with any functions and get an animation where a single piece of information changes between frames (change the background, the images, have the husky run the other way). Later in the course, we'll make animations where multiple pieces of information change. For now, all you need is that functions are the basis of movement in animations — so you can already do a lot with what you've learned.

Part A​

Figure out how to make the husky run slower or faster. What would you change? Try it out!

Part B​

Look at the reactor diagram and the code. Note any observations or questions about reactors as comments.

Problem 4​

What's the difference between an animation and an interactive program? In a game or interactive scene, how elements move is influenced by what a person does (like pressing keys).

So far, our reactor uses next_x to move the husky every few milliseconds. What if we wanted a human to press a key to send the husky back a bit — a startled hop away from the doghouse?

Specifically: if the user presses "b", reduce the husky's x-coordinate by 40 pixels (use the BACK constant). Finish designing back_husky: write tests and the implementation. Since the output differs based on the input, you'll need a conditional.

def back_husky(x: float, key: str) -> float:
"""if the key is 'b', move left by BACK; otherwise return x as given"""

Then add back_husky to your reactor (just add the on_key line; the rest stays the same):

husky_reactor = reactor(
init=0,
to_draw=draw_husky,
on_tick=next_x,
on_key=back_husky,
)

Run the code and try it: the husky should keep running right, and pressing b should scoot it left (click the picture first, so that the keys go to the reactor).

Problem 5​

So far this lab has told you not to worry about tests. Let's revisit that. We've written three functions:

  • draw_husky (float → Image)
  • next_x (float → float)
  • back_husky (float, str → float)

Part A​

Discuss with your partner: do some of these functions seem easier to write examples for? Which ones, and why? Write your answer as a comment.

Our response — read after writing down your own answer

Image-producing functions are harder to write examples for because you have to repeat (almost) the same image-building code in both the function body and the example answer. With functions that return numbers, you can just write the numeric answer.

In general, we don't write tests for functions that produce Images. You should write them for functions that return numbers, strings, and booleans, because you can write the answers without repeating the function body.

Writing examples for something as simple as next_x might seem pointless. The point shows up more clearly with back_husky: a crisp set of examples is easier for someone else to read than an entire if expression.

Remember, examples serve three purposes:

  • help other human readers understand what your function does, in more detail than a doc string
  • help make sure you understand the problem independently of the code
  • help you check that your code produces the answer you expect

Part B​

Until now in the course, we have created functions mainly to:

  • give a name to repeated expressions (so we can reuse them)
  • add clarity by naming intermediate computations

Reactors use functions for a slightly different purpose: they bundle up a computation so that another piece of code (that you didn't write) can use it when needed. The reactor knows how to make animations (draw pictures, update a value, repeat), but only you know the details you want. The function is a communication mechanism: you give those details to another piece of code that performs a common task.

Discuss with your partner: why do we need functions to write an animation? What questions do you have about functions or reactors? Write them as comments.

Problem 6​

Here are some additional features you can implement if you have time. You can also come up with your own.

Part A​

Write a function at_doghouse that takes an x-coordinate and returns a bool indicating whether the husky has reached (or run into) the doghouse.

Hint: Think about how close the husky's x-coordinate needs to be to DOGHOUSE-X. You may need to try a few thresholds, because place_image uses the center of each image.

Add it to your reactor with stop_when:

husky_reactor = reactor(
init=0,
to_draw=draw_husky,
on_tick=next_x,
on_key=back_husky,
stop_when=at_doghouse,
)

Part B​

Modify draw_husky so that once the husky has reached the doghouse, the frame is a different image — for example the same scene plus a text message, or just the doghouse with no husky still standing outside.