Skip to content

Dice Rolling Simulator

Abstract

A Dice Rolling Simulator is one of the most rewarding beginner projects because it does something visibly random — every run produces a different result. Under the hood it teaches you four core skills you will use forever: importing modules from the standard library, defining your own functions, looping until a condition changes, and parsing user input.

In this project we build a dice roller that:

  • Rolls a virtual six-sided die and prints the result.
  • Lets the user roll again or quit at any time.
  • Is easy to extend to support multiple dice, dice of any number of sides (d4, d20, d100), and even Dungeons & Dragons style notation like 3d6+23d6+2.

By the end you will understand random.randintrandom.randint, function definitions with defdef, the while Truewhile True loop, and how a tiny script grows into a real tool.

Prerequisites

  • Python 3.6 or above (download).
  • A code editor or IDE (VS Code, PyCharm, Sublime, etc.).
  • Completed the Hello World project — or general comfort running a .py.py file.
  • No external libraries are needed; randomrandom ships with Python.

Concepts You Will Use

Before writing code, here is what each piece does conceptually:

  • randomrandom module — Python’s standard library tool for pseudo-random numbers. It can pick integers, floats, items from a list, shuffle a sequence, and more.
  • random.randint(a, b)random.randint(a, b) — returns a random integer NN such that a ≤ N ≤ ba ≤ N ≤ b. Both endpoints are inclusive, which is exactly what you want for dice (1 and 6 are valid rolls).
  • defdef — defines a function. Functions let you give a name to a block of code so you can reuse it.
  • while True:while True: — an infinite loop. You leave it with a breakbreak statement when the user wants to quit.
  • input(prompt)input(prompt) — pauses the program, prints a prompt, waits for the user to press Enter, returns whatever they typed as a string.
  • .lower().lower() — converts a string to lowercase, so "Y""Y" and "y""y" both behave the same way.

Getting Started

Create the project

  1. Create a folder named dice-rolling-simulatordice-rolling-simulator.
  2. Open it in your code editor.
  3. Inside, create a file called dicerolling.pydicerolling.py.

Write the code

Add the following to dicerolling.pydicerolling.py:

Dice RollingSource
Dice Rolling
# Dice Rolling Simulator
 
# Import random module
import random
 
# Creating a function to roll the dice
def roll():
    return random.randint(1, 6)
 
# Rolling the dice
while True:
    print(f"You rolled {roll()}")
    play_again = input("Do you want to roll again? (y/n): ")
    if play_again.lower() != "y":
        break
    
print("Thanks for playing!")
Dice Rolling
# Dice Rolling Simulator
 
# Import random module
import random
 
# Creating a function to roll the dice
def roll():
    return random.randint(1, 6)
 
# Rolling the dice
while True:
    print(f"You rolled {roll()}")
    play_again = input("Do you want to roll again? (y/n): ")
    if play_again.lower() != "y":
        break
    
print("Thanks for playing!")

Save the file. Open the integrated terminal and run:

command
C:\Users\username\PythonCentralHub\projects\beginners\dice-rolling-simulator> python dicerolling.py
You rolled 4
Do you want to roll again? (y/n): y
You rolled 5
Do you want to roll again? (y/n): y
You rolled 5
Do you want to roll again? (y/n): n
Thanks for playing!
command
C:\Users\username\PythonCentralHub\projects\beginners\dice-rolling-simulator> python dicerolling.py
You rolled 4
Do you want to roll again? (y/n): y
You rolled 5
Do you want to roll again? (y/n): y
You rolled 5
Do you want to roll again? (y/n): n
Thanks for playing!

Run it several times. Notice the numbers change each run — that is the random module doing its job.

Step-by-Step Explanation

1. Import the random module

dicerolling.py
import random
dicerolling.py
import random

importimport makes another module’s code available in yours. After this line, anything inside the randomrandom module is reachable as random.somethingrandom.something. The module itself lives somewhere in your Python installation; you do not need to know where.

