/*
    This code covers all implementation of our CTCH 204 Final Project

    Slot Machine - Ariel Torrens & Mark Luansing

    Description: This code is the full implementation for the Arduino Uno
    slot machine. It utilizes multiple libraries including a custom header file
    which contains the byte values for the custom I2C LCD screen symbols.

    Functionality: When a user places an RFID chip towards the RC522 scanner,
    the LCD screen will begin to spin 3 symbols. The piezo buzzer makes a note 
    for each symbol change. A global balance variable is initialized at the start 
    with a value of 1500, which goes down by 500 each spin. This value can go into 
    the negatives. The logic within the code always makes it so that the player
    will lose. If the symbols are the same for the first 2, the third will always
    be different, thus causing a loss.
*/
 
// RFC522 required libraries
#include <SPI.h>
#include <MFRC522.h>

// I2C LCD display required libraries
#include <LiquidCrystal_I2C.h>
#include <LiquidCrystal.h>

// Required libraries for custom header file
#include "Arduino.h"
#include <symbols.h>

// Pins needed to be defined for RC522
#define SS_PIN 10
#define RST_PIN 9

MFRC522 mfrc522(SS_PIN, RST_PIN);   // Create MFRC522 instance.
LiquidCrystal_I2C lcd(0x27, 16, 2); // set the LCD address to 0x27 for a 16 chars and 2 line display

// Initialize global variables for logic

// Variables for spin and player balance
int balance = 1500;
int lostMoney = 500;

// Strings for random messages after each spin
String m1 = " TRY AGAIN?";
String m2 = "DONT GIVE UP!";
String m3 = "SPIN TO WIN!";
String messages[3] = {m1, m2, m3};

void setup() 
{
    // RFID scanning setup:
    Serial.begin(9600);   // Initiate a serial communication
    SPI.begin();          // Initiate SPI bus
    mfrc522.PCD_Init();   // Initiate MFRC522
    Serial.println("Waiting for card...");
    Serial.println();

    // I2C LCD screen setup:
    lcd.init();         // initialize the LCD screen
    lcd.backlight();    // Turn on the LCD screen backlight

    lcd.setCursor(2, 0);
    lcd.print("$500 TO SPIN");
    lcd.setCursor(0, 1);
    lcd.print("Balance: $" + String(balance));

    pinMode(2, OUTPUT); // Output pin for red LEDs
    pinMode(4, OUTPUT); // Output pin for piezo buzzer
}

void loop() 
{
    // Constantly look for new card towards scanner
    if ( ! mfrc522.PICC_IsNewCardPresent()) 
    {
        return;
    }
    // Select one of the cards
    if ( ! mfrc522.PICC_ReadCardSerial()) 
    {
        return;
    }
    //Show UID on serial monitor
    Serial.print("UID tag :");
    String content= "";
    byte letter;
    for (byte i = 0; i < mfrc522.uid.size; i++) 
    {
        Serial.print(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " ");
        Serial.print(mfrc522.uid.uidByte[i], HEX);
        content.concat(String(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " "));
        content.concat(String(mfrc522.uid.uidByte[i], HEX));
    }
    Serial.println();
    Serial.print("Message : ");
    content.toUpperCase();
    //if (content.substring(1) == "08 33 22 DA") This code is commented out so that any card should work

    // If any card is scanned by the RC522:
    if (!mfrc522.PICC_IsNewCardPresent()) 
    {
        Serial.println("Card accepted");
        Serial.println();
        testFunction(balance); // Run the main logic function
        delay(1000);           // Wait 1 second to make sure everything worked
    }
} 

/*  Main Slot Machine implementation:
    This function contains all needed logic for the slot machine to function.
    It takes 1 integer parameter which when called in the loop, takes in the global
    balance variable.

    This function utilizes several other functions such as printLines(), createEmpty(),
    spinSymbol(), and built in Arduino function randomSeed()
*/
void testFunction(int &money)
{
    // Clear the display to begin the spin
    lcd.clear();
    printLines();

    // Initialize a variable needed to rig the result
    int rigFinal = 0;

    // select a number for the first symbol to end on
    randomSeed(analogRead(0));
    int selectSymb = random(0,3);

    int result1 = spinSymbol(4, 5, selectSymb);
    createEmpty(4);

    // select a number for the second symbol to end on
    randomSeed(analogRead(0));
    selectSymb = random(0,3);

    int result2 = spinSymbol(7, 8, selectSymb);
    createEmpty(7);

    // These if conditions will cause the third symbol to be different
    // if the first two are the same by utilizing the rigFinal variable,
    // and randomSeed to select a random third symbol that is different.
    if (result1 == result2 && result1 == 0)
    {
        // select a number for the symbol to end on
        randomSeed(analogRead(0));
        selectSymb = random(1,3);
        rigFinal = selectSymb;
    }
    else if (result1 == result2 && result1 == 1)
    {
        rigFinal = 2;
    }
    else if (result1 == result2 && result1 == 2)
    {
        randomSeed(analogRead(0));
        selectSymb = random(0,2);
        rigFinal = selectSymb;
    }

    // Select third symbol using the rigged variable
    int result3 = spinSymbol(10, 11, rigFinal);
    createEmpty(10);

    // Leftover implementation for if the player wins. This code will never run.
    if (result1 == result2 && result2 == result3)
    {
        int bigwin;

        if (result1 == 0)
            bigwin = 1000;
        else if (result1 == 1)
            bigwin = 500;
        else if (result1 == 2)
            bigwin = 100;

        lcd.clear();
        lcd.setCursor(0, 1);
        lcd.print("YOU WON $" + String(bigwin));
        delay(1000);
        money = money + bigwin;
    }
  // Code that will run each time, showcases the loss visuals and activates the LEDs and buzzer.
    else
    {
        lcd.clear();
        lcd.setCursor(4, 0);
        lcd.print("YOU LOST");

        tone(4, 100, 1100);    // Play low buzz

        digitalWrite(2, HIGH); // turn on LED
        delay(600);
        digitalWrite(2, LOW);  // turn off LED

        digitalWrite(4, LOW);  // turn off Piezo Buzzer 
        delay(600);

        digitalWrite(2, HIGH); // turn on LED
        delay(600);
        digitalWrite(2, LOW);  // turn off LED

        delay(400);
        lcd.clear();
        delay(400);

        lcd.setCursor(2, 0);
        lcd.print("BETTER LUCK");
        lcd.setCursor(2, 1);
        lcd.print("NEXT TIME!");

        delay(1000);

        money = money - lostMoney; // Remove 500 dollars from the global balance
    }

    lcd.clear();  // Clear the display

    // Display a random encouraging message from the messages array
    randomSeed(analogRead(0));
    int randomMessage = random(0,2);
    lcd.setCursor(2, 0);
    lcd.print(messages[randomMessage]);

    // Display the user's balance
    lcd.setCursor(0, 1);
    lcd.print("Balance: $" + String(balance));
}

