--- title: Lab 05 tags: Labs-F20, 2020 --- # Lab 5: Reactors/Animations For this lab, we suggest you have a shared Google Doc between you and your partner for written answers, and a Pyret file for your code. ## Learning Objective The objective of this lab is to gain familiarity with creating animations, specifically with one of Pyret's built in tools, the `reactor`. We will practice breaking down the problem of creating an animation step by step using the skills that we've developed throughtout the semester so far. After that, we'll be able to see how Pyret can help us automate some of the process of creating an animation. If you don't feel more comfortable with the concept of a reactor after working on this lab, come to [TA hours](https://cs.brown.edu/courses/csci0111/fall2020/calendar.html)! The content of this lab will be important for Project 2, and your TAs would love to help in any way they can. ## Problem 1 -- Cloudy with a Chance of Cupcakes In this lab, we're going to animate this gif of a cupcake falling onto a plate in Pyret -- perhaps you could imagine that you're an animation programmer at Sony Pictures Animation working on the critically acclaimed film, [Cloudy with a Chance of Meatballs](https://en.wikipedia.org/wiki/Cloudy_with_a_Chance_of_Meatballs_(film)). ![](https://imgur.com/SzBCn8x.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? 3. How do you think the cupcake is moving? 4. Why/how does the cupcake reappear at the top after it lands on the plate? 5. How does the program know when the cupcake is on the plate? Write down your answers to these questions in your Google Doc. ### Problem 1.1 -- Frame by Frame Let's look at that gif again, but this time, we'll look at some sequential frames individually. ![Frame 1: cupcake at top, plate at bottom.](https://imgur.com/0wLDDos.png) ![Frame 2: cupcake slightly lower, plate at bottom.](https://imgur.com/i8pzCQR.png) ![Frame 3: cupcake on plate, plate at bottom.](https://imgur.com/wSXTHWO.png) We can see that our fixed pieces -- the white background, the plate at the bottom -- look the same in each frame. However, our moving part -- the falling cupcake -- is at a different vertical position in each frame. If we gave each of the elements in a given frame a coordinate value, then we could draw the image for that frame. If we could create a whole collection of images, and get Pyret to flip through them quickly, we'd get an animation! ### Problem 1.2 -- Your First Frame The first thing you need to get started is the white background. That's just a `rectangle` (using the `image` library). Now we need the plate and cupcake. 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 from the [`image`](https://www.pyret.org/docs/latest/image.html) library. We used these images: ``` include image PLATE-URL = "https://imgur.com/wneOB5v.png" DESSERT-URL = "https://imgur.com/rLw09co.png" dessert = image-url(DESSERT-URL) plate = image-url(PLATE-URL) HEIGHT = 500 WIDTH = 750 background = rectangle(WIDTH, HEIGHT, "solid", "white") ``` You’re welcome to find (or build!) your own, or just copy our code to get started. **Task:** Write an expression to create the first frame/image in the animation, where the plate is fixed at the bottom-middle of the frame, and the cupcake is currently at the top-middle of the frame. ***Hint:*** You can use the `scale` function to make your dessert/plate larger or smaller. ***Hint:*** 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 plate) 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. ___ ### CHECKPOINT: **Call a TA over once you've made your first frame.** ___ ### Problem 1.3 -- Another One **Task:** Now that you've got your first frame, write an expression to create the next frame/image in the animation, where the plate is fixed in the same place, and the cupcake is currently at the same `x` value but its `y` value has increased. Just use concrete numbers for your new coordinates (don't try to build them off the previous expression). ### Problem 1.4 -- And Another One **Task:** Now that you've got two frames, write an expression to create the next frame/image in the animation, where the plate is fixed at in the same place, and the cupcake is currently at the same `x` value but its `y` value has increased yet again. ### Problem 1.5 -- Reducing Repetition You could make a lot of images this way. Then you'd just need to get Pyret to flip through them. But creating these images is repetitive -- do you really have to copy and paste all the time? What do we usually do when we find we are repeating the same code multiple times? **Task:** Stop and discuss your response with your partner. **Task:** Create a function called `falling-dessert` that takes two `Number` inputs (for `x` and `y`) and produces the frame image with the cupcake at those coordinates. Now, we can create an entire sequence of images more quickly, for example by writing: ``` falling-dessert(375, 0) falling-dessert(375, 20) falling-dessert(375, 40) falling-dessert(375, 60) falling-dessert(375, 80) ... ``` ## Problem 2 -- Connecting the Images Creating `falling-dessert` saved us work in creating individual images, but there's still work to do to generate enough images for an entire animation. Wouldn't it be nice to automate _that_ as well? **Task:** Look at the sequence of `falling-dessert` calls just above. Do you see any sort of pattern across them that we might be able to use to automate the generation of successive frames? Discuss with your partner. In our sequence, the `y` coordinate is increasing by 20 from one image to the next. The same computation (increase by 20) over and over? That also sounds like a function! For example: ``` fun update-coord(y :: Number) -> Number: doc: "generate y coordinate for next image" y + 20 end ``` This is the right idea, but we need to generalize it a bit to be able to combine `update-coord` and `falling-dessert` to generate our full sequence of images. As a first step, we have to have `falling-dessert` and `update-coord` work with `Coordinates` rather than individual numbers (we can explain why later) ### Setup Make sure you include this at the top of your program: ``` include reactors include image data Coord: | coord(x :: Number, y :: Number) end ``` `Coord` here is a special datatype that turns two numbers into a single coordinate. For now, just rely on your intuition that a coordinate is made up of two numbers (`x` and `y`). Don't worry about what the rest of the code here does. To create a new `Coord` named `my-coord` with an `x` value of `3` and a `y` value of `4`: `my-coord = coord(3, 4)` To access the `x` and `y` values of `my-coord`: ``` >> my-coord.x 3 >> my-coord.y 4 ``` **Task:** Edit `falling-dessert` so that its input is now a single `Coord` (rather than two numbers). In the body of the function, extract `x` and `y` from the `Coord` input. **Task:** Re-create the sequence of images that we showed earlier, this time using `Coord` instead of number inputs. **Task:** Similarly, edit `update-coord` to take a `Coord` as an input and produce a `Coord` as output. The produced `Coord` should have the same `x` value as the input `Coord`, while the produced `y` value still increases by 10 (as it did before). ___ ### CHECKPOINT: **Call over a TA once you reach this point.** ___ ## Problem 3 -- Putting it Together: The Reactor We now have two functions that work with coordinates: `falling-dessert` produces an image at one coordinate, while `update-coord` produces the next coordinate at which to draw an image. If we can make these two functions work together, we can get an animation without us having to create images by hand. Luckily, Pyret has something called a `Reactor` that coordinates these functions for us. Add the following code to your file, then run it: ``` my-reactor = reactor: init: coord(375, 0), to-draw: falling-dessert, on-tick: update-coord end interact(my-reactor) ``` You should now see something like the first animation we showed you! (the cupcake will fly past the plate and go off the bottom of the screen -- we'll fix that later) ![](https://imgur.com/SzBCn8x.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: coord(375, 0)` -- the <ins>init</ins>ial coordinate for our reactor; this is the initial value of what will be changing frame by frame * `to-draw: falling-dessert` -- the function that returns an image based on the current coordinate * `on-tick: update-coord` -- the function that produces the next coordinate for our reactor, frame by frame (tick by tick). Its first input will be the `init` value. Here's a visual of what the reactor is doing: ![Breakdown of a reactor.](https://imgur.com/mxVkkkq.png) **Task:** Look at the Reactor diagram and the code. Note any observations or things you are curious about regarding reactors. **Task:** Try to explain why we had to change the contract (types) of `update-coord` from `Number -> Number` to `Coord -> Coord` to make the Reactor work. ___ ### CHECKPOINT: **When you've finished the above tasks, submit [this Google Form](https://forms.gle/RQtwJcWAsCxueX446) with your answer to the second task.** ___ ### Problem 3.1: From Animations to Games So far, our reactor uses `update-coord` to move the cupcake every few milliseconds. Let's make a game from our animation, where the plate is drawn at a random position and the player's goal is to move the falling cupcake to land it on the plate? **Task:** Edit your code to make the plate start at a random position. (***HINT:*** [`num-random(n)` could be helpful!](https://www.pyret.org/docs/latest/numbers.html#%28part._numbers_num-random%29)) Next, we want to have a human player control the movement of the cupcake. You will press keys on your keyboard (such as the arrow keys) to generate a new `Coord` (rather than only have `update-coord` do that automatically). **Task:** Decide how you would like each of the following keys to change the coordinate for the cupcake: * Left arrow * Right arrow * Up arrow * Down arrow **Task:** Write a function `move-dessert` that takes in a `Coord` and a `String` and returns a new `Coord`. The `String` names the pressed key, the input `Coord` represents the current position of the cupcake, and the output `Coord` represents the cupcake’s new position after the keypress. The strings for the arrow keys are `"left"`, `"right"`, `"up"`, and `"down"`. (If you want to add more keys later, you can use a key’s literal character as the string for that key. For example, `“a”` is the a key, `“b”` is the b key, etc.) **Task:** Add the `move-dessert` function to your reactor like this: ``` my-reactor = reactor: init: init-coord, to-draw: falling-dessert, on-tick: update-coord, on-key: move-dessert end ``` Try playing your new game! ___ ### CHECKPOINT: **Call over a TA once you reach this point.** ___ ### Problem 3.2: Additional Features Here are some additional features you can implement if you have time. You could also come up with your own (remember, you can include more behaviors with additional keys). ***Wrap-around:*** What happens if the cupcake misses the plate? Would the cupcake tragically fall to the ground? (Try this out using your current reactor.) Modify your `on-tick` function so that if the cupcake goes off the bottom of the screen, it starts again from the top (PHEW!). ***Collisions:*** Write a function `found-plate` that takes in a `Coord` representing the position of the cupcake and returns a `Boolean` indicating whether the cupcake has landed on (or _collided with_) the dessert. Add it to your reactor like this: ``` my-reactor = reactor: init: init-coord, to-draw: falling-dessert, on-tick: update-coord, on-key: move-dessert, stop-when: found-plate end ``` Next, congratulate yourself for successfully scoring a dessert on your plate by modifying `to-draw` so that if there is a `collision` (`found-plate` returns true), the image becomes a congratulatory image of your choice. ___ ### CHECKPOINT: **Call over a TA once you reach this point.** ___ ### Problem 3.3: Advanced Features What if we wanted more than one changing feature of our animation? For example, what if we wanted to [rain down desserts from the sky](https://www.youtube.com/watch?v=iGVsptoMsKE)? Currently, our program is using a single `Coord` to capture the information that is changing from frame to frame. But a reactor can use **ANY** type to connect the draw and update functions, as long as that type is consistent across the two functions and the initial value. For example, rather than a single `Coord`, your changing information could be an entire `List` of `Coord`, where each `Coord` is information about a separate cupcake. **Task:** Rewrite the reactor and its component functions to handle multiple cupcakes through `List`. You can choose which images respond to your `on-key` function -- be creative! ___ ### CHECKPOINT: **Call over a TA once you reach this point.** ___ ## Cupcake saved! Thanks to your excellent cupcake-navigating skills, you were able to rescue the falling cupcake from its demise! Safely on your plate, you can now sit back, relax, and enjoy.