Learning objectives

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

All of these come back on later assignments and projects, so make sure you understand what you are writing rather than just finishing as fast as you can.

You may well not get through all of this lab, and that is fine. Work with your partner, and understand the code you write rather than racing to the last task.

Setup

Part 1Describing Game Behaviors

Here is a brief video of someone playing a game. The player controls a drone that moves left and right above the forest floor. Embers from a wildfire (orange dots) fall from the sky. The player tries to catch the embers with the drone. The game ends either when an ember hits the ground or when all embers have been extinguished.

The data in this game are the x-y coordinates of the drone and each of the embers. We'll call the collection of embers the "fire set". Working in small groups, propose a set of statements about how these data change as the game is played. For example:

Task 1

Extend this set of statements to cover the rest of the gameplay. Put your answers in your worksheet document. Even though you brainstormed in pairs or small groups, write the statements in your documents individually. Being able to capture behavior ideas as precise statements is a key skill for the course.

Checkpoint

Call a TA over to discuss your plan after about 10 minutes. If you are waiting for a TA, start the next part.

Part 2Creating Animations from Functions

Now, you'll learn how to create a simpler game for yourself (we'll come back to the embers game later)

Step 1Generating Animation Images

We are going to animate a gif of Eve alighting on a landing pad in Pyret — perhaps you are an animation programmer at Pixar working on the sequel to the critically acclaimed film Wall-E.

Animation of a white robot descending through a navy sky onto a landing pad at the bottom of the frame.
The animation you are going to build.

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?

Expressions for frames

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

An animation is a sequence of images that some tool — a program, a film projector — flips through quickly, creating the illusion of motion. Here is an example sequence of those images, usually called frames, from our robot lander.

Frame 1: the robot near the top of the navy background, landing pad at the bottom.
Frame 1
Frame 2: the same scene with the robot lower down the frame.
Frame 2
Frame 3: the robot resting on the landing pad at the bottom of the frame.
Frame 3

We will start by drawing one frame from this animation.

To do that we 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 do not want to build a whole graphic from scratch. It takes the url of a picture on the web and produces a Pyret Image; once loaded, you can manipulate it the way you would any other Image.

The following code loads the images we used to generate the frames. If you prefer different ones, feel free to replace our urls — but do not spend more than a few minutes looking.

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 2

Copy the code above into your Pyret file. Then write an expression that generates the frame image for a robot at y-coordinate 100 and an x-coordinate half the width of the background. If your code is right, you should get an image close to the ones above. Remember the landing pad.

Note: You will probably find place-image more useful than overlay-xy: it lets you fix one image (the background) and put another (the landing pad) on top of it at 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 the top left corner of the image, so increasing y moves down the image.

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

A function to generate a frame

We said animations are made from a sequence of images flipped through quickly. Ignore the flipping aspect. Focus first on generating images in which the robot is at a different y-coordinate each time.

Task 3

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

Now let us make a function that generates robot frames for different y-coordinates.

Task 4

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 a where block for this one — we will come back to that. Remember the landing pad. Generate a sequence of images by hand by making the following calls in the interactions window:

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

Checkpoint

Call a TA over once you have written your frame generator function and created some frames.

Step 2Generating Coordinates for a Sequence of Frames

To program an animation, we need to generate the frames automatically, not by hand in the interactions window. Note that to generate the frames by hand, we wrote the same expression over an over, changing only the coordinate. But that coordinate changed each time according to a pattern. If we could represent that pattern as a function, maybe a program could keep generating new y-coordinates automatically.

Task 5

Add the following to your file. You do not 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 draw-robot and next-y together let us do. Given an initial y-coordinate, we could draw the first frame. We could use next-y to get the next coordinate and draw the second frame. We could repeat that to keep generating images.

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

Task 6

Stop and write down any questions you have at this point in your worksheet. Discuss them with your partner, or ask a TA.

Step 3Making the Animation

Pyret has something called a Reactor that generates sequences of images according to a pattern. Add the following 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)

You should now see something like the first animation we showed you. The robot will fly straight past the landing pad and off the bottom of the screen — you will get a chance to fix that later.

What is a reactor?

A Reactor is 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, interact runs a Reactor in a loop, updating the image being drawn using the to-draw and on-tick functions. For the robot-lander, the reactor has the following components:

Diagram of the reactor loop: init feeds the first y value into to-draw, which produces a frame, while on-tick produces the next y value and the cycle repeats.
The calls a 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, in your worksheet.

The punchline is that you could replace draw-robot and next-y with any functions and 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 two or three weeks we will learn how to make animations where several pieces of information change, and we will come back to reactors then, before you use them on Project 2. For now, note that functions are the basis of character movement in animations, so you can already do a lot with what you have learned.

Step 4From Animations to Interactive Games

What is the difference between an animation and an interactive game? In a game, how elements move is influenced by what a player does — pressing keys, for instance. So far our reactor uses next-y to move the robot every few milliseconds. What if we wanted to let a person press the "b" key to boost the robot upward (by reducing its y-coordinate by 40 pixels)?

Add the following code to your file. It is the start of a function that produces a new "boosted" y-coordinate depending on which key has been pressed. Keys are strings like "A","n", or "=".

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 9

Create a good set of where examples for this function.

Task 10

Finish the function. Since the output differs based on the input, you will need a conditional.

Hint: Use the BOOST constant that we had you copy into your file.

Task 11

Add boost-robot to your reactor by adding the on-key line below to your reactor; the rest is the same as before. Then run the code and play your new game.

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

Optional

If you have time, extend boost-robot to also recognize the "t" key, which turbo boosts the robot upwards by 160 pixels. Think about how you can use BOOST to do it. Or skip ahead to the other ways to extend your animation.

Checkpoint

Call a TA over once you reach this point.

Back to the Embers: Generating and auditing reactor code

Task 12

Now that you've worked with a reactor, prompt Gemini to create the embers game in the Pyret Level Beginner Gem. For the first try, just give Gemini the description at the top of the "Describing Game Behaviors" section. Copy and paste the produced code into a new Pyret file and try to run it. Make notes in your worksheet about how the generated code does or does not behave as you expected.

Task 13

Now have a conversation with Gemini about the code. Give Gemini one of the behavior statements that you wrote and ask it to explain how the code satisfies that behavior. Are you convinced? Answer the corresponding questions in the worksheet.

Task 14

Open the Gemini link afresh (with a new session). Prompt Gemini to again generate the game, but this time include all of your behavioral statements as part of the prompt. Again, copy over the code and play the game. Do you notice differences? Answer the corresponding questions in the worksheet.

Getting help

In lab, flag down a TA. You do not have to be stuck to do it, and you do not have to have tried everything first — that is what they are in the room for. Ask early: a pair that spends forty minutes on a typo has lost the part of the lab that was worth doing.

Your partner counts too. Saying out loud what you think the code does is most of debugging, and it is faster than waiting.

After lab, short questions go on Ed, and anything longer is better brought to office hours.