Program Arcade GamesWith Python And Pygame

 < Previous Home Next >

Chapter 9: Functions

9.1 Introduction to Functions

Functions are used for two reasons. First, they make code easier to read and understand. Second, they allow code to be used more than once.

Imagine a set of code that draws a tree shown in Figure 9.1. To do this, the programmer executes the following commands:

pygame.draw.rect(screen, BROWN, [60, 400, 30, 45])
pygame.draw.polygon(screen, GREEN, [[150, 400], [75, 250], [0, 400]])
pygame.draw.polygon(screen, GREEN, [[140, 350], [75, 230], [10, 350]])


Those three lines of code don't really pop out as obviously drawing a tree! If we have multiple trees or complex objects, it starts getting hard to understand what is being drawn.

By defining a function we can make the program easier to read. To define a function, start by using the def command. After the def command goes the function name. In this case we are calling it draw_tree. We use the same rules for function names that we use for variable names.

Following the function name will be a set of parenthesis and a colon. All the commands for the function will be indented inside. See the example below:

def draw_tree():
pygame.draw.rect(screen, BROWN, [60, 400, 30, 45])
pygame.draw.polygon(screen, GREEN, [[150, 400], [75, 250], [0, 400]])
pygame.draw.polygon(screen, GREEN, [[140, 350], [75, 230], [10, 350]])


By itself, this code will not cause a tree to draw. It will tell the computer how to do draw_tree. You have to call the function to actually run the code in the function and get the tree to draw:

draw_tree()


With a whole library of functions defining different things to be drawn, a final program might look like:

draw_tree()
draw_house()
draw_car()
draw_killer_bunny()


Remember that draw_tree has three lines of code. Each one of these other commands, like draw_house has multiple lines of code. By using functions, we can repeat the commands without repeating all the code contained within, making for a much smaller program.

Function names are very important. If the function names are descriptive, even a non-programmer should be able to read a set of code and get an idea what is happening. Function names follow the same rules as variable names, and should start with a lower case letter.

9.2 Function Parameters

Functions can take parameters. These can be used to increase the flexibility of a function by altering what it does based on parameters passed to it. For example, our function called draw_tree() draws the tree in one specific place. But the function could be changed to take a parameter that specifies where to draw the tree. For example draw_tree(screen, 0, 230) would draw the tree at an (x, y) location of (0, 230).

Adjusting the function for the tree might look like:

def draw_tree(screen, x, y):
pygame.draw.rect(screen, BROWN, [60+x, 170+y, 30, 45])
pygame.draw.polygon(screen, GREEN, [[150+x,170+y],[75+x,20+y], [x,170+y]])
pygame.draw.polygon(screen, GREEN, [[140+x,120+y], [75+x,y], [10+x,120+y]])


This would allow us to draw multiple trees wherever we like:

draw_tree(screen, 0, 230)
draw_tree(screen, 200, 230)
draw_tree(screen, 400, 230)


Here is a different function that can be run without using graphics. This function will calculate and print out the volume of a sphere:

def volume_sphere(radius):
pi = 3.141592653589
volume = (4 / 3) * pi * radius ** 3
print("The volume is", volume)

Parameters are assigned values when a function is called, not when it is defined.

The name of the function is volume_sphere. The data going into the functions will be stored in a new variable called radius. The resulting volume is printed to the screen. The radius variable does not get a value here. Frequently new programmers get confused because parameter variables aren't given a value when the function is defined, so it doesn't look legal. Parameters are given a value when the function is called.

To call this function, use:

volume_sphere(22)


The radius variable in the function is created and initialized with a value of 22. The function's code is run once the execution reaches the call to the function.

What if we need to pass in more than one value? Multiple parameters can be passed to a function, each parameter separated by a comma:

def volume_cylinder(radius, height):
pi = 3.141592653589
volume = pi * radius ** 2 * height
print("The volume is", volume)


That function may be called by:

volume_cylinder(12, 3)


Parameters are done in order, so radius will get the 12, and height will get the 3 value.

9.3 Returning and capturing values

Unfortunately, these example functions are limited. Why? If a person wanted to use the volume_cylinder function to calculate the volume in a six-pack, it wouldn't work. It only prints out the volume of one cylinder. It is not possible to use the function's result for one cylinder's volume in an equation and multiply it by six to get a six-pack volume.

