7. Creating Functions

The goal of this chapter is to learn how to create our own functions to draw. We don’t want to be stuck with just draw_circle commands. We want to be able to define create our own draw_tree or draw_house commands.

A function is a block of code that we can call with just one line. Functions give us the ability to write:

  • Clear, easy-to-read code.
  • The ability to reuse code.

We have already used functions. Now we want define our own. Defining a function is like giving a recipe to computer. Once we give the computer a recipe for banana bread, we just have to tell the computer to “make banana bread.” There’s no need to tell it the steps again.

To create our own drawing functions we need to learn three new skills:

  • How to define a function
  • How to use variables
  • How to create simple mathematical expressions

7.1. How to Define a Function

Defining a function is rather easy.

  • Start with the keyword def, which is short for “define.”
  • Next, give the function a name. There are rules for function names. They must:
    • Start with a lower case letter.
    • After the first letter, only use letters, numbers, and underscores.
    • Spaces are not allowed. Use underscores instead.
    • While upper-case letters can be used, function names are normally all lower-case.
  • After that, we have a set of parenthesis. Inside the parenthesis will go parameters. We’ll explain those in a bit.
  • Next, a colon.
  • Everything that is part of the function will be indented four spaces.
  • Usually we start a function with a multi-line comment that explains what the function does.

Here is an example of a function:

def draw_grass():
    """
    This function draws the grass.
    """
    arcade.draw_lrtb_rectangle_filled(0, 800, 200, 0, arcade.color.BITTER_LIME)

To call the function, all we need to do is:

draw_grass()

Below is a full program that defines and uses the function. Notice that function definitions go below the import statements, and above the rest of the program. While you can put them somewhere else, you shouldn’t.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
"""
This is a sample program to show how to draw using functions
"""

import arcade


def draw_grass():
    """
    This function draws the grass.
    """
    arcade.draw_lrtb_rectangle_filled(0, 800, 200, 0, arcade.color.BITTER_LIME)


arcade.open_window(800, 600, "Drawing with Functions")
arcade.set_background_color(arcade.color.AIR_SUPERIORITY_BLUE)
arcade.start_render()

# Call our function to draw the grass
draw_grass()

arcade.finish_render()
arcade.run()

Great! Let’s make this scene a little better. I’ve created another function called draw_pine_tree which will…you guessed it. Draw a pine tree.

Here’s what it will look like:

../../_images/pine_tree.png

And here’s the code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
"""
This is a sample program to show how to draw using functions
"""

import arcade


def draw_grass():
    """
    This function draws the grass.
    """
    arcade.draw_lrtb_rectangle_filled(0, 800, 200, 0, arcade.color.BITTER_LIME)


def draw_pine_tree():
    """
    This function draws a pine tree.
    """

    # Draw the trunk
    arcade.draw_rectangle_filled(100, 200, 30, 80, arcade.color.BROWN)

    # Draw three levels of triangles
    arcade.draw_triangle_filled(50, 215, 150, 215, 100, 320, arcade.color.FOREST_GREEN)
    arcade.draw_triangle_filled(50, 255, 150, 255, 100, 360, arcade.color.FOREST_GREEN)
    arcade.draw_triangle_filled(60, 295, 140, 295, 100, 400, arcade.color.FOREST_GREEN)

arcade.open_window(800, 600, "Drawing with Functions")
arcade.set_background_color(arcade.color.AIR_SUPERIORITY_BLUE)
arcade.start_render()

# Draw our pretty landscape
draw_grass()
draw_pine_tree()

arcade.finish_render()
arcade.run()

Great! But what if I want a forest? I want lots of trees? Do I create a function for every tree? That’s no fun. How can I create a function that allows me to say where I want the tree? Like what if I wanted to draw three trees and specify (x, y) coordinates of those trees:

draw_pine_tree(45, 92)
draw_pine_tree(220, 95)
draw_pine_tree(250, 90)

To be able to do this, I need to learn about variables, expressions, and function parameters.

7.2. How to Use Variables

