Rock Paper Scissors in Python – A Complete Step-By-Step Guide

I wanted a fun weekend project that actually taught me something useful about Python. Rock Paper Scissors seemed like the right balance — simple rules but enough complexity to make the implementation interesting. I ended up building two versions: the classic three-move game and the extended Rock-Paper-Scissors-Lizard-Spock from The Big Bang Theory. Both are worth knowing.

This article walks through building both versions from scratch. By the end, you will have a working terminal game that handles player input, generates random computer moves, determines winners, and keeps running until the player quits. The same patterns apply to any simple game you want to build in Python.

TLDR

  • A dictionary maps move names to numbers, and a win-lose matrix looks up the outcome
  • Computer moves come from random.randint() with equal probability for each move
  • The game loop runs inside a while True block and breaks only when the player quits
  • Two separate win-lose matrices handle the classic and extended versions
  • The complete code for both versions fits in under 150 lines

What is Rock Paper Scissors in Python?

Rock Paper Scissors is a hand-game usually played between two people. Both players choose one of three moves — rock, paper, or scissors — and reveal them simultaneously. Rock crushes scissors, scissors cuts paper, and paper covers rock. When both players choose the same move, it is a tie.

The Python implementation translates each move into a number and uses a win-lose matrix to look up the outcome. This approach separates the game logic from the user interface, which makes it easy to add features or change rules without rewriting the core code. The extended version, Rock-Paper-Scissors-Lizard-Spock, adds two moves — lizard and Spock — which creates more tie-breaking scenarios and makes the game more interesting to automate.

Game Logic with Win-Lose Matrices

The cleanest way to handle Rock Paper Scissors logic in Python is with a win-lose matrix. Think of it as a lookup table where you check the cell at [player_move, computer_move]. If the value matches the player_move index, the player wins. If it matches the computer_move index, the computer wins. If the value is -1, it is a tie.

The mapping between moves and numbers uses a Python dictionary. This gives you a human-readable way to convert inputs like “rock” into the numeric index 0, which you then use to look up results in the matrix. The same function works for both game versions — you just pass in a different table.

Rock Paper Scissors Lizard Spock diagram

import random

game_map = {0: "rock", 1: "paper", 2: "scissors", 3: "lizard", 4: "Spock"}

rps_table = [[-1, 1, 0],
             [1, -1, 2],
             [0, 2, -1]]

rpsls_table = [[-1, 1, 0, 0, 4],
               [1, -1, 2, 3, 1],
               [0, 2, -1, 2, 4],
               [0, 3, 2, -1, 3],
               [4, 1, 4, 3, -1]]


game_map[0] = 'rock'
game_map[3] = 'lizard'
game_map[4] = 'Spock'

rps_table[0][1] = 1   # Player=rock(0), Computer=paper(1) -> computer wins
rps_table[2][0] = 0   # Player=scissors(2), Computer=rock(0) -> player wins
rps_table[1][1] = -1  # paper vs paper -> tie

The 5×5 matrix for the extended version captures all the relationships from the show. Lizard poisons Spock, Spock smashes scissors, and so on. Each cell tells you who wins the matchup between any two moves. The -1 sentinel value means a tie — it does not match any valid move index.

Building the Game Loop

Every interactive game needs a loop that keeps running until the player decides to quit. In Python, a while loop with a break condition handles this cleanly. The loop displays a menu, reads the player choice, and calls the appropriate function for each game version.


def main_menu():
    while True:
        print()
        print("Let's Play!!!")
        print("Which version of Rock-Paper-Scissors?")
        print("Enter 1 to play Rock-Paper-Scissors")
        print("Enter 2 to play Rock-Paper-Scissors-Lizard-Spock")
        print("Enter 3 to quit")
        print()

        try:
            choice = int(input("Enter your choice = "))
        except ValueError:
            print("Invalid input. Please enter a number.")
            continue

        if choice == 1:
            play_rps()
        elif choice == 2:
            play_rpsls()
        elif choice == 3:
            print("Thanks for playing!")
            break
        else:
            print("Wrong choice. Enter 1, 2, or 3.")


