Enumerations and 5-Way Variation
00:00 In the previous lesson, you learned how to determine who is the winner in rock paper scissors. In this lesson, I’ll show you how to use enumerations to reduce the chance of errors in your code, and then add a Spock to your game.
00:14
There are a few exceptions but, generally, hard-coded strings and numbers are considered bad practice in programming. Looking back at the last program, there are six instances of the word "rock"
, six of "paper"
, and six of "scissors"
. One little spelling error in any one of those six cases and you’ll have a bug in your code. What’s worse, even if your code is bug free, your user has to keep typing "scissors"
. It could get frustrating quickly.
00:42
Python supports the concept of an enumeration, or enum for short. This is a way of grouping symbols or values together. For our code, the "rock"
, "paper"
, and "scissors"
are all choices that the user can make. An enum grouping those together makes sense.
00:59
There are several different types of enums. The one I’ll show you is IntEnum
. This uses integers to represent each value in the enumeration.
01:09
This is the numbers.py
script. I started with the concept shown in beats.py
, but changed the choices into enumerations. The first change required is to import the IntEnum
from the enum
module.
01:24
You create enums by building classes that inherit from an enumeration class. Here, I’ve created the Choice
class by inheriting from IntEnum
.
01:35
Each declaration inside of this enumeration must have an integer associated with it. I’m using 0
for rock, 1
for paper, and 2
for scissors.
01:46
The BEATS
dict needs to be updated. It used to hard-code the rock, paper, and scissors values as strings. Here, I’ve replaced them with the attributes of the Choice
class.
01:57
You access a value from an enum by specifying it with dot notation. The compiler will replace Choice.Rock
with the number 0
, Choice.Paper
with 1
, and Choice.Scissors
with 2
.
02:09
A similar fix needs to be done to update the MESSAGES
dict. With these changes in place, any spelling error in the word scissors will result in a compiler error. Let me just scroll down to the bottom.
02:25 The code for requesting input from the user has changed a little bit. You might expect that, given that it used to be looking for strings and now it’s looking for an integer.
02:33
The entire input section is wrapped in a try
/except
block. The input of an invalid choice will now result in a ValueError
being raised.
02:42
Let’s go through it step by step. Line 46 still asks the user for input. Note the CHOICES_STR
(choices string) variable inside the input prompt. Instead of hard-coding the choices, this string is determined dynamically based on the contents of the Choice
enum. Let me scroll back up to the top to show you where this is done.
03:05
Line 10 does a list comprehension. This is a short-form for creating a list by looping over some values. In this case, all the possible choices in the Choice
enumeration are iterated over and for each choice, a string is built.
03:19
The string contains the name of the choice, square brackets, and the number of the choice inside of the brackets. The all-caps CHOICES
list will have three strings inside of it: "rock[0]"
, "paper[1]"
, and "scissors[2]"
.
03:36 Line 11 takes the list created on line 10 and turns it into a single string with the values separated by commas. This string is a readable version of the choices the user will be presented with.
03:49 With me so far? Good. Let’s go back down to the input code.
03:54
Line 46 prompted the user with the choices string. Line 47 then creates a Choice
enumeration object based on what the user typed in. Two things can go wrong here. One, the thing the user typed in might not convert into an integer, and two, the user might type in an integer that isn’t a valid choice.
04:14
Both of these situations raise a ValueError
. The int()
function raises a ValueError
if it can’t convert the string it was given into an integer, and the Choice
IntEnum
raises a ValueError
if you try to create a Choice
object with a bad value. In either of these cases, the except
clause will run. Line 49 displays an error message, and then line 50 causes the code to continue execution from the top of the loop, giving the user a chance to type something else in.
04:45
The random choice the computer makes has to change as well. Previously, you used the choice()
function from the random
module that randomly chose an item from a list. Now that you’re using an IntNum
, it is better to take advantage of a different function from random
.
04:59
The randint()
function returns a random integer between the two values it is given, inclusively. You need a random choice between 0
, rock, and 2
, scissors.
05:11
Enum objects support the use of the len()
function. By doing it this way, you don’t have to hard-code the last item in the enum, you just use it.
05:20
Remember, though, collections of things in Python are usually zero-indexed. In this case, len()
will return 3
. There are three things in the enum. Subtract 1
from the length to get the appropriate upper boundary for the random integer. Line 53 constructs a Choice
object using the random value just determined.
05:41
As before, the values for the user and computer are passed to show_winner()
. They used to be strings, but now they are Choice
objects, so the show_winner()
function is going to need some modifications.
05:52
Let me just scroll up to it. Previously, the arguments to show_winner()
were expected to be strings. Now, they’re Choice
objects, so a few things have to change inside of here.
06:04
Line 27 shows the first example of this. You can get the text representation of an enum by accessing the .name
property on it. What used to be just user_choice
is now user_choice.name
to get the text version of the number the user typed. As the enum named each value with capital letters, I’m also calling .lower()
on the result.
06:28
There are several other cases inside of show_winner()
where this kind of thing has to be done as well. All of them are places where user or computer choices were displayed, but the change is the same as line 27.
06:42 With all that done, let’s go see this in action.
06:50 A bunch of code was touched in order to make it safer. Not much has changed from the user’s perspective, except now you’re choosing numbers instead of typing out words.
06:59
Let me pick 2
for scissors. Much easier to type.
07:06
And to show the error conditions, I’ll type rock
.
07:11
How about 5
? That’s good. The error conditions work.
07:19 Most of the code changes in the last two scripts were kind of neutral. The code was a bit cleaner and a little shorter, but not by much. Where these changes become important is if you want to make the game more complicated.
07:32 There’s a five-way variation on rock paper scissors called rock, paper, scissors, lizard, Spock. Each item in this version of the game beats and can be beat by two other items.
07:46
Think back to your first version of show_winner()
and that large if-then-else block. Encoding the lizard Spock version would be much worse, wouldn’t it?
07:55
The switch to enumerations makes life easier as well. To get the new version going, all you need to do is update the Choice
enumeration with lizard and Spock and update the BEATS
and MESSAGES
dictionaries.
08:14
This is inside of spock.py
. The code is built on numbers.py
but with the new lizard and Spock changes. The first thing to do is add lizard and Spock to the Choice
IntEnum
.
08:27 Then, update the game rules, encoding how each of the five choices beats out two of the other choices.
08:38 Make a similar set of changes for all of our verbs.
08:44
And then one tiny change, due to my anal retention. I’ve removed the call to .lower()
here on line 38 because Spock is a proper name. It offends me less to have capital-R "Rock"
here than it does to have small-s "spock"
. If you wanted to get fancy, you could add a bit of conditional code to deal with both, but I went with lazy instead. With everything in place, you’re ready to go.
09:12 Let me just run this. Let’s start with lizard.
09:22 You can see how structuring your code correctly can make certain kinds of changes far simpler. Writing a program is always a trade-off. You can do clever things and possibly spend a lot of time for little value, or you can do obvious things and often make your life painful later when you want to make changes. The more you code, the more you’ll get used to these kinds of decisions and the choice will become more obvious with experience. Next up, I’ll wrap up the course with a quick summary.
Become a Member to join the conversation.