Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 35 | ||||
| × | 5 | ||||
| × | 5 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 7 | ||||
| × | 1 | ||||
| × | 2 | ||||
| × | 12 | ||||
Software apps and online services | ||||||
| ||||||
Hand tools and fabrication machines | ||||||
| ||||||
|
This electronic dice system is based on a concept by mbedni.
VideoDesignMost of the design is in the layout of the printed circuit board. It becomes the front panel for a simple box that was created using OpenSCAD. Since the PCB is the front panel, all components that are not required by the user are mounted on the back of the PCB. To continue to use DIP packages for both the ATmega328 and MAX7219 chips, standard IC sockets are mounted on the back by flating out the pins and soldering them like an SMD device.
The green LEDs show which game is active. The Green SELECT button exits a current game and allows selection of a new game. The games are:
- Dice - Simple dice. Press the blue button corresponding to the number of dice you want to play with and press the PLAY button to roll them.
- Poker - Press PLAY and all 5 dice will roll. Use blue buttons to select which dice you want to hold. Press the PLAY button again and the non-held dice will roll again. The system will flash the winning dice.
- Craps - Press PLAY and the left two dice will roll. If a 7 or 11 appear, you win, if a 2 or 3 or 12 appears, you lose. Any other value means you must play again. This time only the right two dice roll. If the left two dice and right two dice match, you win. If you throw a 7 on the right two dice, you lose. You keep rolling the right two dice until a result occurs.
- Yahtzee - Press PLAY and all 5 dice will roll. Use blue buttons to select which dice you want to hold. Press the PLAY button again and the non-selected dice will roll. You can repeat this process up to three times.
- Duel - Press PLAY and the left two dice and right two dice will roll. Each pair represent a player. The player with the higher dice value gets to move their car. The amount the car moves if based on the difference between the values of the two players dice.
The Eagle files for the Schematic and PCB layout have been included. I made my board using the toner method. I also printed on the top side board using the toner method as well.
The 3D printing of the box is simple and requires no support. Drill out the PCB mount holes with a 2.5mm drill and create a thread using a 3mm tap. Use M3 screws to mount the PCB.
The board contains a FTDI pin header so you can program it using a FTDI USB to serial board. If you have a UNO with a DIL 28 pin ATmega328 chip, you can use the UNO to program the ATmega328 and then plug the chip into The Dicer.
NOTE: If your ATmega328 does not come with an Ardunio UNO bootloader, you are going to need to add one first. See Installing an Arduino Bootloader.
/******************************************************************************
* Program: DICER V2
* Author: jbrad2089@gmail.com
*****************************************************************************/
#include "buttons.h"
#include "leds.h"
#include "sound.h"
#define TOTAL_GAMES 5
enum GameEnum { DICE, POKER, CRAPS, YAHTZEE, DUEL };
int currentGame; //Used to store current active game
byte buttonLeds; //Used to hold current button LED state
int currentMode; //Used by games to hold current position in game
void setup()
{
//Serial.begin(9600);
setupSound();
setupButtons();
setupLeds();
buttonLeds = 0;
currentGame = DICE;
setGameLed(currentGame);
initDice();
}
void loop()
{
int button = buttonPressed();
if (button != NO_BUTTON)
{
if (button == SELECT_BTN)
{
currentGame = (currentGame + 1) % TOTAL_GAMES;
setGameLed(currentGame);
switch (currentGame)
{
case DICE: initDice(); break;
case POKER: initPoker(); break;
case CRAPS: initCraps(); break;
case YAHTZEE: initYahtzee(); break;
case DUEL: initDuel(); break;
}
}
else
{
switch (currentGame)
{
case DICE: playDice(button); break;
case POKER: playPoker(button); break;
case CRAPS: playCraps(button); break;
case YAHTZEE: playYahtzee(button); break;
case DUEL: playDuel(button); break;
}
}
}
delay(100);
}
//=================================================================================================
// DICE
//=================================================================================================
//Initialise the gane
void initDice()
{
//default to TWO dice
buttonLeds = S1_LED | S2_LED;
setButtonLeds(buttonLeds);
clearDiceLeds();
}
//Play game - 1st button has been pressed
// S1 - S5 ==> Sets the number of dice
// PLAY ==> Throws selected number of dice
//Continue play until SELECT button is pressed again
void playDice(int button)
{
while (button != SELECT_BTN)
{
if (button == PLAY_BTN)
{
roll(buttonLeds);
playRolledTone();
}
else if (button != NO_BUTTON)
{
switch (button)
{
case S1_BTN: buttonLeds = S1_LED; break;
case S2_BTN: buttonLeds = S1_LED | S2_LED; break;
case S3_BTN: buttonLeds = S1_LED | S2_LED | S3_LED; break;
case S4_BTN: buttonLeds = S1_LED | S2_LED | S3_LED | S4_LED; break;
case S5_BTN: buttonLeds = S1_LED | S2_LED | S3_LED | S4_LED | S5_LED; break;
}
setButtonLeds(buttonLeds);
clearDiceLeds();
}
button = buttonPressed();
}
//Close out the game
initDice();
}
//=================================================================================================
// POKER
//=================================================================================================
int results[7];
//Initialise the gane
void initPoker()
{
buttonLeds = 0;
setButtonLeds(buttonLeds);
clearDiceLeds();
currentMode = 0;
}
//Play game - 1st button has been pressed
// S1 - S5 ==> Holds the dice
// PLAY ==> Throws dice not held - Flashes result.
// PLAY again throws all dice and clears HOLD
//Continue play until SELECT button is pressed again
void playPoker(int button)
{
while (button != SELECT_BTN)
{
if (button == PLAY_BTN)
{
roll(~buttonLeds);
playRolledTone();
currentMode++;
//Serial.println(currentMode);
if (currentMode == 2)
{
//sprintf(temp, "Dice values are: %d, %d, %d, %d, %d", dice1, dice2, dice3, dice4, dice5);
//Serial.println(temp);
//Work out final hand
for (int i=0; i < 7; i++)
{
results[i] = 0;
}
results[dice1]++;
results[dice2]++;
results[dice3]++;
results[dice4]++;
results[dice5]++;
//sprintf(temp, "Results are: 1 = %d, 2 = %d, 3 = %d, 4 = %d, 5 = %d, 6 = %d", results[1], results[2], results[3], results[4], results[5], results[6]);
//Serial.println(temp);
if ((results[1] == 1 && results[2] == 1 && results[3] == 1 && results[4] == 1 && results[5] == 1) ||
(results[2] == 1 && results[3] == 1 && results[4] == 1 && results[5] == 1 && results[6] == 1) ||
(results[1] == 5 || results[2] == 5 || results[3] == 5 || results[4] == 5 || results[5] == 5 || results[6] == 5))
{
//straight or five of a kind
flashDice(DICE_1 | DICE_2 | DICE_3 | DICE_4 | DICE_5);
}
else if (results[1] == 4 || results[2] == 4 || results[3] == 4 || results[4] == 4 || results[5] == 4 || results[6] == 4)
{
//four of a kind
flashDice(getDiceMask(4));
}
else if (results[1] == 3 || results[2] == 3 || results[3] == 3 || results[4] == 3 || results[5] == 3 || results[6] == 3)
{
//full house or 3 of a kind - we don't need to break it down any further
flashDice(getDiceMask(3) | getDiceMask(2));
}
else if (results[1] == 2 || results[2] == 2 || results[3] == 2 || results[4] == 2 || results[5] == 2 || results[6] == 2)
{
//two of a kind or two pair - we don't need to break it down any further
flashDice(getDiceMask(2));
}
//Setup for a new game
currentMode = 0;
buttonLeds = 0;
setButtonLeds(buttonLeds);
}
}
else if (button != NO_BUTTON)
{
switch (button)
{
case S1_BTN: buttonLeds = buttonLeds ^ S1_LED; break;
case S2_BTN: buttonLeds = buttonLeds ^ S2_LED; break;
case S3_BTN: buttonLeds = buttonLeds ^ S3_LED; break;
case S4_BTN: buttonLeds = buttonLeds ^ S4_LED; break;
case S5_BTN: buttonLeds = buttonLeds ^ S5_LED; break;
}
setButtonLeds(buttonLeds);
}
button = buttonPressed();
}
//Close out the game
}
//Returns the mask of dice that have a face value that matches the count
//returns DICE_X mask
byte getDiceMask(int count)
{
byte diceMask = 0;
for(int v = 1; v <= 6; v++)
{
if (results[v] == count)
{
if (dice1 == v) { diceMask |= DICE_1; }
if (dice2 == v) { diceMask |= DICE_2; }
if (dice3 == v) { diceMask |= DICE_3; }
if (dice4 == v) { diceMask |= DICE_4; }
if (dice5 == v) { diceMask |= DICE_5; }
}
}
return diceMask;
}
//=================================================================================================
// CRAPS
//=================================================================================================
//Initialise the gane
void initCraps()
{
buttonLeds = 0;
setButtonLeds(buttonLeds);
clearDiceLeds();
currentMode = 1;
}
//Play Game
//Throws first TWO dice
//7 or 11 - Automatic WIN
//2 or 3 or 12 - Automatic LOSS
//4, 5, 6, 8, 9, and 10 - Throws Last two dice
// - If match player winds
// - If 7, player losses
// - Otherwise player throws last two dice again
void playCraps(int button)
{
int firstThrow;
int lastThrow;
while (button != SELECT_BTN)
{
if (button == PLAY_BTN)
{
if (currentMode == 1)
{
//Throw first two dice
setButtonLeds(0);
clearDiceLeds();
roll(S1_LED | S2_LED);
firstThrow = dice1 + dice2;
if (firstThrow == 7 || firstThrow == 11)
{
//Automatic win
playWinMusic();
}
else if (firstThrow == 2 || firstThrow == 3 || firstThrow == 12)
{
//Automatic loss
playLoseMusic();
}
else
{
//throw again
playRethrowTone();
currentMode = 2;
setButtonLeds(S4_LED | S5_LED);
flashButtonLeds(S4_LED | S5_LED);
}
}
else
{
//Keep throwing until match or 7
roll(S4_LED | S5_LED);
lastThrow = dice4 + dice5;
if (lastThrow == firstThrow)
{
//Win
playWinMusic();
setButtonLeds(0);
currentMode = 1;
}
else if (lastThrow == 7)
{
//Loss
currentMode = 1;
setButtonLeds(0);
playLoseMusic();
}
else
{
//throw again
playRethrowTone();
}
}
}
button = buttonPressed();
}
//Close out the game
initCraps();
}
//=================================================================================================
// YATHZEE
//=================================================================================================
//Initialise the gane
void initYahtzee()
{
//clear LEDs
buttonLeds = 0;
setButtonLeds(buttonLeds);
clearDiceLeds();
currentMode = 0;
}
//Play game - 1st button has been pressed
// S1 - S5 ==> Holds the dice
// PLAY ==> Throws dice not held.
//Continue play until SELECT button is pressed again
void playYahtzee(int button)
{
while (button != SELECT_BTN)
{
if (button == PLAY_BTN)
{
currentMode++;
if (currentMode == 4)
{
//Only allowed a maximum of three throws in Yahtzee
initYahtzee();
}
roll(~buttonLeds);
playRolledTone();
}
else if (button != NO_BUTTON)
{
switch (button)
{
case S1_BTN: buttonLeds = buttonLeds ^ S1_LED; break;
case S2_BTN: buttonLeds = buttonLeds ^ S2_LED; break;
case S3_BTN: buttonLeds = buttonLeds ^ S3_LED; break;
case S4_BTN: buttonLeds = buttonLeds ^ S4_LED; break;
case S5_BTN: buttonLeds = buttonLeds ^ S5_LED; break;
}
setButtonLeds(buttonLeds);
}
button = buttonPressed();
}
//Close out the game
initYahtzee();
}
//=================================================================================================
// DUEL
//=================================================================================================
//Initialise the gane
void initDuel()
{
buttonLeds = 0;
setButtonLeds(buttonLeds);
clearDiceLeds();
}
//Play Game
//Player 1 has left two dice
//Player 2 has right two dice
//Highest player moves their car by the difference between the players dice throws.
void playDuel(int button)
{
while (button != SELECT_BTN)
{
if (button == PLAY_BTN)
{
clearDiceLeds();
roll(S1_LED | S2_LED | S4_LED | S5_LED);
int player1 = dice1 + dice2;
int player2 = dice4 + dice5;
if (player1 > player2)
{
flashDice(S1_LED | S2_LED);
playWinTone();
}
else if (player1 < player2)
{
flashDice(S4_LED | S5_LED);
playWinTone();
}
else
{
//throw again
playLossTone();
}
}
button = buttonPressed();
}
//Close out the game
initDuel();
}
#pragma once
/*
Button handler for Dicer
A0 - SELECT - 1K0 - 2.5V - 512
A0 - S1 - 2K7 - 1.35V - 276
A0 - S2 - 3K3 - 1.16V - 237
A0 - S3 - 5K1 - 0.82V - 167
A1 - S4 - 1K0 - 2.5V - 512
A1 - S5 - 2K7 - 1.35V - 276
A1 - PLAY - 3K3 - 1.16V - 237
*/
enum ButtonEnum { NO_BUTTON, SELECT_BTN, S1_BTN, S2_BTN, S3_BTN, S4_BTN, S5_BTN, PLAY_BTN };
typedef struct
{
int btn;
int pin;
int low;
int high;
} button_type;
#define TOTAL_BUTTON_VALUES 7
button_type buttonValues[TOTAL_BUTTON_VALUES] = {
{SELECT_BTN,A0,300,600},
{S1_BTN,A0,250,300},
{S2_BTN,A0,200,250},
{S3_BTN,A0,100,200},
{S4_BTN,A1,300,600},
{S5_BTN,A1,250,300},
{PLAY_BTN,A1,200,250}
};
void setupButtons()
{
pinMode(A0, INPUT);
pinMode(A1, INPUT);
}
//Tests if any of the buttons have been pressed and released
// returns the button that was pressed
ButtonEnum buttonPressed()
{
ButtonEnum button = NO_BUTTON;
int i = 0;
while (i < TOTAL_BUTTON_VALUES && button == NO_BUTTON)
{
int btn = buttonValues[i].btn;
int pin = buttonValues[i].pin;
int lowRange = buttonValues[i].low;
int highRange = buttonValues[i].high;
int value = analogRead(pin);
if (value >= lowRange && value < highRange)
{
_delay_ms(10);
if (analogRead(pin) >= lowRange && analogRead(pin) < highRange)
{
while (analogRead(pin) >= lowRange && analogRead(pin) < highRange)
{
}
button = btn;
}
}
i++;
}
return button;
}
#pragma once
/*
LED handler for Dicer
Digits are the rows
Segments are the columns
Segment order for each row is SEG_DP, SEG_A, SEG_B, SEG_C, SEG_D, SEG_E, SEG_F, SEG_G
PB3 - MOSI - LOAD - D11
PB4 - MISO - DATA - D12
PB5 - SCK - CLOCK - D13
DICE LAYOUT
===========
DIG_0 DIG_1 DIG_2 DIG_3 DIG_4
+-------------------+ +-------------------+ +-------------------+ +-------------------+ +-------------------+
| SEG_F SEG_A | | SEG_F SEG_A | | SEG_F SEG_A | | SEG_F SEG_A | | SEG_F SEG_A |
| SEG_E SEG_G SEG_B | | SEG_E SEG_G SEG_B | | SEG_E SEG_G SEG_B | | SEG_E SEG_G SEG_B | | SEG_E SEG_G SEG_B |
| SEG_D SEG_C | | SEG_D SEG_C | | SEG_D SEG_C | | SEG_D SEG_C | | SEG_D SEG_C |
+-------------------+ +-------------------+ +-------------------+ +-------------------+ +-------------------+
GREEN GAME LEDS
===============
DIG_5
| SEG_A | SEG_B | SEG_C | SEG_D | SEG_E |
| Game1 | Game2 | Game3 | Game4 | Game5 |
BLUE SWITCH LEDS
================
DIG_6
| SEG_A | SEG_B | SEG_C | SEG_D | SEG_E |
| S1 | S2 | S3 | S4 | S5 |
*/
#include <LedControl.h>
#include "Sound.h"
#define MAX7219_LOAD 11
#define MAX7219_DATA 12
#define MAX7219_CLOCK 13
LedControl lc=LedControl(MAX7219_DATA,MAX7219_CLOCK,MAX7219_LOAD,1);
//Column values for each face on dice
byte diceColumns[] = {B00000000,B00000001,B01001000,B01001001,B01011010,B01011011,B01111110};
//Dice masks (must match switch LED masks)
#define DICE_1 B01000000
#define DICE_2 B00100000
#define DICE_3 B00010000
#define DICE_4 B00001000
#define DICE_5 B00000100
//Button switches
#define SWITCH_ROW 6
byte switchMask = 0;
#define S1_LED B01000000
#define S2_LED B00100000
#define S3_LED B00010000
#define S4_LED B00001000
#define S5_LED B00000100
//Game LEDs
#define GAME_ROW 5
byte gameColumns[] = {B01000000,B00100000,B00010000,B00001000,B00000100};
//LED states - stores the current value of each digit
byte ledStates[7];
//LED flash enable - 0 = not flashing, 1 = flashing
byte ledFlash[7];
//Whether flash is on or off
bool flashState = false;
// Delay between each interrupt - eg: 1 sec ==> (16*10^6) / (1*1024) - 1 (must be <65536) (8Mhz)
#define FLASH_SPEED 7820
//Current dice values
int dice1;
int dice2;
int dice3;
int dice4;
int dice5;
void setupLeds()
{
/*
The MAX72XX is in power-saving mode on startup,
we have to do a wakeup call
*/
lc.shutdown(0,false);
/* Set the brightness to a medium values */
lc.setIntensity(0,1);
/* and clear the display */
lc.clearDisplay(0);
/* Set up timer for background flashing */
cli(); //stop interrupts
//set timer1 interrupt for flash
TCCR1A = 0;// set entire TCCR1A register to 0
TCCR1B = 0;// same for TCCR1B
TCNT1 = 0;//initialize counter value to 0
// set compare match register for 1hz increments
OCR1A = FLASH_SPEED;
// turn on CTC mode
TCCR1B |= (1 << WGM12);
// Set CS10 and CS12 bits for 1024 prescaler
TCCR1B |= (1 << CS12) | (1 << CS10);
// enable timer compare interrupt
TIMSK1 |= (1 << OCIE1A);
// allow interrupts
sei();
//Get random seed from noise on unused analog pin
randomSeed(analogRead(A3));
}
//timer1 interrupt 1Hz toggles flashing of LEDs
ISR(TIMER1_COMPA_vect)
{
//generates pulse wave of frequency 1Hz/2 = 0.5kHz (takes two cycles for full wave- toggle high then toggle low)
for (int i = 0; i < 7; i++)
{
if (flashState)
{
//turn on LEDs
lc.setRow(0, i, ledStates[i]);
}
else
{
//turn off LEDs that have flash enabled
lc.setRow(0, i, ledStates[i] & ~ledFlash[i]);
}
}
flashState = !flashState;
}
//Set the game LED
//game - GameEnum (0 - 4)
void setGameLed(int game)
{
lc.setRow(0, GAME_ROW, gameColumns[game]);
ledStates[GAME_ROW] = gameColumns[game];
ledFlash[GAME_ROW] = (byte)0;
}
//Flash the game LED
//game - GameEnum (0 - 4)
void flashGameLed(int game)
{
ledFlash[GAME_ROW] = gameColumns[game];
}
//Set the switch LEDs
//leds - combination of S1_LED to S5_LED
void setButtonLeds(byte leds)
{
lc.setRow(0, SWITCH_ROW, leds);
ledStates[SWITCH_ROW] = leds;
ledFlash[SWITCH_ROW] = (byte)0;
}
//Flash the switch LEDs
//leds - combination of S1_LED to S5_LED
void flashButtonLeds(byte leds)
{
ledFlash[SWITCH_ROW] = leds;
}
//Set the dice LEDs
//dice - 0 to 4
//value - 1 to 6
void setDiceLeds(int dice, int value)
{
lc.setRow(0, dice, diceColumns[value]);
ledStates[dice] = diceColumns[value];
ledFlash[dice] = (byte)0; //Turn off flash
}
//Flashes the dice LEDs
//dice - 0 to 4
//value - 1 to 6
void flashDiceLeds(int dice, int value)
{
ledFlash[dice] = diceColumns[value];
}
//Flash current dice value
//diceMask is one or more DICE_X constants
void flashDice(byte diceMask)
{
if (diceMask & DICE_1)
{
ledFlash[0] = ledStates[0];
}
if (diceMask & DICE_2)
{
ledFlash[1] = ledStates[1];
}
if (diceMask & DICE_3)
{
ledFlash[2] = ledStates[2];
}
if (diceMask & DICE_4)
{
ledFlash[3] = ledStates[3];
}
if (diceMask & DICE_5)
{
ledFlash[4] = ledStates[4];
}
}
//Clear all LEDs and remove any flashing
void clearDiceLeds()
{
for (int i = 0; i < 5; i++)
{
lc.setRow(0, i, (byte)0);
ledStates[i] = (byte)0;
ledFlash[i] = (byte)0;
}
}
//Get a random roll that isn't the same as the last value
//lastRoll is current dice value
//returns new roll
int newRoll(int lastRoll)
{
int roll = lastRoll;
while (roll == lastRoll)
{
roll = random(1, 7);
}
return roll;
}
//Roll the dice
//diceMask is one or more DICE_X constants
void roll(byte diceMask)
{
int d = 0;
for (int i = 0; i < (random(7, 30)); i++)
{
if (diceMask & DICE_1)
{
dice1 = newRoll(dice1);
setDiceLeds(0, dice1);
}
if (diceMask & DICE_2)
{
dice2 = newRoll(dice2);
setDiceLeds(1, dice2);
}
if (diceMask & DICE_3)
{
dice3 = newRoll(dice3);
setDiceLeds(2, dice3);
}
if (diceMask & DICE_4)
{
dice4 = newRoll(dice4);
setDiceLeds(3, dice4);
}
if (diceMask & DICE_5)
{
dice5 = newRoll(dice5);
setDiceLeds(4, dice5);
}
playRollTone();
delay(d);
d = d + 50;
}
}
#pragma once
/*
Sound handler for Dicer
*/
//Pin that has Piezo electric buzzer
#define SOUND_PIN A2
void setupSound()
{
pinMode(SOUND_PIN, OUTPUT);
}
void playSound(double freqHz, int durationMs)
{
//Calculate the period in microseconds
int periodMicro = int((1/freqHz)*1000000);
int halfPeriod = periodMicro/2;
//store start time
long startTime = millis();
//(millis() - startTime) is elapsed play time
while((millis() - startTime) < durationMs)
{
digitalWrite(SOUND_PIN, HIGH);
delayMicroseconds(halfPeriod);
digitalWrite(SOUND_PIN, LOW);
delayMicroseconds(halfPeriod);
}
}
void playRollTone()
{
playSound(300,50);
}
void playRolledTone()
{
playSound(600, 150);
}
void playWinTone()
{
playSound(800, 150);
}
void playRethrowTone()
{
playSound(400, 150);
}
void playLossTone()
{
playSound(100, 150);
}
void playLoseMusic()
{
delay(400);
//wah wah wah wahwahwahwahwahwah
for(double wah=0; wah<4; wah+=6.541)
{
playSound(440+wah, 50);
}
playSound(466.164, 100);
delay(80);
for(double wah=0; wah<5; wah+=4.939)
{
playSound(415.305+wah, 50);
}
playSound(440.000, 100);
delay(80);
for(double wah=0; wah<5; wah+=4.662)
{
playSound(391.995+wah, 50);
}
playSound(415.305, 100);
delay(80);
for(int j=0; j<7; j++)
{
playSound(391.995, 70);
playSound(415.305, 70);
}
delay(400);
}
void playWinMusic()
{
playSound(880,100); //A5
playSound(988,100); //B5
playSound(523,100); //C5
playSound(988,100); //B5
playSound(523,100); //C5
playSound(587,100); //D5
playSound(523,100); //C5
playSound(587,100); //D5
playSound(659,100); //E5
playSound(587,100); //D5
playSound(659,100); //E5
playSound(659,100); //E5
playSound(880,100); //A5
playSound(988,100); //B5
playSound(523,100); //C5
playSound(988,100); //B5
playSound(523,100); //C5
playSound(587,100); //D5
playSound(523,100); //C5
playSound(587,100); //D5
playSound(659,100); //E5
playSound(587,100); //D5
playSound(659,100); //E5
playSound(659,100); //E5
delay(250);
}
Comments