/* The following three functions are all mostly the same.
   They utilize the header file which contains the bytes for the
   custom made symbols.

   These functions are needed as the I2C LCD screen can only store 8 bytes.
*/ 

void createSeven(int pos1, int pos2)
{
    tone(4, 523, 700);
    lcd.createChar(0, seven1);
    lcd.setCursor(pos1, 0);
    lcd.write(byte(0));

    lcd.createChar(1, seven2);
    lcd.setCursor(pos2, 0);
    lcd.write(byte(1));

    lcd.createChar(2, seven3);
    lcd.setCursor(pos1, 1);
    lcd.write(byte(2));

    lcd.createChar(3, seven4);
    lcd.setCursor(pos2, 1);
    lcd.write(byte(3));
}

void createCherry(int pos1, int pos2)
{
    tone(4, 659, 700);
    lcd.createChar(4, cherries1);
    lcd.setCursor(pos1, 0);
	lcd.write(byte(4));

    lcd.createChar(5, cherries2);
    lcd.setCursor(pos2, 0);
	lcd.write(byte(5));

    lcd.createChar(6, cherries3);
    lcd.setCursor(pos1, 1);
	lcd.write(byte(6));

    lcd.createChar(7, cherries4);
    lcd.setCursor(pos2, 1);
	lcd.write(byte(7));
}

void createBell(int pos1, int pos2)
{
    tone(4, 784, 700);
    lcd.createChar(8, bell1);
    lcd.setCursor(pos1, 0);
    lcd.write(byte(8));

    lcd.createChar(9, bell2);
    lcd.setCursor(pos2, 0);
    lcd.write(byte(9));

    lcd.createChar(10, bell3);
    lcd.setCursor(pos1, 1);
    lcd.write(byte(10));

    lcd.createChar(11, bell4);
    lcd.setCursor(pos2, 1);
    lcd.write(byte(11));
}

// This function's primary use is to clear the space where a symbol lies
// so that the print lines do not get cleared.
void createEmpty(int position)
{
    lcd.setCursor(position, 0);
    lcd.print("  ");

    lcd.setCursor(position, 1);
    lcd.print("  ");
}

/*  This function is used to spin a specific symbol, it takes three
    variables. The first two is the position on the LCD screen. The third
    is to store the result from the spin.

    This funciton utilizes the create functions for the three symbols.
*/
int spinSymbol(int pos1, int pos2, int rigResult)
{
    int spinning = 2;

    while (spinning != 0)
    {
        createSeven(pos1, pos2);
        delay(300);

        createCherry(pos1, pos2);
        delay(300);

        createBell(pos1, pos2);
        delay(300);

        spinning--;
    }

    if (rigResult == 0)
    {
        createSeven(pos1, pos2);
        delay(500);
        createEmpty(pos1);
        createSeven(pos1, pos2);
        delay(1500);
        return 0;
    }
    else if (rigResult == 1)
    {
        createSeven(pos1, pos2);
        delay(300);

        createCherry(pos1, pos2);
        delay(500);
        createEmpty(pos1);
        createCherry(pos1, pos2);
        delay(1500);

        return 1;
    }
    else if (rigResult == 2)
    {
        createSeven(pos1, pos2);
        delay(300);

        createCherry(pos1, pos2);
        delay(300);

        createBell(pos1, pos2);
        delay(500);
        createEmpty(pos1);
        createBell(pos1, pos2);
        delay(1500);

        return 2;
    }
}

// Prints the lines on the display that divide the three spinning symbols
void printLines()
{
    lcd.setCursor(3, 0);
    lcd.print("|");
    lcd.setCursor(3, 1);
    lcd.print("|");

    lcd.setCursor(6, 0);
    lcd.print("|");
    lcd.setCursor(6, 1);
    lcd.print("|");

    lcd.setCursor(9, 0);
    lcd.print("|");
    lcd.setCursor(9, 1);
    lcd.print("|");

    lcd.setCursor(12, 0);
    lcd.print("|");
    lcd.setCursor(12, 1);
    lcd.print("|");
}