2. Define the roll function

dicerolling.py
def roll():
    return random.randint(1, 6)
dicerolling.py
def roll():
    return random.randint(1, 6)
  • def roll():def roll(): declares a new function named rollroll that takes no arguments.
  • The indented body is the function’s code.
  • returnreturn hands a value back to whoever called the function. Here we return a fresh random integer between 1 and 6.
  • Calling roll()roll() now feels like rolling a real die — you do not care how it gets a number, only that you get one.

Why bother with a function for one line? Because:

  • It documents intent: reading roll()roll() is clearer than reading random.randint(1, 6)random.randint(1, 6) everywhere.
  • It is easy to change: if you later want a 20-sided die, you change one line.

3. Loop until the user stops

dicerolling.py
while True:
    print(f"You rolled {roll()}")
    play_again = input("Do you want to roll again? (y/n): ")
    if play_again.lower() != "y":
        break
dicerolling.py
while True:
    print(f"You rolled {roll()}")
    play_again = input("Do you want to roll again? (y/n): ")
    if play_again.lower() != "y":
        break
  • while True:while True: starts a loop that never ends on its own — you exit it manually.
  • print(f"You rolled {roll()}")print(f"You rolled {roll()}") is an f-string. The ff prefix lets you embed expressions inside { }{ }. roll()roll() is called, the result is converted to text, and inserted into the message.
  • input(...)input(...) shows the prompt and waits.
  • play_again.lower() != "y"play_again.lower() != "y" is the check: if the user did not type yy (in any case), breakbreak immediately exits the loop.

4. Say goodbye

dicerolling.py
print("Thanks for playing!")
dicerolling.py
print("Thanks for playing!")

This line runs once, after the loop ends.

Pseudo-Random, Not Truly Random

random.randintrandom.randint does not produce truly random numbers — computers cannot. It produces pseudo-random numbers from a deterministic algorithm seeded by the current time. For dice this is more than fine. For cryptography, banking, or anything security-sensitive, use the secretssecrets module instead.

You can demonstrate the determinism yourself:

seeded.py
import random
random.seed(42)
print(random.randint(1, 6))   # Always prints the same number for seed 42
seeded.py
import random
random.seed(42)
print(random.randint(1, 6))   # Always prints the same number for seed 42

Seeding is useful for reproducible test runs.

Common Mistakes

ProblemFix
NameError: name 'random' is not definedNameError: name 'random' is not definedYou forgot import randomimport random at the top.
The program crashes after one rollYou used if play_again == "y": breakif play_again == "y": break — backwards logic. Use != "y"!= "y" to break.
The program never quits no matter what you type"Y""Y" is not equal to "y""y". Use .lower().lower() to normalize.
random.randint(1, 6.0)random.randint(1, 6.0) errors with TypeErrorTypeErrorrandintrandint needs integers — use random.uniform(1, 6)random.uniform(1, 6) for floats.

Variations to Try

1. Roll multiple dice at once

multi_dice.py
def roll_dice(count):
    return [random.randint(1, 6) for _ in range(count)]
 
results = roll_dice(3)
print(f"You rolled: {results}  total: {sum(results)}")
multi_dice.py
def roll_dice(count):
    return [random.randint(1, 6) for _ in range(count)]
 
results = roll_dice(3)
print(f"You rolled: {results}  total: {sum(results)}")

A list comprehension generates countcount rolls in one line, and sum()sum() totals them.

2. Dice with any number of sides

any_sides.py
def roll(sides=6):
    return random.randint(1, sides)
 
print(roll())      # six-sided
print(roll(20))    # twenty-sided
print(roll(100))   # percentile die
any_sides.py
def roll(sides=6):
    return random.randint(1, sides)
 
print(roll())      # six-sided
print(roll(20))    # twenty-sided
print(roll(100))   # percentile die

sides=6sides=6 is a default argument — if the caller does not specify a value, Python uses 6.

3. Parse D&D-style notation (3d6+23d6+2)

