Python Projects

Rock Paper Scissors Game in Python Using Tkinter (With Source Code & Step-by-Step Guide)

Rock Paper Scissors Game in Python Using Tkinter is a great beginner-friendly GUI project that helps you understand how real desktop applications work. While many Python tutorials start with simple command-line programs, building a graphical interface makes your projects more interactive and practical. With Tkinter, Python’s built-in GUI library, you can easily create windows, buttons, labels, and event-driven applications.

In this tutorial, you will learn how to build a Rock Paper Scissors Game in Python Using Tkinter from scratch. The application will include a clean graphical interface, interactive buttons for choosing moves, a difficulty selection system, score tracking, and a special one-time Power Up feature. Along the way, you will also understand important programming concepts such as event handling, game logic, UI updates, and state management.

What You Will Build

By the end of this guide, you will have a fully functional GUI game that you can run on your computer, modify with new features, and even include in your Python project portfolio.

The best part is that you’ll build this in a single file with standard Python—no extra frameworks needed—and you’ll be able to run it as a normal .py script.

About Game

Rock Paper Scissors is a simple and popular hand game usually played between two players. Each player simultaneously chooses one of three options: Rock, Paper, or Scissors. The winner is decided based on simple rules: Rock beats Scissors, Scissors beats Paper, and Paper beats Rock. If both players choose the same option, the round results in a tie. In this Python Tkinter version of the game, the user selects one option using buttons, and the computer randomly chooses its move. The program then compares both choices, determines the winner, and updates the score accordingly.

Prerequisites

You do not need advanced Python to build this project, but you should be comfortable with basic programming concepts: variables, functions, conditionals (if/else), importing modules, and running a Python file from your editor or terminal. If you have never used Tkinter before, that’s absolutely fine—this tutorial introduces the key concepts as you go.

For tools, you can use any Python editor, but a few common options are VS Code, PyCharm, or Anaconda (though GUI apps are best run as a Python script rather than inside a notebook). The important part is that you can execute a .py file. Tkinter comes bundled with most standard Python installations, so you usually don’t need to install anything extra.

Full Project Structure

This project is intentionally kept simple and beginner‑friendly, so it uses a single file:

  • rock_paper_scissors.py

In professional projects, you might separate UI, constants, and game logic into multiple modules. But for learning—and for publishing a tutorial—one file is ideal because readers can copy, run, and understand everything in one place.

Import Libraries (Why Each Import Matters)

At the very top of the file, we import what we need. These imports are not random—they directly map to features in the application.

import random
import tkinter as tk
from tkinter import messagebox
Code language: JavaScript (javascript)

The random module gives us randomness so the computer can choose different moves each round. tkinter is Python’s standard GUI toolkit; it provides the window, widgets (labels and buttons), and layout system. We import it as tk purely for convenience, because tk.Label and tk.Button are easier to read than tkinter.Label and tkinter.Button. Finally, messagebox gives us simple popup dialogs to show round results and game over messages—these popups make the app feel more interactive and “game‑like” compared to printing text in a console.

If you are a student, it’s worth remembering that most GUI apps are built from small building blocks like this: an interface layer (Tkinter), a bit of randomness for game behavior, and a messaging layer for feedback.

Define Game Constants and Global State (Understanding What Changes)

Before we build the UI, we define the game’s choices, difficulty configuration, and state variables. This section is the “data foundation” of the whole program.

choices = ["Rock", "Paper", "Scissors"]
levels = {"Easy": 0.3, "Medium": 0.5, "Hard": 0.7}  # Probability of AI playing optimally
player_score = 0
computer_score = 0
power_up_used = False
Code language: PHP (php)

The choices list is straightforward: these are the three valid moves. The levels dictionary deserves a deeper explanation because it is where difficulty is modeled. Each difficulty label maps to a probability between 0 and 1. When the computer chooses a move, the program uses this probability to decide whether the AI should attempt an “optimal” play or simply pick randomly. On Easy, the computer behaves more randomly; on Hard, it is more likely to behave strategically (in this code, the strategy function is a placeholder, and you can improve it later).

