Skip to main content

Arduino Dice Roller with Statistics using OLED Display (Wokwi Simulation)

🎲 Simulate Now
00

Project Overview

What You'll Build

Build 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!

01

How It Works

Project Logic

Two buttons control everything. Here's the full flow when you press each:

🟢 When the ROLL Button is Pressed:

1

The Arduino uses the random() function to generate a number between 1 and 6.

2

A graphical dice animation runs on the OLED for 1 second — showing random numbers flashing.

3

The final result is shown as a large dice face with dots on the OLED display.

4

The roll is added to the history array and the count for that number increments.

5

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

;">
02

Components & Wiring

Step 1

Only 4 components needed — all available as virtual parts in Wokwi. If using diagram.json below, wiring is done automatically!

ComponentComponent PinArduino PinWire Colour
SSD1306 OLEDGNDGNDBlack
SSD1306 OLEDVCC5VRed
SSD1306 OLEDSDAA4Blue
SSD1306 OLEDSCLA5Green
Button ROLL1.lPin 2Green
Button ROLL2.lGNDBlack
Button STATS1.lPin 3Blue
Button STATS2.lGNDBlack
Buzzer+ (Positive)Pin 8Red
Buzzer− (Negative)GNDBlack
💡

Both buttons use INPUT_PULLUP mode — no external resistors needed! The OLED uses I²C, so only 2 data wires (SDA + SCL) are required for the display.

03

The Arduino Code — Explained

Step 2

Paste the complete sketch into the sketch.ino tab in Wokwi. The code is split into sections below for easy understanding.

① Setup — Libraries, Pins & Variables

sketch.ino — Part 1/4
/*
 * 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

sketch.ino — Part 2/4
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

sketch.ino — Part 3/4
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

sketch.ino — Part 4/4
// 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 diagram.json tab, delete all text, paste this, then press Ctrl+S.

diagram.json — Auto-wires your circuit
{
  "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 libraries.txt (click "+" in the file tabs) and paste these two lines:

libraries.txt
Adafruit SSD1306
Adafruit GFX Library
💡

Wokwi auto-downloads libraries when the simulation starts. No manual installation needed!

04

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 libraries.txt has the exact names above. The library names are case-sensitive!

🎲

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
FREE SIMULATION

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 Simulation
05

Learning Outcomes

What You'll Learn

This project teaches key concepts in embedded programming, mathematics, and electronics all in one go:

Random number generation with random()
Using arrays for data storage and history
INPUT_PULLUP button logic (no resistors!)
OLED graphics with Adafruit SSD1306 library
Non-blocking timing with millis()
Probability + statistics visualisation
Embedded C++ switch/case patterns
Buzzer tone generation with tone()
🎯

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.

06

Knowledge Check Quiz

Test Yourself!

Answer these 5 questions to check your understanding of the Dice Roller project!

1 What function does Arduino use to generate a random number?

2 What is the theoretical average value of a fair 6-sided dice?

3 Why do we use INPUT_PULLUP for the buttons?

4 Which I²C pins does the SSD1306 OLED use on Arduino UNO?

5 What does randomSeed(analogRead(A0)) do in setup()?

07

More MakeMindz Projects

Explore All 113+
🧠 MakeMindz

Summer Course · Digital Dice Roller with Real-Time Statistics

Simulate free at wokwi.com · All projects at makemindz.com

Comments

try for free