dnd_notation.py
import re, random
 
def parse_and_roll(notation):
    match = re.fullmatch(r"(\d+)d(\d+)([+-]\d+)?", notation.replace(" ", ""))
    if not match:
        raise ValueError(f"Bad notation: {notation}")
    count, sides, modifier = match.groups()
    rolls = [random.randint(1, int(sides)) for _ in range(int(count))]
    total = sum(rolls) + (int(modifier) if modifier else 0)
    return rolls, total
 
print(parse_and_roll("3d6+2"))
# Example output: ([4, 2, 5], 13)
dnd_notation.py
import re, random
 
def parse_and_roll(notation):
    match = re.fullmatch(r"(\d+)d(\d+)([+-]\d+)?", notation.replace(" ", ""))
    if not match:
        raise ValueError(f"Bad notation: {notation}")
    count, sides, modifier = match.groups()
    rolls = [random.randint(1, int(sides)) for _ in range(int(count))]
    total = sum(rolls) + (int(modifier) if modifier else 0)
    return rolls, total
 
print(parse_and_roll("3d6+2"))
# Example output: ([4, 2, 5], 13)

This shows how a simple project naturally grows into using regular expressions and error handling.

4. ASCII-art dice faces

ascii_dice.py
FACES = {
    1: ("┌─────────┐", "│         │", "│    ●    │", "│         │", "└─────────┘"),
    2: ("┌─────────┐", "│  ●      │", "│         │", "│      ●  │", "└─────────┘"),
    # …add 3, 4, 5, 6
}
 
for line in FACES[roll()]:
    print(line)
ascii_dice.py
FACES = {
    1: ("┌─────────┐", "│         │", "│    ●    │", "│         │", "└─────────┘"),
    2: ("┌─────────┐", "│  ●      │", "│         │", "│      ●  │", "└─────────┘"),
    # …add 3, 4, 5, 6
}
 
for line in FACES[roll()]:
    print(line)

A dictionary maps each face to its ASCII art.

5. Statistics over many rolls

stats.py
from collections import Counter
rolls = [random.randint(1, 6) for _ in range(10_000)]
print(Counter(rolls))
stats.py
from collections import Counter
rolls = [random.randint(1, 6) for _ in range(10_000)]
print(Counter(rolls))

You should see roughly 1666 occurrences of each face — proof the distribution is uniform.

Real-World Applications

  • Tabletop games — replace lost dice, run probability experiments before a game session.
  • Monte Carlo simulations — repeated random sampling is the core of physics and finance simulations.
  • Game development — every roguelike, RPG, or loot system relies on randomness.
  • Teaching probability — let students see the law of large numbers by rolling 100,000 dice.

Features

  • Uses Python’s standard library only — no installs.
  • Demonstrates functions, loops, conditionals, and input parsing in under 15 lines.
  • Easy to extend: any-sided dice, multiple dice, parsed notation, GUI.

Next Steps

You now have a working dice simulator. Push yourself with one of these challenges:

  • Add an average that updates after each roll.
  • Add a history of the last 10 rolls.
  • Wrap it in a Tkinter GUI with a big “Roll!” button.
  • Build a web version with Flask that lets a remote player roll dice in a shared room.
  • Turn it into a probability tool that calculates the chance of beating a target with N dice of S sides.

Try it here

Click to roll two dice — each shows 1–6 pips and the total is their sum, exactly what your random.randint(1, 6)random.randint(1, 6) calls produce:

sketch Roll the dice p5.js
Click to roll two dice. Each die shows 1-6 pips; the total is their sum.

Conclusion

In this project you used Python’s randomrandom module to simulate dice, learned to encapsulate behavior in a function, looped until the user wanted to stop, and saw several directions to extend the program. The dice roller is small but it touches every fundamental you need for bigger projects. Find more beginner projects on Python Central Hub and keep that momentum going.

If this helped you, consider buying me a coffee ☕

Buy me a coffee

Was this page helpful?

Let us know how we did