The variables player_score, computer_score, and power_up_used represent the mutable state of the game. These values change while the program is running, and the UI must reflect their current values at all times. In many production designs, developers store this state on the class instance (self.player_score), because global state can become harder to manage as projects grow. However, for a learning project, global state is acceptable because the app is small and easy to reason about.

A useful mental model is: constants like choices rarely change, configuration like levels usually stays stable, but scores and flags change continuously as the user plays.

Build a Class‑Based Tkinter App (Why Use a Class?)

The next step is organizing the application into a class. A class helps keep UI elements and logic together, and it makes the code easier to expand later.

class RockPaperScissorsGame:
    def __init__(self, root):
        self.root = root
        self.root.title("Rock Paper Scissors Game")
        self.root.geometry("500x500")
        self.level = "Medium"
        self.create_widgets()

The __init__ method runs once when we create the game. It receives root, which is the main Tkinter window, and stores it as self.root so every other method can use it. It sets a title to make the window look professional, sets a fixed window size, chooses a default difficulty, and then calls create_widgets().

From a software design perspective, this is a clean pattern: initialization sets up the environment, then UI creation happens in a dedicated method. That separation is valuable because it keeps your constructor short and readable.

Create Widgets and Layout (How the GUI Is Built)

Tkinter apps are made by creating widgets (Label, Button, Frame, OptionMenu) and placing them on the window using geometry managers like pack() and grid(). In this project, we use both: pack() for vertical stacking and grid() for arranging the move buttons in a row.

Title Label

self.label = tk.Label(self.root, text="Rock, Paper, Scissors!", font=("Arial", 16))
self.label.pack(pady=10)
Code language: PHP (php)

This label gives the app a clear heading. The font option makes it readable, and pady=10 adds breathing space. Small layout touches like padding make the UI feel more polished—this is something both students and professional developers should practice.

Difficulty Dropdown with StringVar

self.level_var = tk.StringVar(value=self.level)
tk.Label(self.root, text="Select Difficulty Level:").pack()
tk.OptionMenu(self.root, self.level_var, *levels.keys()).pack()
Code language: PHP (php)

Tkinter uses special variable wrappers like StringVar to connect widget values to your program. Here, self.level_var stores the selected difficulty. The OptionMenu displays the keys from the levels dictionary (Easy, Medium, Hard), and because it is bound to StringVar, the current selection can be read anytime using self.level_var.get().

This approach is important in GUI programming: widgets have their own state, and you need a clean way to retrieve it. Using StringVar avoids messy manual tracking.

Score Label (Keeping UI in Sync)

self.score_label = tk.Label(
    self.root,
    text=f"Score - You: {player_score} | Computer: {computer_score}",
    font=("Arial", 12)
)
self.score_label.pack(pady=10)
Code language: PHP (php)

This label displays a “live” view of the scores. At the start, both are zero. After each round, we update this label using .config(...). In real applications, this pattern is everywhere: store data in variables, then update UI elements when that data changes.

Move Buttons in a Frame (Why Frame + Grid?)

self.buttons_frame = tk.Frame(self.root)
self.buttons_frame.pack()

self.rock_btn = tk.Button(self.buttons_frame, text="Rock", command=lambda: self.play("Rock"))
self.rock_btn.grid(row=0, column=0, padx=5, pady=5)

self.paper_btn = tk.Button(self.buttons_frame, text="Paper", command=lambda: self.play("Paper"))
self.paper_btn.grid(row=0, column=1, padx=5, pady=5)

self.scissors_btn = tk.Button(self.buttons_frame, text="Scissors", command=lambda: self.play("Scissors"))
self.scissors_btn.grid(row=0, column=2, padx=5, pady=5)
Code language: PHP (php)

We use a Frame as a container so that the move buttons can be managed together. Inside the frame, we use grid() because grid is excellent for row/column layouts. This is a good example of mixing layout systems correctly: use pack() for big vertical sections, then use grid() inside a frame for structured groups.

