I think the next step is to write the code for a single round of the game. We’ll limit ourselves to Scene 1 to stay focussed.
Presenting a Scene to the user
First you want to show the description. Start a new Python file in your favorite editor, or in IDLE with the menu command “File” -> “New”.
\ character at the end of lines 6 and 7 tells Python the string is continuing to the next line.
Python would complain at us if we left that out. Let’s run the script and see what we get.
Save your new file as
ifiction.py and run it. Press the
F5 key to run the script if you are using IDLE.
$ python ifiction.py You are standing in a field. To the north of you are some mountains, to the ea st of you is a forest, to the west of you is a cave, and to the south of you i s a valley.
This may not be happening for you, but when I run the script the description text gets cut off at inconvenient points. What happens next is a little more advanced than I was planning to show you, but that is just going to bug me too much if I don’t fix it now.
Wrapping text with the
One of Python’s charms is the fact that it has a
huge standard library. This means
that a lot of things you would like to do have already been written and included
for free. That’s why some folks say that Python comes with “batteries included.”
The standard library is a collection of modules with useful features and functions. I
am just concerned with the
fill function from the
module right now, because I want the text of the description wrapped so that no words
get cut off.
You aren’t automatically carrying all of those libraries around in your script, though.
You need to use the
import command to make the functions in a library available to
Notice that I had to use the module name as part of my call to
fill. That is because
Python needs to know where it can find the
fill function, and for library functions
it uses modulename followed by a dot (.) and then the function. You will be seeing
a lot more of the dot operator as your Python knowledge expands.
There are ways to import the function in a way which removes that requirement, but for now I will stick with the more explicit version because it is easier for us to know where we found the function.
See what it looks like now?
$ python ifiction.py You are standing in a field. To the north of you are some mountains, to the east of you is a forest, to the west of you is a cave, and to the south of you is a valley.
Of course, if the wrapping text isn’t an issue for you, feel free to leave out the
textwrap related code completely.
Back to the game: paths
Now for the paths. We could just print the paths and make the user type in the full path to go anywhere, but that would be unkind. What we want is an easy way to show the list of paths and say “You picked path #1: Go to the mountains”.
Python’s list type is the perfect way to do this. A Python list is a collection of values - they could be literals or other variables - that holds each value in order. This lets you ask for the third item in a list, or ask for each value in order. Let’s start by creating and displaying the paths.
What does it look like now?
$ python ifiction.py You are standing in a field. To the north of you are some mountains, to the east of you is a forest, to the west of you is a cave, and to the south of you is a valley. Go to the mountains Go into the forest Go into the cave Go to the valley
I want to do a bit of formatting to break things up, but you get the idea of what
is going on. We have created a variable called
paths to hold our list of paths.
What can I say? I like my variable names to be obvious. You can recognize a list
by the square brackets
. Items in the list are separated by commas. I like to
put each list item on a line by itself, using indentation to show that we are looking
inside the list. Little things like this make your code easier to read, which gets
very important as your program grows.
Okay, I need to take the next few ideas slowly, because I have put a lot of important new concepts in two lines of code.
for is one of Python’s looping control structures.
for path in paths:
is going step to through each item in the list
paths. That part is straightforward.
Another nice thing about Python code is that you can usually tell what’s going on just by looking at it.
It also creates a variable called
path which will hold the value of the current item in the list.
The first time through the list,
path is set to “Go to the mountains”, the second time through
path is set to “Go into the forest”, and so on.
What is Python going to do with
path? That is decided in the indented line after the colon
I kept it simple for now. All we do is print this path to the screen and move on. Do you see the extra indentation, though? Python uses indentation to know what code is supposed to be executed within the loop. If I try to describe it, I’m just going to make things more confusing than they really are. Just remember this:
- One or more lines at the same indent level are often called a “block”
- Everything right under the
forstatement which is indented belongs to the
- If you indent another level, it better be because you’re starting a new block
- If you unindent, it better be because you are done with the loop block.
An indent without a control statement is an error in Python, except for special
cases like the way I defined
paths. It doesn’t really matter
how much you indent for a block, but it must be consistent throughout your program.
The common standard suggested by Python creator Guido van Rossum is four spaces per
block, with no tab characters used.
for <item> in <list> is useful, and it will probably be the most common tool
in your kit for examining every item in a list. It doesn’t quite work for our
exact needs today, though. We want to build a menu with an easy value for the user
to enter. This will use the
range() function combined with the
 operator for