Let's Play!!!
Which version of Rock-Paper-Scissors?
Enter 1 to play Rock-Paper-Scissors
Enter 2 to play Rock-Paper-Scissors-Lizard-Spock
Enter 3 to quit

Enter your choice = 1

The try/except block catches the ValueError that happens when someone types “hello” instead of a number. Without this, the program crashes on bad input. The continue statement sends the loop back to the top, prompting again instead of crashing. The if-elif pattern for move matching follows the same logic.

Handling Player Input

The player-facing function reads a string, converts it to the numeric mapping, and passes those numbers to the win-lose matrix. I convert the input to lowercase so “Rock”, “ROCK”, and “rock” all work the same way. Special cases for “help” and “exit” let players get instructions or quit from within any game without breaking the outer loop.


def get_player_move():
    print("Enter your move (or 'help' for instructions, 'exit' to quit):")
    inp = input("Enter your move: ").lower().strip()

    if inp == "help":
        return "help"
    elif inp == "exit":
        return "exit"
    elif inp == "rock":
        return 0
    elif inp == "paper":
        return 1
    elif inp == "scissors":
        return 2
    elif inp == "lizard":
        return 3
    elif inp == "spock":
        return 4
    else:
        return None


Enter your move (or 'help' for instructions, 'exit' to quit):
Enter your move: paper
Returned: 1

Enter your move: spock
Returned: 4

Enter your move: hello
Returned: None

This function returns None for invalid input, which the calling code checks to display an error message and prompt again.

Generating Computer Moves and Determining Winners

The computer move comes from Pythons random.randint() function. For the classic version, the range is 0 to 2. For the extended version, it is 0 to 4. This gives each move an equal probability, which is exactly what you want for fair gameplay.


def get_computer_move(version="rps"):
    if version == "rps":
        return random.randint(0, 2)
    else:
        return random.randint(0, 4)


def determine_winner(player_move, computer_move, table):
    result = table[player_move][computer_move]
    if result == player_move:
        return "player"
    elif result == computer_move:
        return "computer"
    else:
        return "tie"


get_computer_move("rps") -> 0, 1, or 2 (random)
get_computer_move("rpsls") -> 0, 1, 2, 3, or 4 (random)

determine_winner(0, 1, rps_table) -> "computer"  # rock vs paper
determine_winner(2, 1, rps_table) -> "player"  # scissors vs paper
determine_winner(1, 1, rps_table) -> "tie"      # paper vs paper

The table is passed in as a parameter so the same function works for both the 3×3 classic matrix and the 5×5 extended matrix. This is a small example of the kind of abstraction that makes code easier to extend and test.

Complete Game Implementation

Putting it all together, here is the full working implementation for both versions of the game. The play_rps() and play_rpsls() functions each run their own inner loop, separate from the main menu loop.


import random
import time
import os

def clear_screen():
    os.system('cls' if os.name == 'nt' else 'clear')

def show_instructions(game_type="rps"):
    if game_type == "rps":
        print("Rock crushes Scissors")
        print("Scissors cuts Paper")
        print("Paper covers Rock")
    else:
        print("Scissors cuts Paper")
        print("Paper covers Rock")
        print("Rock crushes Lizard")
        print("Lizard poisons Spock")
        print("Spock smashes Scissors")
        print("Scissors decapitates Lizard")
        print("Lizard eats Paper")
        print("Paper disproves Spock")
        print("Spock vaporizes Rock")
        print("Rock crushes Scissors")

game_map = {0: "rock", 1: "paper", 2: "scissors", 3: "lizard", 4: "Spock"}
rps_table = [[-1, 1, 0], [1, -1, 2], [0, 2, -1]]
rpsls_table = [[-1, 1, 0, 0, 4], [1, -1, 2, 3, 1],
               [0, 2, -1, 2, 4], [0, 3, 2, -1, 3],
               [4, 1, 4, 3, -1]]