Each button uses command=lambda: self.play("Rock") (or Paper/Scissors). The lambda is critical because command=self.play("Rock") would call the function immediately when the UI loads. Instead, the lambda delays execution until the user clicks the button, which is exactly what we want in event‑driven programming.

Power Up, New Game, and Exit Buttons

self.power_up_btn = tk.Button(self.root, text="Power Up (1-time Use)", command=self.use_power_up)
self.power_up_btn.pack(pady=10)

self.restart_btn = tk.Button(self.root, text="New Game", command=self.restart_game)
self.restart_btn.pack()

self.quit_btn = tk.Button(self.root, text="Exit", command=self.exit_game)
self.quit_btn.pack()
Code language: PHP (php)

These buttons turn a basic game into a more complete “application.” The Power Up introduces a special feature and teaches you how to enable/disable widgets dynamically. New Game teaches how to reset state and refresh UI. Exit demonstrates clean shutdown. Together, they represent the kind of UX features users expect even in small apps.

Exiting the Application (A Clean Close)

def exit_game(self):
    self.root.destroy()
Code language: CSS (css)

destroy() closes the Tkinter window and ends the program gracefully. In desktop apps, it’s always better to close cleanly than to force‑kill the process, because it ensures resources are released and the event loop stops properly.

The Core Gameplay Loop (What Happens When You Click a Move)

In GUI programs, there is no traditional while loop that continuously asks for input. Instead, Tkinter runs an event loop (mainloop) and waits for user actions. The play() method is called when the user clicks Rock, Paper, or Scissors. This method coordinates the entire round.

