Demo slot machine

Demo slot machine


In this final demo, we'll design and implement a slot machine game with 10 symbols and three reels. Just to make it simple we'll just use the numbers from zero to nine as our symbols. Many slot machines would use fruit shapes and other simple shapes, such as bells, stars, and letters. Some other slot machines usually use a specific theme based on popular movies or TV programs as a franchise. Since there are 10 symbols and three reels, that's a total of 1,000 (10^3) possible combinations.

Random slot machine

This random slot machine demo is similar to our previous dice example. This time we are going to generate three random numbers for three reels. The only payout will be when you get three of the same symbols on the payline. To make it simpler, we'll only have one line to play against in this demo. And if the player wins, the game will return 500 times the bet amount.

We'll set up our scene with four GUI text objects to represent the three reels, and the result message.

Our GUI text objects

This is how our new script looks, as shown in the following SlotMachine.cs file::

using UnityEngine;
using System.Collections;

public class SlotMachine : MonoBehaviour {

  public float spinDuration = 2.0f;
  public int numberOfSym = 10;
  private GameObject betResult;

  private bool startSpin = false;
  private bool firstReelSpinned = false;
  private bool secondReelSpinned = false;
  private bool thirdReelSpinned = false;

  private string betAmount = "100";

  private int firstReelResult = 0;
  private int secondReelResult = 0;
  private int thirdReelResult = 0;

  private float elapsedTime = 0.0f;

    //Use this for initialization
  void Start () {
    betResult = gameObject;
    betResult.guiText.text = "";
  }

  void OnGUI() {
    GUI.Label(new Rect(200, 40, 100, 20), "Your bet: ");
    betAmount = GUI.TextField(new Rect(280, 40, 50, 20),
      betAmount, 25);
      if (GUI.Button(new Rect(200, 300, 150, 40),
        "Pull Liver")) {
      Start();
        startSpin = true;
      }
    }

  void checkBet() {
    if (firstReelResult == secondReelResult && 
      secondReelResult == thirdReelResult) {
        betResult.guiText.text = "YOU WIN!";
      }
    else {
      betResult.guiText.text = "YOU LOSE!";
    }
  }

    //Update is called once per frame
    void FixedUpdate () {
      if (startSpin) {
        elapsedTime += Time.deltaTime;
        int randomSpinResult = Random.Range(0,
          numberOfSym);
      if (!firstReelSpinned) {
        GameObject.Find("firstReel").guiText.text = 
          randomSpinResult.ToString();
      if (elapsedTime >= spinDuration) {
        firstReelResult = randomSpinResult;
        firstReelSpinned = true;
        elapsedTime = 0;
      }
    }
      else if (!secondReelSpinned) {
        GameObject.Find("secondReel").guiText.text = 
          randomSpinResult.ToString();
      if (elapsedTime >= spinDuration) {
        secondReelResult = randomSpinResult;
        secondReelSpinned = true;
        elapsedTime = 0;
      }
    }
      else if (!thirdReelSpinned) {
        GameObject.Find("thirdReel").guiText.text = 
          randomSpinResult.ToString();
      if (elapsedTime >= spinDuration) {
        thirdReelResult = randomSpinResult;
          startSpin = false;
          elapsedTime = 0;
          firstReelSpinned = false;
          secondReelSpinned = false;
        checkBet();
        }
      }
    }
  }
}

Attach the script to our betResult guiText object, and then position the guiText element on the screen. We have a button called Pull Lever in the OnGUI() method that will set the startSpin flag to true when clicked. And in our FixedUpdate() method we generate a random value for each reel if the startSpin is true. Finally, once we've got the value for the third reel, then we reset the startSpin to false. While we are getting the random value for each reel, we also keep a track of how much time has elapsed, since the player pulled the lever. Usually in the real world slot machines, each reel would take three to five seconds before landing the result. Hence, we are also taking some time as specified in spinDuration before showing the final random value. If you play the scene and click on the Pull Lever button, you should see the final result as shown in the following screenshot:

Random slot game in action

