Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Rock, Paper, Scissors With Python: A Command Line Game
Game programming is a great way to learn how to program. You use many tools that you’ll see in the real world, plus you get to play a game to test your results! An ideal game to start your Python game programming journey is rock paper scissors.
In this tutorial, you’ll learn how to:
- Code your own rock paper scissors game
- Take in user input with
input()
- Play several games in a row using a
while
loop - Clean up your code with
Enum
and functions - Define more complex rules with a dictionary
Free Bonus: 5 Thoughts On Python Mastery, a free course for Python developers that shows you the roadmap and the mindset you’ll need to take your Python skills to the next level.
What Is Rock Paper Scissors?
You may have played rock paper scissors before. Maybe you’ve used it to decide who pays for dinner or who gets first choice of players for a team.
If you’re unfamiliar, rock paper scissors is a hand game for two or more players. Participants say “rock, paper, scissors” and then simultaneously form their hands into the shape of a rock (a fist), a piece of paper (palm facing downward), or a pair of scissors (two fingers extended). The rules are straightforward:
- Rock smashes scissors.
- Paper covers rock.
- Scissors cut paper.
Now that you have the rules down, you can start thinking about how they might translate to Python code.
Play a Single Game of Rock Paper Scissors in Python
Using the description and rules above, you can make a game of rock paper scissors. Before you dive in, you’re going to need to import the module you’ll use to simulate the computer’s choices:
import random
Awesome! Now you’re able to use the different tools inside random
to randomize the computer’s actions in the game. Now what? Since your users will also need to be able to choose their actions, the first logical thing you need is a way to take in user input.
Take User Input
Taking input from a user is pretty straightforward in Python. The goal here is to ask the user what they would like to choose as an action and then assign that choice to a variable:
user_action = input("Enter a choice (rock, paper, scissors): ")
This will prompt the user to enter a selection and save it to a variable for later use. Now that the user has selected an action, the computer needs to decide what to do.
Make the Computer Choose
A competitive game of rock paper scissors involves strategy. Rather than trying to develop a model for that, though, you can save yourself some time by having the computer select a random action. Random selections are a great way to have the computer choose a pseudorandom value.
You can use random.choice()
to have the computer randomly select between the actions:
possible_actions = ["rock", "paper", "scissors"]
computer_action = random.choice(possible_actions)
This allows a random element to be selected from the list. You can also print the choices that the user and the computer made:
print(f"\nYou chose {user_action}, computer chose {computer_action}.\n")
Printing the user and computer actions can be helpful to the user, and it can also help you debug later on in case something isn’t quite right with the outcome.
Determine a Winner
Now that both players have made their choice, you just need a way to decide who wins. Using an if
… elif
… else
block, you can compare players’ choices and determine a winner:
if user_action == computer_action:
print(f"Both players selected {user_action}. It's a tie!")
elif user_action == "rock":
if computer_action == "scissors":
print("Rock smashes scissors! You win!")
else:
print("Paper covers rock! You lose.")
elif user_action == "paper":
if computer_action == "rock":
print("Paper covers rock! You win!")
else:
print("Scissors cuts paper! You lose.")
elif user_action == "scissors":
if computer_action == "paper":
print("Scissors cuts paper! You win!")
else:
print("Rock smashes scissors! You lose.")
By comparing the tie condition first, you get rid of quite a few cases. If you didn’t do that, then you’d need to check each possible action for user_action
and compare it against each possible action for computer_action
. By checking the tie condition first, you’re able to know what the computer chose with only two conditional checks of computer_action
.
And that’s it! All combined, your code should now look like this:
import random
user_action = input("Enter a choice (rock, paper, scissors): ")
possible_actions = ["rock", "paper", "scissors"]
computer_action = random.choice(possible_actions)
print(f"\nYou chose {user_action}, computer chose {computer_action}.\n")
if user_action == computer_action:
print(f"Both players selected {user_action}. It's a tie!")
elif user_action == "rock":
if computer_action == "scissors":
print("Rock smashes scissors! You win!")
else:
print("Paper covers rock! You lose.")
elif user_action == "paper":
if computer_action == "rock":
print("Paper covers rock! You win!")
else:
print("Scissors cuts paper! You lose.")
elif user_action == "scissors":
if computer_action == "paper":
print("Scissors cuts paper! You win!")
else:
print("Rock smashes scissors! You lose.")
You’ve now written code to take in user input, select a random action for the computer, and decide the winner! But this only lets you play one game before the program finishes running.
Play Several Games in a Row
Although a single game of rock paper scissors is super fun, wouldn’t it be better if you could play several games in a row? Loops are a great way to create recurring events. In particular, you can use a while
loop to play indefinitely:
import random
while True:
user_action = input("Enter a choice (rock, paper, scissors): ")
possible_actions = ["rock", "paper", "scissors"]
computer_action = random.choice(possible_actions)
print(f"\nYou chose {user_action}, computer chose {computer_action}.\n")
if user_action == computer_action:
print(f"Both players selected {user_action}. It's a tie!")
elif user_action == "rock":
if computer_action == "scissors":
print("Rock smashes scissors! You win!")
else:
print("Paper covers rock! You lose.")
elif user_action == "paper":
if computer_action == "rock":
print("Paper covers rock! You win!")
else:
print("Scissors cuts paper! You lose.")
elif user_action == "scissors":
if computer_action == "paper":
print("Scissors cuts paper! You win!")
else:
print("Rock smashes scissors! You lose.")
play_again = input("Play again? (y/n): ")
if play_again.lower() != "y":
break
Notice the highlighted lines above. It’s important to check if the user wants to play again and to break
if they don’t. Without that check, the user would be forced to play until they terminated the console using Ctrl+C or a similar method.
The check for playing again is a check against the string "y"
. But checking for something specific like this might make it harder for the user stop playing. What if the user types "yes"
or "no"
? String comparison is often tricky because you never know what the user might enter. They might do all lowercase, all uppercase, or even a mixture of the two.
Here are the results of a few different string comparisons:
>>> play_again = "yes"
>>> play_again == "n"
False
>>> play_again != "y"
True
Hmm. That’s not what you want. The user might not be too happy if they enter "yes"
expecting to play again but are kicked from the game.
Describe an Action With enum.IntEnum
Because string comparisons can cause problems like you saw above, it’s a good idea to avoid them whenever possible. One of the first things your program asks, however, is for the user to input a string! What if the user inputs "Rock"
or "rOck"
by mistake? Capitalization matters, so they won’t be equal:
>>> print("rock" == "Rock")
False
Since capitalization matters, "r"
and "R"
aren’t equal. One possible solution would be to use numbers instead. Assigning each action a number could save you some trouble:
ROCK_ACTION = 0
PAPER_ACTION = 1
SCISSORS_ACTION = 2
This allows you to reference different actions by their assigned number. Integers don’t suffer the same comparison issues as strings, so this could work. Now you can have the user input a number and compare it directly against those values:
user_input = input("Enter a choice (rock[0], paper[1], scissors[2]): ")
user_action = int(user_input)
if user_action == ROCK_ACTION:
# Handle ROCK_ACTION
Because input()
returns a string, you need to convert the return value to an integer using int()
. Then you can compare the input to each of the actions above. This works well, but it might rely on you naming variables correctly in order to keep track of them. A better way is to use enum.IntEnum
and define your own action class!
Using enum.IntEnum
allows you to create attributes and assign them values similar to those shown above. This helps clean up your code by grouping actions into their own namespaces and making the code more expressive:
from enum import IntEnum
class Action(IntEnum):
Rock = 0
Paper = 1
Scissors = 2
This creates a custom Action
that you can use to reference the different types of actions you support. It works by assigning each attribute within it to a value you specify.
Comparisons are still straightforward, and now they have a helpful class name associated with them:
>>> Action.Rock == Action.Rock
True
Because the member values are the same, the comparison is equal. The class names also make it more obvious that you want to compare two actions.
Note: To learn more about enum
, check out the Build Enumerations of Constants With Python’s Enum.
You can even create an Action
from an int
:
>>> Action.Rock == Action(0)
True
>>> Action(0)
<Action.Rock: 0>
Action
looks at the value passed in and returns the appropriate Action
. This is helpful because now you can take in the user input as an int
and create an Action
from it. No more worrying about spelling!
The Flow(chart) of Your Program
Although rock paper scissors might seem uncomplicated, it’s important to carefully consider the steps involved in playing it so that you can be sure your program covers all possible scenarios. For any project, even small ones, it’s helpful to create a flowchart of the desired behavior and implement code around it. You could achieve a similar result using a bulleted list, but it’d be harder to capture things like loops and conditional logic.
Flowcharts don’t have to be overly complicated or even use real code. Just describing the desired behavior ahead of time can help you fix problems before they happen!
Here’s a flowchart that describes a single game of rock paper scissors:
Each player selects an action and then a winner is determined. This flowchart is accurate for a single game as you’ve coded it, but it’s not necessarily accurate for real-life games. In real life, the players select their actions simultaneously rather than one at a time like the flowchart suggests.
In the coded version, however, this works because the player’s choice is hidden from the computer, and the computer’s choice is hidden from the player. The two players can make their choices at different times without affecting the fairness of the game.
Flowcharts help you catch possible mistakes early on and also let you see if you want to add more functionality. For example, here’s a flowchart that describes how to play games repeatedly until the user decides to stop:
Without writing code, you can see that the first flowchart doesn’t have a way to play again. This approach allows you to tackle issues like these before programming, which helps you create neater, more manageable code!
Split Your Code Into Functions
Now that you’ve outlined the flow of your program using a flowchart, you can try to organize your code so that it more closely resembles the steps you’ve identified. One natural way to do this is to create a function for each step in the flowchart. Functions are a great way to separate larger chunks of code into smaller, more manageable pieces.
You don’t necessarily need to create a function for the conditional check to play again, but you can if you’d like. You can start by importing random
if you haven’t already and defining your Action
class:
import random
from enum import IntEnum
class Action(IntEnum):
Rock = 0
Paper = 1
Scissors = 2
Hopefully this all looks familiar so far! Now here’s the code for get_user_selection()
, which doesn’t take in any arguments and returns an Action
:
def get_user_selection():
user_input = input("Enter a choice (rock[0], paper[1], scissors[2]): ")
selection = int(user_input)
action = Action(selection)
return action
Notice how you take in the user input as an int
and get back an Action
. That long message for the user is a bit cumbersome, though. What would happen if you wanted to add more actions? You’d have to add even more text to the prompt.
Instead, you can use a list comprehension to generate a portion of the input:
def get_user_selection():
choices = [f"{action.name}[{action.value}]" for action in Action]
choices_str = ", ".join(choices)
selection = int(input(f"Enter a choice ({choices_str}): "))
action = Action(selection)
return action
Now you no longer need to worry about adding or removing actions in the future! Testing this out, you can see how the code prompts the user and returns an action associated with the user’s input value:
>>> get_user_selection()
Enter a choice (rock[0], paper[1], scissors[2]): 0
<Action.Rock: 0>
Now you need a function for getting the computer’s action. Like get_user_selection()
, this function should take no arguments and return an Action
. Because the values for Action
range from 0
to 2
, you’re going to want to generate a random number within that range. random.randint()
can help with that.
random.randint()
returns a random value between a specified minimum and maximum (inclusive). You can use len()
to help figure out what the upper bound should be in your code:
def get_computer_selection():
selection = random.randint(0, len(Action) - 1)
action = Action(selection)
return action
Because the Action
values start counting from 0
, and len() starts counting from 1
, it’s important to do len(Action) - 1
.
When you test this, there won’t be a prompt. It will simply return the action associated with the random number:
>>> get_computer_selection()
<Action.Scissors: 2>
Looking good! Next, you need a way to determine a winner. This function will take two arguments, the user’s action and the computer’s action. It doesn’t need to return anything since it’ll just display the result to the console:
def determine_winner(user_action, computer_action):
if user_action == computer_action:
print(f"Both players selected {user_action.name}. It's a tie!")
elif user_action == Action.Rock:
if computer_action == Action.Scissors:
print("Rock smashes scissors! You win!")
else:
print("Paper covers rock! You lose.")
elif user_action == Action.Paper:
if computer_action == Action.Rock:
print("Paper covers rock! You win!")
else:
print("Scissors cuts paper! You lose.")
elif user_action == Action.Scissors:
if computer_action == Action.Paper:
print("Scissors cuts paper! You win!")
else:
print("Rock smashes scissors! You lose.")
This is pretty similar to the first comparison you used to determine a winner. Now you can just directly compare Action
types without worrying about those pesky strings!
You can even test this out by passing different options to determine_winner()
and seeing what gets printed:
>>> determine_winner(Action.Rock, Action.Scissors)
Rock smashes scissors! You win!
Since you’re creating an action from a number, what would happen if your user tried to create an action from 3
? Remember, largest number you’ve defined so far is 2
:
>>> Action(3)
ValueError: 3 is not a valid Action
Whoops! You don’t want that to happen. Where in the flowchart could you add some logic to ensure the user has entered a valid choice?
It makes sense to include the check immediately after the user has made their choice:
If the user enters an invalid value, then you repeat the step for getting the user’s choice. The only real requirement for the user selection is that it’s between 0
and 2
, inclusive. If the user’s input is outside this range, then a ValueError
exception will be raised. To avoid displaying the default error message to the user, you can handle the exception.
Note: Exceptions can be tricky! For more information, check out Python Exceptions: An Introduction.
Now that you’ve defined a few functions that reflect the steps in your flowchart, your game logic is a lot more organized and compact. This is all your while
loop needs to contain now:
while True:
try:
user_action = get_user_selection()
except ValueError as e:
range_str = f"[0, {len(Action) - 1}]"
print(f"Invalid selection. Enter a value in range {range_str}")
continue
computer_action = get_computer_selection()
determine_winner(user_action, computer_action)
play_again = input("Play again? (y/n): ")
if play_again.lower() != "y":
break
Doesn’t that look a lot cleaner? Notice how if the user fails to select a valid range, then you use continue
rather than break
. This makes the code continue to the next iteration of the loop rather than break out of it.
Rock Paper Scissors … Lizard Spock
If you’ve seen The Big Bang Theory, then you may be familiar with rock paper scissors lizard Spock. If not, then here’s a diagram depicting the game and the rules deciding the winner:
You can use the same tools you learned about above to implement this game. For instance, you could add to Action
and create values for lizard and Spock. Then you would just need to modify get_user_selection()
and get_computer_selection()
to incorporate these options. Updating determine_winner()
, however, would be a lot more work.
Instead of adding a lot of if
… elif
… else
statements to your code, you can use a dictionary to help show the relationships between actions. Dictionaries are a great way to show a key-value relationship. In this case, the key can be an action, like scissors, and the value can be a list of actions that it beats.
So what would this look like for your determine_winner()
with only three options? Well, each Action
can beat only one other Action
, so the list would contain only a single item. Here’s what your code looked like before:
def determine_winner(user_action, computer_action):
if user_action == computer_action:
print(f"Both players selected {user_action.name}. It's a tie!")
elif user_action == Action.Rock:
if computer_action == Action.Scissors:
print("Rock smashes scissors! You win!")
else:
print("Paper covers rock! You lose.")
elif user_action == Action.Paper:
if computer_action == Action.Rock:
print("Paper covers rock! You win!")
else:
print("Scissors cuts paper! You lose.")
elif user_action == Action.Scissors:
if computer_action == Action.Paper:
print("Scissors cuts paper! You win!")
else:
print("Rock smashes scissors! You lose.")
Now, instead of comparing to each Action
, you can have a dictionary that describes the victory conditions:
def determine_winner(user_action, computer_action):
victories = {
Action.Rock: [Action.Scissors], # Rock beats scissors
Action.Paper: [Action.Rock], # Paper beats rock
Action.Scissors: [Action.Paper] # Scissors beats paper
}
defeats = victories[user_action]
if user_action == computer_action:
print(f"Both players selected {user_action.name}. It's a tie!")
elif computer_action in defeats:
print(f"{user_action.name} beats {computer_action.name}! You win!")
else:
print(f"{computer_action.name} beats {user_action.name}! You lose.")
You still do the same as before and check the tie condition first. But instead of comparing each Action
, you instead compare the action that the user_input
beats against the computer_action
. Since the key-value pair is a list, you can use the membership operator in
to check if an element resides within it.
Since you no longer use long if
… elif
… else
statements, it’s relatively painless to add checks for these new actions. You can start by adding lizard and Spock to Action
:
class Action(IntEnum):
Rock = 0
Paper = 1
Scissors = 2
Lizard = 3
Spock = 4
Next, add all the victory relationships from the diagram. Make sure to do this underneath Action
so that victories
will be able to reference everything in Action
:
victories = {
Action.Scissors: [Action.Lizard, Action.Paper],
Action.Paper: [Action.Spock, Action.Rock],
Action.Rock: [Action.Lizard, Action.Scissors],
Action.Lizard: [Action.Spock, Action.Paper],
Action.Spock: [Action.Scissors, Action.Rock]
}
Notice how now each Action
has a list containing two elements that it beats. In the basic rock paper scissors implementation, there was only one element.
Because you intentionally wrote get_user_selection()
to accommodate new actions, you don’t have to change anything about that code. The same is true for get_computer_selection()
. Since the length of Action
has changed, the range of random numbers will also change.
Look at how much shorter and more manageable the code is now! To see the full code for your complete program, expand the box below.
import random
from enum import IntEnum
class Action(IntEnum):
Rock = 0
Paper = 1
Scissors = 2
Lizard = 3
Spock = 4
victories = {
Action.Scissors: [Action.Lizard, Action.Paper],
Action.Paper: [Action.Spock, Action.Rock],
Action.Rock: [Action.Lizard, Action.Scissors],
Action.Lizard: [Action.Spock, Action.Paper],
Action.Spock: [Action.Scissors, Action.Rock]
}
def get_user_selection():
choices = [f"{action.name}[{action.value}]" for action in Action]
choices_str = ", ".join(choices)
selection = int(input(f"Enter a choice ({choices_str}): "))
action = Action(selection)
return action
def get_computer_selection():
selection = random.randint(0, len(Action) - 1)
action = Action(selection)
return action
def determine_winner(user_action, computer_action):
defeats = victories[user_action]
if user_action == computer_action:
print(f"Both players selected {user_action.name}. It's a tie!")
elif computer_action in defeats:
print(f"{user_action.name} beats {computer_action.name}! You win!")
else:
print(f"{computer_action.name} beats {user_action.name}! You lose.")
while True:
try:
user_action = get_user_selection()
except ValueError as e:
range_str = f"[0, {len(Action) - 1}]"
print(f"Invalid selection. Enter a value in range {range_str}")
continue
computer_action = get_computer_selection()
determine_winner(user_action, computer_action)
play_again = input("Play again? (y/n): ")
if play_again.lower() != "y":
break
That’s it! You’ve implemented rock paper scissors lizard Spock in Python code. Double-check to make sure you didn’t miss anything and give it a playthrough.
Conclusion
Congratulations! You just finished your first Python game! You now know how to create rock paper scissors from scratch, and you’re able to expand the number of possible actions in your game with minimal effort.
In this tutorial, you learned how to:
- Code your own rock paper scissors game
- Take in user input with
input()
- Play several games in a row using a
while
loop - Clean up your code with
Enum
and functions - Describe more complex rules with a dictionary
These tools will continue to help you throughout your many programming adventures. To keep honing your abilities, you can build a hangman game. If you have any questions, then feel free to reach out in the comments section below.
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Rock, Paper, Scissors With Python: A Command Line Game