CODE CREATIVE
PONG
An intro to Game Design and Pygame
Beginnings
Pong is the game that started it all. Two paddles, one ball, and a joystick was all it took to capture the attention of the youth around the world in the early 80's. Can you begin to imagine the depth of boredom we experienced? I can't even when I try. My brain must be running a survival mechanism to protect itself from traumatic events.

Anyhow, we are going to code our own version of the game that includes images, motion, sounds, beginning/ending screens, and variations of your choice. As the old adage goes, every journey of a thousand miles starts with a single step. We will start by coding the original version, then give it a facelift.
Take a look at the video above to get an idea of what your game will look like by the end of this tutorial.


When creating a video game using Pygame, we need to start by importing the Pygame module. We can do this by placing the following code at the top of our program:
import pygame
Gaming Window
Next, we need to start by creating the window in which the game will be shown to the user. We can do this by using the function ‘display.set_mode(WIN_W, WIN_H)’ with WIN_W and WIN_H being the width and height of the window respectively. A good idea is to create global variables for these values:
import pygame

WIN_W = 920
WIN_H = 570

# Runs imported module
pygame.init()

def main():
	pygame.display.set_caption('Pong')
	screen = pygame.display.set_mode((WIN_W, WIN_H), pygame.SRCALPHA)

if __name__ == "__main__":
	main()
Looking at the code above, the last two lines of code might confuse you.
The short version of what’s happening is that the ‘main’ function is being invoked.
What is actually happening is something we don’t need to understand at the moment, however if you would like a further explanation, I would forward you to this thread found on stackoverflow.

Try running the program above and you will see that pygame creates a window for the briefest of moments and closes it. This is because the ‘display.set_mode’ command is run and then the interpreter moves to the next line of code which is the end of main and the program ends.

Game Loop
Something you have to understand is that Pygame is a video game engine, and video game engines, from lowly Pygame to Unreal Engine 4, requires that a loop to be run throughout the duration of the game. In this loop, all commands will be executed repeatedly changing the state of the game. If a ball is bouncing across the screen, the ball is drawn on the screen in each iteration of the loop, moving its x and y location by just a bit. As the loop runs, it appears that the ball is bouncing across the screen. Once the loop ends, the program ends and thus the game.

Let’s add a game loop to our code:
def main():
	pygame.display.set_caption('Pong')
	screen = pygame.display.set_mode((WIN_W, WIN_H), pygame.SRCALPHA)

	clock = pygame.time.Clock()
	play = True

	# Gameplay
	while play:
		print "hello world"
 
		# Limits frames per iteration of while loop
		clock.tick(60)
One thing that may confuse you in the code above is the call to ‘clock.tick()’. What this is doing is limiting the number of times the loop can run to 60 times per second. You can try changing that value to 3 if you’d like to see what happens.

Run the program now and what do you notice? ‘display.set_mode()’ is called and it creates the window, then the program contains a while loop that runs forever. Since the while loop keeps the program running, it allows us to see the window that was created, instead of instantly ending the program.
It’s important to realize that you must press the stop button on the bottom left-hand side of the Pycharm window to stop the program.


Setting a Background Color
Now, let’s get our screen to have a white background color by adding the following code:
def main():
	pygame.display.set_caption('Pong')
	screen = pygame.display.set_mode((WIN_W, WIN_H), pygame.SRCALPHA)

	clock = pygame.time.Clock()
	play = True
	screen.fill((255, 255, 255))

	# Gameplay
	while play:
 		pygame.event.get()
 		
		# Limits frames per iteration of while loop
		clock.tick(60)
Notice that I have erased the unnecessary ‘print’ command. Also, notice that I added a call to ‘fill’, which fills our screen with the color white. Try running your program and remember you must press the stop button on the bottom left-hand side to stop the program. Our program is easier to understand and manipulate if we create a global variable for the color white.
# Constants
WHITE = (255, 255, 255)

def main():
	...
	
	screen.fill(WHITE)

	# Gameplay loop
	...
