Try   HackMD

Lab 2: Reactors/Animations

Learning Objectives

The amount of programming we've learned so far is already enough to build some interesting programs and raise interesting issues about programs in the real world. This lab will:

  • help you practice writing functions and conditionals
  • show you how animations can be created from functions
  • learn about some positive impacts of technology on the environment

All of these issues will appear again on later assignments or projects, so make sure you're understanding the code you're writing!

You might not get through all of the lab problems, but that's okay!


CHECKPOINT:

Call a TA to check over your VSCode and Python setup!
You won't use Python in this lab, but we'd like to make sure that you have it set up for future use.


Creating Animations from Functions

Step 1 – Generating Animation Images

In this lab, we're going to animate a gif of Eve alighting on a landing pad in Pyret – perhaps you could imagine that you're an animation programmer at Pixar Animation Studios working on the sequel to the critically acclaimed film, Wall-E.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More β†’

Take a few minutes to understand this gif:

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

Write down your answers to these questions in a Google Doc.

Expressions for Frames

In class, we've talked about identifying the structure of a single image, then using that to write a program to produce the image. Here, we have an animation, not a single still image. What is the structure of an animation?

An animation is actually a sequence of images that some tool (a program or film projector) flips through quickly (creating the illusion of motion). Here's an example sequence of these images (usually called frames) from our robot lander:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More β†’

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More β†’

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More β†’

We'll start by drawing one frame from this animation.

To do this, we will need images for the navy background, the landing pad, and Eve herself. The background is just a rectangle. Pyret has a useful function image-url for loading images when you don’t want to build an entire graphic from scratch (URL stands for "Universal Resource Locator"… a web address!). image-url takes in the url of a picture from the web and outputs it as a Pyret Image. Once it’s loaded, you can manipulate it the way you would any other Image.