Since your chance of winning is one out of 100, it becomes boring as you lose several times consecutively. And of course if you've ever played a slot machine, this is not how it works, or at least not anymore. Usually you can have several wins during your play. Even though these small wins don't recoup your principal bet, and in the long run most of the players go broke, the slot machines would render winning graphics and winning sounds, which researchers referred to as losses disguised as wins.

So instead of just one single way to win—winning the jackpot—we'd like to modify the rules a bit so that it pays out smaller returns during the play session.

Weighted probability

Real slot machines have something called a Paytable and Reel Strips (PARS) sheet, which is like the complete design document of the machine. The PARS sheet is used to specify what the payout percentage is, what the winning patterns, and what their prizes are, and so on. Obviously the number of the payout prizes and the frequencies of such wins need to be carefully selected, so that the house (slot machine) can collect the fraction of the bets over time, while making sure to return the rest to the players to make the machine attractive to play. This is known as payback percentage or return to player (RTP). For example, a slot machine with a 90 percent RTP means that over time the machine will return an average of 90 percent of all the bets to the players.

In this demo, we'll not be focusing on choosing the optimal value for the house to yield specific wins over time nor maintaining a particular payback percentage, but rather to demonstrate weighting probability to specific symbols, so that they show up more times than usual. So let's say we'd like to make the symbol zero to appear 20 percent more than by chance on the first and third reel, and return a small payout of half of the bet. In other words, a player will only lose half of their bet if they got zero symbols on the first and third reels, essentially disguising a loss as a small win. Currently, the zero symbol has a probability of 1/10 (0.1) or 10 percent probability to occur. Now we'll make it 30 percent for zero to land on the first and third reels as shown in the following SlotMachineWeighted.cs file:

using UnityEngine;
using System.Collections;

public class SlotMachineWeighted : MonoBehaviour {
  public float spinDuration = 2.0f;
  public int numberOfSym = 10;
  public GameObject betResult;

  private bool startSpin = false;
  private bool firstReelSpinned = false;
  private bool secondReelSpinned = false;
  private bool thirdReelSpinned = false;

  private int betAmount = 100;

  private int creditBalance = 1000;
  private ArrayList weightedReelPoll = new ArrayList();
  private int zeroProbability = 30;

  private int firstReelResult = 0;
  private int secondReelResult = 0;
  private int thirdReelResult = 0;

  private float elapsedTime = 0.0f;

New variable declarations are added, such as zeroProbability to specify the probability percentage of the zero symbol to land on the first and third reels. The weightedReelPoll array list will be used to fill with all the symbols (zero to nine) according to their distribution, so that we can later pick one randomly from the poll like we did in our earlier FSM example. And then we initialize the list in our Start() method as shown in the following code:

  void Start () {
    betResult = gameObject;
    betResult.guiText.text = "";
      for (int i = 0; i < zeroProbability; i++) {
        weightedReelPoll.Add(0);
      }
    nt remainingValuesProb = (100 - zeroProbability)/9;
      for (int j = 1; j < 10; j++) {
        for (int k = 0; k < remainingValuesProb; k++) {
          weightedReelPoll.Add(j);
        }
      }
    }

  void OnGUI() {
    GUI.Label(new Rect(150, 40, 100, 20), "Your bet: ");
    betAmount = int.Parse(GUI.TextField(new Rect(220, 40,
      50, 20), betAmount.ToString(), 25));
    GUI.Label(new Rect(300, 40, 100, 20), "Credits: " + 
      creditBalance.ToString());
      if (GUI.Button(new Rect(200,300,150,40),"Pull Lever")) {
        betResult.guiText.text = "";
        startSpin = true;
      }
    }

And the following is our revised checkBet() method. Instead of just one jackpot win, we are now considering five conditions: jackpot, loss disguised as win, near miss, any two symbols matched on the first and third row, and of course the lose condition:

