The game you will create in this chapter is called Dragon Realm. The player decides between two caves, which hold either treasure or certain doom.
In this game, the player is in a land full of dragons. The dragons all live in caves with their large piles of collected treasure. Some dragons are friendly and share their treasure. Other dragons are hungry and eat anyone who enters their cave. The player approaches two caves, one with a friendly dragon and the other with a hungry dragon, but doesn’t know which dragon is in which cave. The player must choose between the two.
Here’s what the Dragon Realm game looks like when it’s run. The player’s input is in bold.
You are in a land full of dragons. In front of you,
you see two caves. In one cave, the dragon is friendly
and will share his treasure with you. The other dragon
is greedy and hungry, and will eat you on sight.
Which cave will you go into? (1 or 2)
1
You approach the cave...
It is dark and spooky...
A large dragon jumps out in front of you! He opens his jaws and...
Gobbles you down in one bite!
Do you want to play again? (yes or no)
no
It often helps to write down everything you want your game or program to do before you start writing code. When you do this, you are designing the program.
For example, it may help to draw a flowchart. A flowchart is a diagram that shows every possible action that can happen in the game and which actions are connected. Figure 5-1 is a flowchart for Dragon Realm.
To see what happens in the game, put your finger on the START box. Then follow one arrow from that box to another box. Your finger is like the program execution. The program terminates when your finger lands on the END box.
Figure 5-1: Flowchart for the Dragon Realm game
When you get to the “Check for friendly or hungry dragon” box, you can go to the “Player wins” box or the “Player loses” box. At this branching point, the program can go in different directions. Either way, both paths will eventually end up at the “Ask to play again” box.
Open a new file editor window by clicking File New Window. Enter the source code and save it as dragon.py. Then run the program by pressing F5. If you get errors, compare the code you typed to the book’s code with the online diff tool at https://www.nostarch.com/inventwithpython#diff.
dragon.py
1. import random
2. import time
3.
4. def displayIntro():
5. print('''You are in a land full of dragons. In front of you,
6. you see two caves. In one cave, the dragon is friendly
7. and will share his treasure with you. The other dragon
8. is greedy and hungry, and will eat you on sight.''')
9. print()
10.
11. def chooseCave():
12. cave = ''
13. while cave != '1' and cave != '2':
14. print('Which cave will you go into? (1 or 2)')
15. cave = input()
16.
17. return cave
18.
19. def checkCave(chosenCave):
20. print('You approach the cave...')
21. time.sleep(2)
22. print('It is dark and spooky...')
23. time.sleep(2)
24. print('A large dragon jumps out in front of you! He opens his jaws
and...')
25. print()
26. time.sleep(2)
27.
28. friendlyCave = random.randint(1, 2)
29.
30. if chosenCave == str(friendlyCave):
31. print('Gives you his treasure!')
32. else:
33. print('Gobbles you down in one bite!')
34.
35. playAgain = 'yes'
36. while playAgain == 'yes' or playAgain == 'y':
37. displayIntro()
38. caveNumber = chooseCave()
39. checkCave(caveNumber)
40.
41. print('Do you want to play again? (yes or no)')
42. playAgain = input()
Let’s look at the source code in more detail.
This program imports two modules:
1. import random
2. import time
The random module provides the randint() function, which we used in the Guess the Number game from Chapter 3. Line 2 imports the time module, which contains time-related functions.
Functions let you run the same code multiple times without having to copy and paste that code over and over again. Instead, you put that code inside a function and call the function whenever you need to. Because you write the code only once in the function, if the function’s code has a mistake, you only have to change it in one place in the program.
You’ve already used a few functions, like print(), input(), randint(), str(), and int(). Your programs have called these functions to execute the code inside them. In the Dragon Realm game, you’ll write your own functions using def statements.
Line 4 is a def statement:
4. def displayIntro():
5. print('''You are in a land full of dragons. In front of you,
6. you see two caves. In one cave, the dragon is friendly
7. and will share his treasure with you. The other dragon
8. is greedy and hungry, and will eat you on sight.''')
9. print()
The def statement defines a new function (in this case, the displayIntro() function), which you can call later in the program.
Figure 5-2 shows the parts of a def statement. It has the def keyword followed by a function name with parentheses, and then a colon (:). The block after the def statement is called the def block.
Figure 5-2: Parts of a def statement
When you define a function, you specify the instructions for it to run in the following block. When you call a function, the code inside the def block executes. Unless you call the function, the instructions in the def block will not execute.
In other words, when the execution reaches a def statement, it skips down to the first line after the def block. But when a function is called, the execution moves inside of the function to the first line of the def block.
For example, look at the call to the displayIntro() function on line 37:
37. displayIntro()
Calling this function runs the print() call, which displays the “You are in a land full of dragons ...” introduction.
A function’s def statement and def block must come before you call the function, just as you must assign a value to a variable before you use that variable. If you put the function call before the function definition, you’ll get an error. Let’s look at a short program as an example. Open a new file editor window, enter this code, save it as example.py, and run it:
sayGoodbye()
def sayGoodbye():
print('Goodbye!')
If you try to run this program, Python will give you an error message that looks like this:
Traceback (most recent call last):
File "C:/Users/Al/AppData/Local/Programs/Python/Python36/example.py",
line 1, in <module>
sayGoodbye()
NameError: name 'sayGoodbye' is not defined
To fix this error, move the function definition before the function call:
def sayGoodbye():
print('Goodbye!')
sayGoodbye()
Now, the function is defined before it is called, so Python will know what sayGoodbye() should do. Otherwise, Python won’t have the instructions for sayGoodbye() when it is called and so won’t be able to run it.
So far, all of the strings in our print() function calls have been on one line and have had one quote character at the start and end. However, if you use three quotes at the start and end of a string, then it can go across several lines. These are multiline strings.
Enter the following into the interactive shell to see how multiline strings work:
>>> fizz = '''Dear Alice,
I will return to Carol's house at the end of the month.
Your friend,
Bob'''
>>> print(fizz)
Dear Alice,
I will return to Carol's house at the end of the month.
Your friend,
Bob
Note the line breaks in the printed string. In a multiline string, the newline characters are included as part of the string. You don’t have to use the \n escape character or escape quotes as long as you don’t use three quotes together. These line breaks make the code easier to read when large amounts of text are involved.
Line 11 defines another function called chooseCave():
11. def chooseCave():
This function’s code asks the player which cave they want to go in, either 1 or 2. We’ll need to use a while statement to ask the player to choose a cave, which marks the start of a new kind of loop: a while loop.
Unlike a for loop, which loops a specific number of times, a while loop repeats as long as a certain condition is True. When the execution reaches a while statement, it evaluates the condition next to the while keyword. If the condition evaluates to True, the execution moves inside the following block, called the while block. If the condition evaluates to False, the execution moves past the while block.
You can think of a while statement as being almost the same as an if statement. The program execution enters the blocks of both statements if their condition is True. But when the condition reaches the end of the block in a while loop, it moves back to the while statement to recheck the condition.
Look at the def block for chooseCave() to see a while loop in action:
12. cave = ''
13. while cave != '1' and cave != '2':
Line 12 creates a new variable called cave and stores a blank string in it. Then a while loop begins on line 13. The chooseCave() function needs to make sure the player entered 1 or 2 and not something else. A loop here keeps asking the player which cave they choose until they enter one of these two valid responses. This is called input validation.
The condition also contains a new operator you haven’t seen before: and. Just as - and * are mathematical operators, and == and != are comparison operators, the and operator is a Boolean operator. Let’s take a closer look at Boolean operators.
Boolean logic deals with things that are either true or false. Boolean operators compare values and evaluate to a single Boolean value.
Think of the sentence “Cats have whiskers and dogs have tails.” “Cats have whiskers” is true, and “dogs have tails” is also true, so the entire sentence “Cats have whiskers and dogs have tails” is true.
But the sentence “Cats have whiskers and dogs have wings” would be false. Even though “cats have whiskers” is true, dogs don’t have wings, so “dogs have wings” is false. In Boolean logic, things can only be entirely true or entirely false. Because of the word and, the entire sentence is true only if both parts are true. If one or both parts are false, then the entire sentence is false.
The and operator in Python also requires the whole Boolean expression to be True or False. If the Boolean values on both sides of the and keyword are True, then the expression evaluates to True. If either or both of the Boolean values are False, then the expression evaluates to False.
Enter the following expressions with the and operator into the interactive shell:
>>> True and True
True
>>> True and False
False
>>> False and True
False
>>> False and False
False
>>> spam = 'Hello'
>>> 10 < 20 and spam == 'Hello'
True
The and operator can be used to evaluate any two Boolean expressions. In the last example, 10 < 20 evaluates to True and spam == 'Hello' also evaluates to True, so the two Boolean expressions joined by the and operator evaluate as True.
If you ever forget how a Boolean operator works, you can look at its truth table, which shows how every combination of Boolean values evaluates. Table 5-1 shows every combination for the and operator.
Table 5-1: The and Operator’s Truth Table
A and B |
Evaluates to |
True and True |
True |
True and False |
False |
False and True |
False |
False and False |
False |
The or operator is similar to the and operator, except it evaluates to True if either of the two Boolean values is True. The only time the or operator evaluates to False is if both of the Boolean values are False.
Now enter the following into the interactive shell:
>>> True or True
True
>>> True or False
True
>>> False or True
True
>>> False or False
False
>>> 10 > 20 or 20 > 10
True
In the last example, 10 is not greater than 20 but 20 is greater than 10, so the first expression evaluates to False and the second expression evaluates to True. Because the second expression is True, this whole expression evaluates as True.
The or operator’s truth table is shown in Table 5-2.
Table 5-2: The or Operator’s Truth Table
A or B |
Evaluates to |
True or True |
True |
True or False |
True |
False or True |
True |
False or False |
False |
Instead of combining two values, the not operator works on only one value. The not operator evaluates to the opposite Boolean value: True expressions evaluate to False, and False expressions evaluate to True.
Enter the following into the interactive shell:
>>> not True
False
>>> not False
True
>>> not ('black' == 'white')
True
The not operator can also be used on any Boolean expression. In the last example, the expression 'black' == 'white' evaluates to False. This is why not ('black' == 'white') is True.
The not operator’s truth table is shown in Table 5-3.
Table 5-3: The not Operator’s Truth Table
not A |
Evaluates to |
not True |
False |
not False |
True |
Look at line 13 of the Dragon Realm game again:
13. while cave != '1' and cave != '2':
The condition has two parts connected by the and Boolean operator. The condition is True only if both parts are True.
The first time the while statement’s condition is checked, cave is set to the blank string, ''. The blank string is not equal to the string '1', so the left side evaluates to True. The blank string is also not equal to the string '2', so the right side evaluates to True.
So the condition then turns into True and True. Because both values are True, the whole condition evaluates to True, so the program execution enters the while block, where the program will attempt to assign a nonblank value for cave.
Line 14 asks the player to choose a cave:
13. while cave != '1' and cave != '2':
14. print('Which cave will you go into? (1 or 2)')
15. cave = input()
Line 15 lets the player type their response and press ENTER. This response is stored in cave. After this code is executed, the execution loops back to the top of the while statement and rechecks the condition at line 13.
If the player entered 1 or 2, then cave will be either '1' or '2' (since input() always returns strings). This makes the condition False, and the program execution will continue past the while loop. For example, if the user entered '1', then the evaluation would look like this:
But if the player entered 3 or 4 or HELLO, that response would be invalid. The condition would then be True and would enter the while block to ask the player again. The program keeps asking the player which cave they choose until they enter 1 or 2. This guarantees that once the execution moves on, the cave variable contains a valid response.
Line 17 has something new called a return statement:
17. return cave
A return statement appears only inside def blocks where a function—in this case, chooseCave()—is defined. Remember how the input() function returns a string value that the player entered? The chooseCave() function will also return a value. Line 17 returns the string that is stored in cave, either '1' or '2'.
Once the return statement executes, the program execution jumps immediately out of the def block (just as the break statement makes the execution jump out of a while block). The program execution moves back to the line with the function call. The function call itself will evaluate to the function’s return value.
Skip down to line 38 for a moment where the chooseCave() function is called:
38. caveNumber = chooseCave()
On line 38, when the program calls the function chooseCave(), which was defined on line 11, the function call evaluates to the string inside of cave, which is then stored in caveNumber. The while loop inside chooseCave() guarantees that chooseCave() will return only either '1' or '2' as its return value. So caveNumber can have only one of these two values.
There is something special about variables that are created inside functions, like the cave variable in the chooseCave() function on line 12:
12. cave = ''
A local scope is created whenever a function is called. Any variables assigned in this function exist within the local scope. Think of a scope as a container for variables. What makes variables in local scopes special is that they are forgotten when the function returns and they will be re-created if the function is called again. The value of a local variable isn’t remembered between function calls.
Variables that are assigned outside of functions exist in the global scope. There is only one global scope, and it is created when your program begins. When your program terminates, the global scope is destroyed, and all its variables are forgotten. Otherwise, the next time you ran your program, the variables would remember their values from the last time you ran it.
A variable that exists in a local scope is called a local variable, while a variable that exists in the global scope is called a global variable. A variable must be one or the other; it cannot be both local and global.
The variable cave is created inside the chooseCave() function. This means it is created in the chooseCave() function’s local scope. It will be forgotten when chooseCave() returns and will be re-created if chooseCave() is called a second time.
Local and global variables can have the same name, but they are different variables because they are in different scopes. Let’s write a new program to illustrate these concepts:
def bacon():
➊ spam = 99 # Creates a local variable named spam
➋ print(spam) # Prints 99
➌ spam = 42 # Creates a global variable named spam
➍ print(spam) # Prints 42
➎ bacon() # Calls the bacon() function and prints 99
➏ print(spam) # Prints 42
We first make a function called bacon(). In bacon(), we create a variable called spam and assign it 99 ➊. At ➋, we call print() to print this local spam variable, which is 99. At ➌, a global variable called spam is also declared and set to 42. This variable is global because it is outside of all functions. When the global spam variable is passed to print() at ➍, it prints 42. When the bacon() function is called at ➎, ➊ and ➋ are executed, and the local spam variable is created, set, and then printed. So calling bacon() prints the value 99. After the bacon() function call returns, the local spam variable is forgotten. If we print spam at ➏, we are printing the global variable, so the output there is 42.
When run, this code will output the following:
42
99
42
Where a variable is created determines what scope it is in. Keep this in mind when writing your programs.
The next function defined in the Dragon Realm program is named checkCave().
19. def checkCave(chosenCave):
Notice the text chosenCave between the parentheses. This is a parameter: a local variable that is used by the function’s code. When the function is called, the call’s arguments are the values assigned to the parameters.
Let’s go back to the interactive shell for a moment. Remember that for some function calls, like str() or randint(), you would pass one or more arguments between the parentheses:
>>> str(5)
'5'
>>> random.randint(1, 20)
14
>>> len('Hello')
5
This example includes a Python function you haven’t seen yet: len(). The len() function returns an integer indicating how many characters are in the string passed to it. In this case, it tells us that the string 'Hello' has 5 characters.
You will also pass an argument when you call checkCave(). This argument is stored in a new variable named chosenCave, which is checkCave()’s parameter.
Here is a short program that demonstrates defining a function (sayHello) with a parameter (name):
def sayHello(name):
print('Hello, ' + name + '. Your name has ' + str(len(name)) +
' letters.')
sayHello('Alice')
sayHello('Bob')
spam = 'Carol'
sayHello(spam)
When you call sayHello() with an argument in the parentheses, the argument is assigned to the name parameter, and the code in the function is executed. There’s just one line of code in the sayHello() function, which is a print() function call. Inside the print() function call are some strings and the name variable, along with a call to the len() function. Here, len() is used to count the number of characters in name. If you run the program, the output looks like this:
Hello, Alice. Your name has 5 letters.
Hello, Bob. Your name has 3 letters.
Hello, Carol. Your name has 5 letters.
For each call to sayHello(), a greeting and the length of the name argument are printed. Notice that because the string 'Carol' is assigned to the spam variable, sayHello(spam) is equivalent to sayHello('Carol').
Let’s go back to the Dragon Realm game’s source code:
20. print('You approach the cave...')
21. time.sleep(2)
The time module has a function called sleep() that pauses the program. Line 21 passes the integer value 2 so that time.sleep() will pause the program for 2 seconds.
22. print('It is dark and spooky...')
23. time.sleep(2)
Here the code prints some more text and waits for another 2 seconds. These short pauses add suspense to the game instead of displaying the text all at once. In Chapter 4’s Jokes program, you called the input() function to pause until the player pressed ENTER. Here, the player doesn’t have to do anything except wait a couple of seconds.
24. print('A large dragon jumps out in front of you! He opens his jaws
and...')
25. print()
26. time.sleep(2)
With the suspense building, our program will next determine which cave has the friendly dragon.
Line 28 calls the randint() function, which will randomly return either 1 or 2.
28. friendlyCave = random.randint(1, 2)
This integer value is stored in friendlyCave and indicates the cave with the friendly dragon.
30. if chosenCave == str(friendlyCave):
31. print('Gives you his treasure!')
Line 30 checks whether the player’s chosen cave in the chosenCave variable ('1' or '2') is equal to the friendly dragon cave.
But the value in friendlyCave is an integer because randint() returns integers. You can’t compare strings and integers with the == sign, because they will never be equal to each other: '1' is not equal to 1, and '2' is not equal to 2.
So friendlyCave is passed to the str() function, which returns the string value of friendlyCave. Now the values will have the same data type and can be meaningfully compared to each other. We could have also converted chosenCave to an integer value instead. Then line 30 would have looked like this:
if int(chosenCave) == friendlyCave:
If chosenCave is equal to friendlyCave, the condition evaluates to True, and line 31 tells the player they have won the treasure.
Now we have to add some code to run if the condition is false. Line 32 is an else statement:
32. else:
33. print('Gobbles you down in one bite!')
An else statement can come only after an if block. The else block executes if the if statement’s condition is False. Think of this as the program’s way of saying, “If this condition is true, then execute the if block or else execute the else block.”
In this case, the else statement runs when chosenCave is not equal to friendlyCave. Then, the print() function call on line 33 is run, telling the player that they’ve been eaten by the dragon.
The first part of the program defines several functions but doesn’t run the code inside of them. Line 35 is where the main part of the program begins because it is the first line that runs:
35. playAgain = 'yes'
36. while playAgain == 'yes' or playAgain == 'y':
This line is where the main part of the program begins. The previous def statements merely defined the functions. They didn’t run the code inside of those functions.
Lines 35 and 36 are setting up a loop that contains the rest of the game code. At the end of the game, the player can tell the program whether they want to play again. If they do, the execution enters the while loop to run the entire game all over again. If they don’t, the while statement’s condition will be False, and the execution will move to the end of the program and terminate.
The first time the execution comes to this while statement, line 35 will have just assigned 'yes' to the playAgain variable. That means the condition will be True at the start of the program. This guarantees that the execution enters the loop at least once.
Line 37 calls the displayIntro() function:
37. displayIntro()
This isn’t a Python function—it is the function that you defined earlier on line 4. When this function is called, the program execution jumps to the first line in the displayIntro() function on line 5. When all the lines in the function have been executed, the execution jumps back to line 37 and continues moving down.
Line 38 also calls a function that you defined:
38. caveNumber = chooseCave()
Remember that the chooseCave() function lets the player choose the cave they want to enter. When line 17’s return cave executes, the program execution jumps back to line 38. The chooseCave() call then evaluates to the return value, which will be an integer value representing the cave the player chose to enter. This return value is stored in a new variable named caveNumber.
Then the program execution moves on to line 39:
39. checkCave(caveNumber)
Line 39 calls the checkCave() function, passing the value in caveNumber as an argument. Not only does the execution jump to line 20, but the value in caveNumber is copied to the parameter chosenCave inside the checkCave() function. This is the function that will display either 'Gives you his treasure!' or 'Gobbles you down in one bite!' depending on the cave the player chooses to enter.
Whether the player won or lost, they are asked if they want to play again.
41. print('Do you want to play again? (yes or no)')
42. playAgain = input()
The variable playAgain stores what the player types. Line 42 is the last line of the while block, so the program jumps back to line 36 to check the while loop’s condition: playAgain == 'yes' or playAgain == 'y'.
If the player enters the string 'yes' or 'y', then the execution enters the loop again at line 37.
If the player enters 'no' or 'n' or something silly like 'Abraham Lincoln', then the condition is False, and the program execution continues to the line after the while block. But since there aren’t any lines after the while block, the program terminates.
One thing to note: the string 'YES' is not equal to the string 'yes' since the computer does not consider upper- and lowercase letters the same. If the player entered the string 'YES', then the while statement’s condition would evaluate to False and the program would still terminate. Later, the Hangman program will show you how to avoid this problem. (See “The lower() and upper() String Methods” on page 101.)
You’ve just completed your second game! In Dragon Realm, you used a lot of what you learned in the Guess the Number game and picked up a few new tricks. If you didn’t understand some of the concepts in this program, go over each line of the source code again and try changing the code to see how the program changes.
In Chapter 6, you won’t create another game. Instead, you will learn how to use a feature of IDLE called the debugger.
In the Dragon Realm game, you created your own functions. A function is a mini-program within your program. The code inside the function runs when the function is called. By breaking up your code into functions, you can organize your code into shorter and easier-to-understand sections.
Arguments are values copied to the function’s parameters when the function is called. The function call itself evaluates to the return value.
You also learned about variable scopes. Variables created inside of a function exist in the local scope, and variables created outside of all functions exist in the global scope. Code in the global scope cannot make use of local variables. If a local variable has the same name as a global variable, Python considers it a separate variable. Assigning new values to the local variable won’t change the value in the global variable.
Variable scopes might seem complicated, but they are useful for organizing functions as separate pieces of code from the rest of the program. Because each function has its own local scope, you can be sure that the code in one function won’t cause bugs in other functions.
Almost every program has functions because they are so useful. By understanding how functions work, you can save yourself a lot of typing and make bugs easier to fix.