A variable is a value the computer stores in memory that can change. That is, it varies.

You’ve used variables in mathematics before. With computer science, we use them a lot. But in math class, you were given the equation and you had to solve for the variable. In computer science class, we come up with the equation and the computer solves the variable.

Here is a quick example:

# What will this print?
x = 5
print(x)

What will the code above print? It will print 5.

The = is called an assignment operator. It assigns the value on the right side to the variable on the left.

Here’s another example. Very similar, but something is different. What will it print?

# What will this print?
x = 5
print("x")

The code above prints x. Why not 5? Because:

  • If there are no quotes, the computer evaluates code like a mathematical expression.
  • If there are quotes, we treat what is between the quotes as a string of characters and don’t change it.

In fact, that is what we call the characters between the quotes. A string, which is short for “string of characters.” We don’t call it “text.”

The following code won’t print at all:

print(Have a great day!)

The code above will fail because the computer will think that it should evaluate Have a great day! as a mathematical expression. It isn’t, so the computer gets confused and generates an error. That’s why we need quotes:

print("Have a great day!")

7.2.1. Variable and Function Names

Variable names and function names follow the same rules. There are names you should use, names you shouldn’t use, and names you can’t use.

Good variable name examples:
  • temperature_in_celsius
  • tree_position
  • car_speed
  • number_of_children
  • simpson
Legal, but bad variable names:
  • temperatueInCelsius - Uses capital letters. Keep it lower case and use underscores.
  • x - Too short, and not descriptive.
  • Simpson - Starts with a capital letter.
Variable names that won’t work:
  • tree position - Can’t use spaces
  • 4runner - Can’t start with a number

Sometimes we want to create a variable that won’t change. We call these variables constants. By convention, these variable names are in all upper case. They are the only variables that use upper-case. For example:

PI = 3.14159
SCREEN_WIDTH = 600
RED = (255, 0 ,0)

Good variable names help make code readable. Note the example below that calculates miles-per-gallon. It isn’t easy to understand.

# Calculate mpg using confusing variable names
m = 294
g = 10.5
m2 = m / g
print(m2)

But the code below that uses descriptive variable names is easy to understand.

# Calculate mpg using good variable names
miles_driven = 294
gallons_used = 10.5
mpg = miles_driven / gallons_used
print(mpg)

7.3. How to Create Expressions

7.3.1. Using Operators in Expressions

Great! We are part-way there. To really be powerful, variables need to be used with expressions. An expression is simply a mathematical equation like what you’ve used in math before. Here’s an example:

# What will this print?
x = 5 + 10
print(x)

As you can probably guess, this will print out 15. We call the + sign an operator. Here are some other operators:

Operator Description
+ Addition
- Subtraction
* Multiplication
** Exponentiation (raise to the power)
/ Division
// Integer division (rounds down)
% Modulus (gives remainder of division)

There are two things that don’t work like you’d expect. There is no “juxtaposition” used to multiply items. And the = is not an algebraic equality

7.3.2. Juxtaposition Doesn’t Work

Juxtaposition doesn’t work for multiplication. For example, the following will not work:

# The last two lines will error
x = 3
y = 2x
z = 2(3 + x)

You can rewrite the code above to work by explicitly multiplying:

# This code works. Although it doesn't print anything.
x = 3
y = 2 * x
z = 2 * (3 + x)

Easy enough, just remember to use * any time you want to multiply.

7.3.3. Assignment Operators

The = doesn’t work the same as in algebra. The = evaluates what is on the right, and puts it in the variable on the left. For example:

# This works
x = 3 + 4

# This doesn't work because the only thing that can be on the left of
# the = is one variable.
3 + 4 = x

# This works
x = 5
y = 6
z = x + 2 * y

# This doesn't
x = 5
y = 6
2 * z = x + y

This allows us to do some strange things we can’t do in algebra!

# This works, and prints "3"
x = 3
print(x)