The following code loads the images that we used to generate the frames (if you prefer different images, feel free to replace our URLs, but don't spend more than a few minutes looking for what you want).

LANDING-PAD-URL = "https://i.imgur.com/emcz1qe.png"
ROBOT-URL = "https://i.imgur.com/tAHzdNP.png"

robot = image-url(ROBOT-URL)
landing-pad = image-url(LANDING-PAD-URL)

BOOST = 40
HEIGHT = 500
WIDTH = 750
background = rectangle(WIDTH, HEIGHT, "solid", "navy")

# from Additional Game Features
SOLVED-URL = "https://www.syfy.com/sites/syfy/files/styles/1200x680_hero/public/2017/06/best-pixar-movie-romance.jpg"

Task 1: Copy the above code to your Pyret file. Then, write an expression that generates the frame image for when the robot is located at y-coordinate 100 and the x-coordinate that is half the width of the background. If your code is correct, you should see an image that looks close to the ones we provided. Remember to include the landing pad as well!

Note: You might find the function place-image more useful than overlay-xy, as it allows you to fix one image (e.g., the background) and put another (e.g., the landing pad) on top of it using a coordinate. You can nest calls to place-image, just as you have before with overlay-xy.

Note: The origin of the background grid (the point at (0,0)) is at the top left corner of the image. Therefore, increasing y values is equivalent to moving down on the image.

Hint: You can use the scale function to make your robot/landing pad larger or smaller.

A Function to Generate a Frame

We said that animations are made from a sequence of images that get flipped through quickly. Set aside the "flipping through" part. Let's focus first on how to generate images in which the robot is at different y-coordinates in each image.

Task 2 – Discuss with your partner: where specifically would the expression you previously wrote for one frame be edited to place the robot at a different y-coordinate?

Let's now create a function to generate robot frames for different y-coordinates.

Task 3: Create a function called draw-robot that takes one Number input (representing the y coordinate) and produces the frame Image with the robot at that y-coordinate. You do not need to write a where block (examples) for this function (we'll get back to that later). Remember to include the landing pad!


CHECKPOINT:

Call a TA over once you've written your frame generator function.


Step 2 – Generating Coordinates for a Sequence of Frames

With draw-robot, we can generate an entire sequence of images, for example by running:

draw-robot(0)
draw-robot(20)
draw-robot(40)
draw-robot(60)
draw-robot(80)
...

Such a sequence of expressions creates all the frame images for an animation, but we still seem to be writing (roughly) the same expression over and over. If we want to automatically generate frames (as an animation does), it would be great to have a program generate the sequence, not just the individual frame images.

Task 4 – Discuss with your partner: Previously, we have looked at multiple expressions to see whether they differ in a few spots. This time, we want to ask a more sophisticated question: is there a pattern across the differences in the above sequence? Look at each pair of consecutive draw-robot call. What do you notice?

Finish your discussion before opening!

Hopefully you noticed that the y 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. Here, we're manually doing the same computation (increase by 20) over and over. That also sounds like a function!

Task 5: Add the following code to your file (you don't need a where block for now):

fun next-y(y :: Number) -> Number:
  doc: "generate y coordinate for next image"
  y + 20
end

Step back and think about what the combination of draw-robot and next-y could let us do. If we have an initial value of the y coordinate, we could use it to draw the first frame. We could use next-y to get the next coordinate and draw the second frame. We could repeat this sequence to continue generating images.

In other words, these two functions define an animation! If we could get Pyret to use our functions to generate the images and flip through them, we'd be done. Fortunately, Pyret has such a feature.

Task 6: Stop and write any questions you have at this point in your Google Doc. Discuss them with your partner or ask a TA.

Step 3 – Making the Animation

We now have two functions that work with y-coordinates: draw-robot produces an image at one y-coordinate, while next-y produces the next y-coordinate at which to draw an image. If we can call these functions one after the other, over and over, we'll get an animation.

Luckily, Pyret has something called a Reactor that does this for us. Add the following code to your file, then run it:

include reactors # put this line at the top of your file

# put the following part at the bottom of your file, under everything else
robot-reactor = reactor:
  init: 0,   # the initial y-coord value
  to-draw: draw-robot,
  on-tick: next-y
end

interact(robot-reactor)
What the heck is a Reactor?

A Reactor is a another type of data in Pyret (like a String, Number, Image, Boolean, or Table) that keeps track of data for games and animations. Under the hood, the interact function runs a Reactor in a loop, updating the image that is being drawn based on the to-draw and on-tick functions. We'll come back to Reactors in project 2 and have a more detailed lecture about them when we understand more about functions. For now, the information given in this lab should be enough to get you started with making animations with Reactors.


You should now see something like the first animation we showed you! (the robot will fly past the landing pad and go off the bottom of the screen – you'll have a chance to fix that later)

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More β†’

What's a Reactor?

A reactor is a special value in Pyret that has different components that play a part in creating the animation you see when you call interact on the reactor. Let's look at what the components of the reactor are doing:

  • init: 0 – the initial y-coordinate for our reactor; this is the initial value of what will be changing frame by frame
  • to-draw: draw-robot – the function that returns an image based on the current y-coordinate
  • on-tick: next-y – the function that takes the y-coordinate for a frame and returns the coordinate for the next frame (tick by tick). Its first input will be the init value.

Here's a visual of the function calls that the reactor makes behind the scenes:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More β†’

Task 7: Figure out how to make the robot land slower or faster. What would you change in your current code? Try it out!

Task 8: Look at the reactor diagram and the code. Note any observations or things you are curious about regarding reactors in your Google Doc.

The punchline here is that you could replace draw-robot and next-y with ANY functions to get an animation in which a single piece of information changes between frames (change the background, change the images, have the robot fly upwards instead). In 2-3 weeks, we'll learn how to make animations where multiple pieces of information change. At that point, we'll come back to reactors (before you use them on Project 2). For now, all you need to note is that functions are the basis of character movement in animations, so you can do a lot with the content you've learned already!

Reflecting on Wall-E and Computer Science

Task 9: Now that you’ve successfully created an animation of Eve at Pixar Animation Studios, the animation writers have asked for your opinions on CS and its role in conservation. With your lab partner(s) read the following articles: Article 1 (concerning CS and its impact on preserving the Earth, focusing on animals) and Article 2 (focusing on CS and its role in sustainable development). Discuss the articles’ main ideas and anything you found interesting and think of other ways that computer science can be used to help the environment with your partner. Finally, weigh the positive and negative consequences of using CS as a tool for solving environmental issues.

NOTE: if you have any difficulty opening Article 2, you can access it through this link.


CHECKPOINT:

Call over a TA once you reach this point.


Step 4: From Animations to Interactive Games

What's the difference between an animation and an interactive game? In a game, how elements move are influenced by what a player does (like pressing keys). So far, our reactor uses next-y to move the robot every few milliseconds. What if we wanted to let a human press a key to boost the robot upward?

Specifically, we want to modify our animation so that if a user presses the "b" key, the robot gets a "boost", which reduces its y-coordinate by 40 pixels. To do this, we need the following function, which Pyret will call whenever any key gets pressed. This function returns a new y coordinate based on which key got pressed:

fun boost-robot(y :: Number, key :: String) -> Number:
  doc: "if the key is 'b', lower coord by 40; otherwise return y as given"
end

Task 10: Create a good set of where examples for this function.

Task 11: Write the function. Since the output differs based on the input, you'll need a conditional for this. (Hint: Utilize the BOOST that we gave you at the top!)

Task 12: Add the boost-robot function to your reactor like this (just add the on-key line. The rest is the same as before):

my-reactor = reactor:
  init: 0,   # the initial y-coord value
  to-draw: draw-robot,
  on-tick: next-y,
  on-key: boost-robot
end

Run the code and try playing your new game!

Optional Task: If you want to, extend your boost-robot to also recognize key "t", which "turbo boosts" the robot upwards 160 pixels. (Or you can go on to some of the other options to extend your animation.) Think about how you can utilize our BOOST to achieve this task.

Reflecting on Examples

So far, this lab has told you not to worry about writing where blocks. Let's revisit those now.

So far, we've written three functions:

  • draw-robot (Number -> Image)
  • next-y (Number -> Number)
  • boost-robot (Number, String -> Number)

Task 13 – Discuss with your partner: do some of these functions seem easier to write examples/where blocks for? Which ones and why? Write an answer in your google doc.

Our response – read after writing down your own answer

Image-producing functions are harder to write examples for because you have to repeat the same code in both the function body and the example answer. With functions that return numbers, you can just write the single numeric answer (or a simpler computation that produces the answer).


In general, we don't write where examples for functions that produce Images. You should, however, write them for functions that return Numbers, Strings, and Booleans, because you can write the answers without having to basically repeat the function body in the answer.

While writing examples for something as simple as next-y might seem pointless, we start to see the point for boost-robot: a crisp set of examples is a lot easier for someone else to read than them having to process your entire if expression to understand what your code is doing.

Remember: examples serve three purposes:

  • help other human readers understand what your function does, in more detail than doc strings
  • help make sure you understand the problem independently of the code
  • help you check that your code is producing the answer that you expect it to

Reflecting on Functions

Let's step back. until now in the course, we have created functions for two main purposes:

  • Give a name to repeated expressions (to enable reuse)
  • Add clarity by naming intermediate computations

While we motivated writing functions from the first purpose at the start of lab, reactors seem to 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. Here, the reactor needs to know what you want to do when a key is pressed, what image it should draw, etc. The reactor knows how to make animations (draw pictures, update coordinates, repeat), but only YOU (the animation designer) know the details that you want. Hence, the function becomes a communication mechanism: you give details to another piece of code that performs a common task.

Task 14 – Discuss uses of functions with your partner: why do we need functions to write an animation? What questions do you have, whether about functions or reactors? Write them in your Google Doc.


CHECKPOINT:

Call over a TA once you reach this point.


Additional Game Features

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

Collisions:
Write a function found-landing-pad that takes in a y-coordinate representing the position of the robot and returns a Boolean indicating whether the robot has landed on (or collided with) the landing pad.

Hint: Think about the relative difference in the coordinates of the robot and the landing pad in determining whether landing has occured.

Add it to your reactor with the stop-when line as shown below:

my-reactor = reactor:
  init: init-coord,
  to-draw: draw-robot,
  on-tick: next-y,
  on-key: boost-robot,
  stop-when: found-landing-pad
end

Congratulations Image:
You could offer a congratulations message for successfully landing the robot on the landing pad. To do this, modify draw-robot to return a different image once the robot is on the pad.

Robot saved!

Thanks to your excellent animation skills, you were able to rescue Eve from missing their landing pad! Safely on your landing pad, you can now sit back, relax, and enjoy the movie.

image


Brown University CSCI 0111 (Fall 2024)
Have questions? Refer to Ed
Check out our glorious Spotify playlist!