9.3.1 Returning values

This can be solved by using a return statement. For example:

# Add two numbers and return the results
def sum_two_numbers(a, b):
result = a + b
return result


Return is not a function, and does not use parentheses. Don't do return(result).

This only gets us half-way there. Because if we call the function now, not much happens. The numbers get added. They get returned to us. But we do nothing with the result.

# This doesn't do much, because we don't capture the result
sum_two_numbers(22, 15)


9.3.2 Capturing returned values

We need to capture the result. We do that by setting a variable equal to the value the function returned:

# Store the function's result into a variable
my_result = sum_two_numbers(22, 15)
print(my_result)


Now the result isn't lost. It is stored in my_result which we can print or use some other way.

9.3.3 Improving the volume_cylinder example

def volume_cylinder(radius, height):
pi = 3.141592653589
volume = pi * radius ** 2 * height
return volume


Because of the return, this function could be used later on as part of an equation to calculate the volume of a six-pack like this:

six_pack_volume = volume_cylinder(2.5, 5) * 6


The value returned from volume_cylinder goes into the equation and is multiplied by six.

There is a big difference between a function that prints a value and a function that returns a value. Look at the code below and try it out.

# Function that prints the result
def sum_print(a, b):
result = a + b
print(result)

# Function that returns the results
def sum_return(a, b):
result = a + b
return result

# This prints the sum of 4+4
sum_print(4, 4)

# This does not
sum_return(4, 4)

# This will not set x1 to the sum
# It actually gets a value of 'None'
x1 = sum_print(4, 4)

# This will
x2 = sum_return(4, 4)


When first working with functions it is not unusual to get stuck looking at code like this:

def calculate_average(a, b):
""" Calculate an average of two numbers """
result = (a * b) / 2
return result

# Pretend you have some code here
x = 45
y = 56

# Wait, how do I print the result of this?
calculate_average(x, y)


How do we print the result of calculate_average? The program can't print result because that variable only exists inside the function. Instead, use a variable to capture the result:

def calculate_average(a, b):
""" Calculate an average of two numbers """
result = (a * b) / 2
return result

# Pretend you have some code here
x = 45
y = 56

average = calculate_average(x, y)
print(average)


9.4 Documenting Functions

Functions in Python typically have a comment as the first statement of the function. This comment is delimited using three double quotes, and is called a docstring. A function may look like:

def volume_cylinder(radius, height):
"""Returns volume of a cylinder given radius, height."""
pi = 3.141592653589
volume = pi * radius ** 2 * height
return volume


The great thing about using docstrings in functions is that the comment can be pulled out and put into a website documenting your code using a tool like Sphinx. Most languages have similar tools that can help make documenting your code a breeze. This can save a lot of time as you start working on larger programs.

9.5 Variable Scope

The use of functions introduces the concept of scope. Scope is where in the code a variable is “alive” and can be accessed. For example, look at the code below:

# Define a simple function that sets
# x equal to 22
def f():
x = 22

# Call the function
f()
# This fails, x only exists in f()
print(x)


The last line will generate an error because x only exists inside of the f() function. The variable is created when f() is called and the memory it uses is freed as soon as f() finishes.

Here's where it gets complicated.

A more confusing rule is accessing variables created outside of the f() function. In the following code, x is created before the f() function, and thus can be read from inside the f() function.

# Create the x variable and set to 44
x = 44

# Define a simple function that prints x
def f():
print(x)

# Call the function
f()


Variables created ahead of a function may be read inside of the function only if the function does not change the value. This code, very similar to the code above, will fail. The computer will claim it doesn't know what x is.

# Create the x variable and set to 44
x = 44

# Define a simple function that prints x
def f():
x += 1
print(x)

# Call the function
f()


Other languages have more complex rules around the creation of variables and scope than Python does. Because Python is straight-forward it is a good introductory language.

9.6 Pass-by-copy

Functions pass their values by creating a copy of the original. For example:

# Define a simple function that prints x
def f(x):
x += 1
print(x)

# Set y
y = 10
# Call the function
f(y)
# Print y to see if it changed
print(y)


The value of y does not change, even though the f() function increases the value passed to it. Each of the variables listed as a parameter in a function is a brand new variable. The value of that variable is copied from where it is called.