accessing list members.
range is a simple function which returns a list of whole numbers in a certain range.
It normally takes a single argument: the upper edge of the range. All the numbers in
the list will be less than the upper edge. Here is a simple example:
>>> range(3) [0, 1, 2]
range can take additional arguments to set the starting number and the step size,
but this is all we want for now. Oh, notice that the numbering starts at zero. This
is going to be very useful, for reasons which will become clear in a few moments.
I have shown you how to look at a complete list, as well as how to look at each item
in a list one at a time. How do you look at a single item in the list? You use
What do they call that, anyways? I can’t really tell from the Python docs. Let’s call
it the indexing operation. Why “operation?” Because we’re doing something, but not
with a function. Why “indexing?” Because we will be using a specific value to get at
the item, sort of like using the index or table of contents in a book.
So let’s try it out. It is easy to use the indexing operation. Add the left bracket, the index number of the item you want, and then the right bracket. Go back to the Python shell and try it out for yourself, getting the value at index 1 of a list:
>>> items = [ 'apples', 'chocolate bugs', 'bananas'] >>> items 'chocolate bugs'
That was a little confusing, wasn’t it? We were expecting apples but got chocolate bugs instead You would think that the index would be easy: the first item would be at index 1, the second at index 2, and so on. Unfortunately, that’s not the way indexes work in Python. Numbering starts with the first item being zero.
>>> items 'apples'
Zero-based indexing is one of those language features that’s there for historical reasons.
It made perfect sense a long time ago in another language, but now it just serves to
confuse newcomers and create a lot of “off-by-one” errors. You may want to use a mental
trick for reducing confusion: think of the index as the distance from the first item.
The first item is the first item, so the distance is zero:
items. The next item
is one away from the first item, so the distance is one:
items. And so on.
Or you could just subtract one from the number you’re thinking of and get on with it. Things that are shortcuts for me could just be useless clutter for you. I’m happy as long as you remember that list index numbers start at zero.
So where do the index numbers stop? You could count the items in the list code by
hand and work with that number, but that is far too much work. Use Python’s built-in
len is blissfully simple. You hand it a list, and it tells you
how many items are in the list. Try it yourself if you still have that shell open:
>>> len(items) 3
There are three items in the list, so the index starts at zero and ends at two.
works perfectly with
range, which hands you a list of numbers starting at zero and
ending at one less than the upper edge. Back to the shell, where we’ll step through
our list using ’len’, ‘range’, and list indexing.
>>> for index in range(len(items)): ... print index, items[index] ... 0 apples 1 chocolate bugs 2 bananas
Oh, I didn’t mention blocks in the shell, did I? When Python thinks you are in a block,
... instead of
>>>. Indent by hitting spaces (or I just use the tab key
when I’m in the shell). Hitting Enter on a line with no code tells the Python shell
that you are done with the block and it’s time to execute.
Just so you know - indexing operation is just something I came up with after looking
around on the Web a little bit. That is not the official name for
, and I’ll be
updating this section as soon as I find out what that name is.
This has been a quick crash course through list handling in Python. Let’s apply what we’ve learned about lists to our interactive story.
i + 1 instead of
i because … well, Python may count from zero but most
people count from one. We’re writing this for people, not for Python.
We didn’t really need to set up each
menu_item as a variable, but
I thought it would make things easier to read than
print "\t", i+1, paths[i].
You want to aim for readabality when you are starting out or you will quickly become
lost. Actually, it’s a good idea to aim for readability all the way through your
programming life. It will make your code easier to maintain. Besides, putting these
values in a variable leaves room for us to change our mind about
are constructed in the future.
Run the script and see what you get.
You are standing in a field. To the north of you are some mountains, to the east of you is a forest, to the west of you is a cave, and to the south of you is a valley. 1 Go to the mountains 2 Go into the forest 3 Go into the cave 4 Go to the valley
It’s starting to look like something! Now go take a break for a minute. I threw a lot of information at you all at once, and you may still need to process it. You at least need to look at something besides a computer monitor for a few seconds and shake your fingers loose. It’s good for you.
Getting the user’s selection
I am pleased that we have the scene description code working, but user input is still missing.
All we need is
raw_input, which we encountered in the initial Python Babysteps tutorial.
Add a line to get user input and another line to show the result.
Running this code is very exciting indeed:
You are standing in a field. To the north of you are some mountains, to the east of you is a forest, to the west of you is a cave, and to the south of you is a valley. 1 Go to the mountains 2 Go into the forest 3 Go into the cave 4 Go to the valley Make a selection: 3 Choice 3 - Go into the cave
Quitting the game
Our specification mentioned that users may quit the game at any point, so we should add the code to make that possible.
Normal choices are numbers and they start at one, so let’s take the easy way out and say that zero quits the game. The
if selection control structure can be used to recognize the quit command.
A little note about being careful. I spent five minutes debugging the above code. I kept getting
Choice 3 Go into the cave, for every non-zero
choice I entered. Turns out that I had cut and pasted some debugging code from earlier which was using the
i was last set to
so that’s what Python kept printing for me. It can be very easy to get distracted while writing code, and although Python can catch a lot of
errors, you must keep an eye out for little mistakes like that. Once I replace
choice in the last line, everything was happy.
Now, why did I use the
int function on the user input? Keyboard input comes to you in the form of a String, which is a different
type than numbers. If we want to be able to use the input as an index for the
paths list, we need a way to
turn that String into an integer, or whole number. This is exactly what
int does. What happens when the user entry can’t be turned
into a number? That’s part of the next topic.
Ensuring valid choices
User input needs to be in the form of a number. Not only that, but that number needs to be a valid index for
paths. If either of these
turns out to be false, Python panics. Let’s explore this in the shell. As a special treat, I’ll show you a glimpse at making functions
>>> def get_index(): ... list = [ 10, 20, 30 ] ... prompt = "Pick a number (0 - 2): " ... index = int( raw_input(prompt) ) ... print list[index] ... >>>
If you make a mistake, hit Enter twice to end the function definition and start over. Don’t forget your indentation! Incidentally, I chose to put the prompt in its own variable because all of those parentheses on the same line were making me a little dizzy.
get_index with a valid number first.
>>> get_index() Pick a number (0 - 2): 0 10
What happens when you enter a number that’s too big? Try it and see.
>>> get_index() Pick a number (0 - 2): 20 Traceback (most recent call last): File "<stdin>", line 1, in ? File "<stdin>", line 5, in get_index IndexError: list index out of range
How about when you enter something that’s not a number again? Once again, TIAS (Try It And See).
>>> get_index() Pick a number (0 - 2): banana Traceback (most recent call last): File "<stdin>", line 1, in ? File "<stdin>", line 4, in get_index ValueError: invalid literal for int(): banana
Python raises an exception at you whenever it encounters a situation it can’t handle on its own.
An exception is a special type of object that is specially used for errors, accidents, or plain old
weird events in your program. You can plan for them, though, and try to handle them when they happen.
That is going to require a new kind of block: The
try block. This sort of block
is easier to understand in the context of a full program, so let’s go back to
ifiction.py. We’ll start with
catching every kind of exception in one go:
try block usually has at least 2 parts:
try: The code we think might cause an exception to be raise
- One or more
exceptblocks: tells Python what to do when an exception is encountered
We can specify what exceptions we would expect to see, but that is more than I
want to look at right now. We’ll just put up a single
except block that will
catch every exception raised within the
try block. We can test the code by
entering a number that is too large.
You are standing in a field. To the north of you are some mountains, to the east of you is a forest, to the west of you is a cave, and to the south of you is a valley. 1 Go to the mountains 2 Go into the forest 3 Go into the cave 4 Go to the valley (0 Quit) Make a selection: 20 Choice 20 - 20 is not a valid selection!
This is odd-looking. Our error message prints at exactly the spot where the
exceptional situation was encountered: trying to access
paths. Well, at
least I’ve illustrated exactly how dynamic Python is. It doesn’t look ahead to
see if something bad is going to happen, so it has to trust us to know when to
look for an error. I think it would be a little cleaner to put the choice
description in its own variable before printing it. That way we get an
exception before we try to give normal feedback to the user.
Our code is starting to get a little long to show the whole thing for every
little change, so I have decided to focus on the chunk of code that is being
modified. Anyways, you can see that I have made a new
Python will raise the exception about bad indexing here, instead of in the
middle of printing out feedback. We have also made the code more readable in
the process, which is a nice thing.
You are standing in a field. To the north of you are some mountains, to the east of you is a forest, to the west of you is a cave, and to the south of you is a valley. 1 Go to the mountains 2 Go into the forest 3 Go into the cave 4 Go to the valley (0 Quit) Make a selection: 20 20 is not a valid selection!
Our input code handles bad indexes. Test the code again by entering a non-number for
You are standing in a field. To the north of you are some mountains, to the east of you is a forest, to the west of you is a cave, and to the south of you is a valley. 1 Go to the mountains 2 Go into the forest 3 Go into the cave 4 Go to the valley (0 Quit) Make a selection: steak and eggs Traceback (most recent call last): File "ifiction.py", line 35, in ? print choice, "is not a valid selection!" NameError: name 'choice' is not defined
Oops. I raised a whole new exception because
choice isn’t defined until after
it’s converted to a number, but I referred to it in the
except block. Another
intermediate variable will save us from that error.
There are several very small changes here.
choice now refers only to the raw
user input, and we have created a new variable
menu_selection to hold the
choice converted to an integer. This means we had to adjust the variable names
where we were really talking about the number the user provided and not the
keystrokes. You might have noticed that I created an
index variable in the
else block. This is a personal taste thing. I often start out using a raw
expression and later replace it with a variable when I think it would make my
meaning clearer. The “start sloppy and refine as you go” approach happens to
work for me, but use whatever tactic you are most comfortable with.
Make a selection: an iced coffee would good right now an iced coffee would good right now is not a valid selection!
Right. We can recognize bad input from the user. What do we want to do about it?
The best approach may be to continue asking the user for input until we get something
acceptable for the next step that she wants to take. We can use a simple
control structure to handle this.
Now we’ve given our user endless opportunities to enter a valid choice.
Make a selection: Rad Rad is not a valid selection! Make a selection: 42 42 is not a valid selection! Make a selection: 0 You decided to: quit
What is going on here? We have created a variable
next_step and assigned it
the value of ….
None? We are going to be using
next_step in the test
condition of our loop, and Python will complain to us if the variable isn’t
defined before we start testing its value. Using the
None value is more
convenient than arbitrarily declaring a particular value to be invalid and
None is a special value meaning “nothing at all” - not even the
numeric value of zero. Think of the statement
next_step = None as our way
to tell Python “I plan on using a variable called
next_step but I don’t have
a value for it yet. Just remember that I told you I wanted the variable.”
Now for the
while loop. We specify a condition here, similar to the way we
if earlier. The condition is that
next_step must not be
It is an easy enough requirement. The test will fail if we successfully assign
next_step in the loop.
Catching specific exceptions
There is one more minor issue to take care of before we wrap up this stage of
writing the game. It is good that we are handling exceptions raised from user
input, but we are catching every exception that is raised. This doesn’t
sound like a bad thing until you remember that our error message is really
written for a specific kind of error: the user entered something that can’t be
used by our menu handler. There are many
that can go wrong in a Python code. We don’t want to be handling exceptions
that we aren’t ready for. Why not? The error messages won’t make sense, for
starters. Say you decided to hit
Control-C in the program to force quit.
Here’s what we end up seeing:
Make a selection: ^CTraceback (most recent call last): File "ifiction.py", line 40, in ? print choice, "is not a valid selection!" NameError: name 'choice' is not defined
The problem is that the original exception was a
KeyboardInterrupt. We don’t
see that here, because we referred to
choice which isn’t defined until the
user provides some input. This causes a
NameError to be raised, which hides
the original exception. Python usually tells you only about the most recent
exception that happened. If something truly unexpected happens here, we will
never know about it. Python normally tells you about the most recent exception
We caused a
KeyboardInterrupt by hitting
Control-C. Python sees our
except block and hands the
KeyboardInterrupt exception to that
block. Inside the block, we try to include the menu choice in our error message.
choice hasn’t been defined yet. We never entered a choice!
This is a whole new exception, and we don’t have any code to handle it.
Python’s rule is to always stop and inform you of the first unhandled
exception it encounters. and because of the way we defined and wrote our
except block, every exception is treated like bad input - from
to a missing hard drive. As far as Python is concerned, we’ve handled the
NameError caused by
choice in the exception
handler is the surprise that makes Python panic.
That is enough of a lecture. I am sure you understand by now that we want to be specific about what exceptions we are ready for. It is time to make it happen. The two exceptions that we care about are:
- we tried to use a bad number as a list index
- the user input couldn’t be converted to a number
Let’s add a handler for IndexError.
This change now handles
IndexError just fine. Look at what happens when we hit
Make a selection: ^CTraceback (most recent call last): File "ifiction.py", line 30, in ? choice = raw_input("Make a selection: ") KeyboardInterrupt
Of course, we’re only looking for
IndexError, so look what happens right now
if we enter something that isn’t a number.
Make a selection: mmm coffee Traceback (most recent call last): File "ifiction.py", line 31, in ? menu_selection = int(choice) ValueError: invalid literal for int(): mmm coffee
Now we add the code to handle
Yes, Python can handle multiple
except blocks just fine. This can be very
handy. A calculator program would want to handle “no input” differently from
“user tried to divide by zero.” What does the program look like with these changes?
Make a selection: 12 12 is not a valid selection! Make a selection: Can I have a banana? Can I have a banana? is not a valid selection! Make a selection: ^CTraceback (most recent call last): File "ifiction.py", line 30, in ? choice = raw_input("Make a selection: ") KeyboardInterrupt
Wonderful! Our exception handling code is now behaving politely instead of
trying to grab every exception that occurs. Now for the style issue. Both of
except blocks do exactly the same thing. I suppose we could have a
slightly different error message for each kind of exception, and you are free
to do exactly that. It’s not something I’m concerned about, though. I am
comfortable with using the same error message. I would prefer to cut down on
I would like to look again at this code in the future, but for now it is good enough.
We can handle multiple exceptions in the same
except block by placing the
exception types in a special list called a tuple. I am not going to spend
any time on tuples, because I worry that it would only confuse things. All you
need to remember right now is that a tuple looks like an ordinary list using
 to wrap it, and that you should use normal lists unless I tell
It looks like we have all the code we need for handling a single round in our game. I had to cover more new concepts than I thought, because
things can become complicated when we start doing things with user input. We dabbled into importing modules thanks to the way
things were printing out in my shell. We looked at the common control structures for selection and repetition, and we
try for trying out code that we know can misbehave. Here’s the full source of what we’ve done so far, along with a couple
of additional comments intended to clarify what the program is doing.
Now go take a break. I really mean it this time. We have covered a lot, and you need time to process. Listen to some music, have a sandwich, and come back when you’re ready.