Dragon Realm
Contents
Dragon Realm#
Based on: http://inventwithpython.com/invent4thed/chapter5.html
Table of Contents
Introduction#
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.
Part 1: A Script Template: Shebang, Docstring, Scope#
We’re going to start with a bare bones script that will serve as a template for all future scripts.
Follow the instructions in Repl.it Tips to create
a new file called dragon_realm.py
and change your .replit
file to run it.
Copy and paste the following code into it.
code
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3
4"""
5Dragon Realm - A game where the player decides between two caves, which hold
6 either treasure or certain doom.
7 Inspired by: http://inventwithpython.com/invent4thed/chapter5.html
8"""
9
10
11def main():
12 """The Dragon Realm Game"""
13 print("Welcome to Dragon Realm!")
14
15
16# this means that if this script is executed, then
17# the main() function will be called
18if __name__ == '__main__':
19 main()
Part 1.1: Shebang, Encoding#
The very first line of any executable file (script) is the shebang line.
The line starts with a #!
then is immediately followed (without a space) by
the path to the interpreter. In this case it is telling the computer to run
this script using python3
.
The next line tells Python (as well as some editors) what the encoding to expect. That is, what kinds of characters. This line might be different if, for example, we were going to include Chinese characters.
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
Part 1.2: Docstrings#
The first expression in a Python script should always be a
Docstring. A Docstring, surrounded by """
or '''
, is similar to a
comment in that its contents will not be executed. Docstrings however, are
stored by the interpreter as documentation for a particular file, module,
class, or function.
4"""
5Dragon Realm - A game where the player decides between two caves, which hold
6 either treasure or certain doom.
7 Inspired by: http://inventwithpython.com/invent4thed/chapter5.html
8"""
9
10
Part 1.3: Scope, __main__
and main()
#
Up until now we have been writing all our code in the body of the file. (Aside from a few functions in the PyPet project.) This is what is referred to as the global scope or global namespace.
Scope refers to the place where an identifier (variable or function) can be used. When a variable is defined in the body of the file it is available to everything in the file–globally. When a variable is defined in a function it is only available to the code inside of that function.
It is a good idea to keep the amount of code in the global scope to a minimum. This avoids problems like accidentally reusing the same variable name and causing unintended results.
In order to achieve this, organize code into functions. The convention is to
write a function called main()
and call it when your script is executed.
Note that main()
has a docstring too. This will describe the purpose of the
function.
11def main():
12 """The Dragon Realm Game"""
13 print("Welcome to Dragon Realm!")
14
15
16# this means that if this script is executed, then
17# the main() function will be called
18if __name__ == '__main__':
19 main()
Part 2: Beginning and end#
In this section we’ll add a description of the imaginary game world that will be printed when the player first starts the game.
At the end of the game, we’ll ask the player if they want to continue instead of just exiting.
Part 2.1: Global Variables#
Global variables are called that because they are available to everything in the file. Whereas a variable that is defined inside a function is called a local variable, and it goes away when the function is done executing.
A. Conventions#
You generally want to define them at the top of the file, and name them with
ALL_CAPS
to help you tell them apart from local variables.
MY_GLOBAL = True
B. Add WIDTH
#
The WIDTH
global variable is for the number of horizontal characters we want our
game to take up.
Add the global variable
WIDTH
under the docstring and set it to58
as a starting point. (You can always change it later.)
4"""
5Dragon Realm - A game where the player decides between two caves, which hold
6 either treasure or certain doom.
7 Inspired by: http://inventwithpython.com/invent4thed/chapter5.html
8"""
9
10WIDTH = 58
Part 2.2: intro()#
In this section we’ll write the intro()
function which will print out a
paragraph to the player that describes the players surroundings.
A. Multi-line strings#
You can use the docstring syntax to make a string and use it anywhere you would
normally use a string. This is useful if you have a multi-line string such as
the paragraph we want to print in the intro()
function.
This will retain all whitespace–both newlines and indentation.
print("""
a
b
c
d
"""
)
a
b
c
d
B. Newlines#
The backslash (\
) in a string tells Python that the next character has
special meaning. \n
, for example, is for a new line
print("a\nb\nc\n")
a
b
c
C. Add intro()
#
Let’s use the concepts we just learned to define the intro()
function.
Define the
intro()
functionAdd a docstring describing what the function does
Using docstring syntax, print the intro description copied from below (or make up your own!)
13def intro():
14 """Display the introduction description to the player"""
15 print("""You are in a land full of dragons. In front of you,
16you see two caves. In one cave, the dragon is friendly
17and will share his treasure with you. The other dragon
18is greedy and hungry, and will eat you on sight.\n""")
2.3. Keep playing#
In this section we’ll ask the player if they want to keep playing when the game ends, instead of just exiting.
A. input()
#
To make a program interactive, we need to get feedback from the user. To do this
we call the input()
function and whatever the user types will be returned.
The syntax is:
VAR = input(PROMPT)
In this example, whatever the user typed before hitting enter would be assigned
to the variable response
.
response = input("Are you sure? ")
The user would see something like this:
Are you sure? ▉
Be sure to always add the space at the end of the prompt string, otherwise there will be no space between the text the user sees and their cursor.
We’ll use the input()
function to ask the player if they want to keep
playing.
B. Objects and str.lower()
#
In Python, all values are objects. An object is data that can have values and functions attached to it. An objects values are called attributes and its functions are called methods.
For example, list
objects have a method .count()
which will tell you how
many of a certain thing the list contains.
votes = ["red", "blue", "red", "red", "blue", "blue"]
votes.count("blue")
3
If you want to know what methods or attributes an object has you can use the
dir()
function and pass it either an object or a type. (Just scroll past all
the ones that start with __
for now.)
dir(str)
['__add__',
'__class__',
'__contains__',
'__delattr__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__getitem__',
'__getnewargs__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__iter__',
'__le__',
'__len__',
'__lt__',
'__mod__',
'__mul__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__rmod__',
'__rmul__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'capitalize',
'casefold',
'center',
'count',
'encode',
'endswith',
'expandtabs',
'find',
'format',
'format_map',
'index',
'isalnum',
'isalpha',
'isascii',
'isdecimal',
'isdigit',
'isidentifier',
'islower',
'isnumeric',
'isprintable',
'isspace',
'istitle',
'isupper',
'join',
'ljust',
'lower',
'lstrip',
'maketrans',
'partition',
'replace',
'rfind',
'rindex',
'rjust',
'rpartition',
'rsplit',
'rstrip',
'split',
'splitlines',
'startswith',
'strip',
'swapcase',
'title',
'translate',
'upper',
'zfill']
You can use the help()
function to get more information about a method.
You can use either the object (like "hello"
) or the type (like str
)
followed by a dot and the method name.
help(str.lower)
Help on method_descriptor:
lower(self, /)
Return a copy of the string converted to lowercase.
You can see that str
objects have a .lower()
method. Let’s try it out.
"OH HAI THERE".lower()
'oh hai there'
We’ll use the .lower()
function to change the player’s answer to lower case.
That way we’ll understand if they type "YES"
, "Yes"
or "yes"
.
B. Boolean and Membership Operators#
In programming, sometimes we don’t just want to see if something is the same as something else, but the same as a couple of things.
One way that we could do this would be using the or
boolean operator.
answer = "Y"
answer.lower() == "y" or answer.lower() == "yes"
True
We’re going to learn to use the in
operator to easily check if a value
is a
member of a sequence
.
The syntax is:
<value> in <sequence>
For example:
answer = "Y"
answer.lower() in ["y", "yes"]
True
We’ll use the in
operator to check the player’s answer against multiple
acceptable answers.
C. Repeating strings#
Python provides an easy way to repeat a string using multiplication. Just use
the *
operator to multiply a str
by an int
.
"echo... " * 3
'echo... echo... echo... '
This is a handy way to quickly make a visual line that is whatever width you want.
"~" * 30
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
We’ll use this to print a line at the start of each game.
E. while loops#
Sometimes we want our program to repeat the same steps over and over again. One
way to do this is to use a while
loop which will repeat the same block of
code as long as a certain condition is met.
The syntax is:
while CONDITION:
BODY
|
an expression evaluated in a boolean context that determines if the loop keeps going |
|
statements to execute each iteration |
In this example if the user hits enter before typing anything, they’ll be asked again repeatedly until they do.
answer = ""
while not answer:
answer = input("Your choice: ")
Caution
Beware of infinite loops!
Infinite loops happen when the condition is always met. While not inherently bad, they can wreak havoc on your system when unintended.
This is a simple example of a while True
infinite loop.
import time
while True:
print("this is the song that never ends...")
time.sleep(1)
We’ll use a while loop in main()
to let the user keep playing if they want to.
F. In main()
#
Change your docstring to reflect the way
main()
will work when we’re doneAdd a variable
answer
and set it to"yes"
Add a while loop that will keep going as long as lower cased
answer
matches any of a list of the strings["y", "yes"]
In the while loop:
Print a line by multiplying a dash by
WIDTH
. Add a newline at the endCall the
intro()
functionAsk the user if they want to play again using the
input()
function and assign the result to the variableanswer
21def main():
22 """Keep playing the game until the user doesn't say yes"""
23 print("Welcome to Dragon Realm!")
24 again = "yes"
25 while again.lower() in ["y", "yes"]:
26 print("-" * WIDTH, "\n")
27 intro()
28 again = input("Play again? ")
Part 3: Player, choose()
a Cave#
In this section we will prompt the player to choose a cave, then make sure their response is a valid cave.
Add a global variable CAVES
to the top of your script where WIDTH
is
defined.
1WIDTH = 58
2CAVES = ["right", "left"]
Add the valid_cave()
function
1def valid_cave(response):
2 """Return True if response is in the list of valid CAVES"""
3 return response in CAVES
Add the choose()
function
1def choose():
2 """Prompt the player to choose "right" or "left" then return response."""
3 cave = ""
4 while not valid_cave(cave):
5 print("Do you enter the cave on the right or left?")
6 cave = input("(right, left): ").lower()
7
8 if cave in ["q", "quit", "exit"]:
9 exit()
10
11 if not valid_cave(cave):
12 print('Type "right" or "left". \n')
13
14 print()
15 return cave
Edit your main()
function to add cave = choose()
.
1def main():
2 """Keep playing the game until the user doesn't say yes"""
3 print("Welcome to Dragon Realm!")
4 again = "yes"
5 while again.lower() in ["y", "yes"]:
6 print("-" * WIDTH, "\n")
7 intro()
8 cave = choose()
9 again = input("Play again? ")
Part 3.1: Conditionals Expressions Resolve to Boolean Values#
We have used conditional expressions in if-statements
if a == b:
...
And we have used conditional expressions in while-statements
while a < b:
...
A key think to understand is that a conditional expression always resolves to a
Boolean value, either True
or False
.
>>> 2*2 == 4
True
>>> "5" == str(5)
True
>>> import random
>>> 5 < random.randint(0, 10)
False
>>> 57 in [ range(0, 10) ]
False
That means that we can treat a conditional expression as just another value.
Which is why we can return the result of this conditional in the valid_cave()
function.
return response in CAVES
Part 3.2: Method Chaining#
Since all values are objects in Python, all values may have methods. Method chaining is a way to take advantage of that to write less code.
In this case, since input()
always returns a string, we can call lower()
from the return result of input() by chaining them together with a dot.
# these two lines of code...
cave = input("(right, left): ")
cave = cave.lower()
# have the same result as this one
cave = input("(right, left): ").lower()
Part 4: Enter the Cave#
Now that the player has picked a cave, it’s time to tell them what happens when
they enter it. We’ll add a new enter()
function and use the sleep()
function in the time
module to add a delay between messages.
Above your global variables, import the time
module.
1"""
2Dragon Realm - A game where the player decides between two caves, which hold
3 either treasure or certain doom.
4 Inspired by: http://inventwithpython.com/invent4thed/chapter5.html
5"""
6
7import time
Add a global variable DELAY
1"""
2Dragon Realm - A game where the player decides between two caves, which hold
3 either treasure or certain doom.
4 Inspired by: http://inventwithpython.com/invent4thed/chapter5.html
5"""
6
7import time
8
9WIDTH = 58
10DELAY = 1
11CAVES = ["right", "left"]
Add the enter()
function
1def enter(cave):
2 messages = [
3 "You approach the cave...",
4 "It is dark and spooky...",
5 "A large dragon jumps out in front of you!",
6 "He opens his jaws and...",
7 ]
8
9 for message in messages:
10 print(message)
11 time.sleep(DELAY)
And change your main()
function to call enter()
1def main():
2 """Keep playing the game until the user doesn't say yes"""
3 print("Welcome to Dragon Realm!")
4 again = "yes"
5 while again.lower() in ["y", "yes"]:
6 print("-" * WIDTH, "\n")
7 intro()
8 cave = choose()
9 enter(cave)
10 again = input("Play again? ")
Part 5: Prettier output with describe()
#
It is getting a little hard to tell which lines of the game are description and
which parts are prompts. Lets make that clearer by indenting the text. To do
that we’re going to add a function describe()
which we’ll use to print
anything that is not related to getting input.
Add a describe()
function
1def describe(message):
2 print(" ", message)
Then change your intro()
function to call it instead of print()
.
1def intro():
2 """Display the introduction description to the player"""
3 describe("""You are in a land full of dragons. In front of you,
4you see two caves. In one cave, the dragon is friendly
5and will share his treasure with you. The other dragon
6is greedy and hungry, and will eat you on sight.\n""")
And call describe()
in enter()
1def enter(cave):
2 messages = [
3 "You approach the cave...",
4 "It is dark and spooky...",
5 "A large dragon jumps out in front of you!",
6 "He opens his jaws and...",
7 ]
8
9 for message in messages:
10 describe(message)
11 time.sleep(DELAY)
Part 6: Wrap text using the textwrap
module#
That looks nicer, but the intro looks funky because only the first line is
indented. Let’s fix that by using the textwrap
module’s wrap()
function. It
takes two arguments, the string to wrap, and the width to wrap it to. It
returns a list where each item in the list is one line of the string.
Import the textwrap
module
1"""
2Dragon Realm - A game where the player decides between two caves, which hold
3 either treasure or certain doom.
4 Inspired by: http://inventwithpython.com/invent4thed/chapter5.html
5"""
6
7import time
8import textwrap
Add a global variable WRAP
1"""
2Dragon Realm - A game where the player decides between two caves, which hold
3 either treasure or certain doom.
4 Inspired by: http://inventwithpython.com/invent4thed/chapter5.html
5"""
6
7import time
8import textwrap
9
10WIDTH = 58
11WRAP = 50
12DELAY = 1
13CAVES = ["right", "left"]
Then change your describe()
function
1def describe(message):
2 for line in textwrap.wrap(message, WRAP):
3 print(" ", line)
The wrap()
function strips trailing newlines so we’ll need to change the
intro()
function. Remove the \n
and add a print()
statement to the end of
the function.
1def intro():
2 """Display the introduction description to the player"""
3 describe("""You are in a land full of dragons. In front of you,
4you see two caves. In one cave, the dragon is friendly
5and will share his treasure with you. The other dragon
6is greedy and hungry, and will eat you on sight.""")
7 print()
Part 7: Pick the Friendly Dragon#
Next we need to randomly pick a dragon to be the friendly one.
Import the random
module
1"""
2Dragon Realm - A game where the player decides between two caves, which hold
3 either treasure or certain doom.
4 Inspired by: http://inventwithpython.com/invent4thed/chapter5.html
5"""
6
7import time
8import textwrap
9import random
Add a is_friendly()
function
1def is_friendly(dragon):
2 """Return True if dragon is in the randomly chosen friendly one"""
3 friendly = random.randint(0, 1)
4 print("The friendly dragon is:", CAVES[friendly])
5 return dragon == CAVES[friendly]
Add a line to the end of your enter()
function to save the resulting value
1def enter(cave):
2 messages = [
3 "You approach the cave...",
4 "It is dark and spooky...",
5 "A large dragon jumps out in front of you!",
6 "He opens his jaws and...",
7 ]
8
9 for message in messages:
10 describe(message)
11 time.sleep(DELAY)
12
13 nature = is_friendly(cave)
Part 7.1 Accessing List Elements#
You may recall that dictionaries have keys. Dictionary elements can be accessed by adding square brackets to the end of the variable name containing the key.
>>> car = {
"brand": "Ford",
"model": "Mustang",
"year": 1964
}
>>> print(car["brand"])
Ford
List elements have an index number which always starts at 0
and
increases for each element in the list. List elements can be accessed by their
index number the same way that dictionary elements can. Since the index is
always a number, don’t use quotes.
>>> brands = [ "Ford", "Chevrolet", "Honda" ]
>>> print(brands[1])
Chevrolet
List elements are in the order they are added in, unless changed by the programmer.
>>> letters = [ 3, 2, 1 ]
>>> print(letters[0])
3
>>> letters = [ "a", "b", "z", "c", "d" ]
>>> print(letters[2])
z
The CAVES list defined earlier contains the elements [ "right", "left" ]
, which means that the value of CAVES[0]
is "right"
and the value of
CAVES[1]
is "left"
.
Here we generate a random number between 0
and 1
to use as the index in the
CAVES
list, so CAVES[friendly]
will be either "right"
or "left"
.
Then we compare it to the value of dragon
. dragon == CAVES[friendly]
will
resolve to either True
or False
. That is the value that the function
returns.
friendly = random.randint(0, 1)
print("The friendly dragon is:", CAVES[friendly])
return dragon == CAVES[friendly]
Part 8: The Dragon Acts#
Finally, we’ll tell the player what the dragon does.
Edit Your Script
Add a dragon()
function
1def dragon(is_friendly):
2 """Print the dragon action for a friendly or unfriendly dragon"""
3 actions = {
4 # friendlyness: action
5 True: "Gives you his treasure!",
6 False: "Gobbles you down in one bite!",
7 }
8 print()
9 describe(actions[is_friendly])
10 print()
Add a line to the end of your enter()
function to call it
1def enter(cave):
2 messages = [
3 "You approach the cave...",
4 "It is dark and spooky...",
5 "A large dragon jumps out in front of you!",
6 "He opens his jaws and...",
7 ]
8
9 for message in messages:
10 describe(message)
11 time.sleep(DELAY)
12
13 nature = is_friendly(cave)
14 dragon(nature)
Finally, remove or comment out the print()
line in your is_friendly()
function
1def is_friendly(dragon):
2 """Return True if dragon is in the randomly chosen friendly one"""
3 friendly = random.randint(0, 1)
4 # print("The friendly dragon is:", CAVES[friendly])
5 return dragon == CAVES[friendly]
Part 8.1: Dictionary Keys#
In the past we’ve used strings for dictionary keys, but other types can be other keys too.
Ints and Floats can be keys.
>>> dewey = {
... 610: "Medicine & health",
... 610.3: "Medical encyclopedias",
... 610.6: "Medical organizations & professions",
... 610.72: "Medical research",
... 610.9: "Geography and history of medicine",
... }
>>>
>>> dewey[610]
'Medicine & health'
>>> dewey[610.6]
'Medical organizations & professions'
Booleans can be keys
>>> d = { True: "true", False: "false" }
>>> d[True]
'true'
However, True
and False
are equal to 1
and 0
respectively, so you can’t
mix them.
>>> d = { True: "true", False: "false", 1: "one", 0: "zero" }
>>> d[True]
'one'
>>> d[False]
'zero'
>>> d[1]
'one'
>>> d[0]
'zero'
Here we’re using booleans as keys in the actions
dictionary, then looking
them up using actions[is_friendly]
.
actions = {
# friendlyness: action
True: "Gives you his treasure!",
False: "Gobbles you down in one bite!",
}
We could have written the dictionary like this:
actions = {
"friendly": "Gives you his treasure!",
"unfriendly": "Gobbles you down in one bite!",
}
But then we would have had to add an if-statement based on the boolean value of
the is_friendly
variable.
Make it Your Own#
Change the game to make it your own. Here are some ideas.
Add a third cave in the middle with a silly dragon who does a little jig.
Make a dictionary for each dragon and give them other details like names, colors, sizes, if they breath fire. Print the additional values when you walk in the cave.
If the player gets treasure from the dragon, add another level to the game. Perhaps the player encounters a well and can either make a wish with coin, or take a drink. Randomly decide if the wish is granted, or if the water is poisoned.
Add a rarely occurring event that may sometimes take place instead of the usual friendly/unfriendly actions. Perhaps the dragon transforms into a toad, or it falls in love with you. Calculate it by using some datetime information (like, it only occurs on Friday the 13th or after midnight on odd numbered days) combined with some randomness.
What We’ve Learned#
The shebang
Docstrings as documentation
Scope,
main()
, and global variablesDocstrings as multi-line strings
Objects
str.lower()
,time.sleep()
andtextwrap.wrap()
Repeating strings with
*
Escape characters and
\n
Method chaining
List indexes and more dictionary key types
Conditional expressions evaluate to boolean values
The
not
,in
andor
operators