Burning a Surface to the Monitor
Run your program and you will notice that nothing has changed and the screen does not have a white background. This is because of how Pygame works. It is very important for you to understand that in Pygame there are what we call ‘surfaces’. You can think of surfaces as transparent sheets of paper that have sprites or text on them. We must have a different surface for each sprite in our game. We can then print each of those surfaces onto our screen at specific (x, y) coordinates for the user to see. We can print a bird in the corner, a sun in the sky and a tree on the ground. Ultimately, we need to print our surfaces onto a special surface so that the user can see them. The function call to ‘display.set_mode’ creates that special surface that represents the user’s screen.
It’s important that you remember that what you print to this surface will be seen by the user.


In our program, our special surface is stored in the variable ‘screen’. You might be thinking, “I filled the screen with white in my loop, so why isn’t it showing in the window?” The answer is that you need to make a call to ‘display.flip’ to write what you have printed onto the ‘display.set_mode’ surface to the user's screen. You can think of ‘display.flip’ as burning the contents of the ‘screen’ onto the monitor. Let’s do that.
def main():
	pygame.display.set_caption('Pong')
	screen = pygame.display.set_mode((WIN_W, WIN_H), pygame.SRCALPHA)

	clock = pygame.time.Clock()
	play = True
	screen.fill(WHITE)

	# Gameplay
	while play:
 		pygame.event.get()
 		
		# Limits frames per iteration of while loop
		clock.tick(60)
		# Writes to main surface
	
		pygame.display.flip()
Adding a Paddle Sprite
Run your program now and you will see that now your window is white! Wonderful. Now, let’s get a sprite on the screen. To get a paddle onto the screen, we need to make a call to ‘Surface’.
def main():
	pygame.display.set_caption('Pong')
	
	screen = pygame.display.set_mode((WIN_W, WIN_H), pygame.SRCALPHA)
	
	# Paddle information
	paddle_width =20
	paddle_height = 70
	
	paddle = pygame.Surface((paddle_width, paddle_height))
	
	clock = pygame.time.Clock()
	play = True
	screen.fill(WHITE)

	# Gameplay
	while play:
	 	pygame.event.get()
	 	
		# Limits frames per iteration of while loop
		clock.tick(60)
		# Writes to main surface
		pygame.display.flip()	
The invocation of the ‘Surface’ method by default returns a surface that is colored black which is of the dimensions (x,y), or in our case (paddle_width, paddle_height). The surface that is returned is saved in the variable ‘paddle’. Remember that we cannot see this new sprite until we print it to the surface found in the ‘screen’ variable. If you don’t believe me, try running your program.

'Blit'-ing
The way Pygame allows you to print surfaces to other surfaces, including the main surface, is by invoking the ‘blit’ method.
It’s important to understand that ‘blit’ simply prints to a surface before burning it onto the monitor using ‘display.flip’.
If we have a ‘screen’ that is filled white and we want to show a sprite, we need to ‘blit’ the sprite onto ‘screen’ and then when we are finished with the ‘screen’ variable, we need to burn it to the monitor using ‘display.flip’. Let’s try using ‘blit’ to print our paddle onto the screen by adding the following code:
def main():
	pygame.display.set_caption('Pong')
	
	screen = pygame.display.set_mode((WIN_W, WIN_H), pygame.SRCALPHA)
	
	# Paddle information
	paddle_width =20
	paddle_height = 70
	
	paddle = pygame.Surface((paddle_width, paddle_height))
	
	clock = pygame.time.Clock()
	play = True
	screen.fill(WHITE)
	
	# Gameplay
	while play:
		pygame.event.get()
	
		# printing to the main screen
		screen.blit(paddle, (WIN_W/2, WIN_H/2))
	 
	 	# Limits frames per iteration of while loop
		clock.tick(60)
		# Writes to main surface
	
		pygame.display.flip()