def play(self, player_choice):
    global player_score, computer_score, power_up_used
    computer_choice = self.get_computer_choice()
    result = self.determine_winner(player_choice, computer_choice)

    if result == "win":
        player_score += 1
    elif result == "lose":
        computer_score += 1

    self.score_label.config(text=f"Score - You: {player_score} | Computer: {computer_score}")
    messagebox.showinfo("Round Result", f"You chose {player_choice}
Computer chose {computer_choice}
Result: You {result}!")

    if player_score == 5:
        messagebox.showinfo("Game Over", "Congratulations! You won!")
        self.restart_game()
    elif computer_score == 5:
        messagebox.showinfo("Game Over", "Game Over! The computer won!")
        self.restart_game()
Code language: PHP (php)

Let’s break the flow down in a way that is easy to visualize. First, the player’s move is already known because the button click passed it in. Next, the computer’s move is generated based on the selected difficulty. After that, the program calculates the winner using a dedicated rules function. If the player wins, the player score increases; if the player loses, the computer score increases; ties do not change scores. Then the UI is updated immediately by changing the score label text. Finally, a popup explains the round result so the user doesn’t have to guess what happened.

The last part checks for a “match end” condition: if either score reaches 5, the game announces the winner and resets. This is a clean approach because it keeps the rule “first to 5 wins” in one place, and it ensures the UI and state always remain consistent.

Computer Choice and Difficulty (Probability‑Driven Behavior)

The function below is where the difficulty level actually affects gameplay.

def get_computer_choice(self):
    difficulty = self.level_var.get()
    if random.random() < levels[difficulty]:  # AI plays optimally sometimes
        return self.optimal_choice()
    return random.choice(choices)
Code language: PHP (php)

self.level_var.get() reads whatever the user selected in the dropdown. Then we generate a random number between 0 and 1. If that random number is less than the probability stored in levels[difficulty], the AI uses optimal_choice(). Otherwise, it chooses randomly.

This approach is a practical, beginner‑friendly way to simulate difficulty without writing complex AI. Even many commercial games use probability and weighting to create “believable” difficulty rather than pure perfect play.

About optimal_choice() in this code

def optimal_choice(self):
    return random.choice(choices)
Code language: PHP (php)

In the current version, optimal_choice() still returns random moves. Think of it as a placeholder or a “hook” where you can later add smarter AI logic. For example, you could track the player’s last move and bias the computer toward the counter move, or keep frequency counts of player moves and adapt over time. Professionals often design systems with hooks like this because it makes future upgrades simpler.

Winner Decision Function (Encapsulating Game Rules)

def determine_winner(self, player, computer):
    if player == computer:
        return "tied"
    if (player == "Rock" and computer == "Scissors") or \
       (player == "Paper" and computer == "Rock") or \
       (player == "Scissors" and computer == "Paper"):
        return "win"
    return "lose"
Code language: PHP (php)

This method isolates the rules of Rock Paper Scissors so they don’t get mixed into UI code. That separation is important: you should be able to test rule logic without touching GUI. The logic checks for a tie first because that is the simplest condition. Then it checks the three player‑win combinations. If the round is not a tie and not a win, then by elimination it must be a loss.

For students, this is a good example of writing clear conditional logic. For developers, it highlights a simple “rules engine” pattern—small, deterministic function, easy to unit test later.

One‑Time Power Up (State Flags and Button Disabling)

Power ups are common in games because they add strategy and variety. In this project, the Power Up is a one‑time action per match. Implementing it teaches you two valuable GUI skills: using a boolean flag to prevent repeated use, and disabling a button so the UI matches the rules.

def use_power_up(self):
    global power_up_used
    if power_up_used:
        messagebox.showinfo("Power Up", "You have already used the Power Up!")
        return
    power_up_used = True
    self.play("Rock")  # Guarantees a win against Scissors
    messagebox.showinfo("Power Up", "Power Up used! You played Rock this turn.")
    self.power_up_btn.config(state=tk.DISABLED)
Code language: PHP (php)

The method first checks the flag. If the power up has already been used, the program shows a message and exits early. If not, it sets power_up_used = True so it cannot be used again. Then it triggers a round by calling self.play("Rock"), which means the player’s move is forced to Rock for that round. After the round ends, the button is disabled so the interface visually communicates the one‑time rule.

One important learning note: the comment says “guarantees a win against Scissors,” which is only true if the computer chooses Scissors. In practice, Rock can still lose to Paper. This is not a problem for the tutorial; it’s actually a good point for learners because it shows how a feature can be improved later (for example, by changing power up behavior to adapt to the computer’s move).

Restarting the Game (Resetting State + Refreshing UI)

Resetting a game is more than just setting scores to zero. You also have to restore UI elements to their initial state, like re‑enabling buttons.

def restart_game(self):
    global player_score, computer_score, power_up_used
    player_score = 0
    computer_score = 0
    power_up_used = False
    self.power_up_btn.config(state=tk.NORMAL)
    self.score_label.config(text=f"Score - You: {player_score} | Computer: {computer_score}")
Code language: PHP (php)

This method resets all state variables and brings the UI back to the starting conditions. The Power Up button is re‑enabled, and the score label is updated to show zeros again. This is a strong example of keeping state and UI synchronized—if you reset one but forget the other, the program becomes confusing or buggy.

Running the App (Understanding mainloop())

if __name__ == "__main__":
    root = tk.Tk()
    game = RockPaperScissorsGame(root)
    root.mainloop()
Code language: JavaScript (javascript)

This block is how you start the application. The tk.Tk() call creates the main window. Then we create our game class instance and pass the window into it so the class can attach widgets to it. Finally, root.mainloop() starts Tkinter’s event loop. Once mainloop is running, Tkinter listens for events like button clicks and dropdown changes, and it calls your functions (like play() and use_power_up()) when those events happen.

A common beginner mistake is forgetting mainloop(). Without it, the window may appear briefly and then close instantly because the script ends.

Full Code (Copy & Run)

Below is the complete code exactly as used in this tutorial.

import random
import tkinter as tk
from tkinter import messagebox

# Game variables
choices = ["Rock", "Paper", "Scissors"]
levels = {"Easy": 0.3, "Medium": 0.5, "Hard": 0.7}  # Probability of AI playing optimally
player_score = 0
computer_score = 0
power_up_used = False


class RockPaperScissorsGame:
    def __init__(self, root):
        self.root = root
        self.root.title("Rock Paper Scissors Game")
        self.root.geometry("500x500")
        self.level = "Medium"
        self.create_widgets()

    def create_widgets(self):
        self.label = tk.Label(self.root, text="Rock, Paper, Scissors!", font=("Arial", 16))
        self.label.pack(pady=10)

        self.level_var = tk.StringVar(value=self.level)
        tk.Label(self.root, text="Select Difficulty Level:").pack()
        tk.OptionMenu(self.root, self.level_var, *levels.keys()).pack()

        self.score_label = tk.Label(
            self.root,
            text=f"Score - You: {player_score} | Computer: {computer_score}",
            font=("Arial", 12)
        )
        self.score_label.pack(pady=10)

        self.buttons_frame = tk.Frame(self.root)
        self.buttons_frame.pack()

        self.rock_btn = tk.Button(self.buttons_frame, text="Rock", command=lambda: self.play("Rock"))
        self.rock_btn.grid(row=0, column=0, padx=5, pady=5)

        self.paper_btn = tk.Button(self.buttons_frame, text="Paper", command=lambda: self.play("Paper"))
        self.paper_btn.grid(row=0, column=1, padx=5, pady=5)

        self.scissors_btn = tk.Button(self.buttons_frame, text="Scissors", command=lambda: self.play("Scissors"))
        self.scissors_btn.grid(row=0, column=2, padx=5, pady=5)

        self.power_up_btn = tk.Button(self.root, text="Power Up (1-time Use)", command=self.use_power_up)
        self.power_up_btn.pack(pady=10)

        self.restart_btn = tk.Button(self.root, text="New Game", command=self.restart_game)
        self.restart_btn.pack()

        self.quit_btn = tk.Button(self.root, text="Exit", command=self.exit_game)
        self.quit_btn.pack()

    def exit_game(self):
        self.root.destroy()

    def play(self, player_choice):
        global player_score, computer_score, power_up_used
        computer_choice = self.get_computer_choice()
        result = self.determine_winner(player_choice, computer_choice)

        if result == "win":
            player_score += 1
        elif result == "lose":
            computer_score += 1

        self.score_label.config(text=f"Score - You: {player_score} | Computer: {computer_score}")
        messagebox.showinfo(
            "Round Result",
            f"You chose {player_choice}
Computer chose {computer_choice}
Result: You {result}!"
        )

        if player_score == 5:
            messagebox.showinfo("Game Over", "Congratulations! You won!")
            self.restart_game()
        elif computer_score == 5:
            messagebox.showinfo("Game Over", "Game Over! The computer won!")
            self.restart_game()

    def get_computer_choice(self):
        difficulty = self.level_var.get()
        if random.random() < levels[difficulty]:  # AI plays optimally sometimes
            return self.optimal_choice()
        return random.choice(choices)

    def optimal_choice(self):
        return random.choice(choices)

    def determine_winner(self, player, computer):
        if player == computer:
            return "tied"
        if (player == "Rock" and computer == "Scissors") or \
           (player == "Paper" and computer == "Rock") or \
           (player == "Scissors" and computer == "Paper"):
            return "win"
        return "lose"

    def use_power_up(self):
        global power_up_used
        if power_up_used:
            messagebox.showinfo("Power Up", "You have already used the Power Up!")
            return
        power_up_used = True
        self.play("Rock")  # Guarantees a win against Scissors
        messagebox.showinfo("Power Up", "Power Up used! You played Rock this turn.")
        self.power_up_btn.config(state=tk.DISABLED)

    def restart_game(self):
        global player_score, computer_score, power_up_used
        player_score = 0
        computer_score = 0
        power_up_used = False
        self.power_up_btn.config(state=tk.NORMAL)
        self.score_label.config(text=f"Score - You: {player_score} | Computer: {computer_score}")


if __name__ == "__main__":
    root = tk.Tk()
    game = RockPaperScissorsGame(root)
    root.mainloop()
Code language: HTML, XML (xml)

Leave a Comment