Digital Dice Roller
with Real-Time Stats
Build a 6-sided digital dice that tracks probability and statistics live on an OLED display — using Arduino UNO. Simulated 100% free in Wokwi. No hardware needed!
Project Overview
What You'll BuildBuild a smart Digital Dice Roller with real-time statistics tracking using Arduino UNO and an SSD1306 OLED display. It simulates a 6-sided dice and records how many times each number (1–6) appears — teaching students about random number generation, probability, and data tracking.
6-Sided Dice
Animated digital dice simulation with dot graphics
Live Stats
Real-time probability tracking and distribution bars
Roll History
Last 10 rolls displayed on screen
Average
Running average calculated after every roll
Most Common
Detects most & least frequent number automatically
Sound Effects
Buzzer tone changes pitch based on dice value
This project makes probability theory practical and visual — students can observe real-world randomness patterns over time. Roll 20 times and watch the distribution bars even out toward equal probability!
How It Works
Project LogicTwo buttons control everything. Here's the full flow when you press each:
🟢 When the ROLL Button is Pressed:
The Arduino uses the random() function to generate a number between 1 and 6.
A graphical dice animation runs on the OLED for 1 second — showing random numbers flashing.
The final result is shown as a large dice face with dots on the OLED display.
The roll is added to the history array and the count for that number increments.
The buzzer plays a tone — higher pitch for higher rolls (400Hz to 1000Hz).
🔵 When the STATS Button is Pressed:
The screen switches to a statistics dashboard showing all 5 of these:
Total Rolls
Running count of all dice rolls made so far
Average Value
Mean of all rolls, calculated to 2 decimal places
Distribution
Mini bar chart for each number 1–6
Most & Least
Most frequent and least frequent numbers
Components & Wiring
Step 1Only 4 components needed — all available as virtual parts in Wokwi. If using diagram.json below, wiring is done automatically!
| Component | Component Pin | Arduino Pin | Wire Colour |
|---|---|---|---|
| SSD1306 OLED | GND | GND | Black |
| SSD1306 OLED | VCC | 5V | Red |
| SSD1306 OLED | SDA | A4 | Blue |
| SSD1306 OLED | SCL | A5 | Green |
| Button ROLL | 1.l | Pin 2 | Green |
| Button ROLL | 2.l | GND | Black |
| Button STATS | 1.l | Pin 3 | Blue |
| Button STATS | 2.l | GND | Black |
| Buzzer | + (Positive) | Pin 8 | Red |
| Buzzer | − (Negative) | GND | Black |
Both buttons use
The Arduino Code — Explained
Step 2Paste the complete sketch into the sketch.ino tab in Wokwi. The code is split into sections below for easy understanding.
① Setup — Libraries, Pins & Variables
/* * DICE ROLLER WITH STATISTICS * MakeMindz Summer Course | Wokwi Simulation * Features: 6-sided dice, OLED display, stats, buzzer */ #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 // No reset pin needed for most modules Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); // ── Pin definitions ───────────────────── const int BTN_ROLL = 2; // Green ROLL button const int BTN_STATS = 3; // Blue STATS button const int BUZZER = 8; // Piezo buzzer // ── Game state ─────────────────────────── int currentRoll = 0; int totalRolls = 0; int rollHistory[10] = {0}; // Last 10 rolls int rollCounts[6] = {0}; // Count per face (1-6) bool isRolling = false, showStats = false; bool rollPressed = false, statsPressed = false; unsigned long rollAnimStart = 0;
② setup() — Initialise Hardware
void setup() { Serial.begin(9600); pinMode(BTN_ROLL, INPUT_PULLUP); // No external resistor needed! pinMode(BTN_STATS, INPUT_PULLUP); pinMode(BUZZER, OUTPUT); // Start OLED display if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println("OLED not found!"); for(;;); // Stop if display fails } display.clearDisplay(); display.setTextColor(SSD1306_WHITE); // Splash screen showWelcome(); delay(2000); randomSeed(analogRead(A0)); // Seed with noise for better randomness } void showWelcome() { display.clearDisplay(); display.setTextSize(2); display.setCursor(25, 10); display.println("DICE"); display.setCursor(15, 30); display.println("ROLLER"); display.setTextSize(1); display.setCursor(10, 52); display.println("Press to Roll!"); display.display(); }
③ loop() — Main Program Logic
void loop() { unsigned long now = millis(); // ── Roll button ─────────────────────── if (digitalRead(BTN_ROLL) == LOW && !rollPressed && !isRolling) { rollPressed = true; startRoll(); } else if (digitalRead(BTN_ROLL) == HIGH) { rollPressed = false; } // ── Stats button ────────────────────── if (digitalRead(BTN_STATS) == LOW && !statsPressed && !isRolling) { statsPressed = true; showStats = !showStats; // Toggle stats screen tone(BUZZER, 800, 50); } else if (digitalRead(BTN_STATS) == HIGH) { statsPressed = false; } // ── Handle roll animation (1 second) ── if (isRolling) { if (now - rollAnimStart < 1000) { animateRoll(); // Flash random numbers } else { finishRoll(); // Lock in the final result } } else { display.clearDisplay(); if (showStats) { drawStatsScreen(); } else { drawMainScreen(); } display.display(); } delay(50); } void startRoll() { isRolling = true; rollAnimStart = millis(); playRollSound(); } void animateRoll() { display.clearDisplay(); display.setTextSize(2); display.setCursor(10, 10); display.println("ROLLING"); drawLargeDice(64, 40, random(1, 7)); display.display(); } void finishRoll() { isRolling = false; currentRoll = random(1, 7); totalRolls++; // Shift history array and insert new result for (int i = 9; i > 0; i--) rollHistory[i] = rollHistory[i-1]; rollHistory[0] = currentRoll; rollCounts[currentRoll - 1]++; playResultSound(currentRoll); // Flash effect: show result twice for (int i = 0; i < 2; i++) { display.clearDisplay(); drawLargeDice(64, 32, currentRoll); display.display(); delay(150); display.clearDisplay(); display.display(); delay(100); } }
④ Display Functions — Dice Faces + Stats Screen
// Draw dice face with dots at position (x, y) void drawLargeDice(int x, int y, int number) { int size = 20, dotSize = 3; display.fillRoundRect(x-size, y-size, size*2, size*2, 4, SSD1306_WHITE); display.fillRoundRect(x-size+2, y-size+2, size*2-4, size*2-4, 2, SSD1306_BLACK); switch(number) { case 1: display.fillCircle(x, y, dotSize, SSD1306_WHITE); break; case 2: display.fillCircle(x-8, y-8, dotSize, SSD1306_WHITE); display.fillCircle(x+8, y+8, dotSize, SSD1306_WHITE); break; case 3: display.fillCircle(x-8, y-8, dotSize, SSD1306_WHITE); display.fillCircle(x, y, dotSize, SSD1306_WHITE); display.fillCircle(x+8, y+8, dotSize, SSD1306_WHITE); break; case 4: display.fillCircle(x-8, y-8, dotSize, SSD1306_WHITE); display.fillCircle(x+8, y-8, dotSize, SSD1306_WHITE); display.fillCircle(x-8, y+8, dotSize, SSD1306_WHITE); display.fillCircle(x+8, y+8, dotSize, SSD1306_WHITE); break; case 5: display.fillCircle(x-8, y-8, dotSize, SSD1306_WHITE); display.fillCircle(x+8, y-8, dotSize, SSD1306_WHITE); display.fillCircle(x, y, dotSize, SSD1306_WHITE); display.fillCircle(x-8, y+8, dotSize, SSD1306_WHITE); display.fillCircle(x+8, y+8, dotSize, SSD1306_WHITE); break; case 6: display.fillCircle(x-8, y-10, dotSize, SSD1306_WHITE); display.fillCircle(x+8, y-10, dotSize, SSD1306_WHITE); display.fillCircle(x-8, y, dotSize, SSD1306_WHITE); display.fillCircle(x+8, y, dotSize, SSD1306_WHITE); display.fillCircle(x-8, y+10, dotSize, SSD1306_WHITE); display.fillCircle(x+8, y+10, dotSize, SSD1306_WHITE); break; } } // Statistics screen with distribution bars void drawStatsScreen() { display.setTextSize(1); display.setCursor(0, 0); display.print("STATISTICS"); display.drawLine(0, 10, 128, 10, SSD1306_WHITE); if (totalRolls == 0) { display.setCursor(10, 25); display.println("No rolls yet!"); return; } display.setCursor(0, 14); display.print("Total: "); display.print(totalRolls); display.setCursor(0, 24); display.print("Avg: "); display.print(calculateAverage(), 2); display.setCursor(0, 34); display.println("Distribution:"); for (int i = 0; i < 6; i++) { int x = (i % 3) * 42, y = 44 + (i / 3) * 9; display.setCursor(x, y); display.print(i+1); display.print(":"); int bw = map(rollCounts[i], 0, max(totalRolls/6+3, 1), 0, 22); display.drawRect(x+12, y, 24, 7, SSD1306_WHITE); display.fillRect(x+13, y+1, bw, 5, SSD1306_WHITE); } } float calculateAverage() { if (totalRolls == 0) return 0; int sum = 0; for (int i = 0; i < 6; i++) sum += rollCounts[i] * (i + 1); return (float)sum / totalRolls; } void playRollSound() { // Rising tones during animation for (int i = 0; i < 5; i++) { tone(BUZZER, 200+i*100, 50); delay(60); } } void playResultSound(int roll) { // Higher roll = higher pitch! int freq = 400 + (roll * 100); tone(BUZZER, freq, 200); delay(220); tone(BUZZER, freq+100, 150); }
⑤ diagram.json — Paste This into Wokwi for Auto-Wiring
In the Wokwi editor, click the
{
"version": 1,
"author": "MakeMindz",
"editor": "wokwi",
"parts": [
{ "type": "wokwi-arduino-uno", "id": "uno", "top": 0, "left": 0, "attrs": {} },
{ "type": "wokwi-ssd1306", "id": "oled", "top": -100, "left": 100, "attrs": {} },
{ "type": "wokwi-pushbutton", "id": "btn_roll", "top": 150, "left": -20, "attrs": { "color": "green", "label": "ROLL" } },
{ "type": "wokwi-pushbutton", "id": "btn_stats", "top": 150, "left": 60, "attrs": { "color": "blue", "label": "STATS" } },
{ "type": "wokwi-buzzer", "id": "buzzer", "top": 100, "left": -100, "attrs": {} }
],
"connections": [
[ "oled:GND", "uno:GND.1", "black", [ "v0" ] ],
[ "oled:VCC", "uno:5V", "red", [ "v0" ] ],
[ "oled:SDA", "uno:A4", "blue", [ "v0" ] ],
[ "oled:SCL", "uno:A5", "green", [ "v0" ] ],
[ "btn_roll:1.l", "uno:2", "green", [ "v0" ] ],
[ "btn_roll:2.l", "uno:GND.2", "black", [ "v0" ] ],
[ "btn_stats:1.l", "uno:3", "blue", [ "v0" ] ],
[ "btn_stats:2.l", "uno:GND.2", "black", [ "v0" ] ],
[ "buzzer:1", "uno:8", "red", [ "v0" ] ],
[ "buzzer:2", "uno:GND.3", "black", [ "v0" ] ]
]
}
⑥ libraries.txt — Add This File in Wokwi
Create a new file named
Adafruit SSD1306 Adafruit GFX Library
Wokwi auto-downloads libraries when the simulation starts. No manual installation needed!
Run & Test the Simulation
Step 3 · The Fun Part!Press Play — Watch the Boot Screen
Click the green ▶ Play button in Wokwi. You should see:
- OLED shows "DICE ROLLER" and "Press to Roll!" for 2 seconds
- Main screen appears, waiting for your first roll
Compile error? Check that your
Press the Green ROLL Button
- OLED shows "ROLLING" with flashing random dice for 1 second
- Final result flashes twice, then stays on screen
- Buzzer plays a tone — higher pitch for higher numbers!
- Bottom of screen shows last 5 rolls
Roll at least 20 times to see the distribution bars start equalising toward the theoretical ⅙ probability!
Press the Blue STATS Button to Toggle Dashboard
- Total Rolls — running count
- Average — should approach 3.5 with more rolls
- Distribution bars — mini bar chart for each face (1–6)
- Press STATS again to go back to the dice view
Open the Live Simulation on Wokwi
No sign-up required to view. Click "Copy and Tinker" in Wokwi to make your own editable copy.
Open Wokwi SimulationLearning Outcomes
What You'll LearnThis project teaches key concepts in embedded programming, mathematics, and electronics all in one go:
The expected average of a fair 6-sided dice is 3.5. After 50+ rolls, students can observe their average converging toward this value — making abstract probability visually real.
Knowledge Check Quiz
Test Yourself!Answer these 5 questions to check your understanding of the Dice Roller project!
Comments
Post a Comment