So, it's tabletop RPG night, and everyone else has their bag of dice, and you bring out—your laptop? Well, it's a perfect time to show off that dice roll simulator you built in Python. This beginner project should teach you the basics of dice rolls and randomization.
Setting Up Our Project
This is going to be a pretty simple setup. I've already described how we set up Visual Studio for coding with Python, so we'll use that for our basic setup. We'll keep this as simple as possible, so we'll start with our imports:
import tkinter as tkfrom tkinter import ttk
import random
We used Tkinter before when we built our simple expense tracker; in this case, it's the lightest solution. The library allows us to create a simple, modern, yet stylish GUI interface. The random library allows us the functionality for randomizing numbers between two values, which is the essence of a die roll. Let's make things a little prettier.
We're going to simulate a set of RPG dice. For stylistic flair, I made the four-sided dice (d4) and the six-sided dice (d6) a bit fancy to demonstrate ASCII art handling with Tkinter.
def __init__(self, root):self.root = root
self.root.title("Dice Simulator")
self.root.geometry("400x500")
# Dice face configurations using ASCII art
self.d4_faces = {
1: " ╱▲╲\n ╱ 1 ╲\n╱ ╲\n‾‾‾‾‾‾‾",
2: " ╱▲╲\n ╱ 2 ╲\n╱ ╲\n‾‾‾‾‾‾‾",
3: " ╱▲╲\n ╱ 3 ╲\n╱ ╲\n‾‾‾‾‾‾‾",
4: " ╱▲╲\n ╱ 4 ╲\n╱ ╲\n‾‾‾‾‾‾‾"
}
self.d6_faces = {
1: "┌─────────┐\n│ │\n│ ● │\n│ │\n└─────────┘",
2: "┌─────────┐\n│ ● │\n│ │\n│ ● │\n└─────────┘",
3: "┌─────────┐\n│ ● │\n│ ● │\n│ ● │\n└─────────┘",
4: "┌─────────┐\n│ ● ● │\n│ │\n│ ● ● │\n└─────────┘",
5: "┌─────────┐\n│ ● ● │\n│ ● │\n│ ● ● │\n└─────────┘",
6: "┌─────────┐\n│ ● ● │\n│ ● ● │\n│ ● ● │\n└─────────┘"
}
I opted for this because I felt a GUI would be a lot more enticing. When we did our quiz app, we stuck to the terminal, but I'd like a little bit of flair in this app. Don't get too worried about this piece of code. All it does is display our d6 with a face like an actual d6, and the same for a d4. This is also the start of our GUI, with the app title of "Dice Simulator" shown at the top of the window. Let's look at how we're going to do our input.
We want a place for the user to enter the number and type of dice they want to roll. To keep things simple, we'll be maxing out the dice at five, so anything more than that we won't handle. Our user interface will, therefore, have a die selector (for the dice type) and a number of dice entries if we're rolling more than one.
I also want to implement a history system for our rolls, so I'll put that in at the bottom and update it every time we roll a new set of dice. Our code snippet should look something like this:
# Create and configure the main frameself.main_frame = ttk.Frame(self.root, padding="10")
self.main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# Number of dice selector
ttk.Label(self.main_frame, text="Number of Dice:").grid(row=0, column=0, pady=5)
self.num_dice = ttk.Spinbox(self.main_frame, from_=1, to=5, width=5)
self.num_dice.set(1)
self.num_dice.grid(row=0, column=1, pady=5)
# Dice type selector
ttk.Label(self.main_frame, text="Dice Type:").grid(row=1, column=0, pady=5)
self.dice_type = ttk.Combobox(self.main_frame, values=["d4", "d6", "d8", "d12", "d20"], width=5)
self.dice_type.set("d6")
self.dice_type.grid(row=1, column=1, pady=5)
# Roll button
self.roll_button = ttk.Button(self.main_frame, text="Roll Dice!", command=self.roll_dice)
self.roll_button.grid(row=2, column=0, columnspan=2, pady=10)
# Results display
self.result_text = tk.Text(self.main_frame, height=15, width=40, font=('Courier', 10))
self.result_text.grid(row=3, column=0, columnspan=2, pady=10)
# History display
ttk.Label(self.main_frame, text="Roll History:").grid(row=4, column=0, columnspan=2, pady=5)
self.history_text = tk.Text(self.main_frame, height=5, width=40)
self.history_text.grid(row=5, column=0, columnspan=2, pady=5)
self.roll_history = []
# Create and configure the main frame
self.main_frame = ttk.Frame(self.root, padding="10")
self.main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# Number of dice selector
ttk.Label(self.main_frame, text="Number of Dice:").grid(row=0, column=0, pady=5)
self.num_dice = ttk.Spinbox(self.main_frame, from_=1, to=5, width=5)
self.num_dice.set(1)
self.num_dice.grid(row=0, column=1, pady=5)
# Dice type selector
ttk.Label(self.main_frame, text="Dice Type:").grid(row=1, column=0, pady=5)
self.dice_type = ttk.Combobox(self.main_frame, values=["d4", "d6", "d8", "d12", "d20"], width=5)
self.dice_type.set("d6")
self.dice_type.grid(row=1, column=1, pady=5)
# Roll button
self.roll_button = ttk.Button(self.main_frame, text="Roll Dice!", command=self.roll_dice)
self.roll_button.grid(row=2, column=0, columnspan=2, pady=10)
# Results display
self.result_text = tk.Text(self.main_frame, height=15, width=40, font=('Courier', 10))
self.result_text.grid(row=3, column=0, columnspan=2, pady=10)
# History display
ttk.Label(self.main_frame, text="Roll History:").grid(row=4, column=0, columnspan=2, pady=5)
self.history_text = tk.Text(self.main_frame, height=5, width=40)
self.history_text.grid(row=5, column=0, columnspan=2, pady=5)
self.roll_history = []
This gives us a basic UI setup for our dice-rolling app. Let's move on to simulating the rolls.
Rolling Dice Through a Computer
If you've ever played any RPG video game, you've experienced simulated dice rolls. Dice shape how tabletop RPGs play, their ebbs and flows and what happens to characters. A die roll is a randomized number produced between two values, minimum and maximum. In Python, we can use the random library to simulate those rolls. Here's our code:
def roll_single_die(self, sides):"""Simulate rolling a single die with given number of sides."""
return random.randint(1, sides)
def get_dice_face(self, value, dice_type):
"""Get ASCII art representation of a die face."""
if dice_type == 4:
return self.d4_faces.get(value, str(value))
elif dice_type == 6:
return self.d6_faces.get(value, str(value))
return str(value)
def roll_dice(self):
"""Handle the dice rolling action and update the display."""
try:
num_dice = int(self.num_dice.get())
dice_type = int(self.dice_type.get()[1:]) # Extract number from 'd6', 'd20' etc.
# Clear previous results
self.result_text.delete(1.0, tk.END)
# Roll the dice and calculate total
rolls = [self.roll_single_die(dice_type) for _ in range(num_dice)]
total = sum(rolls)
# Display results
if dice_type in [4, 6]: # Show ASCII art for d4 and d6
for roll in rolls:
self.result_text.insert(tk.END, self.get_dice_face(roll, dice_type) + "\n")
else:
roll_str = ", ".join(str(roll) for roll in rolls)
self.result_text.insert(tk.END, f"Rolls: {roll_str}\n")
self.result_text.insert(tk.END, f"\nTotal: {total}")
# Update history
roll_record = f"{num_dice}d{dice_type}: {rolls} = {total}"
self.roll_history.append(roll_record)
if len(self.roll_history) > 5: # Keep only last 5 rolls
self.roll_history.pop(0)
# Update history display
self.history_text.delete(1.0, tk.END)
for record in self.roll_history:
self.history_text.insert(tk.END, record + "\n")
except ValueError:
self.result_text.delete(1.0, tk.END)
self.result_text.insert(tk.END, "Please enter valid numbers")
This provides us with the basic code for simulating our dice rolls. To make the program run, we just need to call our core function:
if __name__ == "__main__":root = tk.Tk()
app = DiceSimulator(root)
root.mainloop()
And that should do it. We now have a dice roll simulator!
When you run the app, you should see something like this:
The app has a few limitations that I intentionally left out to offer you something to expand the app with. There's no support for d10s or d100s (which can be simulated by either rolling 2d10 or rolling 1d100). You can add those yourself if you want to expand the app. Additionally, you could extend this into a networked dice roller and make the UI a bit prettier with some skinning. As usual, all the code for this app is available on my GitHub. Any comments about the code or my approach are always welcome.