  void checkBet() {
    if (firstReelResult == secondReelResult && 
      secondReelResult == thirdReelResult) {
      betResult.guiText.text = "JACKPOT!";
      creditBalance += betAmount * 50;
      }
    else if (firstReelResult ==0 && thirdReelResult ==0) {
      betResult.guiText.text = "YOU WIN" + 
        (betAmount/2).ToString();
        creditBalance -= (betAmount/2);
      }
    else if (firstReelResult == secondReelResult) {
      betResult.guiText.text = "AWW... ALMOST JACKPOT!";
      }
    else if (firstReelResult == thirdReelResult) {
      betResult.guiText.text = "YOU WIN" + 
        (betAmount*2).ToString();
        creditBalance -= (betAmount*2);
      }
    else {
      betResult.guiText.text = "YOU LOSE!";
         creditBalance -= betAmount;
      }
    }

In the checkBet() method, we designed our slot machine to return 50 times if they hit the jackpot, only to lose 50 percent of their bet, if the first and third reels are zero, and two times if the first and third reels are matched with any other symbol. And we generate values for the three reels in the FixedUpdate() method as shown in the following code:

  void FixedUpdate () {
    if (!startSpin) {
      return;
    }
      elapsedTime += Time.deltaTime;
      int randomSpinResult = Random.Range(0,
        numberOfSym);
      if (!firstReelSpinned) {
         GameObject.Find("firstReel").guiText.text = 
           randomSpinResult.ToString();
      if (elapsedTime >= spinDuration) {
        int weightedRandom = Random.Range(0,
          weightedReelPoll.Count);
          GameObject.Find("firstReel").guiText.text = 
            weightedReelPoll[weightedRandom].ToString();
            firstReelResult = 
              (int)weightedReelPoll[weightedRandom];
            firstReelSpinned = true;
            elapsedTime = 0;
          }
        }
      else if (!secondReelSpinned) {
        GameObject.Find("secondReel").guiText.text = 
          randomSpinResult.ToString();
      if (elapsedTime >= spinDuration) {
        secondReelResult = randomSpinResult;
        secondReelSpinned = true;
        elapsedTime = 0;
      }
    }

For the first reel, during the spinning period, we really show the real random values. But once the time is up, we choose the value from our poll that is already populated with symbols according to the probability distributions. So our zero symbol would have 30 percent more chance of occurring than the rest, as shown in the following screenshot:

Loss disguised as a win

Actually the player is losing on his bets, if you get two zero symbols on the first and third reel. But we make it seem like a win. It's just a lame message here, but if we can combine it with nice graphics; maybe with fireworks, and nice winning sound effects, this can really work, and attract players to bet more, and pull that lever again and again.

Near miss

If the first and second reels return the same symbol, then we have to provide the near miss effect to the players by returning the random value to the third reel close to the second one. We can do this by checking the third random spin result first. If the random value is the same as the first and second results, then this is a jackpot, and we shouldn't alter the result. But if it's not, then we should modify the result so that it is close enough to the other two. Check the comments in the following code:

      else if (!thirdReelSpinned) {
        GameObject.Find("thirdReel").guiText.text = 
          randomSpinResult.ToString();
      if (elapsedTime < spinDuration) {
        return;
      }
      if ((firstReelResult == secondReelResult)
        && randomSpinResult != firstReelResult) {
        randomSpinResult = firstReelResult - 1;
      if (randomSpinResult < firstReelResult)
        randomSpinResult = firstReelResult - 1;
      if (randomSpinResult > firstReelResult)
        randomSpinResult = firstReelResult + 1;
      if (randomSpinResult < 0) randomSpinResult = 9;
      if (randomSpinResult > 9) randomSpinResult = 0;
        GameObject.Find("thirdReel").guiText.text = 
          randomSpinResult.ToString();
        thirdReelResult = randomSpinResult;
      }
      else {
        int weightedRandom = Random.Range(0,
          weightedReelPoll.Count);
        GameObject.Find("thirdReel").guiText.text = 
          weightedReelPoll[weightedRandom].ToString();
          thirdReelResult = 
            (int)weightedReelPoll[weightedRandom];
         }
         startSpin = false;
         elapsedTime = 0;
         firstReelSpinned = false;
         secondReelSpinned = false;
         checkBet();
      }
    }
}

And if that "near miss" happens, you should see it as shown in the following screenshot:

A near miss

We can go even further by adjusting the probability in real-time based on the bet amount. But that'd be too creepy. Another thing we could add to our game is a check to make sure the player can't bet more money than they already have. Also, we could add a game over message that appears when the player has bet all their money.