We can see that the invocation to ‘blit’ required the main surface found in ‘screen’ and takes the paddle surface and (x, y) as arguments.
It’s important to realize that ‘blit’ needs the ‘screen’ so that it knows where this surface should be printed on to, ‘blit’ needs the paddle variable to know what image to paste onto the screen and ‘blit’ needs (WIN_W/2, WIN_H/2) to know where on the gaming window to paste the paddle.
Try running your program and you’ll see a paddle!

One thing to realize before moving forward is that Pygame treats the top left-hand corner as (0,0) and the bottom right-hand corner as (WIN_W, WIN_H). This may be counter-intuitive for you, but that’s what made sense to the designers of Pygame.

Now Try:

Now try and re-position the paddle in the correct starting location on the left side of the screen. Use the global variables WIN_W and WIN_H in your positional calulation so that the paddle is placed in the correct location no matter what window dimensions are used.

Now Try:

Now that your paddle is in the right location, try adding second paddle to your screen. Be sure to change your existing code and call the first paddle ‘paddle_1’ and the second paddle ‘paddle_2’. Also be sure to use variables for the (x, y) locations of each paddle. Let’s call them (x1, y1) for the left paddle and (x2, y2) for the right paddle. Remember that variables should be created and initialized at the top of your main function.

Now Try:

Now can you make a ball of dimensions 20x20 pixels appear in the middle of your screen?

You should have a window that looks very similar to this:

Exiting with the Red Button
Before we start with the motion characteristics of the paddles, have you noticed that when you try and click the red button to close the Pygame window your cursor turns into the rainbow ball of death? Let’s fix that annoying behavior. Add this ‘for loop’ to your while loop:
# Gameplay
while play:
	# Checks if window exit button pressed
	for event in pygame.event.get():
		if event.type == pygame.QUIT: sys.exit()
 
	# printing to the main screen
	screen.blit(paddle1, (x1, y1))
	screen.blit(paddle2, (x2, y2))
	screen.blit(ball, (WIN_W/2, WIN_H/2))
  
	# Limits frames per iteration of while loop
	clock.tick(60)
	
	# Writes to main surface
	pygame.display.flip()
If you add this to your code, you will get an error because we are using a method called ‘exit’ that comes from the ‘sys’ module. However, we haven’t imported the module to our program. You can import the module by adding the following code to the top line of your program:
import sys
import pygame
Now our invocation to ‘exit’ should work. Test your program and you will notice that you can now click on the red button at the top left corner of the window and it closes! Let me explain: Pygame has an array that stores the keyboard inputs from the user. For example, if the user presses ‘a’, ‘s’, ‘d’, ‘f’ and ‘red button’, those entries are stored in an array called event. The ‘for loop’ scrolls through those events and performs actions based on the conditional statement that the programmer defines. We can see that so far we only have one condition that checks if the value on the ‘event’ array is equal to ‘QUIT’, which is indicative of the red button being pressed. If this is the case, the algorithm will exit the program.

Paddle Movement Variables
We are going to use this system of reading the events on the array to check if the user pressed the up key or down key so we can move the paddle accordingly. First add the following code to your ‘for loop’:
# Checks if window exit button pressed
for event in pygame.event.get():
	if event.type == pygame.QUIT: sys.exit()
	# Keypresses
	elif event.type == pygame.KEYDOWN:
		if event.key == pygame.K_UP:
			moveUP = True
			moveDOWN = False
		elif event.key == pygame.K_DOWN:
			moveUP = False
			moveDOWN = True
	
	elif event.type == pygame.KEYUP:
		if event.key == pygame.K_UP:
			moveUP = False
		elif event.key == pygame.K_DOWN:
			moveDOWN = False
Looking at the first ‘elif’, we can see that the conditional is checking if the user pressed down a key on the keyboard. Next, the algorithm is checking if the up key or down key were pressed and sets the ‘moveUP’ and ‘moveDOWN’ variables accordingly. The next ‘elif’ is used to check if the user released a key on the keyboard and set ‘moveUP’ and ‘moveDOWN’ accordingly. Remember that we need to create and initialize ‘moveUP’ and ‘moveDOWN’ earlier in our program. Be sure to do this outside of the loop!

