Pong is a game played by two players who each control a paddle placed at opposite sides of the screen. A ball is hit back and forth between the two paddles, and the top and bottom walls, until the ball goes past one player's paddle. The player who misses the ball loses the point. The game ends when one player reaches 11 points (you may change this number as long as it is greater than 3).
Table of Contents
- Learning Objectives
- Installation
- Planning
- Grade Rubric and Schedule
- Helpful Code and Advice
- Submit Your Work
- Practice modeling data with Objects
- Reuse code from previous projects to create something new
- Practice abstraction
- Apply the algorithm for detecting collisions between objects
Pong is not installed by default with the other projects. As such, you will need to install it yourself. To do so, go to your workspace and open up a terminal. Within the terminal, enter the following commands:
- cd asd-projects
- git clone https://github.com/operationspark/asd-pong pong
- rm -rf pong/.git*
- git add pong
- git commit -m "added pong"
- git push
- cd ..
Always start any programming task by clarifying what you want to do and then breaking it down into small steps. Small steps can get you just about anywhere if you’ve got enough time. If you get stuck, break it down smaller!
With your partner, consider each of these questions and make sure you are aligned on your answers:
- Describe the gameplay
- What are the conditions when the game begins?
- Does the game have an end? If so, what are the conditions for when it ends?
- What
if
s will there be?
- What are the visual game components? For example, in Bouncing Box, the game components were the board and the box.
- Which will be static? (the board)
- Which will be animated? (the box)
- What data will you need to manage each game component? For example, in Bouncing Box, the data values were
positionX
,speedX
, andpoints
.
- What events will occur in this game? (timer events, keyboard events, clicking events?)
- How do those events affect the data of the program?
- For each "event", write out the high-level logic of what will happen. It is better (and tricky) to be as specific as you can while remaining high-level!
For example: in bouncing box, when the box is clicked:
- The speed is increased
- The point total goes up by 1 and is displayed on the box
- The position of the box is reset to 0
- All code in proper sections (setup, core, helpers, etc.) - 5 points
- Use comments to describe setup and functions - 5 points
- Avoid magic numbers - 5 points
- Use helper functions to separate concerns - 5 points
- Generalize functions (i.e. only one collision detection function for all ball-paddle collisions; hard-coding to check both in a single function doesn't count) - 5 points
- Get the ball, paddles, and two score boxes to display on screen - 10 points
- Declare variables to store the data for the program - 5 points
- Use a factory function to create objects - 5 points
- Register keyboard events - 10 points
- Get the ball moving at game start (should be random to a degree) - 5 points
- Update paddle and ball positions - 5 points
- Make keyboard events react to the proper keys - 10 points
- Handle paddle-wall collisions and ball-wall collisions - 10 points
- Handle scoring (increase score and reset ball) - 5 points
- Handle ball-paddle collisions - 5 points
- End the game after a certain score (at least 3) - 5 points
Below are some helpful hints and suggestions that will hopefully aid you in this project. These hints will be broken up by weekly tasks.
HTML for Game Items:
Open the index.html
file. You should see this in the body:
<body>
<div id="board">
<div id="gameItem"></div>
</div>
</body>
Each project in this class will be build on some kind of board
with various gameItems
that are on the board. For this project, there are a number of required game items:
- the left paddle
- the ball
- the right paddle
- the score for player1
- the score for player2
Each one of these game items needs to be represented in HTML and, for the most part, <div>
s can be used. To create a <div>
with a particular id
attribute, place the id=""
attribute inside the opening tag:
<div id="uniqueGameItemName"></div>
Not all of these game items will need objects. It is up to you to decide which ones do and which ones don't. You also may want more elements than just the ones mentioned, but those are the bare minimum required.
CSS for Game Items
Open the index.css
file.
Adding CSS makes our gameItems actually become visible. For all projects in this course, we'll be using simple 2D shapes since they are relatively easy to render with basic HTML and CSS skills.
The following properties will be useful for determining the appearance of our DOM elements:
background-color
: the color of the elementwidth
: the width of the element in pixelsheight
: the height of the element in pixelsborder-radius
: how rounded the edges of the element are. Leaving out this property will leave the element as a rectangle. Setting this value to half ofwidth
orheight
will make the shape a circle (assuming width and height are the same).
The following properties will allow us to place our elements anywhere on the screen, relative to the board
.
position: absolute
: allows us to use thetop
andleft
properties to position HTML elements anywhere we want on the screen relative to the parent element.top
: the y-coordinate location of the element on a flipped y-axis (value increases as you move downwards).left
: the x-coordinate location of the element.
Overall, the CSS should look like this:
#id {
/* appearance */
background-color: white;
width: 20px;
height: 20px;
border-radius: 20px;
/* positioning */
position: absolute;
top: 100px;
left: 100px;
}
Suggestions for this project:
- Each paddle should have a unique
background-color
- Both paddles should have
width: 20px;
andheight: 80px;
- The ball should have
width:20px;
,height:20px
andborder-radius: 10px;
Using Factory Functions and Objects
We will need to manage the data for each game item in this project: the ball and each paddle.
Use objects to manage this data. For example, in bouncing box, we could organize the data for the box like so (shortening positionX
and positionY
to x
and y
:
var box = {};
box.x = 0;
box.y = 100;
box.width = 200;
box.height = 200;
box.speedX = 1;
box.speedY = 1;
box.id = "#box";
Notably, we are now storing the id of the HTML element in a variable. This will tie the data that manages each game item to the HTML element that is being controlled.
For bouncing box, we would refactor the moveBox()
function this:
function moveBox() {
box.x += box.speedX; // update the position of the box along the x-axis
$(box.id).css("left", box.x); // draw the box in the new location, positionX pixels away from the "left"
}
Since you'll be creating objects to represent the ball and each paddle, use a factory function to ensure that each gameItem
has the data below:
gameItem.id
gameItem.x
gameItem.y
gameItem.speedX
gameItem.speedY
gameItem.width
gameItem.height
When creating a factory function, the function should return an object that has a specific set of properties already assigned to it. The properties that you want customized for each object should be parameterized (turned into parameters/variables).
For example, consider this data for animal objects:
var spot = {};
spot.name = "spot";
spot.species = "dog";
spot.owner = "Farmer Fred";
var daisy = {};
daisy.name = "daisy";
daisy.species = "bird";
spot.owner = "Farmer Fred";
var bessy = {};
bessy.name = "bessy";
bessy.species = "cow";
spot.owner = "Farmer Fred";
Since each object shares the same properties; name
, species
, and owner
, I can create a factory function that reduces the repetitive creation of those objects.
For each value that is unique, I will add a parameter to my factory function. Any values that are shared can be hard-coded into the object.
// Initialization
var spot = Animal("spot", "dog");
var daisy = Animal("daisy", "bird");
var bessy = Animal("bessy", "cow");
// Factory Function
function Animal(name, species) {
var animal = {};
animal.name = name;
animal.species = species;
animal.owner = "Farmer Fred";
return animal;
}
Please keep in mind that the factory function you create should use jQuery to extract CSS information to initialize the x
, y
, width
, and height
values of your objects. As a reminder, you can get such information as follows:
var x = parseFloat($("#id").css("left"));
var y = parseFloat($("#id").css("top"));
var width = $("#id").width();
var height = $("#id").height();
Registering Keyboard Events
This is something you should already have plenty of practice with. However, there are some minor differences this time. Notably, there are two paddles that must be interacted with. Both paddles should react to both "keyup"
and "keydown"
events.
There are two ways to approach this issue. You can either make a total of four event handlers (one "keyup" and "keydown" per paddle), or you can make just two. To keep things simple (and shorter), you should follow the latter approach and only make two event handlers.
Handler 1: Should handle the "keydown" event for both paddles. Just make sure your conditions check for the various keys you care about (up arrow, down arrow, 'W', and 'S'), and have the relevant paddle move in the appropriate direction.
Handler 2: Should handle the "keyup" event for both paddles. As with the "keydown" handler, make sure your conditions check for the various keys you care about (up arrow, down arrow, 'W', and 'S'), and have the relevant paddle stop moving when one of its keys is released.
Check out the Walker project for ideas on how to move an object with your keyboard. Below is an example of simply printing whenever the ENTER
key is pressed down.
var KEYCODE = {
ENTER: 13,
};
function handleKeyDown() {
var keycode = event.which;
console.log(keycode);
if (keycode === KEYCODE.ENTER) {
console.log("enter pressed");
}
}
Use https://keycode.info/ to find out the keycode for any key.
Moving the Ball
Getting the ball to move is kind of important, so let's talk about how to do this. The best approach is as follows.
- In the "helper functions" area, create a new function called
startBall
that has no parameters and only gives theball
object a starting position and speed. - Up in the "one-time setup" section, call the
startBall
function.
In the startBall
function, you should give the ball
object a new x
and y
position (that way, you can reuse the function after a point is scored!). It should, of course, be placed at the center of the board. Furthermore, you should give it initial speedX
and speedY
values. These speed values should be random. It's up to you how to make them random, but it's important to be careful, at least with the speedX
value. For that reason, the following equation is suggested for speedX
.
randomNum = (Math.random() * 3 + 2) * (Math.random() > 0.5 ? -1 : 1);
That equation will assign a value either between -5
and -2
, or a value between 2
and 5
. If you want to change the range of values, you should only change the 3
and 2
values of the equation. The 3
says what the spread should be (bigger number means bigger range of possible values), and the 2
says what the minimum absolute value of randomNum
will be.
Repositioning DOM Elements
We'll need to reposition the ball and each paddle on each update of the timer. Luckily, we've learned how to move things in the past. This time we want to move multiple objects, but since moving an object is basically the same every time, you should only make one function to handle that. Here's how to approach the problem.
- Step 1: Create a function (call it
moveObject
), with a single parameter. That parameter will take the object you want to move as an argument. - Step 2: In the function, use the parameter and dot notation to change the current
x
andy
values of the object based on the object's current speed. - Step 3: After updating
x
andy
, use jQuery to update the"left"
and"top"
properties of the corresponding DOM element
Recall that you should use the jQuery .css()
function to draw the element in the new position. For example, to change how far left or right an element is, you could write:
$("elementID").css("left", positionX);
If we wanted to move the element vertically instead, you would do the same thing, but for the "top"
property:
$("elementID").css("top", positionY);
Of course, "elementID"
, positionX
, and positionY
should all be obtained using the function's parameter and dot notation when writing your own function, which this example does not do.
Collisions with Walls
In order to detect collisions with walls, you need to know three things.
- The position of the wall
- The position of your object
- The size of your object
When you start your project, you will not know the positions of all walls.
To fix this, you should create two new const
values under the "Constant Variables" section. These two new values should be
BOARD_WIDTH
BOARD_HEIGHT
You can obtain the values of BOARD_WIDTH
and BOARD_HEIGHT
using $("#board").width()
and $("#board").height()
, respectively.
That will give you the x
position of the right side of the board and the y
position of the bottom side of the board. As for the left and top of the board, both of those values are 0
, which is fine if you simply hard-code.
Once you know those values, detecting a collision with a wall is easy. You have four scenarios:
- If an object's
x
value goes past the left side of the box, then it collided with it. - If an object's
y
value goes past the top side of the box, then it collided with it. - If an object's
x + width
value goes past the right side of the box, then it collided with it. - If an object's
y + height
value goes past the bottom side of the box, then it collided with it.
IMPORTANT: You should make this collision detection be a single function (call it
wallCollision
) with a single parameter. The parameter should take in the object being checked as an argument (theboard
should not be an argument, however). This way, you only need to write the collision detection once, and you can use it not only for both paddles, but for the ball as well!
SUGGESTION: The wall collisions can also be handled using the same min/max approach used in the Image Filtering project. Take a look at the
keepInBounds
function there. With some slight modifications, you can actually use that function for both your paddles and ball hitting the top and bottom walls. Give it a try if you'd like to challenge yourself and create more efficient code.
NOTE: You can use the
wallCollision
function for the ball as well as the paddles. However, this will only work for detecting collisions with the top and bottom of the board.
Scoring
Scoring has three parts to it.
- Detecting that scoring has taken place
- Updating the score
- Resetting the ball
Each of these parts is a simple task.
- Detection - check if the ball collides with the left or right wall (can be done in the
wallCollision
function) - Update the appopriate score in memory, then redraw the scoring element to display the updated score (reminder:
$("#scoreId").text(updatedScore)
will change the element with id"scoreID"
to display whatever value is stored in the variableupdatedScore
) - Simply call your
startBall
function that you created back in Week 2
Collisions Between Objects
In games, collisions will occur frequently between objects. Having a function that can tell if two objects are colliding would be really convenient! The skeleton for such a function looks like this:
function doCollide(obj1, obj2) {
// return false if the objects do not collide
// return true if the objects do collide
}
and we would use such a function in our program like this:
if (doCollide(ball, paddleLeft)) {
// bounce ball off paddle Left
}
You should have already created a doCollide
function by this point. If you have, then you merely need to copy it into your code in the "helper functions" section, then call it twice. Once should be checking if the ball
collides with the leftPaddle
, and the other time should check if the ball
collides with the rightPaddle
.
If you have not created the doCollide
function, then below is a rough explanation on how to do so.
Click for Explanation
Any object passed to our `doCollide` function should store the data for an HTML element. Therefore, they must have an `$element` storing the jQuery object for the HTML element as well as `x` and `y` properties that store where the `$element` is.If you haven't set up your object data to represent the ball and the paddles, go back and do so before continuing
For now, let's assume that we have a generic
gameItem
that is passed to the function as one of our objects. It's HTML, CSS, and JavaScript look like this:<div id="gameItem"></div>#gameItem { position: absolute; left: 100px; /* distance from the left side of the screen */ top: 50px; /* distance from the top of the screen */ }var gameItem = {}; gameItem.$element = $("#gameItem"); gameItem.x = 100; // same as "left" gameItem.y = 50; // same as "top" // speedX and speedY aren't needed for nowAssuming that you are dealing with two
gameItem
objects,objA
andobjB
, thedoCollide
function's pseudocode would look like this:IF the left side of objA is left of the right side of objB AND the right side of objA is right of the left side of objB AND the top side of objA is above the bottom side of objB AND the bottom side of objA is below the top side of objB: return true ELSE: return false
Ending the Game
This one is easy. If either player scores enough points to win, then simply call the endGame()
function. The endGame()
function has already been created for you.
Submit your work regularly. Because these files are already being tracked by your GitHub repo, you can skip the "git add" step. Instead, enter the following commands:
git add -A
git commit -a -m "saving pong"
git push
Congratulations on completing Pong!