/*
* DICE ROLLER WITH STATISTICS
* Features:
* - Roll button for 6-sided dice
* - OLED display shows current roll
* - Tracks last 10 rolls
* - Real-time probability tracking
* - Statistics: Average, most/least common
* - Animated dice roll effect
*/
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Pin definitions
const int BTN_ROLL = 2;
const int BTN_STATS = 3;
const int BUZZER = 8;
// Game state
int currentRoll = 0;
int totalRolls = 0;
int rollHistory[10] = {0}; // Last 10 rolls
int rollCounts[6] = {0}; // Count of each number (1-6)
bool isRolling = false;
bool showStats = false;
// Button debouncing
bool rollPressed = false;
bool statsPressed = false;
unsigned long lastRollTime = 0;
unsigned long rollAnimStart = 0;
void setup() {
Serial.begin(9600);
Serial.println("Dice Roller Starting...");
// Initialize buttons
pinMode(BTN_ROLL, INPUT_PULLUP);
pinMode(BTN_STATS, INPUT_PULLUP);
pinMode(BUZZER, OUTPUT);
// Initialize display
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("Display failed!");
for(;;);
}
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
// Show welcome screen
showWelcome();
delay(2000);
// Seed random number generator
randomSeed(analogRead(A0));
}
void loop() {
unsigned long now = millis();
// Check roll button
if (digitalRead(BTN_ROLL) == LOW && !rollPressed && !isRolling) {
rollPressed = true;
startRoll();
} else if (digitalRead(BTN_ROLL) == HIGH) {
rollPressed = false;
}
// Check stats button
if (digitalRead(BTN_STATS) == LOW && !statsPressed && !isRolling) {
statsPressed = true;
showStats = !showStats;
tone(BUZZER, 800, 50);
} else if (digitalRead(BTN_STATS) == HIGH) {
statsPressed = false;
}
// Handle rolling animation
if (isRolling) {
if (now - rollAnimStart < 1000) {
// Animate rolling
animateRoll();
} else {
// Finish roll
finishRoll();
}
} else {
// Display current view
display.clearDisplay();
if (showStats) {
drawStatsScreen();
} else {
drawMainScreen();
}
display.display();
}
delay(50);
}
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();
}
void startRoll() {
isRolling = true;
rollAnimStart = millis();
playRollSound();
}
void animateRoll() {
display.clearDisplay();
// Show "ROLLING..." text
display.setTextSize(2);
display.setCursor(10, 10);
display.println("ROLLING");
// Show random numbers flashing
int tempRoll = random(1, 7);
drawLargeDice(64, 40, tempRoll);
display.display();
}
void finishRoll() {
isRolling = false;
// Generate final roll
currentRoll = random(1, 7);
totalRolls++;
// Update history (shift array and add new roll)
for (int i = 9; i > 0; i--) {
rollHistory[i] = rollHistory[i-1];
}
rollHistory[0] = currentRoll;
// Update counts
rollCounts[currentRoll - 1]++;
// Play result sound
playResultSound(currentRoll);
// Log to serial
Serial.print("Roll #");
Serial.print(totalRolls);
Serial.print(": ");
Serial.println(currentRoll);
// Flash the result
for (int i = 0; i < 2; i++) {
display.clearDisplay();
drawLargeDice(64, 32, currentRoll);
display.display();
delay(150);
display.clearDisplay();
display.display();
delay(100);
}
}
void drawMainScreen() {
// Title
display.setTextSize(1);
display.setCursor(0, 0);
display.print("DICE ROLLER");
// Roll count
display.setCursor(90, 0);
display.print("#");
display.print(totalRolls);
// Draw line
display.drawLine(0, 10, 128, 10, SSD1306_WHITE);
if (currentRoll > 0) {
// Show current roll (large)
drawLargeDice(64, 35, currentRoll);
// Show last 5 rolls (small)
display.setTextSize(1);
display.setCursor(0, 56);
display.print("Last:");
for (int i = 0; i < min(5, totalRolls); i++) {
display.print(" ");
display.print(rollHistory[i]);
}
} else {
// First time - show instructions
display.setTextSize(1);
display.setCursor(15, 25);
display.println("Press ROLL");
display.setCursor(10, 35);
display.println("button to start");
}
// Button labels
display.drawLine(0, 52, 128, 52, SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(0, 56);
display.print("Roll");
display.setCursor(92, 56);
display.print("Stats");
}
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;
}
// Total rolls
display.setCursor(0, 14);
display.print("Total Rolls: ");
display.print(totalRolls);
// Average
float avg = calculateAverage();
display.setCursor(0, 24);
display.print("Average: ");
display.print(avg, 2);
// Distribution (probability bars)
display.setCursor(0, 34);
display.println("Distribution:");
for (int i = 0; i < 6; i++) {
int x = 0;
int y = 44 + (i / 3) * 9;
int col = i % 3;
x = col * 42;
// Number
display.setCursor(x, y);
display.print(i + 1);
display.print(":");
// Mini bar
int barWidth = 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, barWidth, 5, SSD1306_WHITE);
}
// Most/Least common
int mostCommon = getMostCommon();
int leastCommon = getLeastCommon();
// Button labels
display.drawLine(0, 52, 128, 52, SSD1306_WHITE);
display.setCursor(0, 56);
display.print("Roll");
display.setCursor(92, 56);
display.print("Back");
}
void drawLargeDice(int x, int y, int number) {
int size = 20;
int dotSize = 3;
// Draw dice square
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);
// Draw dots based on number
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;
}
}
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;
}
int getMostCommon() {
int maxCount = 0;
int maxIndex = 0;
for (int i = 0; i < 6; i++) {
if (rollCounts[i] > maxCount) {
maxCount = rollCounts[i];
maxIndex = i;
}
}
return maxIndex + 1;
}
int getLeastCommon() {
int minCount = totalRolls + 1;
int minIndex = 0;
for (int i = 0; i < 6; i++) {
if (rollCounts[i] < minCount && rollCounts[i] > 0) {
minCount = rollCounts[i];
minIndex = i;
}
}
return minIndex + 1;
}
void playRollSound() {
// Rolling sound effect
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);
}
Comments
Post a Comment