The Problem: Create a basic 1970s style Pong game for one player using the Processing programming language. The paddle will be on the right and the ball will bounce off of the three other sides. If the ball passes the paddle while the ball is traveling to the right, game play ends. The paddle will be controlled by the keyboard’s UP and DOWN arrow keys.
The purpose of this article is to present my problem solving process as I create Pong. I’m going to deliberately walk through the process as a novice programmer might. Students rarely get to see how their instructors think through a problem, unless the instructor live-codes in front of them. But, even then, students are confused by the changing dynamics of the solution as the instructor begins to understand the problem better and refactors his or her code in real-time.
First, let me make sure I understand the problem space. A quick diagram often helps:
- There’s a playing field of some unspecified dimensions
- there’s a ball (a circle)
Ok, that seems like a good start. Now, let me do the simplest thing that can possibly work to represent what I know about the game. In Processing, there’s a standard skeleton for basic programs:
- void setup() {
- }
- void draw() {
- }
Now, let me create the playing field (window) in the setup method and create a ball (circle or ellipse) in the draw method. The Processing reference manual tells me that when drawing an ellipse, the default drawing mode assumes that the ellipse method takes four arguments: x and y coordinates for the center of the ellipse and width and height values for the horizontal and vertical diameters of the ellipse.
Further, the setup method is executed just once, before the program gets started. The draw method is an implicit loop in Processing; it gets called many times per second to redraw the window.
- void setup() {
- size(400, 400);
- }
- void draw() {
- ellipse(20, 20, 20, 20);
- }
If I run my code, I get a 400 by 400 pixel window and a circle located at (20,20) that is 20 pixels wide and 20 pixels high, but so far, it just sits there.
- the ball moves from left to right
Well, that implies that the x and y coordinates of the ellipse need to vary, so I’m going to need to refactor my code to use variables for the x and y coordinates of the ball. When I refactor, I want to reorganize my code, but not change the functionality: the code should be organized better, but the program should do the same thing. I like to use obvious variable names, so my code is easy to read and understand, so I’ll use “ballX” and “ballY”. (nota bene: when you find yourself prefixing variable names with the name of objects you’re manipulating, it suggests strongly that you should consider object-oriented design/programming.)
- float ballX = 20;
- float ballY = 20;
- void setup() {
- size(400, 400);
- }
- void draw() {
- ellipse(ballX, ballY, 20, 20);
- }
I run my code again, and indeed it behaves exactly the same as it did before the refactor. Now, I’ll add the least code I can to get the ball to move from left to right a little bit each time it’s drawn. It’s often difficult for novices to break down a task like “move the ball to the right side of the screen” into the smaller, discrete steps necessary to get the task done. The key, I think, is to think of a loop (in this case, the implicit loop of the draw method) like a flipbook animation from your childhood. You’re not going to just magically have the ball appear at the right edge of the window. Instead, you’re going to move the ball just a little toward the right edge each time it’s drawn.
It’s probably best for me to think about how much of a change I want ballX to undergo during each drawing cycle. It’s customary to refer to a small change in x as “dX”, the “delta x” or “change in x” value. Since x represents the number of pixels from the left edge of the window, adding just 2 pixels to the ball’s location each time it is drawn should have a smooth animation effect. So, I’ll set the initial value of dX to 2 and add dX to ballX each time the draw method is executed.
- float ballX = 20;
- float ballY = 20;
- float dX = 2;
- void setup() {
- size(400, 400);
- }
- void draw() {
- ellipse(ballX, ballY, 20, 20);
- ballX = ballX + dX;
- }
When I run my code, the ball moves!! Not well, but… it moves! Each drawing of the ball is being left on the playing field. In Processing, to start each drawing cycle with a clean window, you set the background of the window. Here, I’ll set it to a solid color using the RGB color model: (255, 255, 255), which is the value I want for red, green, and blue components of the color. Setting them all to 255 causes the background to be white.
- float ballX = 20;
- float ballY = 20;
- float dX = 2;
- void setup() {
- size(400, 400);
- }
- void draw() {
- background(255, 255, 255);
- ellipse(ballX, ballY, 20, 20);
- ballX = ballX + dX;
- }
When I re-run it, the ball now moves cleanly… and keeps moving, right off the right side of the window. I want to add code that says that if the ball reaches the right side of the window, it should move back the other way. That’s not ultimately how game play will work, but I want to be able to see the ball moving around successfully, before I worry about how the game ends. I know that in this particular case, the right side of the window is at x coordinate 400, because that’s how big I made the window in the setup method. So, if the ball’s x position reaches (or passes) 400 pixels, I’ll reverse the direction of the change (dX). If a dX value of 2 moves the ball 2 pixels to the right, then a dX value of -2 would move the ball 2 pixels to the left. So all I should need to do is change the sign of dX (lines 13-15). Notice that I’ve added a comment at line 14 using the // notation. Generally, comments should describe why you’re writing the code, not how; the code itself tells me how.
- float ballX = 20;
- float ballY = 20;
- float dX = 2;
- void setup() {
- size(400, 400);
- }
- void draw() {
- background(255, 255, 255);
- ellipse(ballX, ballY, 20, 20);
- if (ballX > 400) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- ballX = ballX + dX;
- }
If I run the code, I get a ball that bounces off of the right side of the window, then disappears off the left side!
- the ball bounces when it hits the left of the playing field
Well, I know that the left side of the window is at x coordinate 0 (zero), so I can repeat my code that handles bouncing on the right side (lines 13-15) and change it a little to handle bouncing on the left side of the window.
- float ballX = 20;
- float ballY = 20;
- float dX = 2;
- void setup() {
- size(400, 400);
- }
- void draw() {
- background(255, 255, 255);
- ellipse(ballX, ballY, 20, 20);
- if (ballX > 400) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballX < 0) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- ballX = ballX + dX;
- }
This seems to give me the bouncing ball I want, but I’m bothered by the magic number 400 I’ve used at line 13. In general, I won’t know how wide or high the playing field window is. I need to use a variable that represents the width of the window and, fortunately, Processing provides such a variable for me already: width.
- float ballX = 20;
- float ballY = 20;
- float dX = 2;
- void setup() {
- size(400, 400);
- }
- void draw() {
- background(255, 255, 255);
- ellipse(ballX, ballY, 20, 20);
- if (ballX > width) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballX < 0) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- ballX = ballX + dX;
- }
So far, I’ve only addressed the ball’s movement left and right. It doesn’t move up and down at all. Let me write a few lines of code (lines 4 and 23, below) just the way I’ve handled dX and create a dY variable that behaves similarly.
- float ballX = 20;
- float ballY = 20;
- float dX = 2;
- float dY = 2;
- void setup() {
- size(400, 400);
- }
- void draw() {
- background(255, 255, 255);
- ellipse(ballX, ballY, 20, 20);
- if (ballX > width) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballX < 0) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- ballX = ballX + dX;
- ballY = ballY + dY;
- }
Well that seems OK… the ball bounces from the upper-left corner to the lower-right corner, but it’s not very interesting. Let me spice things up a little by changing the magic numbers I’ve used for dX and dY to random numbers in the range [+1..+2] at lines 3 and 4.
- float ballX = 20;
- float ballY = 20;
- float dX = random(1, 2);
- float dY = random(1, 2);
- void setup() {
- size(400, 400);
- }
- void draw() {
- background(255, 255, 255);
- ellipse(ballX, ballY, 20, 20);
- if (ballX > width) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballX < 0) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- ballX = ballX + dX;
- ballY = ballY + dY;
- }
When I run it a few times, I notice that the ball works well, if it happens to reach the right and left sides of the window, but when it reaches the top or bottom of the window, it disappears. I can add two additional if statements similar to the two I already have, but this time they’ll work with the variables ballY and dY. Also, the new code (lines 22-28, below) doesn’t need to know about the width of the window, but it does need to know about the height of the window and, again, Processing provides us with a convenient variable named height.
- the ball bounces when it hits the top of the playing field
- the ball bounces when it hits the bottom of the playing field
- float ballX = 20;
- float ballY = 20;
- float dX = random(1, 2);
- float dY = random(1, 2);
- void setup() {
- size(400, 400);
- }
- void draw() {
- background(255, 255, 255);
- ellipse(ballX, ballY, 20, 20);
- if (ballX > width) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballX < 0) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballY > height) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- if (ballY < 0) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- ballX = ballX + dX;
- ballY = ballY + dY;
- }
Alright, now that the ball seems to be moving around well, I’ll turn my attention to the paddle.
- there’s a paddle (a rectangle) near the right of the playing field
I’m going to make some assumptions about the placement of the paddle. First, it won’t be touching the right side of the window. Second, it will be 10 pixels wide and 30 pixels tall. I’m also placing it arbitrarily 10 pixels from the top of the window. I’m pulling these numbers out of thin air (they’re magic numbers), so I may well need to change them or replace them with variables later.
- float ballX = 20;
- float ballY = 20;
- float dX = random(1, 2);
- float dY = random(1, 2);
- void setup() {
- size(400, 400);
- }
- void draw() {
- background(255, 255, 255);
- ellipse(ballX, ballY, 20, 20);
- rect(width – 15, 10, 10, 30);
- if (ballX > width) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballX < 0) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballY > height) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- if (ballY < 0) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- ballX = ballX + dX;
- ballY = ballY + dY;
- }
So, now I have an unmoving, inert paddle! Well, it’s progress… I’ll deal with the movement of the paddle, first. I know that I want to change the vertical position of the paddle, which is its y coordinate. So far, the position of the paddle has been hard coded with the magic number 10 (in line 14, above). I’ll need to refactor that into a variable, since its value will change over time, much like ballX and ballY. I’ll call it paddleY, to be consistent and obvious.
- float ballX = 20;
- float ballY = 20;
- float dX = random(1, 2);
- float dY = random(1, 2);
- float paddleY = 10;
- void setup() {
- size(400, 400);
- }
- void draw() {
- background(255, 255, 255);
- ellipse(ballX, ballY, 20, 20);
- rect(width – 15, paddleY, 10, 30);
- if (ballX > width) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballX < 0) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballY > height) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- if (ballY < 0) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- ballX = ballX + dX;
- ballY = ballY + dY;
- }
To be completely consistent, I’d like to replace my magic use of “width – 15” (line 15, above) with a variable (line 17, below), too. It’s the x coordinate of the paddle, so I’ll create a paddleX variable, just like the paddleY variable. I’m going to run into one problem, though. The variables I define (lines 1-6, below) are declared and initialized before the setup method executes. That means that the size of the window hasn’t been set yet (line 8), so I can’t initialize the value of paddleX, since it’s determined relative to the width of the window. I can only declare my intention to use it (line 5). Later, after the size of the window has been set (line 9), then I can initialize the value of paddleY (line 10) and replace my magic number with the variable (line 17).
- float ballX = 20;
- float ballY = 20;
- float dX = random(1, 2);
- float dY = random(1, 2);
- float paddleX;
- float paddleY = 10;
- void setup() {
- size(400, 400);
- paddleX = width – 15;
- }
- void draw() {
- background(255, 255, 255);
- ellipse(ballX, ballY, 20, 20);
- rect(paddleX, paddleY, 10, 30);
- if (ballX > width) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballX < 0) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballY > height) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- if (ballY < 0) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- ballX = ballX + dX;
- ballY = ballY + dY;
- }
- the paddle can move up and down using the keyboard’s UP and DOWN arrow keys as input
Let’s deal with the problem of having the paddle respond to key presses, now. I do a quick search of the Processing Reference web pages and find some example code for the keyCode property that is close to what I want to do.
- color fillVal = color(126);
- void draw() {
- fill(fillVal);
- rect(25, 25, 50, 50);
- }
- void keyPressed() {
- if (key == CODED) {
- if (keyCode == UP) {
- fillVal = 255;
- } else if (keyCode == DOWN) {
- fillVal = 0;
- }
- } else {
- fillVal = 126;
- }
- }
In their example, pressing the arrow keys results in a changed fill color (lines 10-14, above). For my pong game, I want the arrow keys to change the value of paddleY, the paddle’s vertical position. The variables, draw method (1-6), and else statement (lines 11-13) from the example are irrelevant to my pong game.
As a programmer I often need to find examples of how a task is performed in a given language, and then modify the example to match my circumstances better. When I do that, it’s good practice to add a comment explaining where I got the original code and acknowledging that it’s not my original creation. First, it provides me and other programmers with a road map, if I or they ever need to maintain the program I’ve written. Second, it makes clear which parts of the code are my original creation and which I’ve borrowed from other sources. If I were writing an English essay, I would use quotation marks and proper citations. When writing code, I add comments and provide URLs, email addresses, etc. to cite my sources.
- float ballX = 20;
- float ballY = 20;
- float dX = random(1, 2);
- float dY = random(1, 2);
- float paddleX;
- float paddleY = 10;
- void setup() {
- size(400, 400);
- paddleX = width – 15;
- }
- void draw() {
- background(255, 255, 255);
- ellipse(ballX, ballY, 20, 20);
- rect(paddleX, paddleY, 10, 30);
- if (ballX > width) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballX < 0) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballY > height) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- if (ballY < 0) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- ballX = ballX + dX;
- ballY = ballY + dY;
- }
- // based on code from http://processing.org/reference/keyCode.html
- void keyPressed() {
- if (key == CODED) {
- if (keyCode == UP) {
- paddleY = paddleY – 30;
- } else if (keyCode == DOWN) {
- paddleY = paddleY + 30;
- }
- }
- }
I have once again put in some magic numbers: 30 at lines 17, 43, and 45, above. It’s the paddle height, but that’s not obvious from the code. So, let me refactor that to a variable.
- float ballX = 20;
- float ballY = 20;
- float dX = random(1, 2);
- float dY = random(1, 2);
- float paddleX;
- float paddleY = 10;
- float paddleH = 30;
- void setup() {
- size(400, 400);
- paddleX = width – 15;
- }
- void draw() {
- background(255, 255, 255);
- ellipse(ballX, ballY, 20, 20);
- rect(paddleX, paddleY, 10, paddleH);
- if (ballX > width) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballX < 0) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballY > height) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- if (ballY < 0) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- ballX = ballX + dX;
- ballY = ballY + dY;
- }
- // based on code from http://processing.org/reference/keyCode.html
- void keyPressed() {
- if (key == CODED) {
- if (keyCode == UP) {
- paddleY = paddleY – paddleH;
- } else if (keyCode == DOWN) {
- paddleY = paddleY + paddleH;
- }
- }
- }
Now that we have a paddle, we want to reconsider when the ball should bounce on the right side of our window. As the code is written, it bounces with it reaches the edge of the window itself, but we want it to bounce when it reaches the left-hand face of the paddle, which is located along a vertical line located at paddleX. I’ve updated line 20, below.
- float ballX = 20;
- float ballY = 20;
- float dX = random(1, 2);
- float dY = random(1, 2);
- float paddleX;
- float paddleY = 10;
- float paddleH = 30;
- void setup() {
- size(400, 400);
- paddleX = width – 15;
- }
- void draw() {
- background(255, 255, 255);
- ellipse(ballX, ballY, 20, 20);
- rect(paddleX, paddleY, 10, paddleH);
- if (ballX > paddleX) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballX < 0) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballY > height) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- if (ballY < 0) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- ballX = ballX + dX;
- ballY = ballY + dY;
- }
- // based on code from http://processing.org/reference/keyCode.html
- void keyPressed() {
- if (key == CODED) {
- if (keyCode == UP) {
- paddleY = paddleY – paddleH;
- } else if (keyCode == DOWN) {
- paddleY = paddleY + paddleH;
- }
- }
- }
But that’s not the end of the story. As it’s written, the ball will bounce when it reaches the invisible vertical line, even if it isn’t touching the face of the paddle.
- the ball bounces when it hits the paddle (approaching from the left)
So, we need to update line 20 so that we check not only the horizontal position of the ball (which we’ve been doing all along), but also the vertical position of the ball with respect to the vertical position of the paddle.
- float ballX = 20;
- float ballY = 20;
- float dX = random(1, 2);
- float dY = random(1, 2);
- float paddleX;
- float paddleY = 10;
- float paddleH = 30;
- void setup() {
- size(400, 400);
- paddleX = width – 15;
- }
- void draw() {
- background(255, 255, 255);
- ellipse(ballX, ballY, 20, 20);
- rect(paddleX, paddleY, 10, paddleH);
- if ((ballX > paddleX) && (ballY >= paddleY) && (ballY <= paddleY + paddleH)) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballX < 0) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballY > height) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- if (ballY < 0) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- ballX = ballX + dX;
- ballY = ballY + dY;
- }
- // based on code from http://processing.org/reference/keyCode.html
- void keyPressed() {
- if (key == CODED) {
- if (keyCode == UP) {
- paddleY = paddleY – paddleH;
- } else if (keyCode == DOWN) {
- paddleY = paddleY + paddleH;
- }
- }
- }
I’m getting nervous about the complexity of the logic on line 20; other programmers (including my future self!) may not understand what my intention was in writing that code. In fact, I haven’t really given a name to what it is I’m trying to do on that line! A large part of programming is communication. As a programmer, I’m trying to communicate my intentions not only to the computer, so that it can execute them, but also to other programmers who may read my code. I’ve tried to create a domain specific set of variable names (ballX, ballY, dX, dY, paddleX, paddleY, and paddleH) to make my code more readable by humans. If I only cared about the computer understanding my code, I could just as easily have used bX and bY or iLoveRockAndRollX and iLoveRockAndRollY. But humans (me, my instructors, my students, my friends, my future employers) need to read the code, too, so I’d like to make it as understandable as possible.
With that in mind, I’m going to extract the logic from line 20 into its own method with a method name that makes my intentions clear(er). My new method will return a boolean value (either true or false) depending on whether there has been a collision.
- float ballX = 20;
- float ballY = 20;
- float dX = random(1, 2);
- float dY = random(1, 2);
- float paddleX;
- float paddleY = 10;
- float paddleH = 30;
- void setup() {
- size(400, 400);
- paddleX = width – 15;
- }
- void draw() {
- background(255, 255, 255);
- ellipse(ballX, ballY, 20, 20);
- rect(paddleX, paddleY, 10, paddleH);
- if (collision()) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballX < 0) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballY > height) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- if (ballY < 0) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- ballX = ballX + dX;
- ballY = ballY + dY;
- }
- boolean collision() {
- return (ballX > paddleX) && (ballY >= paddleY) && (ballY <= paddleY + paddleH);
- }
- // based on code from http://processing.org/reference/keyCode.html
- void keyPressed() {
- if (key == CODED) {
- if (keyCode == UP) {
- paddleY = paddleY – paddleH;
- } else if (keyCode == DOWN) {
- paddleY = paddleY + paddleH;
- }
- }
- }
The collision method, so far, is still not transparent to the casual reader. I’d like to refactor the code within that method to make my intentions clearer.
- float ballX = 20;
- float ballY = 20;
- float dX = random(1, 2);
- float dY = random(1, 2);
- float paddleX;
- float paddleY = 10;
- float paddleH = 30;
- void setup() {
- size(400, 400);
- paddleX = width – 15;
- }
- void draw() {
- background(255, 255, 255);
- ellipse(ballX, ballY, 20, 20);
- rect(paddleX, paddleY, 10, paddleH);
- if (collision()) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballX < 0) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballY > height) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- if (ballY < 0) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- ballX = ballX + dX;
- ballY = ballY + dY;
- }
- boolean collision() {
- boolean returnValue = false; // assume there is no collision
- if (ballX > paddleX) {
- if ((ballY >= paddleY) && (ballY <= paddleY + paddleH)) {
- returnValue = true;
- }
- }
- return returnValue;
- }
- // based on code from http://processing.org/reference/keyCode.html
- void keyPressed() {
- if (key == CODED) {
- if (keyCode == UP) {
- paddleY = paddleY – paddleH;
- } else if (keyCode == DOWN) {
- paddleY = paddleY + paddleH;
- }
- }
- }
That refactoring has revealed a problem with my thinking. So far, I’ve been testing only that the ball is to the right of the left-hand face of the paddle. If the ball somehow managed to get BETWEEN the right edge of the window and the paddle, it would still count as a collision and the ball would start back toward the left. I’ve noticed this because my testing of the horizontal position of the ball at line 42 isn’t parallel to my testing of the vertical position of the ball at line 43. In other words, I’m testing that the ball is between the top and bottom of the paddle, but I’m only testing that it’s to the right of the left-most side of the paddle.
In order to fix this, I need to know how wide the paddle is. Unfortunately, I’ve left that as a magic number (10) at line 18, until now. I’ll extract that to a variable (paddleW) and update my collision method to incorporate that at line 43.
- float ballX = 20;
- float ballY = 20;
- float dX = random(1, 2);
- float dY = random(1, 2);
- float paddleX;
- float paddleY = 10;
- float paddleW = 10;
- float paddleH = 30;
- void setup() {
- size(400, 400);
- paddleX = width – 15;
- }
- void draw() {
- background(255, 255, 255);
- ellipse(ballX, ballY, 20, 20);
- rect(paddleX, paddleY, paddleW, paddleH);
- if (collision()) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballX < 0) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballY > height) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- if (ballY < 0) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- ballX = ballX + dX;
- ballY = ballY + dY;
- }
- boolean collision() {
- boolean returnValue = false; // assume there is no collision
- if ((ballX >= paddleX) && (ballX <= paddleX + paddleW)) {
- if ((ballY >= paddleY) && (ballY <= paddleY + paddleH)) {
- returnValue = true;
- }
- }
- return returnValue;
- }
- // based on code from http://processing.org/reference/keyCode.html
- void keyPressed() {
- if (key == CODED) {
- if (keyCode == UP) {
- paddleY = paddleY – paddleH;
- } else if (keyCode == DOWN) {
- paddleY = paddleY + paddleH;
- }
- }
- }
Finally, I want to end the game, if the player missed the paddle.
- the game ends when the ball reaches the right of the playing field
I notice that, if the ball’s x coordinate is greater than the window’s width, then the ball must have passed by the paddle, so the game should end. There are other possible conditions you might use, but this one seems simple and obvious, at the moment.
- float ballX = 20;
- float ballY = 20;
- float dX = random(1, 2);
- float dY = random(1, 2);
- float paddleX;
- float paddleY = 10;
- float paddleW = 10;
- float paddleH = 30;
- void setup() {
- size(400, 400);
- paddleX = width – 15;
- }
- void draw() {
- background(255, 255, 255);
- ellipse(ballX, ballY, 20, 20);
- rect(paddleX, paddleY, paddleW, paddleH);
- if (ballX > width) {
- fill(255, 0, 0, 100);
- rect(0, 0, width, height);
- noLoop();
- }
- if (collision()) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballX < 0) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballY > height) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- if (ballY < 0) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- ballX = ballX + dX;
- ballY = ballY + dY;
- }
- boolean collision() {
- boolean returnValue = false; // assume there is no collision
- if ((ballX >= paddleX) && (ballX <= paddleX + paddleW)) {
- if ((ballY >= paddleY) && (ballY <= paddleY + paddleH)) {
- returnValue = true;
- }
- }
- return returnValue;
- }
- // based on code from http://processing.org/reference/keyCode.html
- void keyPressed() {
- if (key == CODED) {
- if (keyCode == UP) {
- paddleY = paddleY – paddleH;
- } else if (keyCode == DOWN) {
- paddleY = paddleY + paddleH;
- }
- }
- }
I have also noticed that by using the ball’s x and y coordinates in my collision method, and leaving the default ellipse drawing mode set to CENTER, it’s actually the center point of the ball that I’m using as the hot spot (have you hit the wall? have you hit the paddle?) when I should probably be using the ball’s left-, right-, top-, and bottom-most points as the hot spots.
In order to be able to compute the left-, right-, top-, and bottom-most points of the ball, I’m going to need to know the radius of the ball (or, equivalantly the diameter). Unfortuneately, I again left that as a magic number (line 16, above), so I’ll first need to refactor that and extract the radius to a variable (lines 3, 18).
- float ballX = 20;
- float ballY = 20;
- float ballR = 10;
- float dX = random(1, 2);
- float dY = random(1, 2);
- float paddleX;
- float paddleY = 10;
- float paddleW = 10;
- float paddleH = 30;
- void setup() {
- size(400, 400);
- paddleX = width – 15;
- }
- void draw() {
- background(255, 255, 255);
- ellipse(ballX, ballY, 2 * ballR, 2 * ballR);
- rect(paddleX, paddleY, paddleW, paddleH);
- if (ballX > width) {
- fill(255, 0, 0, 100);
- rect(0, 0, width, height);
- noLoop();
- }
- if (collision()) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballX < 0) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballY > height) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- if (ballY < 0) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- ballX = ballX + dX;
- ballY = ballY + dY;
- }
- boolean collision() {
- boolean returnValue = false; // assume there is no collision
- if ((ballX >= paddleX) && (ballX <= paddleX + paddleW)) {
- if ((ballY >= paddleY) && (ballY <= paddleY + paddleH)) {
- returnValue = true;
- }
- }
- return returnValue;
- }
- // based on code from http://processing.org/reference/keyCode.html
- void keyPressed() {
- if (key == CODED) {
- if (keyCode == UP) {
- paddleY = paddleY – paddleH;
- } else if (keyCode == DOWN) {
- paddleY = paddleY + paddleH;
- }
- }
- }
Now, I can add methods that compute the bounds of the ball on demand (lines 58-72).
- float ballX = 20;
- float ballY = 20;
- float ballR = 10;
- float dX = random(1, 2);
- float dY = random(1, 2);
- float paddleX;
- float paddleY = 10;
- float paddleW = 10;
- float paddleH = 30;
- void setup() {
- size(400, 400);
- paddleX = width – 15;
- }
- void draw() {
- background(255, 255, 255);
- ellipse(ballX, ballY, 2 * ballR, 2 * ballR);
- rect(paddleX, paddleY, paddleW, paddleH);
- if (ballX > width) {
- fill(255, 0, 0, 100);
- rect(0, 0, width, height);
- noLoop();
- }
- if (collision()) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballX < 0) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballY > height) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- if (ballY < 0) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- ballX = ballX + dX;
- ballY = ballY + dY;
- }
- boolean collision() {
- boolean returnValue = false; // assume there is no collision
- if ((ballX >= paddleX) && (ballX <= paddleX + paddleW)) {
- if ((ballY >= paddleY) && (ballY <= paddleY + paddleH)) {
- returnValue = true;
- }
- }
- return returnValue;
- }
- float ballLeft() {
- return ballX – ballR;
- }
- float ballRight() {
- return ballX + ballR;
- }
- float ballTop() {
- return ballY – ballR;
- }
- float ballBottom() {
- return ballY + ballR;
- }
- // based on code from http://processing.org/reference/keyCode.html
- void keyPressed() {
- if (key == CODED) {
- if (keyCode == UP) {
- paddleY = paddleY – paddleH;
- } else if (keyCode == DOWN) {
- paddleY = paddleY + paddleH;
- }
- }
- }
It’s usually good practice when refactoring to only change one thing at a time (i.e., only add one new method, only change one variable name, etc.) and then retest your code. That way, you limit the range of mistakes, typos, logic errors, etc. that you might make at any one time.
So, I’ll begin by finding all the lines of code that I intended to refer to the right-most point of the ball. It seems that lines 22 and 50 should be my targets.
- float ballX = 20;
- float ballY = 20;
- float ballR = 10;
- float dX = random(1, 2);
- float dY = random(1, 2);
- float paddleX;
- float paddleY = 10;
- float paddleW = 10;
- float paddleH = 30;
- void setup() {
- size(400, 400);
- paddleX = width – 15;
- }
- void draw() {
- background(255, 255, 255);
- ellipse(ballX, ballY, 2 * ballR, 2 * ballR);
- rect(paddleX, paddleY, paddleW, paddleH);
- if (ballRight() > width) {
- fill(255, 0, 0, 100);
- rect(0, 0, width, height);
- noLoop();
- }
- if (collision()) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballX < 0) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballY > height) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- if (ballY < 0) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- ballX = ballX + dX;
- ballY = ballY + dY;
- }
- boolean collision() {
- boolean returnValue = false; // assume there is no collision
- if ((ballRight() >= paddleX) && (ballX <= paddleX + paddleW)) {
- if ((ballY >= paddleY) && (ballY <= paddleY + paddleH)) {
- returnValue = true;
- }
- }
- return returnValue;
- }
- float ballLeft() {
- return ballX – ballR;
- }
- float ballRight() {
- return ballX + ballR;
- }
- float ballTop() {
- return ballY – ballR;
- }
- float ballBottom() {
- return ballY + ballR;
- }
- // based on code from http://processing.org/reference/keyCode.html
- void keyPressed() {
- if (key == CODED) {
- if (keyCode == UP) {
- paddleY = paddleY – paddleH;
- } else if (keyCode == DOWN) {
- paddleY = paddleY + paddleH;
- }
- }
- }
Now, I’ll find all the lines of code that I intended to refer to the left-most point of the ball. It seems that lines 32 and 50 should be my targets.
- float ballX = 20;
- float ballY = 20;
- float ballR = 10;
- float dX = random(1, 2);
- float dY = random(1, 2);
- float paddleX;
- float paddleY = 10;
- float paddleW = 10;
- float paddleH = 30;
- void setup() {
- size(400, 400);
- paddleX = width – 15;
- }
- void draw() {
- background(255, 255, 255);
- ellipse(ballX, ballY, 2 * ballR, 2 * ballR);
- rect(paddleX, paddleY, paddleW, paddleH);
- if (ballRight() > width) {
- fill(255, 0, 0, 100);
- rect(0, 0, width, height);
- noLoop();
- }
- if (collision()) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballLeft() < 0) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballY > height) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- if (ballY < 0) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- ballX = ballX + dX;
- ballY = ballY + dY;
- }
- boolean collision() {
- boolean returnValue = false; // assume there is no collision
- if ((ballRight() >= paddleX) && (ballLeft() <= paddleX + paddleW)) {
- if ((ballY >= paddleY) && (ballY <= paddleY + paddleH)) {
- returnValue = true;
- }
- }
- return returnValue;
- }
- float ballLeft() {
- return ballX – ballR;
- }
- float ballRight() {
- return ballX + ballR;
- }
- float ballTop() {
- return ballY – ballR;
- }
- float ballBottom() {
- return ballY + ballR;
- }
- // based on code from http://processing.org/reference/keyCode.html
- void keyPressed() {
- if (key == CODED) {
- if (keyCode == UP) {
- paddleY = paddleY – paddleH;
- } else if (keyCode == DOWN) {
- paddleY = paddleY + paddleH;
- }
- }
- }
Test my code to confirm it’s still functioning, and then I’ll find all the lines of code that I intended to refer to the top-most point of the ball. It seems that lines 40 and 51 should be my targets.
- float ballX = 20;
- float ballY = 20;
- float ballR = 10;
- float dX = random(1, 2);
- float dY = random(1, 2);
- float paddleX;
- float paddleY = 10;
- float paddleW = 10;
- float paddleH = 30;
- void setup() {
- size(400, 400);
- paddleX = width – 15;
- }
- void draw() {
- background(255, 255, 255);
- ellipse(ballX, ballY, 2 * ballR, 2 * ballR);
- rect(paddleX, paddleY, paddleW, paddleH);
- if (ballRight() > width) {
- fill(255, 0, 0, 100);
- rect(0, 0, width, height);
- noLoop();
- }
- if (collision()) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballLeft() < 0) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballY > height) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- if (ballY < 0) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- ballX = ballX + dX;
- ballY = ballY + dY;
- }
- boolean collision() {
- boolean returnValue = false; // assume there is no collision
- if ((ballRight() >= paddleX) && (ballLeft() <= paddleX + paddleW)) {
- if ((ballY >= paddleY) && (ballY <= paddleY + paddleH)) {
- returnValue = true;
- }
- }
- return returnValue;
- }
- float ballLeft() {
- return ballX – ballR;
- }
- float ballRight() {
- return ballX + ballR;
- }
- float ballTop() {
- return ballY – ballR;
- }
- float ballBottom() {
- return ballY + ballR;
- }
- // based on code from http://processing.org/reference/keyCode.html
- void keyPressed() {
- if (key == CODED) {
- if (keyCode == UP) {
- paddleY = paddleY – paddleH;
- } else if (keyCode == DOWN) {
- paddleY = paddleY + paddleH;
- }
- }
- }
Test my code once more to confirm it’s still functioning, and then I’ll find all the lines of code that I intended to refer to the bottom-most point of the ball. It seems that lines 36 and 51 should be my targets.
- float ballX = 20;
- float ballY = 20;
- float ballR = 10;
- float dX = random(1, 2);
- float dY = random(1, 2);
- float paddleX;
- float paddleY = 10;
- float paddleW = 10;
- float paddleH = 30;
- void setup() {
- size(400, 400);
- paddleX = width – 15;
- }
- void draw() {
- background(255, 255, 255);
- ellipse(ballX, ballY, 2 * ballR, 2 * ballR);
- rect(paddleX, paddleY, paddleW, paddleH);
- if (ballRight() > width) {
- fill(255, 0, 0, 100);
- rect(0, 0, width, height);
- noLoop();
- }
- if (collision()) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballLeft() < 0) {
- dX = -dX; // if dX == 2, it becomes -2; if dX is -2, it becomes 2
- }
- if (ballBottom() > height) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- if (ballTop() < 0) {
- dY = -dY; // if dY == 2, it becomes -2; if dY is -2, it becomes 2
- }
- ballX = ballX + dX;
- ballY = ballY + dY;
- }
- boolean collision() {
- boolean returnValue = false; // assume there is no collision
- if ((ballRight() >= paddleX) && (ballLeft() <= paddleX + paddleW)) {
- if ((ballBottom() >= paddleY) && (ballTop() <= paddleY + paddleH)) {
- returnValue = true;
- }
- }
- return returnValue;
- }
- float ballLeft() {
- return ballX – ballR;
- }
- float ballRight() {
- return ballX + ballR;
- }
- float ballTop() {
- return ballY – ballR;
- }
- float ballBottom() {
- return ballY + ballR;
- }
- // based on code from http://processing.org/reference/keyCode.html
- void keyPressed() {
- if (key == CODED) {
- if (keyCode == UP) {
- paddleY = paddleY – paddleH;
- } else if (keyCode == DOWN) {
- paddleY = paddleY + paddleH;
- }
- }
- }
There are additional features I’d like to add (the ability to reset the game or to use mouse input, for example) and refactorings I’d like to make (turn this into an object oriented program), but this is a working, playable game at this point that solves the problem, as stated. A well-trained programmer will no doubt be appalled at the coding style, but I’ve intentionally tried not to use shorthand notations or to introduce solutions or idioms that would be unfamiliar to the novice programmer.
The fact that I have multiple methods (setup, draw, keyPressed, and now collision) that are all accessing the same variables raises a serious concern for me as does my use of special methods to handle pseudo-properties of the ball (ballTop, ballBottom, ballLeft, ballRight). How can I hope to keep track of where variables are used and modified? I’m making my code difficult to maintain and understand by using instance variables all over the place. A better solution would be to use object-oriented programming techniques so that I can encapsulate the variables and behaviors related to the ball within an object (perhaps named Ball) and encapsulate the variables and behaviors related to the paddle within another object (named Paddle). I’m going to set aside my misgivings for now and leave the refactoring of this code into an object-oriented structure for another blog post.
Thanks for this clear and easy to understand example! I’m currently teaching a high school 1 – semester Intro to Computer Science course, and we just finished learning about coding objects in Processing. Did you ever refactor this project?
Unfortunately, that’s the only game I’ve documented in a think-aloud way. Thank you for the feedback! I’m starting a new project just this week that has similar think-aloud style, but isn’t about games and will likely rely mostly on R.
At North Haven Community School, North Haven, ME, I’ve developed over the last 6 years a Coding(code.org and Scratch) Robotics(WeDo & EV3) program for grades K-8. This year as a change of pace and in 7-8 I’ve ventured forth to expose these students to a text based programming language, Processing. As a capstone project for the 1st trimester I am going back to the 1070s with your Pong game. Thanks for describing in great detail your “Thinking through a basic pong game in Processing.”
Do you have any more games in Processing or Python?
Thanks
Edwin R. Sage
In the section of the code that detect ball-paddle collisions, consider increasing dX (delta x, the change in the x position per time step) a little bit. Possibly by 1% or 2%, but you’d also want to enforce a speed limit, too, so it doesn’t get too crazy: dX = min(dX * 1.02, 4);
I was a bit sloppy in the code in that I used the geometric center of the ball, but the left and right edges of the paddle. So, if the ball radius is > the paddle width, the ball may appear to pass through the paddle. Were I willing to make the code a bit more complex, I could compute the left and right edges of the ball, too, and use those.
Thanks for this detailed walk-through. I’m really enjoying the Processing language. Finally getting to play things on a screen w/o needing 100s of lines of code. Note that I couldn’t get the “r” button to activate. When the ball hits the wall, the background changes etc… per lines 35-38. When I hit the “r” key, nothing happens. Have not figured out why yet. BUT! I made the game repeat automatically by removing noLoop from line 38 and using reset() instead. If you lose, it just starts again. Good times!
Check out https://processing.org — it’s its own programming language, based on Java.