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:
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!
Call a TA to check over your VSCode and Python setup!
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.
Take a few minutes to understand this gif:
Write down your answers to these questions in your Google Doc.
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:
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).
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.
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.
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).
Call a TA over once you've written your frame generator function.
With draw-robot
, we can generate an entire sequence of images, for example by running:
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 you read on
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):
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.
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:
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)
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 frameto-draw: draw-robot
– the function that returns an image based on the current y
-coordinateon-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:
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!
Call over a TA once you reach this point.
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:
Task 9: Create a good set of where
examples for this function.
Task 10: Write the function. Since the output differs based on the input, you'll need a conditional for this.
Task 11: Add the boost-robot
function to your reactor like this (just add the on-key
line. The rest is the same as before):
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 150 pixels. (Or you can go on to some of the other options to extend your animation.)
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 12 – 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.
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:
Let's step back. until now in the course, we have created functions for two main purposes:
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 13 – 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
Call over a TA once you reach this point.
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:
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.
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.
Brown University CSCI 0111 (Fall 2023)
Do you have feedback? Fill out this form.