/*
  Slot Machine
  
  Ver. 2023.05.15
  
  Management of slot machine operations. 
  Arduino Nano runs three stepper motors and a sound card.

  https://www.arduino.cc/reference/en/

  Electrical Components

  Arduino NANO
    https://store-usa.arduino.cc/products/arduino-nano?selectedStore=us
  Three stepper motors, 4SHG-240A46S
  Three stepper motor drivers, DRV8825
    https://www.pololu.com/product/2133
  DFPlayer Mini MP3 Player  
    https://wiki.dfrobot.com/DFPlayer_Mini_SKU_DFR0299
  Three Optic Sensors
  Switch

  Power Supply      Arduino     Stepper motor drivers     Stepper motor     Optic Sensors     DFPlayer      Start Switch  
  24VDC ----------------------> VIN (1,2,3) -------------> Pin 6 (1,2,3)
  12VDC ----------> VIN
  5 VDC ----------------------> V (1,2,3) --------------------------------> Pin 8 (1,2,3)---> VCC
                           |--> DIR
  GND ------------> GND ------> GND, G (1,2,3) ---------------------------> Pin 10(1,2,3)---> GND --------> Pin
                           |--> EN
                                1A (1,2,3) ----------------> Pin 4 (1,2,3) 
                                1B (1,2,3) ----------------> Pin 2 (1,2,3) 
                                2A (1,2,3) ----------------> Pin 3 (1,2,3) 
                                2B (1,2,3) ----------------> Pin 1 (1,2,3) 
                    D2 -------> STEP (1)
                    D3 ---------------------------------------------------> Pin 9 (1)
                    D4 -------> STEP (2)
                    D5 ---------------------------------------------------> Pin 9 (2)
                    D6 -------> STEP (3)
                    D7 ---------------------------------------------------> Pin 9 (3)
                    D8 -----------------------------------------------------------------------------------> Pin
                    D9 
                    D10 --------------------------------------------------------------------> TX
                    D11 --------------------- (R 10K) --------------------------------------> RX
                    D12
                    D13
                               

# -------------------------- #
#  Design by Ephraim Herman  #
# -------------------------- #

*/

#include "Arduino.h"
#include "SoftwareSerial.h"
#include "DFRobotDFPlayerMini.h"

SoftwareSerial mySoftwareSerial(10, 11); // RX, TX
DFRobotDFPlayerMini myDFPlayer;

const int NUM_WHEEL_STEPS = 11;
const int STEPS_PER_REVOLUTION = 200;
const float WHEEL_ONE_STEP_INTERVAL = STEPS_PER_REVOLUTION / NUM_WHEEL_STEPS;
const int BONUS_JACKPOT = 5;
const int CHERRIES = 4;
const int DOUBLE_BAR = 1;
const int MYDFPLAYER_BUSY_PIN = 9;
const int NUM_OF_WHEELS = 3;
const int ONE_BAR = 0;
const int POSITION_PIN_1 = 3;
const int POSITION_PIN_2 = 5;
const int POSITION_PIN_3 = 7;
const int SEVEN = 3;
const int STEP_PIN_1 = 2;
const int STEP_PIN_2 = 4;
const int STEP_PIN_3 = 6;
const int SWITCH_PIN = 8;
const int TRIPLE_BAR = 2;
const int WHEEL_1 = 0;
const int WHEEL_2 = 1;
const int WHEEL_3 = 2;
const int WON_IN_THE_GAME_005 = 5;
const int LOST_IN_THE_GAME_004 = 4;
const int THE_GAME_STARTED_001 = 1;
const int THE_WHEELS_ARE_SPINNING_002 = 2;
const int THE_WHEEL_STOPPED_003 = 3;
/*
// DIR = HIGH; =====================================================
const int WHEEL_RESULTS[NUM_OF_WHEELS][NUM_WHEEL_STEPS] = {
  {ONE_BAR, DOUBLE_BAR, SEVEN, ONE_BAR, BONUS_JACKPOT, TRIPLE_BAR, 
   CHERRIES, DOUBLE_BAR, SEVEN, TRIPLE_BAR, BONUS_JACKPOT},
  {DOUBLE_BAR, ONE_BAR, SEVEN, DOUBLE_BAR, BONUS_JACKPOT, TRIPLE_BAR, 
   CHERRIES, ONE_BAR, SEVEN, TRIPLE_BAR, BONUS_JACKPOT},
  {ONE_BAR, TRIPLE_BAR, SEVEN, ONE_BAR, BONUS_JACKPOT, DOUBLE_BAR, 
   CHERRIES, TRIPLE_BAR, SEVEN, DOUBLE_BAR, BONUS_JACKPOT}};
*/   
// DIR = LOW; ======================================================
const int WHEEL_RESULTS[NUM_OF_WHEELS][NUM_WHEEL_STEPS] = {
  {ONE_BAR, BONUS_JACKPOT, TRIPLE_BAR, SEVEN, DOUBLE_BAR, CHERRIES,
  TRIPLE_BAR, BONUS_JACKPOT, ONE_BAR, SEVEN, DOUBLE_BAR},
  {DOUBLE_BAR, BONUS_JACKPOT, TRIPLE_BAR, SEVEN, ONE_BAR, CHERRIES,
  TRIPLE_BAR, BONUS_JACKPOT, DOUBLE_BAR, SEVEN, ONE_BAR},
  {ONE_BAR, BONUS_JACKPOT, DOUBLE_BAR, SEVEN, TRIPLE_BAR, CHERRIES,
  DOUBLE_BAR,  BONUS_JACKPOT, ONE_BAR, SEVEN, TRIPLE_BAR}};
