Recall: Last time we learned about algorithms and some of their mathematical properties. So how do we get back to programming in Python? Let's consider a simple algorithm for computing the factorial of a nonnegative integer. First we write it in pseudocode:
factorial
answer=1
if n>0
for i = 1 to n
answer=answer * 1
return answer
So we just saw how to define a simple value-returning function in Python. Now we are going to see how we can use function definitions to make the design process of a program much easier.
Pig is a two player game played with a single die. The object of the game is to accumulate 100 points. During each turn a player repeatedly rolls the die earning points equal to the face value of the die on each roll until:
Let's consider how we would write a program in Python to interactively play this game. We will start by wishing Python had built in functions that allow us to do everything we need. Eventually, we will have to write these functions ourselves but the process of imagining what they would do for us will greatly inform our design of the program.
Okay, so that's a first very coarse iteration. Now we can attack each one of these steps one at a time. What we will do is come up with a function in Python
Display greeting
This one is pretty simple. We'll just write a function that prints a greeting. Something like this:
def greeting():
print('Welcome to Pig Deathmatch!')
print('Prepare to be challenged!')
That was easy enough. We can do last step just as easily. Display goodbye
def goodbye():
print('Thanks for playing!')
Okay, easy. But what about actually playing the game? This one requires a little bit more refinement. Let's work on it.
Play the game
Not hard. Let's see if we can refine this a little bit. Notice that we use the word until in step 3. This is often a good indicator that a while loop may be used to capture the reptition.
Let's try again,
Play the game
while (nobody has won the game)
Player 1 takes their turn
Player 2 takes their turn
Display the result
Let's refine a little more. What does nobody has won the game really mean? It means that neither player has accumulated 100 points yet. So let's write it like that.
while (player1_score<100 and player2_score<100)
Player 1 takes their turn
Player 2 takes their turn
Display the result
Almost there! This is almost right. What's wrong though? Does player 2 really always play after player 1? What about if player 1 wins the game?
while (player1_score<100 and player2_score<100)
Player 1 takes their turn
if player1_score<100
Player 2 takes their turn
Display the result
Excellent! So now we just need to articulate more precisely what 'Player 1 takes their turn' means and what 'Player 2 takes their turn' means. That is, we need to write more algorithms.
Player 1 takes their turn
Let's refine this.
turn_total=0
while(player wants to roll again and rolls no 1)
roll the die
print the result
if roll == 1
turn_total=0
end the turn
else
turn_total = turn_total + 1
ask the user if they want to roll again or not
return turn_total
That's better. It's not Python, but it's pretty close. Notice that we'll have to write one more function for rolling the die. That's easy though. We already saw how we can do that in the roshambo program.
So what's left? The computer player, Player 2.
Player 2 takes their turn
So how does the computer decide? We need to invent a strategy for the computer. One option is to have it roll up to x number of times. Another is to keep rolling until it's accumulated x number of points. There are many other options. We'll go with this simple approach: Keep rolling until 20 points are accumulated or a 1 is rolled. Translating the above into something a little closer to Python and incorporating this strategy give us
turn_total = 0
while (turn_total <20 and rolls no 1)
roll the die
print the result
if roll == 1
turn_total=0
end the turn
else
turn_total=turn_total+1
return turn_total
And now to code....
So now that we have done all of this we are ready to write it up in Python. When you're coding on your own, it's really important to go through this process. It will save you time!!
#********************************************
#Pig.py
#
#Plays the interactive dice game Pig
#
#Written by Cannon
#********************************************
import random
score = 0
# The main function for the applicaiton
def main():
greeting()
playgame()
goodbye()
def playgame():
'''This function plays the game Pig'''
p1score=0
p2score=0
#establish a while loop for the game
while (p1score <100 and p2score <100):
print ('your score total is now: {} \n \n'.format(p1score))
p1score=p1score + playerMove()
print ('your score total is now: {} \n \n'.format(p1score))
if(p1score<100):
p2score=p2score + computerMove()
print ('the computer score total is now:{}\n \n'.format(p2score))
if (p1score>p2score):
print ('you win!')
else:
print ('you lose!')
def playerMove():
'''manages the human players move in Pig. Returns their score'''
roundScore=0
again='y'
#establish a while loop for the player's turn
while again=='y':
roll=rollDice()
if roll==1:
print ('you rolled a 1')
roundScore=0
again='n'
else:
print( 'you rolled a {}'.format(roll))
roundScore=roundScore+roll
print( 'your round score is {}'.format(roundScore))
again=input('roll again? (y/n)')
print ('your turn is over')
return roundScore
def computerMove():
'''plays the computer players turn in Pig returns their turn score'''
roundScore=0
again='y'
#establish a while loop for the computer's turn
while again=='y':
roll=rollDice()
if roll==1:
print ('computer rolled a 1')
roundScore=0
again='n'
else:
print( 'computer rolled a {}'.format(roll))
roundScore=roundScore+roll
if roundScore < 20:
print( 'computer will roll again')
else:
again='n'
print( 'computer turn is over')
print( 'computer round score is {}'.format(roundScore))
return roundScore
def rollDice():
'''Rolls a 6 sided die and returns an int 1-6'''
face=int(random.random()*6+1)
return face
def greeting():
'''Displays a greeting for the game Pig'''
print( 'welcome to Pig Death Match!')
def goodbye():
'''Displays a goodbye message for the game Pig'''
print()
print()
print('Toodles!')
What just happened?
We started with an idea. We then wrote in very simple pseudocode an algorithm version of our idea. We did this imagining that there existed built-in functions to do the stuff we wanted. We then refined our algorithm filling in pseudocode for each one of the functions we imagined existing. Finally, we wrote it all down in Python!
So now we know how to use functions to aid in the design of our Pig program. Writing your own functions has many benefits most of which are derived from encapsulation.
Encapsulation: The hiding of the details of an operation which allows programmers to use coarser operations when designing and writing programs. The programmer need not know the details of the finer operations, just how to use them.
The term is most often used in the context of data-types in Object-oriented programming but it is useful here too. Your textbook does a great job of motivating the use of functions in Section 6.1.1. Please read this carefully!
Previously we have imported the math and random modules so that we may use the functions that they contain. Contrast this to the use of built-in functions, functions that don't live in modules that need to be imported. In the last two examples we saw that we can write our own functions in Python. The fun doesn't stop there, we can create our own modules to store those functions as well. Typically we will store related functions in a sinle module so that we can import that module whenever we want access to that collection of functions. To start, it's okay if you think of a module as a file that serves as a place to keep your functions.
This example consists of the three files rectangle.py, circle.py, and geometry.py listed below.
# The rectangle module has functions that perform
# calculations related to rectangles.
# The area function accepts a rectangle's width and
# length as arguments and returns the rectangle's area.
def area(width, length):
return width * length
# The perimeter function accepts a rectangle's width
# and length as arguments and returns the rectangle's
# perimeter.
def perimeter(width, length):
return 2 * (width + length)
# The circle module has functions that perform
# calculations related to circles.
import math
# The area function accepts a circle's radius as an
# argument and returns the area of the circle.
def area(radius):
return math.pi * radius**2
# The circumference function accepts a circle's
# radius and returns the circle's circumference.
def circumference(radius):
return 2 * math.pi * radius
# This program allows the user to choose various
# geometry calculations from a menu. This program
# imports the circle and rectangle modules.
import circle
import rectangle
# The main function.
def main():
# The choice variable controls the loop
# and holds the user's menu choice.
choice = 0
while not(choice == 5):
# display the menu.
display_menu()
# Get the user's choice.
choice = int(input('Enter your choice: '))
# Perform the selected action.
if choice == 0:
r = float(input("Enter the circle's radius: "))
print('The area is ', circle.area(r))
elif choice == 2:
radius = float(input("Enter the circle's radius: "))
print('The circumference is ', \
circle.circumference(radius))
elif choice == 3:
width = float(input("Enter the rectangle's width: "))
length = float(input("Enter the rectangle's length: "))
print('The area is ', rectangle.area(width, length))
elif choice == 4:
width = float(input("Enter the rectangle's width: "))
length = float(input("Enter the rectangle's length: "))
print('The perimeter is ', \
rectangle.perimeter(width, length))
elif choice == 5:
print("Exiting the program...")
else:
print("Error: invalid selection.")
# The display_menu function displays a menu.
def display_menu():
print(" MENU for ")
print("1) Area of a circle")
print("2) Circumference of a circle")
print("3) Area of a rectangle")
print("4) Perimeter of a rectangle")
print("5) Quit")
# Call the main function.
main()
What just happened?
We created a circle module and a rectangle module that store functions we wrote to compute the areas and perimeters of those shapes. Those modules don't actually do anything, we just use them to store our function definitions. Then we wrote the geometry.py file that contains the function definition for the main function. You can think of the main function as the command and control function. It's running the show, calling all of the other functions as needed. In terms of the Pig example from last lecture, the main function is the implementation coarsest of the algorithms we created at the beginning of the wishful programming process.
The Heron method for determining the square root of a number is an old (ancient even!) algorithm. It's very intuitive. Suppose we want to calculate the square root of x. Let's guess y. If we guessed right then x/y should equal y, right? Unless we made a really lucky guess, it won't. So we'll use the average of y and x/y as our next guess and that should be a little closer to the right answer. We'll continue this way until successive guesses are close enough to suit our needs. More formally:
Suppose we are trying to determine the square root of x. Let our first guess $$x_0 = 1$$ and let our nth guess be $$ x_n = \frac{x_{n-1} + x/x_{n-1}}{2}$$ and we keep guessing until successive guesses are withing some tolerance. Now we can easily turn this into a function in Python.
def heron(a,tolerance):
'''Uses the Heron method to approximate the square root'''
oldGuess = 1.0
newGuess = (a / oldGuess + oldGuess) / 2
while abs(oldGuess - newGuess) > tolerance:
oldGuess = newGuess
newGuess = (a / oldGuess + oldGuess) / 2
return newGuess
# We can break the function up into smaller functions if we want
def next_Guess(a, oldGuess):
'''part of the Heron method for finding square roots'''
return (a / oldGuess + oldGuess) / 2
def has_next_guess(a, tolerance, oldGuess):
'''predicate function for Heron method for finding square roots'''
newGuess = next_Guess(a, oldGuess)
return (abs(oldGuess -newGuess) > tolerance)
def root_approx(a, tolerance):
'''Uses the Heron method to approximate the square root'''
guess = 1.0
while has_next_guess(a, tolerance, guess):
guess = next_Guess(a, guess)
return next_Guess(a,guess)
import heron
import math
def main():
TEST = 2
EPSILON = .000001
print('Heron answer: ', heron.root_approx(TEST, EPSILON))
print('Math answer: ', math.sqrt(TEST))
main()
What just happened?
In the first file, heron.py we created a module to store our root approximating function. We wrote the functin in two different ways, just for fun. The second file, heron_test.py is used to define the main function where we test out our implementation of the heron method and use it to calculate the square root of 2. We compare our answer with the answer the math module gives us.