Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 3 | ||||
| × | 3 | ||||
| × | 1 | ||||
| × | 8 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
| ||||||
Hand tools and fabrication machines | ||||||
| ||||||
|
While looking at projects on the Adafruit site, I came across a fruit machine project that has a cool 3D printed lever that incorporates its own return spring. The Adafruit fruit machine is built around a fairly large 32x64 RGB Matrix (4mm pitch) display. Since I had three 60mm x 60mm Red/Green LED matrixes that were left over from another project, I thought that I would modify my Matrix Slot Machine to incorporate this 3D printed lever.
VideoDesignThe Red/Green matrix modules are common anode. Each matrix has 16 cathodes (8 red and 8 green). The cathodes are connected to a DM13A 16 Channel shift register with each output being a constant current LED driver. The anodes for each matrix are connected to a single set of 8 P-Channel MOSFETs which are driven by a 3 to 8 line decoder/driver so as to reduce the pin count needed for the microprocessor.
I designed a PCB to hold the LED drivers and their associated components.
I have included the Eagle files in case you want to get the board commercially made or do as I did and make it yourself. I used the Toner method.
Start by adding the resistors, capacitors and MOSFETs.
Next add the links if you are using a single sided PCB
Solder on a 10pin header for the connections to the microprocessor board.
The 3 x DM13A and 1 x 74HC138 ICs are DIL variants. Their IC sockets have their pins flattened out and soldered to the board like a SMD device.
Finally add the red/green common anode matrix modules
Use the following settings in your slicer software:
Slot MachineV2 - Front.stl - 0.2 layer height, 20% infill, supports touching build plate, brim. After printing, drill the holes that hold the handle with a 2.5mm drill and create a thread with a 3mm tap.
Slot MachineV2 - Back.stl- 0.2 layer height, 20% infill, no supports, brim
Slot MachineV2 - Diffuser.stl - 0.1 layer height, 20% infill, no supports, brim, print first 3 layers in white, change to black on the start of layer 4.
ball-handle.stl - 0.2 layer height, 20% infill, no supports (2 required)
matrix-handle.stl - 0.2 layer height, 20% infill, no supports, brim, supports touching build plate
Assembling the leverThe lever assembly is screwed on to the side of the front using M3 screws. Use countersunk head (flat head) screws. Place the switch on the holder and run the wires to the inside of the case.
The display module should be a tight fit. If it is too tight, file it down a bit and if it is too loose, add some blue painters tape as padding on the inside of the case.
I added a two pin right angle header to D2 and GND so that the button cable can be removed to separate the back panel.
The display board is connected to the microprocessor with nine wires:
D6 - LATCH
D7 - BLANK
D11 - DATA
D13 - CLOCK
D3 - A
D4 - B
D5 - C
GND - GND
VCC - 5V
The speaker is connected to D8 (-ve side) and D9 (+ve side).
Make sure you set the 3A regulator to output 5V BEFORE you wire it to the 5V rail of the Arduino Pro Mini.
ProgrammingYou will need a FTDI module to program the Arduino Pro Mini (6 pin header). The FTDI module will connect to your USB port on your computer. Load up the Arduino IDE and set the board to a Arduino Pro Mini 5V 16Mhz and upload the sketch.
Final assembly/* Red/Green Matrx Slot Machine
* Concept, symbols and payout table: Daniel J. Murphy
* Hardware and Software design: John Bradnam (jbrad2089@gmail.com)
*/
/*
The Red/Green Matrix Slot Machine
LED test
*/
//#define DEBUG //Comment out when ready to release
#define REELS_IN_FLASH //Comment out to have Reels defined in RAM
#define FONT_IN_FLASH //Comment out to have Font defined in RAM
//#define RESET_EEPROM //Uncomment to reinitialise EEPROM
#include <SPI.h>// SPI Library used to clock data out to the shift registers
#include <TimerFreeTone.h> // https://bitbucket.org/teckel12/arduino-timer-free-tone/wiki/Home
#include <EEPROM.h>
#include "Wheel.h"
#include "Piano.h"
#define SWITCH 2 //Slot machine lever
#define LATCH_PIN 6 //can use any pin you want to latch the shift registers (YEL)
#define BLANK_PIN 7 // same, can use any pin you want for this, just make sure you pull up via a 1k to 5V (GRN)
#define DATA_PIN 11 // used by SPI, must be pin 11 (BRN)
#define CLOCK_PIN 13 // used by SPI, must be 13 (ORG)
#define ANODE_A 3 //74138 A Input (BLU)
#define ANODE_B 4 //74138 B Input (PUR)
#define ANODE_C 5 //74138 C Input (GRY)
#define SPEAKER_N 8 //Negative side of speaker
#define SPEAKER_P 9 //Postive side of speaker
#define BUZZER_DDR DDRB // This is for the slot machines piezo buzzer
#define BUZZER_PORT PORTB
#define BUZZER_PIN DDB1 //D9
//Close Encounters
#define NUM_NOTES 6
const int closeEncounters[] PROGMEM = { // notes in the melody:
NOTE_A2, NOTE_B2, NOTE_G2, NOTE_G1, NOTE_D2, REST // "Close Encounters" tones
};
#define ROWS 8 //Number of rows of LEDs
#define LEDS_PER_ROW 48 //Number of leds on each row
#define BYTES_PER_ROW 6 //Number of bytes required to hold one bit per LED in each row
#define WHEELS 3 //Number of wheels on machine
enum COLOR_VALUE { COL_RED, COL_GREEN, COL_ORANGE };
/* Timing constants that ontrol how the reels spin */
#define START_DELAY_TIME 10
#define INCREMENT_DELAY_TIME 5
#define PAUSE_TIME 1000
#define MAX_DELAY_BEFORE_STOP 100
#define MIN_SPIN_TIME 1000
#define MAX_SPIN_TIME 3000
#define FLASH_REPEAT 10
#define FLASH_TIME 100
#define DIGIT_DELAY_TIME 50
/* spinDigit holds the information for each wheel */
struct spinDigit
{
unsigned long delayTime;
unsigned long spinTime;
unsigned long frameTime;
uint8_t row;
uint8_t symbol;
bool stopped;
};
spinDigit spin[WHEELS];
//- Payout Table
/* Probabilities based on a 1 credit wager
Three spaceships: 1 / (25 * 25 * 25) = 0.000064
Any three symbols: 24 / 15625 = 0.001536
Two spaceships: (24 * 3) / 15625 = 0.004608
One spaceship: (24 * 24 * 3)/ 15625 = 0.110592
Two symbols match: (23 * 3 * 24) / 15625 = 0.105984
House win, 1 minus sum of all probabilities = 0.777216
_
P R O O F
Actual Actual
Winning Combination Payout Probablility Count Probability
=================== ====== ============ ======== ===========*/
#define THREE_SPACESHIP_PAYOUT 600 // 0.000064 0.00006860 see the excel spreadsheet
#define THREE_SYMBOL_PAYOUT 122 // 0.001536 0.00151760 that accompanies this program.
#define TWO_SPACESHIP_PAYOUT 50 // 0.004608 0.00468740
#define ONE_SPACESHIP_PAYOUT 3 // 0.110592 0.11064389
#define TWO_SYMBOL_PAYOUT 2 // 0.105984 0.10575249
#define THREE_SEVEN_PAYOUT 61
#define TWO_SEVEN_PAYOUT 25
#define ONE_SEVEN_PAYOUT 2
#define THREE_ALIEN_PAYOUT 30
#define TWO_ALIEN_PAYOUT 4
#define STARTING_CREDIT_BALANCE 5000 // Number of credits you have at "factory reset".
#define DEFAULT_HOLD 0 // default hold is zero, over time the machine pays out whatever is wagered
#define MINIMUM_WAGER 5 //
#define WAGER_INCREMENT 5 //
#define MAGIC 0xBAD // Used to detect if EEPROM data is good
/* structure that gets stored in EEPROM */
struct retained
{
unsigned long magic; // magic number
unsigned long payedOut; // sum of all payouts
unsigned long wagered; // sum of all wagers (profit = payouts - wagers)
unsigned long plays; // the number of spins
unsigned long twoMatchCount; // number of times two symbols have matched
unsigned int threeMatchCount; // number of times three symbols have matched
unsigned long shipOneMatchCount; // number of times one ship has appeared
unsigned int shipTwoMatchCount; // number of time two ships have appeared
unsigned int shipThreeMatchCount; // number of times three ships have appeared (Jackpot!)
unsigned long sevenThreeMatchCount; // number of times three sevens have matched
unsigned long sevenTwoMatchCount; // number of time two sevens have appeared
unsigned long sevenOneMatchCount; // number of times one seven has appeared
unsigned long alienThreeMatchCount; // number of times three aliens have matched
unsigned long alienTwoMatchCount; // number of time two aliens have appeared
unsigned long eepromWrites; // number of times we've written to EEprom. 100,000 is the approximate maximum
long creditBalance; // the credit balance.
int hold; // the house advantage, in percent, usually between 1 and 15, 2 bytes
unsigned int seed; // random seed
};
/* global variables to reduce stack size on ATTiny85 */
retained stats;
unsigned long wagered;
unsigned long payout;
double owedExcess = 0;
byte ledStates[ROWS][BYTES_PER_ROW]; //Store state of each LED (either off or on)
byte ledNext[ROWS][BYTES_PER_ROW]; //Double buffer for fast updates
int activeRow = 0; //this increments through the anode levels
void setup()
{
#ifdef DEBUG
Serial.begin(115200);
#endif
SPI.setBitOrder(MSBFIRST);//Most Significant Bit First
SPI.setDataMode(SPI_MODE0);// Mode 0 Rising edge of data, keep clock low
SPI.setClockDivider(SPI_CLOCK_DIV2);//Run the data in at 16MHz/2 - 8MHz
noInterrupts();// kill interrupts until everybody is set up
clearDisplay(); //Clear the primary buffer
refresh(); //Transfer to display buffer
activeRow = 0;
//We use Timer 1 to refresh the display
TCCR1A = B00000000; //Register A all 0's since we're not toggling any pins
TCCR1B = B00001011; //bit 3 set to place in CTC mode, will call an interrupt on a counter match
//bits 0 and 1 are set to divide the clock by 64, so 16MHz/64=250kHz
TIMSK1 = B00000010; //bit 1 set to call the interrupt on an OCR1A match
OCR1A = 600; // you can play with this, but I set it to 600, which means:
// our clock runs at 250kHz, which is 1/250kHz = 4us
// with OCR1A set to 600, this means the interrupt will be called every (600+1)x4us=2.4mS,
// which gives a refresh rate (all 8 rows) of 52 times per second
//finally set up the Outputs
pinMode(LATCH_PIN, OUTPUT);//Latch
pinMode(DATA_PIN, OUTPUT);//MOSI DATA
pinMode(CLOCK_PIN, OUTPUT);//SPI Clock
//Setup pins to 3 to 8 channel multiplexer
pinMode(ANODE_A, OUTPUT);
pinMode(ANODE_B, OUTPUT);
pinMode(ANODE_C, OUTPUT);
pinMode(SWITCH,INPUT_PULLUP);
pinMode(SPEAKER_N, OUTPUT);
pinMode(SPEAKER_P, OUTPUT);
digitalWrite(SPEAKER_N, LOW);
//Read from EEPROM the initial stats
readRetainedData(&stats);
//Set up each LED Matrix.
randomSeed(stats.seed);
for (uint8_t j = 0; j < WHEELS; j++)
{
spin[j].row = random(0, SYMBOLS) << 3; //Start each wheel on a random symbol
}
clearDisplay();
//pinMode(BLANK_PIN, OUTPUT);//Output Enable important to do this last, so LEDs do not flash on boot up
SPI.begin();//start up the SPI library
interrupts();//let the show begin, this lets the multiplexing start
delay(500);
//Play splash screen
playSplashScreen();
//Display the current credit balance
displayNumber(stats.creditBalance, COL_RED);
}
ISR(TIMER1_COMPA_vect)
{
PORTD |= 1 << BLANK_PIN; //The first thing we do is turn all of the LEDs OFF, by writing a 1 to the blank pin
//Turn on all columns
for (int shift_out = 0; shift_out < BYTES_PER_ROW; shift_out++)
{
SPI.transfer(ledStates[activeRow][shift_out]);
}
//Enable row that we just outputed the column data for
digitalWrite(ANODE_A, (activeRow & 0x01) ? LOW : HIGH);
digitalWrite(ANODE_B, (activeRow & 0x02) ? LOW : HIGH);
digitalWrite(ANODE_C, (activeRow & 0x04) ? LOW : HIGH);
PORTD |= 1<<LATCH_PIN;//Latch pin HIGH
PORTD &= ~(1<<LATCH_PIN);//Latch pin LOW
PORTD &= ~(1<<BLANK_PIN);//Blank pin LOW to turn on the LEDs with the new data
activeRow = (activeRow + 1) % ROWS; //increment the active row
pinMode(BLANK_PIN, OUTPUT);
}
//-----------------------------------------------------------------------------------
// Main loop
void loop()
{
waitOnButtonPress();
spinTheWheels();
delay(PAUSE_TIME);
//All stopped, time to pay out
wagered = MINIMUM_WAGER;
double winnings = wagered * (payout - (payout * (stats.hold / 100.0))); //winnings are the amount wagered times the payout minus the hold.
long roundWinnings = (long) round(winnings);
owedExcess += winnings - roundWinnings; // owedExcess is the change; credits between -1 and 1.
if (owedExcess >= 1 || owedExcess <= -1)
{ // if we can pay out some excess
int roundOwedExcess = (int) round(owedExcess);
roundWinnings += roundOwedExcess; // add the rounded portion to the winnings
owedExcess -= roundOwedExcess; // subtract out what we added to continue to track the excess
}
roundWinnings -= wagered; // you pay for your bet whether you won or not!
stats.payedOut += roundWinnings;
stats.wagered += wagered;
adjustCreditBalance(stats.creditBalance + roundWinnings);
updateRetainedData(&stats);
}
//--------------------------------------------------------------------------------------------
//Spins all the wheels and updates payout
void spinTheWheels()
{
//Reset wheels for the spin
unsigned long totalTime = millis();
for (uint8_t j = 0; j < WHEELS; j++)
{
totalTime = totalTime + random(MIN_SPIN_TIME, MAX_SPIN_TIME);
spin[j].delayTime = START_DELAY_TIME;
spin[j].spinTime = totalTime;
spin[j].frameTime = millis() + spin[j].delayTime;
spin[j].stopped = false;
}
bool allStopped = false;
while (!allStopped)
{
//Scroll each symbol up
for (uint8_t j = 0; j < WHEELS; j++)
{
if (!spin[j].stopped && millis() > spin[j].frameTime)
{
spin[j].frameTime = millis() + spin[j].delayTime;
displayWheelSymbol(j);
spin[j].row = (spin[j].row + 1) % TOTAL_SYMBOL_ROWS;
beepWheel();
if (millis() > spin[j].spinTime)
{
//Stop if delayTime exceeds MAX_DELAY_BEFORE_STOP
//Only stop on complete symbol
if (spin[j].delayTime > MAX_DELAY_BEFORE_STOP && (spin[j].row % 8) == 1)
{
spin[j].stopped = true;
spin[j].symbol = spin[j].row >> 3;
if (j == (WHEELS - 1))
{
//All wheels are now stopped
allStopped = true;
refresh();
highlightWinAndCalculatePayout();
}
}
else if (spin[j].delayTime <= MAX_DELAY_BEFORE_STOP)
{
spin[j].delayTime = spin[j].delayTime + INCREMENT_DELAY_TIME;
}
}
}
}
refresh();
yield();
}
}
//--------------------------------------------------------------------------------------------
//Display the current symbol of the specified wheel
void displayWheelSymbol(int wheel)
{
for (int8_t i = 7; i >= 0; i--)
{
displayReelRow(wheel, i, getReelRow((spin[wheel].row + i) % TOTAL_SYMBOL_ROWS));
}
}
//-----------------------------------------------------------------------------------
//Read a row from the reels either from FLASH memory or RAM
// row - 0 to 7
uint16_t getReelRow(uint8_t row)
{
#ifdef REELS_IN_FLASH
return pgm_read_word(&reel[row]);
#else
return reel[row];
#endif
}
//--------------------------------------------------------------------------------------------
//Display the current symbol of the specified wheel
// m - matrix (0..2)
// r - row (0..7)
// b - 8 column bits (1 = on, 0 = off) + 2 color bits (00xxxxxxxx - red, 01xxxxxxxx - green, 11xxxxxxxx - orange
void displayReelRow(int m, int r, uint16_t b)
{
COLOR_VALUE color = (COLOR_VALUE)(b >> 8);
for (int c = 0; c < 8; c++)
{
switch(color)
{
case COL_RED:
setBitInArray(r, mapColumn(m, c, true), b & 0x80);
setBitInArray(r, mapColumn(m, c, false), false);
break;
case COL_GREEN:
setBitInArray(r, mapColumn(m, c, true), false);
setBitInArray(r, mapColumn(m, c, false), b & 0x80);
break;
case COL_ORANGE:
setBitInArray(r, mapColumn(m, c, true), b & 0x80);
setBitInArray(r, mapColumn(m, c, false), b & 0x80);
break;
}
b = b << 1;
}
}
//-----------------------------------------------------------------------------------
//Read a row from the font either from FLASH memory or RAM
// digit - 0 to 9
// row - 0 to 5
uint16_t getFontRow(uint8_t digit, uint8_t row)
{
#ifdef FONT_IN_FLASH
return pgm_read_byte(&font[digit][row]);
#else
return font[digit][row];
#endif
}
//-----------------------------------------------------------------------------------
//animate the change in credit balance
void adjustCreditBalance(long newBalance)
{
unsigned int difference;
int8_t direction;
if (stats.creditBalance != newBalance)
{
if (stats.creditBalance > newBalance)
{
difference = stats.creditBalance - newBalance;
direction = -1;
}
else
{
difference = newBalance - stats.creditBalance;
direction = 1;
}
for (unsigned int i = 0; i < difference; i++)
{
stats.creditBalance += direction;
displayNumber(stats.creditBalance, COL_RED);
beepDigit();
delay(DIGIT_DELAY_TIME);
}
}
}
//-----------------------------------------------------------------------------------
//Display a 6 digit number
// number - (0 to 999999)
// color = COLOR_VALUE constant
void displayNumber(long number, COLOR_VALUE color)
{
clearDisplay();
if (number > 999999)
{
number = 999999;
}
for (uint8_t i = 0; i < 6; i++)
{
if (number > 0 || i == 0)
{
displayDigit((5 - i) >> 1, 2, ((5 - i) & 0x01) << 2, number % 10, color);
}
number = number / 10;
}
refresh();
}
//-----------------------------------------------------------------------------------
//Display the current symbol of the specified wheel
// m - matrix (0..2)
// y - 0..7 (0 is top)
// x - 0..7 (0 is left)
// d - digit (0..9)
// color = COLOR_VALUE constant
void displayDigit(int m, int y, int x, uint8_t d, COLOR_VALUE color)
{
for (int r = 0; r < 5; r++)
{
uint8_t b = getFontRow(d,r);
uint8_t ry = (r + y) & 07;
for (int c = 0; c < 4; c++)
{
uint8_t cx = (c + x) & 07;
switch(color)
{
case COL_RED:
setBitInArray(ry, mapColumn(m, cx, true), b & 0x08);
setBitInArray(ry, mapColumn(m, cx, false), false);
break;
case COL_GREEN:
setBitInArray(ry, mapColumn(m, cx, true), false);
setBitInArray(ry, mapColumn(m, cx, false), b & 0x08);
break;
case COL_ORANGE:
setBitInArray(ry, mapColumn(m, cx, true), b & 0x08);
setBitInArray(ry, mapColumn(m, cx, false), b & 0x08);
break;
}
b = b << 1;
}
}
}
//-----------------------------------------------------------------------------------
//Maps matrix, column and color to LED column number
// m = matrix (0 - left, 1 - middle, 2 - right)
// c = column (0-7) 0 being farest left hand and 7 being farest right
// g = green - true for green, false for red
// returns physical column (0 to 47)
int mapColumn(int m, int c, bool g)
{
return ((2 - m) << 4) + ((c < 4) ? (8 + (c << 1) + ((g) ? 0 : 1)) : (8 - ((c - 3) << 1) + ((g) ? 1 : 0)));
}
//Sets the bit in the 6 byte array that corresponds to the physical column and row
// r = row (0 - top row to 7 - bottom row)
// c = column (0 to 47)
// on = true to switch bit on, false to switch bit off
void setBitInArray(int r, int c, bool on)
{
int by = c >> 3;
uint8_t bi = c - (by << 3);
if (on)
{
ledNext[7 - r][by] |= (1 << bi);
}
else
{
ledNext[7 - r][by] &= ~(1 << bi);
}
}
//-----------------------------------------------------------------------------------
//Clears the working buffer
void clearDisplay()
{
//Clear out ledStates array
for (int r = 0; r < ROWS; r++)
{
for (int c = 0; c < BYTES_PER_ROW; c++)
{
ledNext[r][c] = 0;
}
}
}
//-----------------------------------------------------------------------------------
//Transfers the working buffer to the display buffer
void refresh()
{
memcpy(ledStates, ledNext, ROWS * BYTES_PER_ROW);
}
//--------------------------------------------------------------------------------------------
//Work out if the player has one anything
//If they have, flash winning sequence and update payout multiplier
void highlightWinAndCalculatePayout()
{
payout = 0;
unsigned long shipPayout = 0;
unsigned long alienPayout = 0;
unsigned long sevenPayout = 0;
unsigned long otherPayout = 0;
uint8_t shipMatches = 0;
uint8_t alienMatches = 0;
uint8_t sevenMatches = 0;
uint8_t otherMatches = 0;
uint8_t symbol = 0;
for (uint8_t y = 0; y < WHEELS; y++)
{
switch (spin[y].symbol)
{
case SPACESHIP:
shipMatches++;
break;
case ALIEN_1:
case ALIEN_2:
case ALIEN_3:
case ALIEN_4:
alienMatches++;
break;
case SEVEN_1:
case SEVEN_2:
sevenMatches++;
break;
}
for (uint8_t x = 0; x < WHEELS; x++)
{
if (spin[y].symbol == spin[x].symbol && y != x)
{
otherMatches++;
symbol = spin[y].symbol;
}
}
}
bool flash = true;
switch (shipMatches)
{
case 3: shipPayout = THREE_SPACESHIP_PAYOUT; stats.shipThreeMatchCount++;break;
case 2: shipPayout = TWO_SPACESHIP_PAYOUT; stats.shipTwoMatchCount++;break;
case 1: shipPayout = ONE_SPACESHIP_PAYOUT; stats.shipOneMatchCount++;break;
}
switch (sevenMatches)
{
case 3: sevenPayout = THREE_SEVEN_PAYOUT; stats.sevenThreeMatchCount++; break;
case 2: sevenPayout = TWO_SEVEN_PAYOUT; stats.sevenTwoMatchCount++; break;
case 1: sevenPayout = ONE_SEVEN_PAYOUT; stats.sevenOneMatchCount++; break;
}
switch (alienMatches)
{
case 3: alienPayout = THREE_ALIEN_PAYOUT; stats.alienThreeMatchCount++; break;
case 2: alienPayout = TWO_ALIEN_PAYOUT; stats.alienTwoMatchCount++; break;
}
switch (otherMatches)
{
case 6: otherPayout = THREE_SYMBOL_PAYOUT; stats.threeMatchCount++; break;
case 2: otherPayout = TWO_SYMBOL_PAYOUT; stats.twoMatchCount++; break;
}
//Get the best payout and flash the winning sequence
payout = max(max(max(shipPayout, sevenPayout), alienPayout), otherPayout);
if (payout == shipPayout) symbol = SPACESHIP;
else if (payout == sevenPayout) symbol = SEVEN_1;
else if (payout == alienPayout) symbol = ALIEN_1;
else if (payout == 0) flash = false;
#ifdef DEBUG
Serial.println( \
"ship:" + String(shipMatches) + ", x:" + String(shipPayout) + \
"; seven:" + String(sevenMatches) + ", x:" + String(sevenPayout) + \
"; alien:" + String(alienMatches) + ", x:" + String(alienPayout) + \
"; other:" + String(otherMatches) + ", x:" + String(otherPayout) + \
"; symol:" + String(symbol) + ", x:" + String(payout));
#endif
//Play win sound based on amount won
if (payout > 500) winSound(5);
else if (payout > 50) winSound(3);
else if (payout > 10) winSound(2);
else if (payout > 0) winSound(1);
if (flash)
{
flashSymbol(symbol);
}
//Count every spin
stats.plays++;
}
//---------------------------------------------------------------------------
//Flashes any wheel that is showing the specified symbol
void flashSymbol(uint8_t symbol)
{
bool on = true;
for (uint8_t r = 0; r < FLASH_REPEAT; r++)
{
for (uint8_t j = 0; j < WHEELS; j++)
{
uint8_t s = spin[j].symbol;
uint8_t row = s << 3;
if (symbol == ALIEN_1 && (s == ALIEN_2 || s == ALIEN_3 || s == ALIEN_4))
{
s = ALIEN_1;
}
if (symbol == SEVEN_1 && s == SEVEN_2)
{
s = SEVEN_1;
}
if (s == symbol)
{
for (int8_t i = 7; i >= 0; i--)
{
if (on)
{
displayReelRow(j, i, 0);
}
else
{
displayReelRow(j, i, getReelRow((row + i) % TOTAL_SYMBOL_ROWS));
}
}
}
}
refresh();
on = !on;
delay(FLASH_TIME);
}
}
//---------------------------------------------------------------------------
//Play the opening anaimation
void playSplashScreen()
{
//Show aliens walking
int thisNote = 0;
int noteDuration = 500;
clearDisplay();
for (uint8_t k = 0; k < 1; k++)
{
for (uint8_t j = 0; j < WHEELS; j++)
{
for (uint8_t r = 0; r < 3; r++)
{
for (uint8_t n = 0; n <= j; n++)
{
for (int8_t i = 7; i >= 0; i--)
{
displayReelRow(n, i, getReelRow((ALIEN_1 << 3) + i));
}
}
refresh();
TimerFreeTone(SPEAKER_P, pgm_read_byte(closeEncounters + thisNote) * 3, noteDuration);
thisNote = (thisNote + 1) % NUM_NOTES;
for (uint8_t n = 0; n <= j; n++)
{
for (int8_t i = 7; i >= 0; i--)
{
displayReelRow(n, i, getReelRow((ALIEN_2 << 3) + i));
}
}
refresh();
TimerFreeTone(SPEAKER_P, pgm_read_byte(closeEncounters + thisNote) * 3, noteDuration);
thisNote = (thisNote + 1) % NUM_NOTES;
}
}
//playMelody();
}
//Move ship from right to left clearing out the aliens
winSound(2);
for (int p = WHEELS * 8 - 1; p > -8; p--)
{
for (uint8_t c = 0; c < 8; c++)
{
//Display each column at position p
int pc = p + c;
if (pc >= 0 && pc < (WHEELS << 3))
{
for (uint8_t r = 0; r < 8; r++)
{
int8_t b = getReelRow((SPACESHIP << 3) + r) & (1 << c);
setBitInArray(r, mapColumn((pc >> 3), pc & 0x07, COL_GREEN), b);
setBitInArray(r, mapColumn((pc >> 3), pc & 0x07, COL_RED), 0);
}
}
//Clear last column
pc = pc + 1;
if (pc >= 0 && pc < (WHEELS << 3))
{
for (uint8_t r = 0; r < 8; r++)
{
setBitInArray(r, mapColumn((pc >> 3), pc & 0x07, COL_RED), 0);
setBitInArray(r, mapColumn((pc >> 3), pc & 0x07, COL_GREEN), 0);
}
}
}
refresh();
beepWheel();
delay(100);
}
clearDisplay();
refresh();
}
//-----------------------------------------------------------------------------------
//Read from EEPROM
void readRetainedData(retained* p)
{
for (uint8_t addr = 0; addr < sizeof(retained); addr++)
{
*((byte *)p + addr) = EEPROM.read(addr);
}
#ifdef RESET_EEPROM
p->magic = 0;
#endif
if (p->magic != MAGIC)
{
//Initialise the data
p->magic = MAGIC;
p->payedOut = 0;
p->wagered = 0;
p->plays = 0;
p->twoMatchCount = 0;
p->threeMatchCount = 0;
p->twoMatchCount = 0;
p->shipOneMatchCount = 0;
p->shipTwoMatchCount = 0;
p->shipThreeMatchCount = 0;
p->sevenThreeMatchCount = 0;
p->sevenTwoMatchCount = 0;
p->sevenOneMatchCount = 0;
p->alienThreeMatchCount = 0;
p->alienTwoMatchCount = 0;
p->eepromWrites = 1;
p->creditBalance = STARTING_CREDIT_BALANCE;
p->hold = DEFAULT_HOLD;
p->seed = analogRead(A0);
//Write it to EEPROM for the first time
for (uint8_t addr = 0; addr < sizeof(retained); addr++)
{
EEPROM.write(addr, *((byte *)p + addr));
}
}
//On power on, if the player has no money, reset the credit
if (p->creditBalance <= 0)
{
p->creditBalance = STARTING_CREDIT_BALANCE;
}
}
//-----------------------------------------------------------------------------------
//Update to EEPROM
void updateRetainedData(retained* p)
{
//Record writes
p->eepromWrites++;
//store a new seed so that it isn't the same at next power on
p->seed = random(0,65535);
//Write it to EEPROM for the first time
for (uint8_t addr = 0; addr < sizeof(retained); addr++)
{
EEPROM.update(addr, *((byte *)p + addr));
}
}
//-----------------------------------------------------------------------------------
//Wait until player presses the button
void waitOnButtonPress()
{
bool released = false;
while (!released)
{
while (digitalRead(SWITCH) == HIGH)
{
yield();
delay(10);
}
delay(20);
if (digitalRead(SWITCH) == LOW)
{
while (digitalRead(SWITCH) == LOW)
{
yield();
delay(10);
}
released = true;
}
}
}
//-----------------------------------------------------------------------------------
//Turn on and off buzzer quickly
void beepWheel()
{
BUZZER_PORT |= (1 << BUZZER_PIN); // turn on buzzer
delay(20);
BUZZER_PORT &= ~(1 << BUZZER_PIN); // turn off the buzzer
}
//-----------------------------------------------------------------------------------
//Turn on and off buzzer quickly
void beepDigit()
{
BUZZER_PORT |= (1 << BUZZER_PIN); // turn on buzzer
delay(5);
BUZZER_PORT &= ~(1 << BUZZER_PIN); // turn off the buzzer
}
//-----------------------------------------------------------------------------------
//Play the winning siren multiple times
void winSound(uint8_t repeat)
{
for (uint8_t i = 0; i < repeat; i++)
{
playSiren();
}
}
//-----------------------------------------------------------------------------------
//Play the siren sound
void playSiren()
{
#define MAX_NOTE 4978 // Maximum high tone in hertz. Used for siren.
#define MIN_NOTE 31 // Minimum low tone in hertz. Used for siren.
for (int note = MIN_NOTE; note <= MAX_NOTE; note += 5)
{
TimerFreeTone(SPEAKER_P, note, 1);
}
}
//-----------------------------------------------------------------------------------
//Play the "Close Encounters" melody
void playMelody()
{
for (int thisNote = 0; thisNote < NUM_NOTES; thisNote++)
{
// to calculate the note duration, take one second divided by the note type.
//e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc.
int noteDuration = 500;
TimerFreeTone(SPEAKER_P, pgm_read_byte(closeEncounters + thisNote) * 3, noteDuration);
delay(100);
}
}
#pragma once
#define SYMBOLS 25
#define TOTAL_SYMBOL_ROWS (SYMBOLS << 3)
#define SPACESHIP 18
#define ALIEN_1 19
#define ALIEN_2 20
#define ALIEN_3 21
#define ALIEN_4 15
#define SEVEN_1 5
#define SEVEN_2 11
#define R 0x0000
#define G 0x0100
#define O 0x0200
#ifdef REELS_IN_FLASH
const uint16_t reel[] PROGMEM =
#else
const uint16_t reel[] =
#endif
{ // 0 star
G+B10011001, //0
G+B01011010,
G+B00111100,
G+B11111111,
G+B11111111,
G+B00111100,
G+B01011010,
G+B10011001,
// 1 one spot on dice
G+B00000000, // 8
G+B00000000,
G+B00000000,
G+B00011000,
G+B00011000,
G+B00000000,
G+B00000000,
G+B00000000,
// 2 three bars
G+B11111111, // 16
G+B11111111,
G+B00000000,
G+B11111111,
G+B11111111,
G+B00000000,
G+B11111111,
G+B11111111,
// 3 heart
R+B01100110, // 24
R+B11111111,
R+B11111111,
R+B11111111,
R+B11111111,
R+B01111110,
R+B00111100,
R+B00011000,
// 4 two spots on dice
G+B00000000, // 32
G+B01100000,
G+B01100000,
G+B00000000,
G+B00000000,
G+B00000110,
G+B00000110,
G+B00000000,
// 5 seven
R+B00000000, // 40
R+B01111110,
R+B01111110,
R+B00001100,
R+B00011000,
R+B00111000,
R+B00111000,
R+B00000000,
// 6 dollar sign
G+B00011000, // 48
G+B00111100,
G+B01011010,
G+B00111000,
G+B00011100,
G+B01011010,
G+B00111100,
G+B00011000,
// 7 three spots on dice
G+B00000000,
G+B01100000,
G+B01100000,
G+B00011000,
G+B00011000,
G+B00000110,
G+B00000110,
G+B00000000,
// 8 #
G+B00100100,
G+B00100100,
G+B11111111,
G+B00100100,
G+B00100100,
G+B11111111,
G+B00100100,
G+B00100100,
// 9 one bar
G+B00000000,
G+B00000000,
G+B00000000,
G+B11111111,
G+B11111111,
G+B00000000,
G+B00000000,
G+B00000000,
// 10 four on dice
G+B00000000,
G+B01100110,
G+B01100110,
G+B00000000,
G+B00000000,
G+B01100110,
G+B01100110,
G+B00000000,
// 11 inverse seven
R+B11111111,
R+B10000001,
R+B10000001,
R+B11110011,
R+B11100111,
R+B11000111,
R+B11000111,
R+B11111111,
// 12 9 spots
G+B11011011,
G+B11011011,
G+B00000000,
G+B11011011,
G+B11011011,
G+B00000000,
G+B11011011,
G+B11011011,
// 13 five on dice
G+B00000000,
G+B01100110,
G+B01100110,
G+B00011000,
G+B00011000,
G+B01100110,
G+B01100110,
G+B00000000,
// 14 two bars
G+B00000000,
G+B11111111,
G+B11111111,
G+B00000000,
G+B00000000,
G+B11111111,
G+B11111111,
G+B00000000,
// 15 Alien 0 (120)
O+B01000010,
O+B00100100,
O+B01111110,
O+B11011011,
O+B11111111,
O+B11111111,
O+B10100101,
O+B00100100,
// 16 smile face (128)
G+B00000000,
G+B00100100,
G+B00000000,
G+B00011000,
G+B01000010,
G+B01000010,
G+B00111100,
G+B00011000,
// 17 6 on dice (136)
G+B00000000,
G+B11011011,
G+B11011011,
G+B00000000,
G+B00000000,
G+B11011011,
G+B11011011,
G+B00000000,
// 18 SpaceShip (144)
R+B00000000,
R+B00000000,
R+B00111100,
R+B01111110,
R+B10101011,
R+B01111110,
R+B00111100,
R+B00000000,
// 19 Alien 1 (152)
O+B00011000,
O+B00111100,
O+B01111110,
O+B11011011,
O+B11111111,
O+B00100100,
O+B01011010,
O+B10100101,
// 20 Alien 2 (160)
O+B00011000,
O+B00111100,
O+B01111110,
O+B11011011,
O+B11111111,
O+B00100100,
O+B01011010,
O+B01000010,
// 21 Alien 3 (168)
O+B00000000,
O+B10000001,
O+B11111111,
O+B11011011,
O+B11111111,
O+B01111110,
O+B00100100,
O+B01000010,
// 22 club
G+B00011000,
G+B00111100,
G+B01011010,
G+B11111111,
G+B11111111,
G+B01011010,
G+B00011000,
G+B01111110,
// 23 spade
G+B00011000,
G+B00111100,
G+B01111110,
G+B11111111,
G+B11111111,
G+B01011010,
G+B00011000,
G+B01111110,
// 24 dialmod
R+B00011000,
R+B00111100,
R+B01111110,
R+B11111111,
R+B11111111,
R+B01111110,
R+B00111100,
R+B00011000,
};
#ifdef FONT_IN_FLASH
const uint8_t font[10][5] PROGMEM =
#else
const uint8_t font[10][5] =
#endif
{
{ //0
B00001110,
B00001010,
B00001010,
B00001010,
B00001110,
},
{ //1
B00000100,
B00001100,
B00000100,
B00000100,
B00001110,
},
{ //2
B00001110,
B00000010,
B00001110,
B00001000,
B00001110,
},
{ //3
B00001110,
B00000010,
B00001110,
B00000010,
B00001110,
},
{ //4
B00001000,
B00001010,
B00001110,
B00000010,
B00000010,
},
{ //5
B00001110,
B00001000,
B00001110,
B00000010,
B00001110,
},
{ //6
B00001110,
B00001000,
B00001110,
B00001010,
B00001110,
},
{ //7
B00001110,
B00000010,
B00000100,
B00000100,
B00000100,
},
{ //8
B00001110,
B00001010,
B00001110,
B00001010,
B00001110,
},
{ //9
B00001110,
B00001010,
B00001110,
B00000010,
B00001110,
}
};
#ifndef piano_h
#define piano_h
// Constants for notes
#define REST 0
#define NOTE_B0 31
#define NOTE_C1 33
#define NOTE_CS1 35
#define NOTE_D1 37
#define NOTE_DS1 39
#define NOTE_E1 41
#define NOTE_F1 44
#define NOTE_FS1 46
#define NOTE_G1 49
#define NOTE_GS1 52
#define NOTE_A1 55
#define NOTE_AS1 58
#define NOTE_B1 62
#define NOTE_C2 65
#define NOTE_CS2 69
#define NOTE_D2 73
#define NOTE_DS2 78
#define NOTE_E2 82
#define NOTE_F2 87
#define NOTE_FS2 93
#define NOTE_G2 98
#define NOTE_GS2 104
#define NOTE_A2 110
#define NOTE_AS2 117
#define NOTE_B2 123
#define NOTE_C3 131
#define NOTE_CS3 139
#define NOTE_D3 147
#define NOTE_DS3 156
#define NOTE_E3 165
#define NOTE_F3 175
#define NOTE_FS3 185
#define NOTE_G3 196
#define NOTE_GS3 208
#define NOTE_A3 220
#define NOTE_AS3 233
#define NOTE_B3 247
#define NOTE_C4 262
#define NOTE_CS4 277
#define NOTE_D4 294
#define NOTE_DS4 311
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_FS4 370
#define NOTE_G4 392
#define NOTE_GS4 415
#define NOTE_A4 440
#define NOTE_AS4 466
#define NOTE_B4 494
#define NOTE_C5 523
#define NOTE_CS5 554
#define NOTE_D5 587
#define NOTE_DS5 622
#define NOTE_E5 659
#define NOTE_F5 698
#define NOTE_FS5 740
#define NOTE_G5 784
#define NOTE_GS5 831
#define NOTE_A5 880
#define NOTE_AS5 932
#define NOTE_B5 988
#define NOTE_C6 1047
#define NOTE_CS6 1109
#define NOTE_D6 1175
#define NOTE_DS6 1245
#define NOTE_E6 1319
#define NOTE_F6 1397
#define NOTE_FS6 1480
#define NOTE_G6 1568
#define NOTE_GS6 1661
#define NOTE_A6 1760
#define NOTE_AS6 1865
#define NOTE_B6 1976
#define NOTE_C7 2093
#define NOTE_CS7 2217
#define NOTE_D7 2349
#define NOTE_DS7 2489
#define NOTE_E7 2637
#define NOTE_F7 2794
#define NOTE_FS7 2960
#define NOTE_G7 3136
#define NOTE_GS7 3322
#define NOTE_A7 3520
#define NOTE_AS7 3729
#define NOTE_B7 3951
#define NOTE_C8 4186
#define NOTE_CS8 4435
#define NOTE_D8 4699
#define NOTE_DS8 4978
#define END_OF_TUNE 0xFFFF
#define DUR_8 0xE000
#define DUR_6 0xC000
#define DUR_4 0x8000
#define DUR_3 0x6000
#define DUR_2 0x4000
#define DUR_1 0x2000
#endif
Comments