// DIR end. =======================================================
const int whellMaxDelayTime = 16;
const int whellMinDelayTime = 2;

boolean gameCycleHasBegun = false;
boolean firstRun = true;
boolean myDFPlayerOK = false;
boolean wheelActive[NUM_OF_WHEELS] = {false, false, false};
int wheelRandomPosition[NUM_OF_WHEELS] = {0, 0, 0};
int stopPosition[NUM_OF_WHEELS] = {0, 0, 0};
int wheelPosition[NUM_OF_WHEELS] = {0, 0, 0};
int presetMode = 0;
unsigned long blindTime = 0;
unsigned long buttonPressTime = 0;
unsigned long buttonReleaseTime = 0;
unsigned long minimuActicrTime[NUM_OF_WHEELS] = {0, 0, 0};
unsigned long nextPositionTime[NUM_OF_WHEELS] = {0, 0, 0};
unsigned long nextStepTime[NUM_OF_WHEELS] = {0, 0, 0};
unsigned long stepTime[NUM_OF_WHEELS] = {0, 0, 0};


void playMelody(int trackNum) {
  if (!firstRun) {
    myDFPlayer.volume(20);
    myDFPlayer.play(trackNum);
  }
}

boolean ifSwitchReleased(boolean currentSwitchStat) {
  static boolean lastSwitchStat = HIGH;
  if (gameCycleHasBegun) return false;
  if (currentSwitchStat != lastSwitchStat) {
    blindTime = millis() + 100;
    lastSwitchStat = currentSwitchStat;
    if (currentSwitchStat == HIGH) {
      buttonReleaseTime = millis();
    } else {
      buttonPressTime = millis();
    }
    return currentSwitchStat;
  } else {
    return false;
  }
}

void startWheelActuation(int wheelIndex) {
  wheelActive[wheelIndex] = true;
  stepTime[wheelIndex] = whellMaxDelayTime;
  nextPositionTime[wheelIndex] = millis() + 1000;
  wheelPosition[wheelIndex] = -1;
  if (firstRun == true) {
    minimuActicrTime[wheelIndex] = millis() + 2000;
    if (myDFPlayerOK == true) {
      wheelRandomPosition[wheelIndex] = 2;
    } else {
      wheelRandomPosition[wheelIndex] = 6;
    }
  } else {
    minimuActicrTime[wheelIndex] = millis() + random(5000, 10000);
    if (presetMode == -1) {
      wheelRandomPosition[wheelIndex] = random(NUM_WHEEL_STEPS);
    } else {
      wheelRandomPosition[wheelIndex] = 0;
      while (WHEEL_RESULTS[wheelIndex][wheelRandomPosition[wheelIndex]] != presetMode) wheelRandomPosition[wheelIndex]++;
    }
  }
  stopPosition[wheelIndex] = int(WHEEL_ONE_STEP_INTERVAL * wheelRandomPosition[wheelIndex]);
}

