Coin Toss & Dice Roll Probability Simulator – A Fun Desktop App to Learn Probability

 

Coin Toss & Dice Roll Probability Simulator – A Fun Desktop App to Learn Probability

Probability is one of the most exciting concepts in mathematics. Whether you’re flipping a coin, rolling a dice, or predicting uncertain outcomes, probability helps us understand how likely an event is to happen. To make learning probability more engaging, I created a simple and interactive Coin Toss & Dice Roll Probability Simulator using Python. This desktop app is perfect for students, teachers, data science beginners, and anyone who wants to explore randomness in a visual and enjoyable way.


What Is This Simulator?

The Coin Toss & Dice Roll Probability Simulator is a lightweight Python desktop application that lets you run thousands of random trials to study probability. You can simulate:

  • Coin Tosses (with fair or biased coins)

  • Dice Rolls (with any number of sides—6, 8, 10, 20, etc.)

  • Multiple Dice at once

  • Large numbers of simulations (100 to 100,000+)

The app visually displays the results using bar charts drawn on a built-in canvas, so you can instantly see how randomness behaves as the number of trials increases.


Why I Built This App

Many beginners struggle with understanding probability because real-life randomness does not always match our expectations. For example:

  • You may get 8 Heads out of 10 flips

  • You may roll the number “4” five times in a row

  • Small sample sizes behave differently from large ones

This app helps users understand these patterns by allowing them to simulate thousands of trials and observe:

  • Experimental probability vs. theoretical probability

  • Frequency distribution

  • How results become stable when sample size increases

It’s a perfect tool for classroom demonstrations, learning statistics, or simply satisfying your curiosity about randomness.


Key Features of the App

1. Coin Toss Simulator

  • Choose number of tosses

  • Adjust probability of Heads (to simulate biased coins)

  • View results in a clear bar graph

  • See total Heads and Tails instantly

2. Dice Roll Simulator

  • Select number of sides (6-sided, 8-sided, 10-sided, etc.)

  • Roll 1 or multiple dice at once

  • Simulate thousands of rolls

  • Visualize how numbers distribute among outcomes

3. Live Simulation with Progress Bar

The app shows a real-time progress bar for long simulations so users can track progress while running 10,000+ trials.

4. Export Data to CSV

All results can be exported into a CSV file for analysis, charting, or classroom assignments.

5. Clean User Interface

Built with Python’s Tkinter library, the app is lightweight, fast, and easy to use, even for non-programmers.


Educational Benefits

This simulator is useful for:

🎓 Students

Understand randomness, probability distribution, and sample size effects.

👩‍🏫 Teachers

Demonstrate probability experiments without needing physical coins and dice.

🧠 Data Science Beginners

Practice working with randomness, sampling, probability, and basic statistical thinking.

🎮 Enthusiasts

Use it for fun experiments—like testing dice fairness or simulating board-game outcomes.


How It Helps You Learn Probability

With this app, you can answer questions such as:

  • Why do results get more accurate with 100,000 trials?

  • How does bias change the outcome of coin tosses?

  • What does a fair 6-sided dice distribution look like?

  • Why does randomness never produce “perfect” results in small samples?

By experimenting with different simulation sizes, users learn how mathematics and randomness interact in the real world.


Final Thoughts

The Coin Toss & Dice Roll Probability Simulator makes learning probability simple, visual, and enjoyable. It brings abstract mathematical ideas into a real, interactive experience. Whether you're a student, teacher, data enthusiast, or hobbyist, this app gives you an easy way to explore the world of probability right from your desktop.

"""

Coin Toss & Dice Roll Probability Simulator

Single-file tkinter desktop application.


Features:

- Simulate biased/unbiased coin tosses and n-sided dice rolls

- Choose number of trials, coin bias (probability of Heads), number of dice and sides

- Run simulation and view counts, empirical probabilities, and simple bar charts drawn on a Tkinter Canvas

- Live progress for long simulations with a progress bar

- Export results to CSV


Run: save this file as coin_dice_simulator.py and run `python coin_dice_simulator.py`.

Requires: standard Python 3 (no external packages required)

"""


import tkinter as tk

from tkinter import ttk, messagebox, filedialog

import random

import csv

import threading

import time