Actually Moving the Paddle
Now try running your program. You will see that even if you press the up or down arrows that your paddle doesn’t move. Why is this? It’s because we still need to adjust the y-value of our paddle based on the values of ‘moveUP’ and moveDOWN’. We need to increment the y–value if the down arrow is pressed and decrement the y-value if the up arrow was pressed.

Add the following code to your while loop after the ‘for loop’. Be sure to place it outside of the ‘for loop’ that checks for the user’s keystrokes!
for event in pygame.event.get():
	if event.type == pygame.QUIT: sys.exit()
	...

# Adjust speed
if moveUP or moveDOWN:
	if moveUP:
		y2 -= speed	
	if moveDOWN:
		y2 += speed
At this point, double check that you are:

1. Defining and initializing x1 and y1 for the positional values of the left paddle and x2 and y2 for the right paddle.

2. Defining a ‘speed’ variable that will contain the speed at which the paddles will move when the user presses up or down. Be sure to declare it where you are declaring the other variables for paddle.

3. Defining and initializing ‘moveUP’ and ‘moveDOWN’ at the beginning of your program. These variables should be initialized to ‘False’ so the game starts with no paddle movement.


If you have done all the above steps correctly, your program should allow you to move the right paddle up and down using the up and down arrow keys. If you are confused on how the algorithm accomplishes this, study the code closely.

*** As a personal tip, what I do when faced with complex code that I don’t understand is I print it out and review it from time to time until I become comfortable enough with it that I just begin to understand what the algorithm is doing. Then, I review it some more until I understand most of it. Then, I look at it some more until understand everything about it and can use the same algorithm in a different context. Sometimes this process takes a few minutes, other times a few days, and sad but true, very often this process takes weeks. I would suggest you do the same if you are looking at an algorithm you need to understand but have no idea how it functions.

Now Try:

Now modify your program so that you are able to move your left paddle in the same way using the ‘w’ and ‘s’ keys.


Trailing Sprites

Back to our program, you will notice that your paddle is now able to move up and down however it leaves a trail, which results in a window that looks like the following:

The reason this is the case is that every surface that we print on top of the main surface is transparent.
When we begin to print the paddle a bit higher on the y-axis, it is placed a bit higher and printed, but the last printed paddle is still there printed on the main surface from the last iteration of the game loop.
Repeat this process over time and you are left with a moving sprite that leaves a trail. How do we stop this from happening? We can fill the entire background with white just before we ‘blit’ the paddles and ball. By filling the entire screen with white every time we enter the while loop, we are effectively wiping the drawing slate clean erasing what was printed earlier and printing the left paddle, right paddle and ball on a freshly painted while surface before burning it to the monitor when invoking the ‘display.flip’ method.

In order to do this, we would take the command ‘screen.fill(white)’ from outside of the game loop and run it just before blit-ing the paddles and ball in the game loop. Now run your program. The paddles should now move up and down without tracers!

Paddles Moving Off the Screen

At this moment, your program should allow your paddles to move without leaving tracers, but there is still a problem: you are allowed to move the paddles off the screen. That isn’t right. To fix that, we need to specify some conditions that will stop the paddle’s y value from changing if the current y value of the paddle is less than zero or greater than the height of the game window. To stop the paddle from moving off the top of the gaming window, we need to stop the paddle’s y value from decreasing below the value 0. This is done by using a condition just before we blit our paddle at the bottom of our while loop:
# paddle movement
if y2 < 0:
	y2 = 0
Now Try:

Now modify this branch statement so that the paddle doesn’t go off the bottom of the screen. Be sure to use the global variable WIN_H in your condition so that your algorithm works for any sized gaming window.


Ball Movement and Rects

We moved the paddles by changing the (x, y) values before blitting them to the screen. Essentially, we simply erased the last printed paddle and printed the next paddle at a y location which is incremented or decremented based on the user hitting the up or down arrows. We could use this same method for the ball, however, this time we are going to move the ball using rects.