# This works too, even if it is invalid in algebra.
# It takes the value of x (which is 3) and adds one. Then stores
# the result (4) back in x. So we'll print "4".
x = x + 1
print(x)

The = sign is also considered an operator. Specifically an “assignment operator.” Here are some other “assignment” operators:

Operator Description
= Assignment
+= Increment
-= Decrement
*= Multiply/Add
# This works, and prints "3"
x = 3
print(x)

# Make x bigger by one
x = x + 1
print(x)

# Make x bigger by one, just like before
x += 1
print(x)

# Make x smaller by five
x += 5
print(x)

7.3.4. Using Expressions In Function Calls

We can use expressions even in the calls that we make. For example, what if we want to draw a circle in the center of the screen?

We could do something like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import arcade

SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600

arcade.open_window(SCREEN_WIDTH, SCREEN_HEIGHT, "Drawing Example")

arcade.set_background_color(arcade.color.WHITE)

arcade.start_render()

# Instead of this:
# arcade.draw_circle_filled(400, 300, 50, arcade.color.FOREST_GREEN)
# do this:
arcade.draw_circle_filled(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, 50, arcade.color.FOREST_GREEN)

arcade.finish_render()
arcade.run()

7.3.5. Order of Operations

Python will evaluate expressions using the same order of operations that are expected in standard mathematical expressions. For example this equation does not correctly calculate the average:

average = 90 + 86 + 71 + 100 + 98 / 5

The first operation done is 98/5. The computer calculates:

\[90+86+71+100+\frac{98}{5}\]

rather than the desired:

\[\dfrac{90+86+71+100+98}{5}\]

By using parentheses this problem can be fixed:

average = (90 + 86 + 71 + 100 + 98) / 5

7.4. Returning and Capturing Values

Functions can not only take in values, functions can return values.

7.4.1. Returning values

For example:

Function that returns two numbers added together

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

Note: 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)

7.4.2. Capturing Returned Values

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

# Capture the function's result into a variable
# by putting "my_result =" in front of it.
# (Use whatever variable name best describes the data,
# don't blindly use 'my_result' for everything.)
my_result = sum_two_numbers(22, 15)

# Now that I captured the result, print it.
print(my_result)

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

7.4.3. Volume Cylinder Example

Function that returns the volume of a cylinder

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)

7.5. 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.

7.6. 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.

7.7. 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.

7.8. Functions Calling Functions

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

7.8.1. Example 1

In this example, note that if you don’t use a function, it doesn’t run.

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

def b():
    print("B")

def c():
    print("C")

a()

7.8.2. Example 2

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

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

def c():
    print("C")

a()

7.8.3. Example 3

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

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

def c():
    print("C")

a()

7.8.4. Example 4

# 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()

7.8.5. Example 5

# 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)

7.8.6. Example 6

While line 3 of this example increases x, the x variable in the function is a different variable than the x that is in the rest of the program. So that x never changes.

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

x = 3
a(x)

print(x)

7.8.7. Example 7

This example is similar to the prior example, but we return x at the end. Turns out, it doesn’t matter. Because we never do anything with the return value. So the global variable x still doesn’t increase. See the next example.

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

x = 3
a(x)

print(x)

7.8.8. Example 8

This example take the value returned from a and stores it back into x. How? By doing x = a(x) instead of just a(x).

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

x = 3
x = a(x)

print(x)

7.8.9. Example 9

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

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

7.8.10. Example 10

While you can have two return statements in a function, once you hit the first return the function ends. In this case, return y never runs, because we already returned from the function in the prior line.

# 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)

7.8.11. Example 11

This is not something you can do in every programming language. You can return two values by using a comma and listing them.

# 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)

7.8.12. Example 12

If you return two values out of a function, you can capture them this way.

# 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)

7.8.13. Example 13

# 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)

7.8.14. Example 14

We will talk more about these next two examples when we talk about “lists” and “classes” later. These examples don’t operate like you might expect at first. Take a look and see what is different. We’ll explain why it works differently later.

# 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)

7.8.15. Example 15

