# 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](https://disney.fandom.com/wiki/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](https://en.wikipedia.org/wiki/WALL-E). ![](https://imgur.com/lcnma6F.gif) 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: ![Frame 1: robot at top, landing pad at bottom.](https://imgur.com/QNmQbfr.png =300x) ![Frame 2: robot slightly lower, landing pad at bottom.](https://imgur.com/HSuru7t.png =300x) ![Frame 3: robot on landing pad, landing pad at bottom.](https://imgur.com/Sl5ocHO.png =300x) 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 <code>image-url</code> for loading images when you don’t want to build an entire graphic from scratch _(URL stands for "Universal Resource Locator"... a web address!)_. <code>image-url</code> 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) HEIGHT = 500 WIDTH = 750 background = rectangle(WIDTH, HEIGHT, "solid", "navy") ``` **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`](https://www.pyret.org/docs/latest/image.html#%28part._image_place-image%29) 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? :::spoiler **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) ``` :::spoiler **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 `Reactor`s 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 `Reactor`s. ::: <br> 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) ![](https://imgur.com/lcnma6F.gif) #### 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 <ins>init</ins>ial 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: ![Breakdown of a reactor.](https://i.imgur.com/RSZZ9kn.png) **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](https://new.nsf.gov/news/computers-play-crucial-role-preserving-earth) (concerning CS and its impact on preserving the Earth, focusing on animals) and [Article 2](https://g00078816.medium.com/the-effect-of-computer-science-in-sustainable-development-39d9e1f940fa) (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](https://drive.google.com/file/d/1xhTla1aN5el_shCn7UooQI5nV2p6wlYv/view?usp=sharing).** ___ #### 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. **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 150 pixels. (Or you can go on to some of the other options to extend your animation.) ### 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. :::spoiler 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). ::: <br> 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. ------ > Brown University CSCI 0111 (Fall 2024) > Feedback form: tell us about your lab experience today [here](https://forms.gle/52Fi9HdMFRgW1vD67)!