Students use the Animation Design Worksheet to decompose a 2-player game of Pong, and implement it as a Reactor-based program.
Product Outcomes |
|
Materials |
|
🔗Setting up the Paddles 45 minutes
Overview
Students decompose a complex problem (implementing Pong) into simpler sub-problems, and implement the paddle portion of the game.
Launch
In Structures, Reactors, and Animations, you practiced decomposing simple animations into their data structures and functions. Let’s consider how a 2-player game of Pong works:
-
There are two "players", each represented by a paddle on either side of the screen.
-
Each paddle can move up and down, as long as they remain on the screen.
-
There is also a ping-pong ball, which moves at any angle and can be on or off the screen.
Let’s start out by adding the paddles, making sure they can move up and down, and then we’ll add the ball later.
Using a blank Animation Design Worksheet, figure out how the paddles behave throughout the game, and decide what Data Structure you’ll need to represent those behaviors.
Students should realize that each paddle is simply a y-coordinate, since neither paddle can ever move left or right.
Here is one possible structure that we could use to model the two players:
# a PongState has each paddle's y-coordinates
# (no x-coordinate needed, since the paddles only go up/down!)
data pongState:
| pong(
paddle1Y :: Number,
paddle2Y :: Number)
end
We can imagine a few sample PongState
instances, in which the paddles are at different locations on the screen. If you haven’t already, it would be a good idea to define a sample state for when the game starts, and maybe two other states where the paddles are at other locations.
We’ll need to answer some questions, in order to write our draw-state
function.
- What will the paddles look like?
- What does the background look like?
- How wide is the background? How tall is it?
- Define the function draw-state
, and try drawing your sample PongState
instances to make sure they look the way you expect them to.
Investigate
The paddles don’t move on their own, so right now there’s no next-state-tick
function. However, they DO move when a user hits a key! That means we’ll need to define next-state-key
, and answer a few questions in the process:
-
What key makes
paddle1Y
increase? Decrease? -
What key makes
paddle2Y
increase? Decrease? -
How much does each paddle move when it goes up or down?
-
What happens if some other key is pressed?
Have students discuss their answers to these questions, before moving on to next-state-key.
Use the Design Recipe to write the code for next-state-key
At this point, we know how to change the PongState
in response to a key-press and how to draw that PongState
as an image. Let’s build a reactor
, which uses a PongState
instance as the starting state and hooks up these functions to the on-key
and to-draw
event handlers.
pong-react = reactor:
init: pongState(200, 200),
on-key: next-state-key,
to-draw: draw-state
end
When you run this reactor with pong-react.interact()
, you should see your initial instance drawn on the screen, and the paddle positions should change based on the keys you press! Do all four keys do what you expect them to do? What happens if you hit some other key?
Right now, what happens if you keep moving one of the paddles up or down? Will it go off the edge of the screen? We should prevent that!
-
Discuss with your partner: what needs to change to stop the paddles from going offscreen?
-
You can use an Animation Design Worksheet if you want to be precise.
-
Change your code to stop the paddles from going offscreen!
Give the class 2-3 minutes to discuss, and then have different teams share back before they start to implement.
Synthesize
How many different solutions did you come up with? What do they have in common?
🔗Adding the Ball 45 minutes
Overview
Students modify the game State to add a ball, which can move in two dimensions.
Launch
Now that we’ve got our paddles set up, it’s time to start thinking about the ball.
-
When does the ball move? On its own, or only when a key is pressed?
-
Does the ball’s position change? If so, by how much?
-
What do we need, to keep track of the ball’s position?
-
Does the ball’s direction change?
-
What do we need, to keep track of the ball’s direction?
-
When does the ball’s direction change?
Investigate
Use an Animation Design Worksheet to add one part of the ball’s behavior to your game.
You probably needed to add ballX
and ballY
fields to your State, to make sure the ball could move in any direction.
-
Were they Numbers? Strings? Booleans?
-
Did your
draw-state
function need to change? What aboutnext-state-key
? -
Did you need to write
next-state-tick
? If so, what did you do?
Some students will hard-code numbers for moving the ball. That’s okay! Once they start thinking about changing direction, those numbers will have to become fields in pongState, which change in response to paddle collisions.
Now the game is starting to come together! We’ve got two paddles moving up and down, and we make sure they stay on the screen. Meanwhile, we have a ball that can move in any direction…but so far the ball doesn’t know how to bounce! It’s time to plan out what bouncing will look like, and wire it all together.
-
How do you know when the ball has hit the top or bottom wall of the screen?
-
Write
is-on-wall
, using the Design Recipe to help you.
The goal of this activity is to have students get their collision-detection working, in preparation for the bouncing behavior.
-
When a ball moves up and to the right, what happens to
ballX
andballY
? -
When that ball hits a wall, what should happen?
-
How does the ball’s direction change after it hits a wall?
-
After it’s changed direction, how does the ball’s position change?
-
Use the Animation Design Worksheet to plan out the bouncing behavior.
This activity is pretty sophisticated! You’ll want to make sure there are plenty of visual scaffolds for students, or (even better!) have them generate these diagrams themselves.
By now, you may have noticed that the direction of the ball needs to change, and therefore needs to be added to our PongState
structure. There are different ways we could represent direction: it could be a String (e.g. “north”, “southeast”, “west”, etc), or it could be a pair of Numbers that represent how much the ball is moving in the x- and y-direction from frame to frame.
What other ways could you represent direction? What are the pros and cons of each representation?
Note: the pair-of-numbers representation is deeply aligned to physics, in which the pair represents a vector that translates the ball’s position over time.
Here is one way to represent this, using Numbers to keep track of direction:
# a PongState has each paddle's y-coordinates,
# the ball's (x, y) coordinates and the (Δx, Δy)
# values for the changing location of the ball
data pongState:
| pong(
paddle1Y :: Number, paddle2Y :: Number,
ballX :: Number, ballY :: Number,
moveX :: Number, moveY :: Number)
end
When the game begins, we can start out with moveX
and moveY
being specific numbers that move the ball up and to the right. We can change these later, or even make them randomized every time the game starts!
Before we worry about the paddles, let’s start by thinking about the top and bottom walls of the game screen.
-
What should happen if the ball hits the top of bottom of the screen?
-
How would you detect a collision with the top or bottom wall?
-
Make the ball bounce off the top and bottom, using the Animation Design Worksheet and the Design Recipe to help you if you get stuck!
Now let’s make some sample instances for when the game begins, when the ball is about to hit a wall, and then immediately after:
# paddles are at the starting position, ball is at (300, 200)
# and moving Δ20 to the right, and Δ10 each tick
pongStateA = pong(200, 200, 300, 200, 20, 10)
# the ball (x=150, y=280) is about to hit the top
pongStateB = pong(200, 300, 150, 280, 20, 10)
# after the ball (x=550, y=280) hits the top wall, it keeps
# going right (Δ20), but now it moves down instead of up (Δ-10)
pongStateC = pong(200, 300, 550, 320, 20, -10)
The ball starts out moving up and to the right, but once it hits a wall the direction needs to change. Instead of moving up (adding 10 each tick), it’s now moving down (adding -10 each tick) after bouncing off the wall.
Note: Once the ball hits the wall, its y-position needs to change! If the ball stays where it is, it will still be considered to have "hit" the wall on the next tick. This will cause the ball to jitter back and forth, as it constantly hits the same wall over and over.
Change next-state-tick
so that it generates the next PongState
using the ball’s previous position and the move
fields. Then, add conditionals to next-state-tick
so that it will change the direction of the ball when it’s hit a wall.
Some students may ask about having the ball change angle based on where the it hits the paddle. This is a terrific question, and students should be encouraged to think about this behavior after they’ve implemented the simpler behavior.
Let’s walk through our new next-state-tick
function:
# next-state-tick :: pongState -> pongState
# move the ball, based on direction fields
fun next-state-tick(w):
if (is-on-wall(w)):
pong(w.paddle1Y, w.paddle2Y, # paddles don't change position
w.ballX + w.moveX, # ball moves from X to X+ΔX,
w.ballY + (w.moveY * -1), # and from Y to Y-ΔY
w.moveX, w.moveY * -1) # Δy reverses direction
else:
pong(
w.paddle1Y, w.paddle2Y,
w.ballX + w.moveX, w.ballY + w.moveY,
w.moveX, w.moveY)
end
end
If a collision with an upper or lower wall occurs, we need to do two things.
(1) Move the ball to it’s next position, and make sure that new position is far enough away from the paddle so that it won’t be considered another collision.
(2) Flip the y-direction so that the ball is moving in the opposite direction. This is easy to do, by multiplying moveY
by −1.
Now it’s time to start thinking about a different kind of collision: what happens when the ball hits a paddle?
-
How do you know when the ball has hit
paddle1
?paddle2
? -
Use the Design Recipe to write
hit-paddle1
andhit-paddle2
. -
Change
next-state-tick
so it checks for a paddle collision in addition to the wall collision.
🔗Synthesize 5 minutes
You’ve got the beginnings of a very nice Pong game! What are some features you might want to add?
Let students brainstorm ideas. Some suggestions: keeping score, a game-over event, a splash screen…
These materials were developed partly through support of the National Science Foundation, (awards 1042210, 1535276, 1648684, 1738598, 2031479, and 1501927). Bootstrap by the Bootstrap Community is licensed under a Creative Commons 4.0 Unported License. This license does not grant permission to run training or professional development. Offering training or professional development with materials substantially derived from Bootstrap must be approved in writing by a Bootstrap Director. Permissions beyond the scope of this license, such as to run training, may be available by contacting contact@BootstrapWorld.org.