# Example 15
# New concept!
# Covered in more detail in a later chapter
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)

7.9. How to Create a Custom Drawing Function

Here is a set of examples where we take a program that already exists and put everything in functions.

First the original program:

../../_images/snowman1.png
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import arcade

SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600


arcade.open_window(SCREEN_WIDTH, SCREEN_HEIGHT, "Drawing with Functions")
arcade.set_background_color(arcade.color.DARK_BLUE)
arcade.start_render()

# Draw the ground
arcade.draw_lrtb_rectangle_filled(0, SCREEN_WIDTH, SCREEN_HEIGHT / 3, 0, arcade.color.AIR_SUPERIORITY_BLUE)

# Draw a snow person

# Snow
arcade.draw_circle_filled(300, 200, 60, arcade.color.WHITE)
arcade.draw_circle_filled(300, 280, 50, arcade.color.WHITE)
arcade.draw_circle_filled(300, 340, 40, arcade.color.WHITE)

# Eyes
arcade.draw_circle_filled(285, 350, 5, arcade.color.BLACK)
arcade.draw_circle_filled(315, 350, 5, arcade.color.BLACK)

#  Finish and run
arcade.finish_render()
arcade.run()

7.9.1. Make The Main Function

Next, create a main() function. Put everything in it, and call the main function.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import arcade

SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600


def main():
    arcade.open_window(SCREEN_WIDTH, SCREEN_HEIGHT, "Drawing with Functions")
    arcade.set_background_color(arcade.color.DARK_BLUE)
    arcade.start_render()

    # Draw the ground
    arcade.draw_lrtb_rectangle_filled(0, SCREEN_WIDTH, SCREEN_HEIGHT / 3, 0, arcade.color.AIR_SUPERIORITY_BLUE)

    # Draw a snow person

    # Snow
    arcade.draw_circle_filled(300, 200, 60, arcade.color.WHITE)
    arcade.draw_circle_filled(300, 280, 50, arcade.color.WHITE)
    arcade.draw_circle_filled(300, 340, 40, arcade.color.WHITE)

    # Eyes
    arcade.draw_circle_filled(285, 350, 5, arcade.color.BLACK)
    arcade.draw_circle_filled(315, 350, 5, arcade.color.BLACK)

    # Finish and run
    arcade.finish_render()
    arcade.run()


# Call the main function to get the program started.
main()

When you do this, run your program and make sure it still works before proceeding.

7.9.2. Make The Drawing Functions

Next, pick an item to move to a function. Start with an easy one if you have it. I chose grass because it was only one line of code, and I wasn’t going to ever try to position it with x, y.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import arcade

SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600


def draw_grass():
    """ Draw the ground """
    arcade.draw_lrtb_rectangle_filled(0, SCREEN_WIDTH, SCREEN_HEIGHT / 3, 0, arcade.color.AIR_SUPERIORITY_BLUE)


def main():
    arcade.open_window(SCREEN_WIDTH, SCREEN_HEIGHT, "Drawing with Functions")
    arcade.set_background_color(arcade.color.DARK_BLUE)
    arcade.start_render()

    draw_grass()

    # Draw a snow person

    # Snow
    arcade.draw_circle_filled(300, 200, 60, arcade.color.WHITE)
    arcade.draw_circle_filled(300, 280, 50, arcade.color.WHITE)
    arcade.draw_circle_filled(300, 340, 40, arcade.color.WHITE)

    # Eyes
    arcade.draw_circle_filled(285, 350, 5, arcade.color.BLACK)
    arcade.draw_circle_filled(315, 350, 5, arcade.color.BLACK)

    # Finish and run
    arcade.finish_render()
    arcade.run()


# Call the main function to get the program started.
main()

Then, I took a more complex shape and put it in a function.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import arcade

SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600


def draw_grass():
    """ Draw the ground """
    arcade.draw_lrtb_rectangle_filled(0, SCREEN_WIDTH, SCREEN_HEIGHT / 3, 0, arcade.color.AIR_SUPERIORITY_BLUE)