void nextStep(int wheelIndex) {
  if (millis() > nextStepTime[wheelIndex]) {
    if (stepTime[wheelIndex] > whellMinDelayTime) stepTime[wheelIndex]--;
    if (digitalRead(wheelIndex * 2 + 2) == LOW) {
      digitalWrite(wheelIndex * 2 + 2, HIGH);
      nextStepTime[wheelIndex] = millis() + whellMinDelayTime;
    } else {
      digitalWrite(wheelIndex * 2 + 2, LOW);
      nextStepTime[wheelIndex] = millis() + stepTime[wheelIndex];
      if (wheelPosition[wheelIndex] != -1) wheelPosition[wheelIndex] = (wheelPosition[wheelIndex] + 1) % STEPS_PER_REVOLUTION;
    }
  }
}

void checkWheelPosition(int wheelIndex) {
  if (millis() > nextPositionTime[wheelIndex]) {
    if (digitalRead((wheelIndex + 1) * 2 + 1) == LOW) {
      wheelPosition[wheelIndex] = 0;
      nextPositionTime[wheelIndex] = millis() + 1000;
    }
  }
}

void stopWheelIfReachedDestination(int wheelIndex) {
  if (millis() > minimuActicrTime[wheelIndex]) {
    if (wheelPosition[wheelIndex] == stopPosition[wheelIndex]) {
      playMelody(THE_WHEEL_STOPPED_003);
      wheelActive[wheelIndex] = false;
    }
  }
}

void setup() {
  delay(1000);
  mySoftwareSerial.begin(9600);
  delay(1000);
  randomSeed(analogRead(0));
  myDFPlayerOK = myDFPlayer.begin(mySoftwareSerial);
  if (myDFPlayerOK) myDFPlayer.setTimeOut(500);
  pinMode(STEP_PIN_1, OUTPUT);
  pinMode(POSITION_PIN_1, INPUT_PULLUP);
  pinMode(STEP_PIN_2, OUTPUT);
  pinMode(POSITION_PIN_2, INPUT_PULLUP);
  pinMode(STEP_PIN_3, OUTPUT);
  pinMode(POSITION_PIN_3, INPUT_PULLUP);
  pinMode(SWITCH_PIN, INPUT_PULLUP);
  pinMode(MYDFPLAYER_BUSY_PIN, INPUT_PULLUP);
  firstRun = true;
  gameCycleHasBegun = false;
}

void loop() {
  if (millis() > blindTime || firstRun == true) {
    if (ifSwitchReleased(digitalRead(SWITCH_PIN)) || firstRun == true) {
      firstRun = false;
      gameCycleHasBegun = true;
      playMelody(THE_GAME_STARTED_001);
      delay(3000);
      playMelody(THE_WHEELS_ARE_SPINNING_002);
      if (buttonReleaseTime - buttonPressTime > 3000 && buttonReleaseTime - buttonPressTime < 5000) {
        presetMode = random(6);
      } else {
        presetMode = -1;
      }
      for (int wheelIndex = 0; wheelIndex < NUM_OF_WHEELS; wheelIndex++) if (!wheelActive[wheelIndex]) startWheelActuation(wheelIndex);
    }
  }

  for (int wheelIndex = 0; wheelIndex < NUM_OF_WHEELS; wheelIndex++) {
    if (wheelActive[wheelIndex]) {
      nextStep(wheelIndex);
      checkWheelPosition(wheelIndex);
      stopWheelIfReachedDestination(wheelIndex);
    }
  }

  if (gameCycleHasBegun) {
    if (wheelActive[WHEEL_1] == false && wheelActive[WHEEL_2] == false && wheelActive[WHEEL_3] == false) {
      if (WHEEL_RESULTS[WHEEL_1][wheelRandomPosition[WHEEL_1]] == WHEEL_RESULTS[WHEEL_2][wheelRandomPosition[WHEEL_2]] && WHEEL_RESULTS[WHEEL_1][wheelRandomPosition[WHEEL_1]] == WHEEL_RESULTS[WHEEL_3][wheelRandomPosition[WHEEL_3]]) {
        playMelody(WON_IN_THE_GAME_005);
      } else {
        playMelody(LOST_IN_THE_GAME_004);
      }
      gameCycleHasBegun = false;
    }
  }
}
A4988