Have you noticed how, so far, our programs aren’t very, well, interesting? That is, all our code has strictly been a set of statements the interpreter evaluates from top to bottom—no twists in the plot, no sudden turns, no surprises, no independent thinking. For code to be more interesting, it needs to make decisions, to control its own destiny, and to do things more than once straight through. And in this chapter that’s exactly what we’re going to learn to do. Along the way we’ll learn about the mysterious game called shoushiling, meet a character named Boole, and see how a data type with only two values could be worth our time. We’re even going to learn how to deal with the dreaded infinite loop. Let’s get started!
We may even create one just for the fun of it!
Passed down from the ancient Chinese Han dynasty, the game shoushiling has been used to settle court case decisions, to decide multimillion-dollar deals, and perhaps most importantly, to determine who gets to sit in the front seat of the car.
Today you know the game as Rock, Paper, Scissors, and we’re going to implement it so that you can play the game against a rather tough opponent: your computer.
If you’re never heard of Rock, Paper, Scissors we’re going to go over its simple rules now; and if you do know the game, this will be a good review for you. The game is played by two players, who each, upfront, secretly choose either rock, paper, or scissors. The two players then typically count out loud to three (or shout “rock-paper-scissors!”) and then show their choice through their hand position, which is either, you guessed it, a rock, paper, or scissors. The winner can be determined by this chart:
Given the computer doesn’t have hands, we’ll have to change the way the game works, at least a little—what we’ll do is have the computer preselect its choice of rock, paper, or scissors, but not tell us. We’ll then enter our choice, and the computer will compare the two before revealing the winner.
It helps to see an example of how the game is going to be played. Below in the Python Shell you’ll find a few rounds of Rock, Paper, Scissors being played that show each possible outcome: the user wins, the computer wins, and a draw.
The first thing we need to do is figure out the flow of the game. Let’s put our pseudocode skills to use by reviewing this high-level design for the game. Notice we’ve added something new this time too: a diagram that helps map out the flow of the game in the form of a flowchart.
Here’s the basic idea:
User starts the game.
The computer determines what its choice is going to be: rock, paper, or scissors.
Game play begins.
Get the user’s choice.
Examine the user’s choice. If it is invalid (not rock, paper, or scissors), go back to step 2A.
Determine who wins by the rules of the game.
If it is the same as the computer’s, set the winner to a tie and move on to step 3.
Tell the user who won along with what the computer’s choice was.
Now we have a high-level idea of the kinds of things the program needs to do. Next we’ll dig into each step and figure out a few more details.
Looking at our high-level design, the first thing we need to do is have the computer make its choice in the game; that is, it needs to choose rock, paper, or scissors. To make the game interesting, that choice should be random and not predictable by the user.
Making random choices is a task many programs need to perform, and you’ll find practically every programming language provides a way to generate random numbers. Let’s see how we can get a random number in Python, and how we turn that into a choice of rock, paper, or scissors.
Python ships with a lot of prebuilt code—that is, code you don’t have to write yourself. You’ll often find prebuilt code supplied in the form of a module (sometimes called a library), and we’ll be discussing modules in detail later in the book. But for now, we’d like to use the
random module, and to do that we import it into our code using Python’s
Here’s how we do that:
After you’ve imported the
random module, you’re all ready to make use of the many random functions it provides. We’re going to use just one of them for now:
We’re going to dive into all the specifics of this notation later in the book, but for now, just take it all in.
Alright, we know how to generate a random number of either
2, and we’re going to use that number to represent the computer’s game choice. So, if we generate a
0, then the computer’s choice is rock; if it’s a
1, it will be paper; and if it’s
2, the choice is scissors. Let’s write that code:
Q: How are random numbers going to help?
A: Think of generating a random number like throwing the dice. In this case we have three choices (rock, paper, scissors), so generating a random number of 0, 1, or 2 is sort of like having a dice with three sides. Once we generate a random number, we’ll then associate that number with our choices, so 0 = rock, 1 = paper, and 2 = scissors.
Q: Why do you start with 0 for the random numbers? Why not generate the numbers 1, 2, and 3? That makes more sense.
A: Ah, not to a computer scientist. Programmers usually think of sequences of numbers starting at zero. This will start to feel more natural (and sensical) as you see it used in a variety of ways in code. For now, just go with the flow.
Q: Are random numbers truly random?
A: No, random numbers generated by a digital computer are pseudorandom, meaning not truly random. Pseudorandom numbers, at some level, have patterns that are predictable, whereas truly random numbers do not. To generate true random numbers, we have to make use of natural phenomena like radioactive decay—not a very convenient method for everyday use. For most programming applications, though, pseudorandom numbers are generally sufficient.
Q: So import gives me a way to access Python code written by someone else?
A: Python developers take useful code and make it available in modules. By using import, you can make use of their code and use it along with your own. For instance, the random module includes many functions you can use to generate random numbers. Right now we’re just making use of the randint function, but we’ll be seeing more of this module as the book progresses.
By using the random module we’ve now implemented a way for the computer to randomly make its choice, but it’s a little unsatisfying. Why? Well, our goal was to have the computer choose rock, paper, or scissors, and we’ve done that by mapping those choices to the integers
2, but wouldn’t it be nicer if we had a variable that was set to a string
"scissors" instead? Let’s make that happen. But to do that we’re going to have to step back and learn about how to make decisions in Python.
Python makes decisions by asking questions with yes or no answers, only in Python we call those true or false answers. The questions themselves are just expressions, similar to the expressions you’ve already learned, but instead of evaluating to strings or integers or floating-point numbers, they evaluate to
False. Here’s an example:
You can assign the result of this expression to a variable as well, and you can even print it if you want.
False belong to their own data type, the Boolean type. Let’s take a look at it...
Oh, forgive us, we’ve been talking about a brand new data type, but we haven’t formally introduced you. The Boolean data type is a simple one; it has only two values, and, as you can guess, they are
You can treat Booleans like any other type in that you can store them in a variable, print them, or use them in expressions. Let’s get some practice in with them, and then we’re going to see how to use them to make decisions.
Now that we know about Boolean expressions and relational operators, like > and < and ==, we can use them to make decisions in code. To do that we use the
if keyword combined with a Boolean expression. Here’s an example:
But we don’t have to stop there: we can supply an alternative set of statements to execute if the conditional expression is False.
But there’s more: we can even set up a whole series of conditions, by using the
elif keyword. Admittedly,
elif is a strange keyword, but it’s just a contraction of “else if,” so don’t let it throw you. Let’s see how
We’re still finishing up the first stage of our Rock, Paper, Scissors game. Remember, before our Boolean diversion, we wanted to improve our code so that the computer could pick a string,
"scissors", instead of a number
2. Now that you’ve learned how to use Python’s if statement, you’re all set to do that. What we’re going to do now is write some code that, depending on the value of
random_choice, sets a new variable,
computer_choice, to one of those three strings.
Now that we have the computer’s choice, it’s time to get the user’s choice. After Chapter 3, you’re a pro at getting user input. Let’s start by prompting the user and storing the response in a variable called
We said there were a lot of other useful functions in the
random module, and there are. One of those is the function
choice, and here’s how it works:
So now that you mention it, this is exactly what we’d want to use if we were to implement this again because this approach uses less code and is more readable. That said, if we’d used this from the beginning of this chapter, we would have had no reason to talk about decisions, or Boolean values, or relational operators, or conditionals, or data types...well, you see the point.
But we’re glad you asked because
choice is a great function to use to do just this kind of thing, especially after the next chapter when you totally understand lists.
NOTE: for those Type A’s out there dying to update their code to use random.choice, go for it, if you must. All you need to do is replace every line between the import and the input statement with the code above. That said, this isn’t necessary at this point, but you know how you are, so we’re letting you know.
Now that we’ve got the user’s choice, let’s examine it. According to our flowchart, we’ve got three possibilities:
|The user and the computer made the same choice, and we have a tie.|
|The user and the computer made different choices, and we need to determine who won.|
|The user entered an invalid choice, and needs to enter another choice.|
This possibility is where the user enters a word that isn’t rock, paper, or scissors; we’re going to come back and handle this case a bit later.
We’ll tackle these in order (saving the last one for a bit later in the chapter), but first, let’s observe that no matter who wins, or if there is a tie, we need some kind of variable to hold that information. So let’s use a variable, winner, that will hold the outcome of the game, which will be either
‘Computer’. Create that variable and give it an initial value like this:
Here we’ve assigned the empty string to the new variable winner as an initial value. An empty string is a string that has no characters in it (yet it’s still a string). You might think of it like this: a laundry basket is still a laundry basket even if it currently has no laundry in it. Right? You’ll find this kind of thing pops up all over programming languages: empty strings, empty lists, empty files, and so on. For us, setting
winner to an empty string gives us a way to indicate that
winner is going to be a string, even if we’re not yet in a position to put any meaningful characters in it (because we haven’t computed the outcome).
Although there’s nothing wrong with this approach, later in the book we’ll see an alternative, that, for Python, is a better way to provide an initial value for winner.
Now that we’ve created the
winner variable to hold the outcome of the game, let’s proceed with implementing the possibilities at the top of the page. Looking at the first item above, where the user and the computer make the same choice, we’ll need to set
‘Tie’. To do that, we need to first write the code to compare the user’s and computer’s choices, and, again, if they are the same, then we’ll set our new
winner variable to
We’ve got a new variable to add,
winner, and we’ve got some new code that compares the user’s and computer’s choice to see if they are the same, in which case we have a tie. Let’s take a look at all the code together:
So now that we’ve written the code to deal with a tie, we’re ready for the interesting part of the code: figuring out who won. We already have everything we need to decide a winner—we’ve got the computer’s choice in the variable
computer_choice, and we’ve got the user’s choice in the variable
user_choice. So what we need at this point is to figure out the logic of determining who won. To do that it really pays to study our Rock, Paper, Scissors diagram to see if we can break the process of determining the winner down into a simple set of rules. Here’s another insight too: if we pick a side, say, by figuring out the ways the computer can win, then we know if the computer doesn’t win, the user does. That can really simplify our logic because we only need to look at one set of cases.
So with that in mind, let’s take a look at all the cases where the computer wins:
Let’s step through this in more detail. We’ve already determined if there is a tie, so we can rule out that case. So, assuming there isn’t a tie, who won? The computer or the user? Well, let’s start by looking at the cases where the computer wins:
The computer wins if it chooses paper and the user chooses rock.
The computer wins if it chooses rock and the user chooses scissors.
The computer wins if it chooses scissors and the user chooses paper.
In all other cases the computer does not win.
So what about the user? What are the cases where the user wins? Well, we could enumerate those just like we did with the computer, but do we really need to? We’ve assumed there isn’t a tie (and our code already takes care of that), and we’ve gone through the cases where the computer is going to win, and if none of those applies, guess what: the user wins. So we don’t need to write any code to determine if the user wins, we just need to know the computer doesn’t win in order to call the user a winner.
Let’s work through the actual logic to bring this all together.
As you can see there are three ways the computer can win, and for each way we have to test two conditions, like “did the computer choose paper?” AND “did the user choose rock?” But, so far, in our coding, we’ve never had to test two conditions at once. That said, we do know how to test for a single condition, like, if the computer chose paper:
And if the user chose rock:
But how do we test for both conditions?
To do that we can use a Boolean operator. It sounds fancy, but Boolean operators are just a way to combine Boolean expressions together, and, for now, there are only three of them to know about:
We’ll see one additional Boolean operator in a bit.
To test if the computer chose paper AND the user chose rock, we can use the and Boolean operator and combine our expressions, like this:
And we can use this Boolean expression with an if statement:
As you’ve already seen, the and operator is
True if, and only if, both of its conditions (or we can call them operands) are
True. But what about
not; how do they work? Like
or is used to combine two Boolean values or expressions, and is True if either of those values or expressions evaluates to
The not operator, on the other hand, when placed before any single Boolean value or expression, gives you the opposite of the Boolean value—in other words, if not’s operand evaluates to
True then not evaluates to
False, and if its operand is False then not evaluates to
True. We like to say that not negates its operand.
Now it’s time to display the winner. If you look at the sample output again, either the user or the computer wins, or there’s a tie.
Let’s first take care of the code to handle the tie. Looking at our existing code, if there’s a tie then the winner variable will be assigned to the value ‘Tie’. So, let’s set up a condition for this case:
If there isn’t a tie, we need to announce the winner, which is conveniently stored in the winner variable.
It’s a good time to step back and look at all the code you’ve written. There’s actually enough code that if you revisited it in the future you might have to remind yourself of what each piece does and how it all fits together. You might also have to study the code to remember the design decisions you made and why you made them.
Also notice that the code has an inherent structure and is pretty well organized in that it’s divided into pieces that handle the parts of our algorithm (or the actions in the corresponding flowchart). Let’s mark these sections and also add some notes to remind us in the future of how all this works.
It’s also handy to document your code for anyone else who might want to take a look at it, like another programmer.
But isn’t this silly that we’re documenting this code in a book? After all, you’ve got real, live code on your computer. Why don’t we document the actual code so the documentation is right there when you need it? Let’s see how to do that.
Comments are one form of documentation; later in the book we’ll look at help documentation, which is meant for coders who just want to use your code, not necessarily understand it.
With Python, and pretty much any programming language, you can add human-readable comments right into your code. To add a comment with Python, type a hash character (#) on any line, and then your comment. Python will conveniently ignore anything you type after the hash. With comments, the general idea is to add remarks to your code that are going to be read by you, or other programmers, with the goal of providing additional information about the design, structure, or approach you used in the code. Let’s look at an example:
You realize that we haven’t quite finished our game, right? Check out the To Do list: we haven’t dealt with that possibility of invalid user input. Now the user is supposed to enter “rock” or “scissors” or “paper,” but they might not; they might mistype, like “scisors,” or they might just be troublemakers who decide to enter “dog,” “hammer,” or “no.” So, when you’re creating an app or program that’s going to be used by actual people, you want to keep in mind that they often make mistakes, and your code needs to deal with that.
So, let’s deal with it.
But first we have to figure out how we want the game to behave when the user enters an invalid answer. Looking back at the flowchart, our original intent was to have the program reprompt the user if the input was invalid.
Perhaps something like this:
Users often make mistakes. Make sure your code anticipates and handles these mistakes—even if the only user is you.
We can always make it more elaborate later, but for now we’ll just reprompt the user until we get a valid input.
Are you ready to get this coded and finish this game? We just need to make sure we know how to approach coding two aspects of this:
How do we detect invalid input?
How do we continually prompt the user until we get a valid answer?
How do we detect if the user’s input is invalid? Well, you probably know we’re going to make use of our new Boolean logic skills, but what does an expression that detects invalid answers look like? Sometimes it’s good to just talk things out: we know if the user’s choice is invalid if:
Hopefully your Boolean expression in the last Sharpen exercise was close to our solution. Here it is again, this time as part of an if statement:
That looks like a perfectly acceptable statement. But sometimes really long lines like this are quite unwieldy once we start typing them into an editor, or if we have to go back and read them later. It would be nice if we could reformat the statement a bit and make it look more like:
The only problem is when we try to break the code into more than one line, Python complains about our syntax.
There is another way—we can wrap a set of parentheses around the expression, like this:
And Python is just fine with the reformatting of the code.
Okay, now that we know how to detect an invalid user choice, we still need to figure out how to reprompt the user. Let’s spend a little time thinking through how that might work....
No, but we could, and you bring up a good point. First of all, what is the issue here? Well, the strings
‘ROCK’, for example, are different strings because Python treats strings as case sensitive. In other words, in Python (and almost every programming language), the following equality test would evaluate to
So if the user enters
Rock instead of
rock, right now our code would say that entry was invalid (and our logic code, for that matter, wouldn’t know what to do with
That said, the suggestion does seem very reasonable—after all, if you enter the word
rock no matter the capitalization, it should count as a valid answer.
So what do we do? Well, we could just add in additional logic to test all permutations of upper- and lowercase letters for the words
scissors, and that would work. However, it would make our code very complex, and there are better ways to approach this problem that we’re going to learn about later in the book.
But right now, let’s just assume that the user needs to enter an answer in lowercase, and we’ll point out how this could have been more easily solved when we get to it later in the book.
Our first attempt failed. We tried to test the user input and then if it wasn’t valid, prompt again. The problem is, this solution only works once. If the user enters “rocknroll” on the second try, then that string will be accepted as the valid user input.
Now we could keep adding
if statements for a second and third and fourth try, but that would lead to a coding mess, and our requirements are to reprompt the user as many times as it takes.
The problem is, given our Python knowledge, we only know how to do things once. What we really need to be able to do is write code in a way that it can repeat over and over, as many times as needed. We need a way to do things more than once.
You do a lot of things more than once:
Lather, rinse, repeat…
Wax on, wax off…
Keep turning the pages of the book, until it’s done.
Of course you’ll often need to do things in code more than once, and Python gives you a couple of ways to repeatedly execute code in a loop using its
while and for statements. We’ll look at both of these ways of looping, but let’s focus on
while for now.
We’ve talked a lot about expressions that evaluate to Boolean values, like
scoops > 0, and these kinds of expressions are the key to the while statement. Here’s how the
while statement works:
Seeing as this is your first while loop, let’s trace through a round of its execution to see exactly how it works. Notice we’ve added a declaration for the variable scoops at the top of the code, and initialized it to the value
Now let’s start executing this code. First we set
scoops = 5 while scoops > 0: print(‘Another scoop!’) scoops = scoops - 1 print(“Life without ice cream isn’t the same.”)
A note from readers who have read this before you: read the next several pages slowly and carefully. There’s a lot to take in and you really want to get how this works into your brain.
After that we encounter the
while statement. When we evaluate a
while statement, the first thing we do is evaluate the conditional to see if it’s
Because the conditional is
True, we start executing the block of code. The first statement in the body prints the string
"Another scoop!" to the shell.
The next statement subtracts one from the number of scoops and then sets
scoops to that new value,
That’s the last statement in the block, so we loop back up to the conditional and start over again.
Evaluating our conditional again, this time
4. But that’s still more than zero.
Once again we write the string
"Another scoop!" to the shell.
The next statement subtracts one from the number of scoops and sets
scoops to that new value, which is
That’s the last statement in the block, so we loop back up to the conditional and start over again.
Evaluating our conditional again, this time
3. But that’s still more than zero.
Once again we write the string
"Another scoop!" to the shell.
And as you can see, this continues. Each time we loop, we decrement (reduce
scoops by 1), write another string to the shell, and keep going.
Until the last time...this time something’s different.
0, and so our conditional evaluates to
False. That’s it, folks; we’re not going to go through the loop anymore, and we’re not going to execute the block. This time, we bypass the block and execute the statement that follows it.
Now we execute the other
"Life without ice cream isn’t the same". We’re done!
Now that you know how to use while, you’re all ready to get this code reprompting the user. To do that we just need to make a couple simple changes to the previous attempt: we’re going to start by initializing
user_choice to the empty string, and then we’re going to replace the if keyword with
What’s the best thing to do after coding your new game? Play a few rounds, of course! Sit back, relax, and let everything in this chapter sink in as you try to defeat the computer at Rock, Paper, Scissors. Of course, you’re not quite done yet—you’ve still got the extra credit, the bullet points, and a crossword to do, but take some time and enjoy the game first.
Before we wrap up this chapter we need to talk about infinite loops. You see, when you write code without loops it just goes straight through—you know it’s going to end, someday. But with loops, things get more interesting.
Let’s say you’ve just written your latest code, you feel good about it, and you confidently run it. What happens next? Nothing. Any output slows to a crawl. Your program seems to be doing something, but what, you’re not quite sure. Whatever it is, it’s taking a long time.
You just encountered an infinite loop—that is, a loop that is looping and looping and is never going to end, ever.
It’s easier to get into this situation than it sounds. In fact, sooner or later you’re going to encounter one, so it might as well be now. Let’s create one:
So what do you do when you have an out-of-control program running on your computer? If you’re using IDLE, simply close the shell window to terminate the program. If you’re using your computer’s command line, then typically a tap of Control+C (Ctrl+C on some keyboards) will terminate the program as well.
And what do you do with your code? Well, infinite loops are logic errors. You’ve create some logic that never lets the loop end, so examine the conditional of your loop (or loops) and trace through the execution of your code until you determine what about the conditional logic is wrong. In our case, we simply need to rewrite the
counter + 1 as
counter - 1, so that the code counts down.