This is reasonably straight forward in the prior example. Where it gets confusing is if both the code that calls the function and the function itself have variables named the same. The code below is identical to the prior listing, but rather than use y it uses x.

# Define a simple function that prints x
def f(x):
x += 1
print(x)

# Set x
x = 10
# Call the function
f(x)
# Print x to see if it changed
print(x)


The output is the same as the program that uses y. Even though both the function and the surrounding code use x for a variable name, there are actually two different variables. There is the variable x that exists inside of the function, and a different variable x that exists outside the function.

9.7 Functions Calling Functions

It is entirely possible for a function to call another function. For example, say the functions like the following were defined:

def arm_out(whichArm, palmUpOrDown):
# code would go here

def hand_grab(hand, arm):
# code goes here


Then another function could be created that calls the other functions:

def macarena():
arm_out("right", "down")
arm_out("left", "down")
arm_out("right", "up")
arm_out("left", "up")
hand_grab("right", "left arm")
hand_grab("left", "right arm")
# etc


9.8 Main Functions and Globals

Global variables are pure evil.

As programs get large it is important to keep the code organized and put into functions. Python allows us to write code at “indent level 0.” This describes most of the code we've written so far. Our code is lined up on the left and not contained in functions.

This philosophy is like heaping your clothes on the middle of your floor, or keeping all your tools in a pile on the workbench. It only works well when you don't have much stuff. Even when you don't have much stuff it is still messy.

All your code and all your variables should be placed in functions. This will keep your code organized. It will also help when you need to track down a bug in the program. Variables created at “indent level 0” are called global variables. Global variables are a very bad thing. Why? Because any piece of code anywhere can change their value. If you have a 50,000 line program, each line of code can change that global variable. If instead you keep the variable in a function, then only that code in the function can change the variable. Thus, if you have an unexpected value in a variable, you only need to look through the maybe 50 lines of code in your function. Otherwise you have to check every line of code in your entire program!

A better way to write a program in Python would be to follow this pattern:

def main():
print("Hello world.")

main()


In this case all the code that I normally would have run at indent level 0 is placed in the main function. The last line of the file calls main.

But wait! There's another problem we need to fix. In Chapter 14, we will talk about how to break our program into multiple files. We can use the import command to bring in functions from other modules we created. If we used the import command on this module, it would automatically start running the main function. We don't want that. We want the program that imports it to control when the function is called.

To fix this problem we can have our program check a global variable defined automatically by Python. (I know, I just said global variables were bad, right?) That variable is called __name__, with two underscores before and after it. We can check it to see if this code is being imported or run. If the code is being run, Python will automatically set the value of that variable to __main__. By using an if statement we will only call the main function if the code is being run. Otherwise the code will just define the main function. The code that imported it can call the function when desired.

This is how all your Python code should be run:

def main():
print("Hello world.")

if __name__ == "__main__":
main()


One of the reasons I love Python as an first language is that you aren't required to use this complexity until you need it. Other languages, like Java, require it no matter how small your program.

To make things easier in this book we do not show our examples using this pattern. But after this book your programs will likely be complex enough that it will make life easier if you don't “throw all your clothes in a pile” so to speak.

If you are super-enthused about programming, try writing your programs starting this way now. While it may be a bit more challenging to begin with, it will make writing programs easier later on. It is also a good way to learn about how to properly manage your data and its scope.

Here is an example that shows how to do the base PyGame template using this pattern:

Using this template is not required if I'm the one teaching the course. I'm OK during your first semester if you pile your clothes in the middle of the floor. I'm just happy you are wearing clothes. (For the neat-freaks, we can clean this program up even more when we get to the chapter on “classes.”)

9.9 Examples

For each of the examples below, thing about what would print. Check to see if you are right. If you didn't guess correctly, spend to the time to understand why.

# Example 1
def a():
print("A")

def b():
print("B")

def c():
print("C")

a()

# Example 2
def a():
b()
print("A")

def b():
c()
print("B")

def c():
print("C")

a()

# Example 3
def a():
print("A")
b()

def b():
print("B")
c()

def c():
print("C")

a()

# Example 4
def a():
print("A start")
b()
print("A end")

def b():
print("B start")
c()
print("B end")