class SimulatorApp(tk.Tk):

    def __init__(self):

        super().__init__()

        self.title("Coin Toss & Dice Roll Probability Simulator")

        self.geometry("900x620")

        self.minsize(760, 520)


        self._build_ui()


        # Simulation state

        self.results = {}

        self._stop_sim = False


    def _build_ui(self):

        # Top: controls

        ctrl_frame = ttk.LabelFrame(self, text="Simulation Controls", padding=10)

        ctrl_frame.pack(fill="x", padx=10, pady=8)


        # Simulation type

        ttk.Label(ctrl_frame, text="Simulate:").grid(row=0, column=0, sticky="w")

        self.sim_type = tk.StringVar(value="coin")

        ttk.Radiobutton(ctrl_frame, text="Coin Toss", variable=self.sim_type, value="coin", command=self._toggle_options).grid(row=0, column=1, sticky="w")

        ttk.Radiobutton(ctrl_frame, text="Dice Roll", variable=self.sim_type, value="dice", command=self._toggle_options).grid(row=0, column=2, sticky="w")

        ttk.Radiobutton(ctrl_frame, text="Both", variable=self.sim_type, value="both", command=self._toggle_options).grid(row=0, column=3, sticky="w")


        # Number of trials

        ttk.Label(ctrl_frame, text="Trials:").grid(row=1, column=0, sticky="w", pady=(8,0))

        self.trials_var = tk.IntVar(value=1000)

        self.trials_spin = ttk.Spinbox(ctrl_frame, from_=1, to=10000000, textvariable=self.trials_var, width=12)

        self.trials_spin.grid(row=1, column=1, sticky="w", pady=(8,0))


        # Coin options

        self.coin_frame = ttk.Frame(ctrl_frame)

        self.coin_frame.grid(row=2, column=0, columnspan=4, sticky="w", pady=(8,0))

        ttk.Label(self.coin_frame, text="Coin bias (P(Heads)):").grid(row=0, column=0, sticky="w")

        self.coin_bias = tk.DoubleVar(value=0.5)

        self.coin_spin = ttk.Spinbox(self.coin_frame, from_=0.0, to=1.0, increment=0.01, textvariable=self.coin_bias, width=8)

        self.coin_spin.grid(row=0, column=1, sticky="w", padx=(6,10))


        # Dice options

        self.dice_frame = ttk.Frame(ctrl_frame)

        self.dice_frame.grid(row=3, column=0, columnspan=4, sticky="w", pady=(8,0))

        ttk.Label(self.dice_frame, text="Dice sides:").grid(row=0, column=0, sticky="w")

        self.dice_sides = tk.IntVar(value=6)

        ttk.Spinbox(self.dice_frame, from_=2, to=100, textvariable=self.dice_sides, width=6).grid(row=0, column=1, sticky="w", padx=(6,10))

        ttk.Label(self.dice_frame, text="Number of dice per roll:").grid(row=0, column=2, sticky="w")

        self.dice_count = tk.IntVar(value=1)

        ttk.Spinbox(self.dice_frame, from_=1, to=10, textvariable=self.dice_count, width=6).grid(row=0, column=3, sticky="w", padx=(6,0))


        # Buttons

        btn_frame = ttk.Frame(ctrl_frame)

        btn_frame.grid(row=4, column=0, columnspan=4, pady=(10,0), sticky="e")

        self.run_btn = ttk.Button(btn_frame, text="Run Simulation", command=self.run_simulation)

        self.run_btn.pack(side="left", padx=(0,6))

        self.stop_btn = ttk.Button(btn_frame, text="Stop", command=self._stop, state="disabled")

        self.stop_btn.pack(side="left", padx=(0,6))

        self.export_btn = ttk.Button(btn_frame, text="Export CSV", command=self.export_csv, state="disabled")

        self.export_btn.pack(side="left")


        # Progress bar

        self.progress = ttk.Progressbar(self, orient="horizontal", length=400, mode="determinate")

        self.progress.pack(padx=14, pady=(2,8), anchor="e")


        # Middle: results and charts

        mid_frame = ttk.Frame(self)

        mid_frame.pack(fill="both", expand=True, padx=10, pady=6)

        mid_frame.columnconfigure(0, weight=1)

        mid_frame.columnconfigure(1, weight=1)

        mid_frame.rowconfigure(0, weight=1)


        # Left: Text results

        results_frame = ttk.LabelFrame(mid_frame, text="Results", padding=8)

        results_frame.grid(row=0, column=0, sticky="nsew", padx=(0,6))

        results_frame.rowconfigure(0, weight=1)

        results_frame.columnconfigure(0, weight=1)


        self.results_text = tk.Text(results_frame, state="disabled", wrap="word")

        self.results_text.grid(row=0, column=0, sticky="nsew")

        results_scroll = ttk.Scrollbar(results_frame, orient="vertical", command=self.results_text.yview)

        results_scroll.grid(row=0, column=1, sticky="ns")

        self.results_text.configure(yscrollcommand=results_scroll.set)


        # Right: Canvas charts

        chart_frame = ttk.LabelFrame(mid_frame, text="Charts", padding=8)

        chart_frame.grid(row=0, column=1, sticky="nsew", padx=(6,0))

        chart_frame.rowconfigure(0, weight=1)

        chart_frame.columnconfigure(0, weight=1)


        self.chart_canvas = tk.Canvas(chart_frame, background="#ffffff")

        self.chart_canvas.grid(row=0, column=0, sticky="nsew")


        # Bottom: quick help

        help_frame = ttk.LabelFrame(self, text="How to use", padding=8)

        help_frame.pack(fill="x", padx=10, pady=(0,10))

        help_label = ttk.Label(help_frame, text=("Choose simulation type, set trials and options, then click Run Simulation. "

                                                "Results will show counts and simple bar charts. Export to CSV if needed."))

        help_label.pack()


        self._toggle_options()


    def _toggle_options(self):

        t = self.sim_type.get()

        if t == "coin":

            self.coin_frame.grid()

            self.dice_frame.grid_remove()

        elif t == "dice":

            self.coin_frame.grid_remove()

            self.dice_frame.grid()

        else:

            self.coin_frame.grid()

            self.dice_frame.grid()


    def run_simulation(self):

        try:

            trials = int(self.trials_var.get())

            if trials <= 0:

                raise ValueError

        except Exception:

            messagebox.showerror("Invalid trials", "Please enter a positive integer for trials.")

            return


        # Prepare

        self.run_btn.config(state="disabled")

        self.stop_btn.config(state="normal")

        self.export_btn.config(state="disabled")

        self.results_text.config(state="normal")

        self.results_text.delete("1.0", "end")

        self.results_text.config(state="disabled")

        self.chart_canvas.delete("all")

        self.progress['value'] = 0

        self.results = {}

        self._stop_sim = False


        # Run in background thread to keep UI responsive

        thread = threading.Thread(target=self._simulate_thread, args=(trials,))

        thread.daemon = True

        thread.start()


    def _stop(self):

        self._stop_sim = True

        self.stop_btn.config(state="disabled")


    def _simulate_thread(self, trials):

        sim_type = self.sim_type.get()

        coin_p = float(self.coin_bias.get()) if sim_type in ("coin", "both") else None

        sides = int(self.dice_sides.get()) if sim_type in ("dice", "both") else None

        dice_count = int(self.dice_count.get()) if sim_type in ("dice", "both") else None


        # Initialize counters

        coin_counts = {"Heads": 0, "Tails": 0}

        dice_counts = {}  # key: sum or face


        for i in range(trials):

            if self._stop_sim:

                break


            if sim_type in ("coin", "both"):

                # single toss per trial for coin simulation

                r = random.random()

                if r < coin_p:

                    coin_counts["Heads"] += 1

                else:

                    coin_counts["Tails"] += 1


            if sim_type in ("dice", "both"):

                # roll dice_count dice and sum results

                total = 0

                for _ in range(dice_count):

                    total += random.randint(1, sides)

                dice_counts[total] = dice_counts.get(total, 0) + 1


            # update progress every so often

            if (i + 1) % max(1, trials // 100) == 0 or i == trials - 1:

                progress_val = int((i + 1) / trials * 100)

                self._safe_update_progress(progress_val)


        # store and show results

        self.results['coin'] = coin_counts

        self.results['dice'] = dice_counts

        self._safe_finish()


    def _safe_update_progress(self, val):

        try:

            self.progress['value'] = val

            self.update_idletasks()

        except Exception:

            pass


    def _safe_finish(self):

        # run on main thread

        self.after(0, self._finish_simulation)


    def _finish_simulation(self):

        self.run_btn.config(state="normal")

        self.stop_btn.config(state="disabled")

        self.export_btn.config(state="normal")

        self.progress['value'] = 100

        self._render_results()


    def _render_results(self):

        self.results_text.config(state="normal")

        self.results_text.delete("1.0", "end")


        sim_type = self.sim_type.get()


        if sim_type in ("coin", "both"):

            coin_counts = self.results.get('coin', {"Heads":0, "Tails":0})

            total = coin_counts.get('Heads',0) + coin_counts.get('Tails',0)

            self.results_text.insert("end", f"Coin tosses (total = {total})\n")

            if total > 0:

                ph = coin_counts.get('Heads',0)/total

                pt = coin_counts.get('Tails',0)/total

                self.results_text.insert("end", f"  Heads: {coin_counts.get('Heads',0)} ({ph:.4f})\n")

                self.results_text.insert("end", f"  Tails: {coin_counts.get('Tails',0)} ({pt:.4f})\n")

            else:

                self.results_text.insert("end", "  No coin data.\n")

            self.results_text.insert("end", "\n")


        if sim_type in ("dice", "both"):

            dice_counts = self.results.get('dice', {})

            total = sum(dice_counts.values())

            self.results_text.insert("end", f"Dice rolls (trials = {total})\n")

            if total > 0:

                # show sorted distribution

                for k in sorted(dice_counts.keys()):

                    self.results_text.insert("end", f"  {k}: {dice_counts[k]} ({dice_counts[k]/total:.4f})\n")

            else:

                self.results_text.insert("end", "  No dice data.\n")

            self.results_text.insert("end", "\n")


        self.results_text.config(state="disabled")


        # draw charts

        self.chart_canvas.delete("all")

        w = self.chart_canvas.winfo_width()

        h = self.chart_canvas.winfo_height()

        if w < 100 or h < 80:

            # schedule redraw when widget has size

            self.after(150, self._render_results)

            return


        pad = 20

        chart_w = max(200, w - pad*2)

        chart_h = max(120, h - pad*2)


        # If both, draw two small charts vertically

        charts = []

        if sim_type in ("coin", "both"):

            charts.append(('coin', self.results.get('coin', {})))

        if sim_type in ("dice", "both"):

            charts.append(('dice', self.results.get('dice', {})))


        n = len(charts)

        if n == 0:

            return


        section_h = chart_h // n

        for idx, (kind, data) in enumerate(charts):

            top = pad + idx * section_h

            left = pad

            bottom = top + section_h - 10

            right = left + chart_w

            # draw border

            self.chart_canvas.create_rectangle(left-2, top-2, right+2, bottom+2, outline="#ccc")


            if not data:

                self.chart_canvas.create_text((left+right)//2, (top+bottom)//2, text="No data to display")

                continue


            # normalize and draw bars

            items = sorted(data.items(), key=lambda x: x[0])

            keys = [str(k) for k, _ in items]

            vals = [v for _, v in items]

            total = sum(vals) if sum(vals) > 0 else 1

            maxv = max(vals)

            bar_gap = 8

            bar_w = max(12, (chart_w - (len(vals)+1)*bar_gap) / max(1, len(vals)))

            # baseline

            baseline = bottom - 30

            # labels area

            for i, (k, v) in enumerate(items):

                bx = left + bar_gap + i * (bar_w + bar_gap)

                by = baseline

                # height proportional

                bh = int((v / maxv) * (baseline - top - 20)) if maxv > 0 else 0

                self.chart_canvas.create_rectangle(bx, by-bh, bx+bar_w, by, fill="#4a90e2", outline="black")

                # value text

                self.chart_canvas.create_text(bx + bar_w/2, by-bh-10, text=str(v), anchor="s")

                # key label

                self.chart_canvas.create_text(bx + bar_w/2, bottom - 10, text=str(k), anchor="n")


            # chart title

            title = "Coin toss distribution" if kind == 'coin' else "Dice roll distribution (sum)"

            self.chart_canvas.create_text(left+6, top+8, anchor="nw", text=title, font=(None, 10, 'bold'))


    def export_csv(self):

        if not self.results:

            messagebox.showwarning("No data", "Run a simulation first before exporting.")

            return

        path = filedialog.asksaveasfilename(defaultextension='.csv', filetypes=[('CSV files','*.csv'), ('All files','*.*')])

        if not path:

            return

        try:

            with open(path, 'w', newline='', encoding='utf-8') as f:

                writer = csv.writer(f)

                writer.writerow(['Type', 'Key', 'Count'])

                coin = self.results.get('coin', {})

                for k, v in coin.items():

                    writer.writerow(['coin', k, v])

                dice = self.results.get('dice', {})

                for k, v in sorted(dice.items()):

                    writer.writerow(['dice', k, v])

            messagebox.showinfo("Saved", f"Results exported to {path}")

        except Exception as e:

            messagebox.showerror("Error", f"Failed to save CSV: {e}")


if __name__ == '__main__':

    app = SimulatorApp()

    app.mainloop()

https://github.com/gagandeep44489/DiscreteStrucutreAndAlgoApp/blob/main/Coin%20Toss%20%26%20Dice%20Roll%20Probability%20Simulator.py

Comments

Popular posts from this blog

NAND / NOR Logic Simulator: A Python Desktop App for Understanding Universal Logic Gates

Subset Sum Problem Visualizer Using Python (Dynamic Programming GUI Tool)

String Matching Algorithm Trainer (KMP & Rabin-Karp) – Python Desktop App