def draw_snow_person():
    """ Draw a snow person """

    # Snow
    arcade.draw_circle_filled(300, 200, 60, arcade.color.WHITE)
    arcade.draw_circle_filled(300, 280, 50, arcade.color.WHITE)
    arcade.draw_circle_filled(300, 340, 40, arcade.color.WHITE)

    # Eyes
    arcade.draw_circle_filled(285, 350, 5, arcade.color.BLACK)
    arcade.draw_circle_filled(315, 350, 5, arcade.color.BLACK)


def main():
    arcade.open_window(SCREEN_WIDTH, SCREEN_HEIGHT, "Drawing with Functions")
    arcade.set_background_color(arcade.color.DARK_BLUE)
    arcade.start_render()

    draw_grass()
    draw_snow_person()

    # Finish and run
    arcade.finish_render()
    arcade.run()


# Call the main function to get the program started.
main()

But this draws the snowman only at one spot. I want to draw lots of snowmen, anywhere I put them!

To do this, let’s add an x and y:

../../_images/snowman2.png
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import arcade

SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600


def draw_grass():
    """ Draw the ground """
    arcade.draw_lrtb_rectangle_filled(0, SCREEN_WIDTH, SCREEN_HEIGHT / 3, 0, arcade.color.AIR_SUPERIORITY_BLUE)


def draw_snow_person(x, y):
    """ Draw a snow person """

    # Draw a point at x, y for reference
    arcade.draw_point(x, y, arcade.color.RED, 5)

    # Snow
    arcade.draw_circle_filled(300 + x, 200 + y, 60, arcade.color.WHITE)
    arcade.draw_circle_filled(300 + x, 280 + y, 50, arcade.color.WHITE)
    arcade.draw_circle_filled(300 + x, 340 + y, 40, arcade.color.WHITE)

    # Eyes
    arcade.draw_circle_filled(285 + x, 350 + y, 5, arcade.color.BLACK)
    arcade.draw_circle_filled(315 + x, 350 + y, 5, arcade.color.BLACK)


def main():
    arcade.open_window(SCREEN_WIDTH, SCREEN_HEIGHT, "Drawing with Functions")
    arcade.set_background_color(arcade.color.DARK_BLUE)
    arcade.start_render()

    draw_grass()
    draw_snow_person(50, 50)

    # Finish and run
    arcade.finish_render()
    arcade.run()


# Call the main function to get the program started.
main()

But that’s not perfect. If you’ll note, I added a dot at the x and y. The snowman draws way off from the dot, because originally I didn’t try to draw it at 0, 0. I need to recenter the snowman on the dot.

We need to re-center the shape onto the spot we are drawing. Typically you’ll need to subtract from all the x and y values the same amount.

../../_images/snowman3.png
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import arcade

SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600


def draw_grass():
    """ Draw the ground """
    arcade.draw_lrtb_rectangle_filled(0, SCREEN_WIDTH, SCREEN_HEIGHT / 3, 0, arcade.color.AIR_SUPERIORITY_BLUE)


def draw_snow_person(x, y):
    """ Draw a snow person """

    # Draw a point at x, y for reference
    arcade.draw_point(x, y, arcade.color.RED, 5)

    # Snow
    arcade.draw_circle_filled(x, 60 + y, 60, arcade.color.WHITE)
    arcade.draw_circle_filled(x, 140 + y, 50, arcade.color.WHITE)
    arcade.draw_circle_filled(x, 200 + y, 40, arcade.color.WHITE)

    # Eyes
    arcade.draw_circle_filled(x - 15, 210 + y, 5, arcade.color.BLACK)
    arcade.draw_circle_filled(x + 15, 210 + y, 5, arcade.color.BLACK)


def main():
    arcade.open_window(SCREEN_WIDTH, SCREEN_HEIGHT, "Drawing with Functions")
    arcade.set_background_color(arcade.color.DARK_BLUE)
    arcade.start_render()

    draw_grass()
    draw_snow_person(150, 140)
    draw_snow_person(450, 180)

    # Finish and run
    arcade.finish_render()
    arcade.run()


