John Bradnam
Published © GPL3+

One Arm Bandit

A tri-color personal slot machine with a 3D printed realistic lever to spin the wheels.

AdvancedFull instructions provided12 hours3,270

Things used in this project

Hardware components

Arduino Pro Mini 328 - 5V/16MHz
SparkFun Arduino Pro Mini 328 - 5V/16MHz
×1
DC-DC Buck Step Down Module 3.3V 5V 9V 12V 3A Adjustable Voltage Regulator Power
×1
17mm Diam Piezo Piezoelectric Passive Buzzers
×1
60mmx60mm Red/Green 8x8 LED matrix
×3
DM13A LED Driver
×3
74HC138 3 to 8 Line Decoder/Driver
×1
AO3401 P-Channel MOSFET SOT-23
×8
Resistors 0805 SMD
8 x 100 OHM 8 x 1K OHM 3 x 2K7 OHM
×1
5.5 x 2.1mm DC Power Supply Jack Socket Female Panel Mount Connector
×1
Adafruit Tactile Switch Buttons (6mm slim) x 20 pack
×1
Capacitors 0805 SMD
3 x 0.1uF ceramic
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

STL Files

See comments regards slicing

Schematics

Schematic

PCB

Eagle Files

Schematic and PCB in Eagle format

Code

RGMatrixSlotMachineV1.ino

C/C++
/* 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);
  }
}

Wheel.h

C Header File
#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,
  }
};

Piano.h

C Header File
#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

Credits

John Bradnam

John Bradnam

14 projects • 115 followers
Thanks to Adafruit and seawarrior181.

Comments