What are rects you may ask? Rects are a Pygame object that are used to store the rectangular coordinates of a surface. Every surface has a rect and a rect has different properties that are useful such as the x coordinate of the left and right side, the y coordinate of the top and bottom and the width and height in pixels. Rects are a useful way to handle positional information regarding surfaces. Changing the x or y property of a rect moves the rect to that location when you blit it to the main screen. This aspect of rects make them very convenient for moving surfaces that have graphics printed on them such as a ball or paddle.

Since our paddles and ball are surface objects, they each have a corresponding rect that contains their location. Add this code just after you create your ball surface:
ball = pygame.Surface((ball_width, ball_height))
ballRect = pygame.Rect(WIN_W/2, WIN_H/2-(ball_height/2), ball_width, ball_height)
The first line stores a surface that is 20 pixels by 20 pixels and by default is colored black. We are using this surface to represent our ball. The second line is retrieving the associated rect of the surface containing the ‘ball’. When looking at the arguments of the ‘Rect’ method, we can see that we must give it an (x, y) coordinate, which will be used to move the location of the ball surface to that point.
Using rects, we can reposition surfaces without having to specify an (x, y) coordinate since the rect contains this information. This presents a different way to print a surface to the main screen.
We will use rects to print the ball on the main screen in our game loop, which will effectively allow us to animate the ball.

Blit Takes Rect

One other thing to mention, is that when we blit the ball, Pygame allows us to pass in a rect as the second argument. This makes sense if we remember that the blit method takes in the surface to be blitted as the first argument and an (x, y) coordinate as the second argument. Since the ball’s rect contains the associated surface’s coordinates, it should be able to be passed to the blit method to specify where to print the ball.

To use the rect to move the ball, we need to blit using the ball’s rect. Let’s do that by replacing:
# printing to the main screen
screen.blit(paddle1, (x1, y1))
screen.blit(paddle2, (x2, y2))
screen.blit(ball, (WIN_W/2, WIN_H/2))
with:
# printing to the main screen
screen.blit(paddle1, (x1, y1))
screen.blit(paddle2, (x2, y2))
screen.blit(ball, ballRect)
If you now run your program, you will see that the ball is printed at the location of the ball’s rect which was defined earlier to be in the middle of the screen. We can see that replacing the (x, y) coordinate with the ball’s rect in the second argument of the blit method is a valid way of setting the location of a surface the main screen.

Moving the Ball

We have already seen how we can move a sprite by changing the (x, y) coordinates, then blitting the surface to the main surface. We could absolutely use that method to move the ball as well as the paddles, but instead we are going to use the rect to move the ball. Let’s start by defining a variable ‘ball_speed’ that contains an array that contains the x-axis speed and the y-axis speed:
ball_width = ball_height = 20
# Add this array
ball_speed = [5, 5]
ball = pygame.Surface((ball_width , ball_height))
ballRect = pygame.Rect(WIN_W/2, WIN_H/2-(ball_height/2), ball_width, ball_height)
Next, we need to move the ball along at that speed along the x & y-axis. We can do that with the following code:
# Game Loop: code that moves the paddle
           
ballRect = ballRect.move(ballSpeed)
 
# Code that blits images to the main screen
The move method moves ‘ballRect’ by a (x, y) coordinate represented by ‘ball_speed’. Since our ball was initially placed at approximately (WIN_W/2, WIN_H_2), by invoking the method ‘move’ and passing it our ‘ball_speed’ array, we are able to move the ball image from (WIN_W/2, WIN_H/2) to (WIN_W/2 + 5, WIN_H/2 + 5). This will move the ball 5 pixels downward and 5 pixels to the right with each iteration of the game loop. The ball will move in a downward diagonal direction. Run your program and see that this is the case.

Bouncing off the Walls

We have added motion to the ball, however we want the ball to bounce off the walls instead of run off the bottom right of the screen. In order to do this, we need to program the behavior of the ball in our game loop. Before we do, we must understand that every Rect has a ‘left’, ‘top’, ‘width’ and ‘height’ property that can be accessed at any time. We will use these attributes to figure out if the ball has hit the top of the game window. Add the following conditional statement to your code:
#code that moves the paddle