def c():
print("C start and end")

a()

# Example 5
def a(x):
print("A start, x =",x)
b(x + 1)
print("A end, x =",x)

def b(x):
print("B start, x =",x)
c(x + 1)
print("B end, x =",x)

def c(x):
print("C start and end, x =",x)

a(5)

# Example 6
def a(x):
x = x + 1

x = 3
a(x)

print(x)

# Example 7
def a(x):
x = x + 1
return x

x = 3
a(x)

print(x)

# Example 8
def a(x):
x = x + 1
return x

x = 3
x = a(x)

print(x)

# Example 9
def a(x, y):
x = x + 1
y = y + 1
print(x, y)

x = 10
y = 20
a(y, x)

# Example 10
def a(x, y):
x = x + 1
y = y + 1
return x
return y

x = 10
y = 20
z = a(x, y)

print(z)

# Example 11
def a(x, y):
x = x + 1
y = y + 1
return x, y

x = 10
y = 20
z = a(x, y)

print(z)

# Example 12
def a(x, y):
x = x + 1
y = y + 1
return x, y

x = 10
y = 20
x2, y2 = a(x, y) # Most computer languages don't support this

print(x2)
print(y2)

# Example 13
def a(my_data):
print("function a, my_data =  ", my_data)
my_data = 20
print("function a, my_data =  ", my_data)

my_data = 10

print("global scope, my_data =", my_data)
a(my_data)
print("global scope, my_data =", my_data)

# Example 14
def a(my_list):
print("function a, list =  ", my_list)
my_list = [10, 20, 30]
print("function a, list =  ", my_list)

my_list = [5, 2, 4]

print("global scope, list =", my_list)
a(my_list)
print("global scope, list =", my_list)

# Example 15
# New concept!
# Covered in more detail in Chapter 12
def a(my_list):
print("function a, list =  ", my_list)
my_list[0] = 1000
print("function a, list =  ", my_list)

my_list = [5, 2, 4]

print("global scope, list =", my_list)
a(my_list)
print("global scope, list =", my_list)

"""
This is a sample text-only game that demonstrates the use of functions.
The game is called "Mudball" and the players take turns lobbing mudballs
at each other until someone gets hit.
"""

import math
import random

def print_instructions():
""" This function prints the instructions. """

# You can use the triple-quote string in a print statement to
# print multiple lines.
print("""
Welcome to Mudball! The idea is to hit the other player with a mudball.
Enter your angle (in degrees) and the amount of PSI to charge your gun
with.
""")

def calculate_distance(psi, angle_in_degrees):
""" Calculate the distance the mudball flies. """
return distance

def get_user_input(name):
""" Get the user input for psi and angle. Return as a list of two
numbers. """
# Later on in the 'exceptions' chapter, we will learn how to modify
# this code to not crash the game if the user types in something that
# isn't a valid number.
psi = float(input(name + " charge the gun with how many psi? "))
angle = float(input(name + " move the gun at what angle? "))
return psi, angle

def get_player_names():
""" Get a list of names from the players. """
print("Enter player names. Enter as many players as you like.")
done = False
players = []
while not done:
player = input("Enter player (hit enter to quit): ")
if len(player) > 0:
players.append(player)
else:
done = True

print()
return players

def process_player_turn(player_name, distance_apart):
""" The code runs the turn for each player.
If it returns False, keep going with the game.
If it returns True, someone has won, so stop. """
psi, angle = get_user_input(player_name)

distance_mudball = calculate_distance(psi, angle)
difference = distance_mudball - distance_apart

# By looking ahead to the chapter on print formatting, these
# lines could be made to print the numbers is a nice formatted
# manner.
if difference > 1:
print("You went", difference, "yards too far!")
elif difference < -1:
print("You were", difference * -1, "yards too short!")
else:
print("Hit!", player_name, "wins!")
return True

print()
return False

def main():
""" Main program. """

# Get the game started.
print_instructions()
player_names = get_player_names()
distance_apart = random.randrange(50, 150)

# Keep looking until someone wins
done = False
while not done:
# Loop for each player
for player_name in player_names:
# Process their turn
done = process_player_turn(player_name, distance_apart)
# If someone won, 'break' out of this loop and end the game.
if done:
break

if __name__ == "__main__":
main()