What You'll Build: Rock Paper Scissors in Python
In this tutorial, you'll build a complete rock paper scissors game in Python that runs in the terminal. The finished version tracks scores across rounds, validates user input with regular expressions, and lets the player replay as many times as they want.
Here's what a round looks like when you run the full code:
Rock, Paper, Scissors - Shoot!
Choose your weapon [R]ock, [P]aper, or [S]cissors: R
You chose: R
I chose: S
R beats S, You win!
Score - You: 1, Computer: 0
Do you wish to play again? (Yes or No):
If you've never played before: two participants each throw a hand sign at the same time. A fist means rock, a flat palm means paper, and two extended fingers mean scissors. Rock smashes scissors, scissors cut a piece of paper, and paper covers rock. Simple rules, but translating them into code teaches you a surprising amount.
This is one of the best beginner Python projects because it covers a lot of ground in a small amount of code: random number generation, while loops, if-elif-else conditionals, functions, and input validation. Let's build it step by step.
Prerequisites
You'll need Python 3.10 or later installed on your machine. If you don't have it yet, grab it from python.org. You can also follow along directly in our browser-based Python editor without installing anything.
Create a new file called rock_paper_scissors.py and you're ready to go.
Step 1: Import the Modules
This project uses three modules from Python's standard library, so there's nothing extra to install:
import random
import os
import re
The random module handles the computer choice by picking a random number mapped to rock, paper, or scissors. The os module clears the terminal between rounds so the output stays clean. And the re module gives us regex pattern matching for validating what the player types.
Step 2: Build the Replay Function
Before writing the main game loop, let's handle what happens after each round. The check_play_status() function asks the player if they want to keep going:
def check_play_status():
valid_responses = ['yes', 'no', 'y']
while True:
try:
response = input('Do you wish to play again? (Yes or No): ')
if response.lower() not in valid_responses:
raise ValueError('Yes or No only')
if response.lower() in ['yes', 'y']:
return True
else:
os.system('cls' if os.name == 'nt' else 'clear')
print('Thanks for playing!')
exit()
except ValueError as err:
print(err)
The function keeps a list of accepted responses and loops until the player gives a valid choice. Typing "yes" or "y" returns True, which tells the main game loop to run another round. Typing "no" clears the screen, prints a goodbye message, and exits the program.
If the player types anything outside the list, the function raises a ValueError and prompts them again. This is a simple pattern, but it's worth noting: in production code, you'd typically return False instead of calling exit() directly, since exit() is really meant for the interactive interpreter. For a beginner tutorial like this, it works fine.
Step 3: Write the Game Logic
Now for the core of the project. The play_rps() function handles everything: taking the user choice, generating the computer choice, comparing them, and updating the score.
Initialize Scores and Start the Loop
def play_rps():
user_score = 0
computer_score = 0
play = True
while play:
os.system('cls' if os.name == 'nt' else 'clear')
print('')
print('Rock, Paper, Scissors - Shoot!')
The scores start at zero, and the game runs inside a while loop that continues as long as play is True. Each round starts by clearing the terminal. The os.system() call uses cls on Windows and clear on Linux or macOS, so the correct command runs regardless of operating system.
Get and Validate Player Input
user_choice = input('Choose your weapon [R]ock, [P]aper, or [S]cissors: ')
if not re.match("^[RrPpSs]$", user_choice):
print('Invalid choice! Please choose: [R]ock, [P]aper, or [S]cissors.')
continue
The player types a single letter: R, P, or S. The re.match() call checks whether the input matches the regex pattern ^[RrPpSs]$. Here's what that pattern means:
^anchors the match to the start of the string[RrPpSs]allows exactly one character from this set (uppercase or lowercase)$anchors to the end, ensuring nothing extra follows
If the input isn't a valid choice, the game prints an error and continue restarts the loop without counting the round. You could achieve the same thing with a simple if user_choice.upper() not in ['R', 'P', 'S'] check, but using regex here is a good excuse to practice pattern matching on a small, understandable example.
Generate the Computer's Move
print(f'You chose: {user_choice.upper()}')
choices = ['R', 'P', 'S']
opp_choice = random.choice(choices)
print(f'I chose: {opp_choice}')
random.choice() picks one item from the list at random, giving the computer an equal probability of selecting rock, paper, or scissors. The result is stored in opp_choice and printed so the player can see what the computer picked. Both values are displayed using f-strings, which let you embed variables directly inside a string.
Determine the Winner
if opp_choice == user_choice.upper():
print('It\'s a Tie!')
elif (opp_choice == 'R' and user_choice.upper() == 'S') or \
(opp_choice == 'S' and user_choice.upper() == 'P') or \
(opp_choice == 'P' and user_choice.upper() == 'R'):
print(f'{opp_choice} beats {user_choice.upper()}, I win!')
computer_score += 1
else:
print(f'{user_choice.upper()} beats {opp_choice}, You win!')
user_score += 1
The rules of the game are straightforward: rock beats scissors, scissors beats paper, and paper covers rock. The code checks for three outcomes:
- If both picks are identical, it's a tie. Neither score changes.
- If the computer's pick beats the player's (all three losing conditions are listed explicitly with
or), the computer scores a point. - Otherwise, the player wins by default.
Listing all losing conditions in a single elif block means we don't need to spell out every winning condition separately. If it's not a tie and the computer didn't win, the player must have won. This keeps the logic short without sacrificing clarity.
Display the Score and Ask to Replay
print(f'Score - You: {user_score}, Computer: {computer_score}')
play = check_play_status()
After each round, the current score prints and check_play_status() decides whether the loop continues. If the player says no, the program exits. If they say yes, play stays True and another round begins.
Step 4: Add the Entry Point
if __name__ == '__main__':
play_rps()
This guard ensures the game only runs when you execute the file directly. If you import this module into another script (say, to reuse check_play_status()), the game won't start automatically. It's a small detail, but it's standard Python practice and worth building the habit early.
Refactoring: Use a Dictionary Instead of if-elif-else
The version above works, but the win-checking logic has a limitation. If you wanted to add more options (like "Lizard" and "Spock" from the extended version), you'd need to add a growing number of elif conditions. A cleaner approach is to use a dictionary that maps each choice to the choice it beats:
win_map = {
'R': 'S', # Rock beats Scissors
'S': 'P', # Scissors beats Paper
'P': 'R', # Paper beats Rock
}
With this dictionary, the entire winner check becomes:
if opp_choice == user_choice.upper():
print("It's a Tie!")
elif win_map[opp_choice] == user_choice.upper():
print(f'{opp_choice} beats {user_choice.upper()}, I win!')
computer_score += 1
else:
print(f'{user_choice.upper()} beats {opp_choice}, You win!')
user_score += 1
Instead of chaining three conditions with or, you do a single dictionary lookup. If the computer's choice maps to the player's choice, the computer wins. This scales much better. Adding Lizard and Spock means adding two entries to win_map (with lists of what each choice beats) instead of rewriting a pile of conditionals.
I'd recommend starting with the explicit if-elif-else approach to understand the logic, then refactoring to the dictionary version once it clicks. That progression from "make it work" to "make it clean" is how real refactoring works in practice.
Python Concepts This Project Covers
One reason rock paper scissors is such a popular first project is how many fundamentals it packs into a short script. Here's what you practiced and where each concept shows up:
| Concept | Where It Appears | Real-World Application |
|---|---|---|
random.choice() |
Generating the computer's move | A/B testing, shuffling, sampling |
while loops |
Game loop, input validation loop | Server loops, retry logic, polling |
if-elif-else |
Winner determination | Routing, access control, business rules |
re.match() |
Validating player input | Form validation, parsing, data cleaning |
| Functions | play_rps(), check_play_status() |
Code reuse, testing, modularity |
| f-strings | Score display, round results | Logging, user-facing messages, templates |
| Exception handling | try/except ValueError |
API error handling, input sanitization |
__name__ == '__main__' |
Entry point guard | CLI tools, importable library modules |
Full Code: Rock Paper Scissors in Python
Here's the complete, working game. Copy it into a file or paste it into the hackr.io Python editor to try it out. Welcome to your first Python game:
import random
import os
import re
def check_play_status():
valid_responses = ['yes', 'no', 'y']
while True:
try:
response = input('Do you wish to play again? (Yes or No): ')
if response.lower() not in valid_responses:
raise ValueError('Yes or No only')
if response.lower() in ['yes', 'y']:
return True
else:
os.system('cls' if os.name == 'nt' else 'clear')
print('Thanks for playing!')
exit()
except ValueError as err:
print(err)
def play_rps():
user_score = 0
computer_score = 0
play = True
while play:
os.system('cls' if os.name == 'nt' else 'clear')
print('\nRock, Paper, Scissors - Shoot!')
user_choice = input('Choose your weapon [R]ock, [P]aper, or [S]cissors: ')
if not re.match("^[RrPpSs]$", user_choice):
print('Invalid choice! Try again.')
continue
print(f'You chose: {user_choice.upper()}')
choices = ['R', 'P', 'S']
opp_choice = random.choice(choices)
print(f'I chose: {opp_choice}')
if opp_choice == user_choice.upper():
print("It's a Tie!")
elif (opp_choice == 'R' and user_choice.upper() == 'S') or \
(opp_choice == 'S' and user_choice.upper() == 'P') or \
(opp_choice == 'P' and user_choice.upper() == 'R'):
print(f'{opp_choice} beats {user_choice.upper()}, I win!')
computer_score += 1
else:
print(f'{user_choice.upper()} beats {opp_choice}, You win!')
user_score += 1
print(f'Score - You: {user_score}, Computer: {computer_score}')
play = check_play_status()
if __name__ == '__main__':
play_rps()
Next Steps: Extend the Game
Once you've got the basic version running, here are three ways to push further:
Add a best-of mode. Instead of playing indefinitely, let the player pick a target (best of 3, best of 5) and end the game when someone hits the winning int threshold. This is a good exercise in adding a counter variable and a second exit condition to your while loop.
Build a smarter opponent. Track the player's history in a list and have the computer weight its picks toward whatever beats the player's most common choice. This introduces frequency analysis and gives you practice with Python dictionaries and the collections.Counter class.
Add a GUI. The terminal version is great for learning, but a graphical version using Tkinter or Pygame takes the same logic and pairs it with event-driven programming. If you want more game projects to try next, check out our Tic Tac Toe, Hangman, or Number Guessing Game tutorials.
Frequently Asked Questions
How do I add more rounds to Rock Paper Scissors in Python?
Add a variable to track the target score (for example, max_wins = 3) and change the while loop condition to also check whether either score has reached that target. When one player hits the threshold, print the final result and break out of the loop instead of calling check_play_status().
Can I use a dictionary instead of if-elif-else for the game logic?
Yes, and it's the recommended approach once you understand the basics. Create a dictionary like {'R': 'S', 'S': 'P', 'P': 'R'} where each key maps to the choice it beats. Then a single lookup replaces the entire chain of conditionals. This pattern scales much better if you add more options later.
How do I add Rock Paper Scissors Lizard Spock in Python?
Extend the choices list and update the win conditions. Each choice now beats two others: rock beats scissors and lizard, scissors beats paper and lizard, paper beats rock and Spock, lizard beats paper and Spock, and Spock beats rock and scissors. A dictionary-based approach handles this much more cleanly than chaining elif statements.
Why use re.match() instead of a simple if statement for input validation?
For this specific game, a simple if user_choice.upper() not in ['R', 'P', 'S'] check would work just as well. The regex approach is included here as a learning opportunity because regex is a critical skill for form validation, data parsing, and text processing in real projects. Starting with a simple pattern helps you build comfort before tackling more complex expressions.
How do I make the computer play smarter based on player history?
Store each of the player's picks in a list. After a few rounds, use collections.Counter to find their most frequent choice, then have the computer pick whatever beats it. For example, if the player picks rock 60% of the time, the computer should favor paper. This introduces basic frequency analysis and makes the game progressively harder.
What Python version do I need to build this game?
The code in this tutorial runs on Python 3.6 and later, since it uses f-strings (introduced in 3.6). Python 3.10 or later is recommended for the best developer experience, including improved error messages that help beginners debug issues faster.
How do I turn this into a two-player game?
Replace the random.choice() call with a second input() prompt for the second player. To prevent cheating, you can clear the screen between turns using os.system() so one participant can't see the other's pick. For a networked version, look into Python's socket module or a framework like Flask to handle communication between two players.
What's the difference between exit() and sys.exit() in Python?
exit() is a convenience function meant for the interactive interpreter. In scripts, sys.exit() is the proper way to terminate because it raises SystemExit, which can be caught by calling code. For a small game script either works, but sys.exit() is the better habit to build for real applications.