# If ball hits the top
if ballRect.top > WIN_H – ballRect.height:
	ball_speed[1] = -ball_speed[1]

ballRect = ballRect.move(ball_speed)
 
# code that blit images to the main screen
Looking at the condition in the unary conditional statement, we can see that we are checking if the bottom of the ball is at the bottom of the window. Since rects don’t have a ‘bottom’ property, we have to check if “ballRect.top > WIN_H - ballRect.height”. If you remember that at the top of the window the y value is 0 and as one moves downward, the y-axis increases, we can see that ‘WIN_H – ballRect.height’ would be the exact value when the bottom of the ball image is touching the bottom of the screen. At this moment, we want the x value of the ball_speed to stay the same, however, we want the y value to reverse so that the ball will bounce and start traveling upward instead of downward. To accomplish this, we need to set “ball_speed[1] = -ball_speed[1]”. Run your game and you will see that the ball now does bounce off the bottom wall.

Now Try:

Now add to our conditional statement so that the ball will bounce off the walls to the left and right side.

Now Try:

Now add to our unary conditional statement and create a conditional statement that will tell the ball to bounce when the ball hits the top of the game window.

Bouncing off the Paddles

Now, our Pong game has two paddles that can move based on keystrokes and a ball that bounces around the walls. We now need to make the ball bounce of the paddles. Let’s start by first considering the right paddle. We need to understand that the ball’s speed in the x direction must change signs when we hit the paddle. However, how can we check if the ball hits the right paddle? We can see that the ball will hit the right paddle if its y-position is between the y-position of the top of the paddle and the bottom of the paddle. We also need to understand that the ball hits the paddle if its x-position is at the x position of the left side of the right paddle. Let’s start by coding the x position portion of the algorithm. Copy this code into your program:
# If ball hits the top or bottom
...
           
ballRect = ballRect.move(ball_speed)

# If ball hits the paddles
if ballRect.right == x2:
	ball_speed[0] = -ball_speed[0]
 
# code that blit images to the main screen
Looking at the unary conditional statement above, we can see that the condition checks if the right side of the ball is equal to the x position of the right paddle. Try running your program and you’ll see that the ball most likely runs right through the paddle. Why is this so?
The x position of the ball is increasing in increments of 5, so it is unlikely that the ball’s x position will be equal to the x position of the right paddle.


To make this point clear, add a print statement that outputs the x position of the ball and the x position of the paddle. Compare the two list of values as the print statement outputs 60 times a second. Pay special attention to the point where the ball goes through the right paddle. Can you see where the x position of the ball should be exactly the x position of the paddle? You can see that they are not equal because this is an unlikely occurrence. These two values will get close, but very rarely will they equal each other because we are adding 5 to the x position of the ball every iteration of the game loop. If we were adding 1 to the x position of the ball every time through the game loop, then our condition above would be valid.

What can we do to remedy this situation? We can check for a range of numbers using a set of inequalities. Try changing your condition to the following:
# If ball hits the top or bottom
...
           
ballRect = ballRect.move(ball_speed)

# If ball hits the paddles
if ballRect.right > x2 - 5 and ballRect.right < x2 + 5:
	ball_speed[0] = -ball_speed[0]
 
# code that blit images to the main screen
Since we are giving the x position of the ball a range of numbers as it crosses the left side of the right paddle, we can now see that our conditional statement is true every time the ball ‘hits’ the left side of the paddle. The end result is that now our ball bounces off the right paddle.

However, we can see that our algorithm still isn’t complete because we need to add to our condition so that the ball doesn’t bounce if it is above or below the paddle.

Now Try:

Now add to our conditional statement so that the ball also bounces off the right paddle if it lands on the paddle rather than only bouncing off the x position of the paddle.

Now Try:

Now add to our unary conditional statement and write the code to have the ball bounce off the left paddle.

Now Try:

Finally, take away the code that has the ball bounce off the left and right walls.

A
rubric
can be found here.