def play_rps():
    while True:
        print("--- Rock-Paper-Scissors ---")
        inp = input("Enter move (rock/paper/scissors), 'help', or 'exit': ").lower().strip()
        if inp == "exit":
            break
        if inp == "help":
            show_instructions("rps")
            continue
        if inp == "rock":
            player_move = 0
        elif inp == "paper":
            player_move = 1
        elif inp == "scissors":
            player_move = 2
        else:
            print("Invalid move.")
            continue

        comp_move = random.randint(0, 2)
        print(f"Computer chooses: {game_map[comp_move].upper()}")
        result = rps_table[player_move][comp_move]
        if result == player_move:
            print("YOU WIN!")
        elif result == comp_move:
            print("COMPUTER WINS!")
        else:
            print("TIE GAME!")
        time.sleep(1)
        clear_screen()

def play_rpsls():
    while True:
        print("--- Rock-Paper-Scissors-Lizard-Spock ---")
        inp = input("Enter move, 'help', or 'exit': ").lower().strip()
        if inp == "exit":
            break
        if inp == "help":
            show_instructions("rpsls")
            continue
        move_map = {"rock": 0, "paper": 1, "scissors": 2, "lizard": 3, "spock": 4}
        if inp not in move_map:
            print("Invalid move.")
            continue
        player_move = move_map[inp]
        comp_move = random.randint(0, 4)
        print(f"Computer chooses: {game_map[comp_move].upper()}")
        result = rpsls_table[player_move][comp_move]
        if result == player_move:
            print("YOU WIN!")
        elif result == comp_move:
            print("COMPUTER WINS!")
        else:
            print("TIE GAME!")
        time.sleep(1)
        clear_screen()

def main_menu():
    while True:
        print("Which version?")
        print("1: Rock-Paper-Scissors")
        print("2: Rock-Paper-Scissors-Lizard-Spock")
        print("3: Quit")
        try:
            choice = int(input("Enter choice: "))
        except ValueError:
            print("Enter a number.")
            continue
        if choice == 1:
            play_rps()
        elif choice == 2:
            play_rpsls()
        elif choice == 3:
            print("Thanks for playing!")
            break
        else:
            print("Enter 1, 2, or 3.")

if __name__ == "__main__":
    main_menu()


Which version?
1: Rock-Paper-Scissors
2: Rock-Paper-Scissors-Lizard-Spock
3: Quit
Enter choice: 1
--- Rock-Paper-Scissors ---
Enter move (rock/paper/scissors), 'help', or 'exit': rock
Computer chooses: SCISSORS
YOU WIN!

The time.sleep(1) call pauses briefly after each round so the player can see the result before the screen clears for the next round. This creates a clean rhythm of play without requiring the player to scroll through a long history of moves.

FAQ

Q: Why use a win-lose matrix instead of if-elif conditions?

The matrix approach scales better as you add moves. With three moves, you can write six if-elif branches. With five moves, you need twenty-five comparisons. The matrix handles any number of moves with the same two-line lookup.

Q: Could the computer be made to learn from player patterns?

A common approach is to track the frequency of each player move over time and have the computer bias its selection toward whatever beats the most common player choice. This requires a history list and a weighted random selection instead of random.randint().

Q: How do you add a score tracker?

Declare a dictionary scores = {“player”: 0, “computer”: 0, “tie”: 0} before the game loop. After each round, increment the appropriate key based on the result. Print the dictionary at the end when the player quits.

Q: What does the -1 mean in the win-lose matrix?

The -1 is a sentinel value that means tie. It does not match any valid move index (0, 1, 2 for classic, 0-4 for extended), so the code treats it as a draw. Any negative number works here, but -1 is the conventional choice for a sentinel in Python.

Q: Can this run in a web browser instead of the terminal?

Yes, with a web framework like Flask or FastAPI. The input() calls become form submissions and print statements become HTML responses. The game logic functions (determine_winner, show_instructions) do not need to change — only the input and output layer changes.

I kept the game logic deliberately simple. The win-lose matrix approach is easy to understand, easy to extend, and easy to test. Once the basic structure works, adding features like score tracking, player history, or a GUI becomes a matter of adding code rather than rewriting what you already have.

Aprataksh Anand
Aprataksh Anand

A Computer Science Engineer, content writing is Aprataksh's first rodeo. A passionate coder along with a love for gaming, he can be found passing his free time with friends in a CS:GO lobby.

Articles: 13