# Call the main function to get the program started.
main()

7.10. How To Animate A Drawing Function

We can animate our drawing if we want. Here are the steps.

7.10.1. Create An on_draw Method

Right now our program only draws our image once. We need to move all the drawing code in our main to an on_draw function. Then we’ll tell the computer to draw that over and over.

Continuing from our last example, our program will look like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import arcade

SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600


def draw_grass():
    """ Draw the ground """
    arcade.draw_lrtb_rectangle_filled(0, SCREEN_WIDTH, SCREEN_HEIGHT / 3, 0, arcade.color.AIR_SUPERIORITY_BLUE)


def draw_snow_person(x, y):
    """ Draw a snow person """

    # Draw a point at x, y for reference
    arcade.draw_point(x, y, arcade.color.RED, 5)

    # Snow
    arcade.draw_circle_filled(x, 60 + y, 60, arcade.color.WHITE)
    arcade.draw_circle_filled(x, 140 + y, 50, arcade.color.WHITE)
    arcade.draw_circle_filled(x, 200 + y, 40, arcade.color.WHITE)

    # Eyes
    arcade.draw_circle_filled(x - 15, 210 + y, 5, arcade.color.BLACK)
    arcade.draw_circle_filled(x + 15, 210 + y, 5, arcade.color.BLACK)


def on_draw(delta_time):
    """ Draw everything """
    arcade.start_render()

    draw_grass()
    draw_snow_person(150, 140)
    draw_snow_person(450, 180)


def main():
    arcade.open_window(SCREEN_WIDTH, SCREEN_HEIGHT, "Drawing with Functions")
    arcade.set_background_color(arcade.color.DARK_BLUE)

    # Call on_draw every 60th of a second.
    arcade.schedule(on_draw, 1/60)
    arcade.run()


# Call the main function to get the program started.
main()

Do this with your own program. Nothing will move, but it should still run.

7.10.2. Add Variable To Control Where We Draw Our Item

Next, we are going to create a variable inside of the on_draw function. This variable will hold our x value. Each time we call on_draw, we’ll change x so that it moves to the right.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import arcade

SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600


def draw_grass():
    """ Draw the ground """
    arcade.draw_lrtb_rectangle_filled(0, SCREEN_WIDTH, SCREEN_HEIGHT / 3, 0, arcade.color.AIR_SUPERIORITY_BLUE)


def draw_snow_person(x, y):
    """ Draw a snow person """

    # Draw a point at x, y for reference
    arcade.draw_point(x, y, arcade.color.RED, 5)

    # Snow
    arcade.draw_circle_filled(x, 60 + y, 60, arcade.color.WHITE)
    arcade.draw_circle_filled(x, 140 + y, 50, arcade.color.WHITE)
    arcade.draw_circle_filled(x, 200 + y, 40, arcade.color.WHITE)

    # Eyes
    arcade.draw_circle_filled(x - 15, 210 + y, 5, arcade.color.BLACK)
    arcade.draw_circle_filled(x + 15, 210 + y, 5, arcade.color.BLACK)


def on_draw(delta_time):
    """ Draw everything """
    arcade.start_render()

    draw_grass()
    draw_snow_person(on_draw.snow_person1_x, 140)
    draw_snow_person(450, 180)

    # Add one to the x value, making the snow person move right
    # Negative numbers move left. Larger numbers move faster.
    on_draw.snow_person1_x += 1


# Create a value that our on_draw.snow_person1_x will start at.
on_draw.snow_person1_x = 150


def main():
    arcade.open_window(SCREEN_WIDTH, SCREEN_HEIGHT, "Drawing with Functions")
    arcade.set_background_color(arcade.color.DARK_BLUE)

    # Call on_draw every 60th of a second.
    arcade.schedule(on_draw, 1/60)
    arcade.run()


# Call the main function to get the program started.
main()

For more information, see the Bouncing Rectangle Example.