Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Locked learning resources

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

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:10 Let’s go do just that.

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:19 How about Spock?

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.