6

A slot machine has 5 reels and displays 3 symbols per reel (no spaces or "empty" symbols).

Payout can occur in a number of ways. Some examples...

  • A special diamond symbol appears
  • 3 Lucky 7's appear
  • All five symbols in the payline are identical
  • All five symbols are the same number, but different color
  • Etc.

There are also multiple paylines that need to be checked for a payout.

Examples of 30 different paylines

What is the most efficient way to calculate winnings for each spin? Or, is there a more efficient way than brute force to apply each payout scenario to each payline?

mbeckish
  • 10,485
  • 5
  • 30
  • 55
benny.bennett
  • 700
  • 1
  • 8
  • 25
  • 1
    Please provide some more information. How many symbols are there, and how many positions does each of the reels have? In fact, the topic reminds me of the fact that as long as I know about slot machines, I always wondered whether most people playing them actually understand the rules of the game or just keep pushing buttons randomly... – Codor Aug 18 '14 at 20:26
  • 2
    I'm actually looking for something more generic and reusable. Essentially, I'm asking how to find specific patterns in a 2D array in a more efficient way than brute iteration. – benny.bennett Aug 18 '14 at 23:45

2 Answers2

10

Every payout besides the paylines seem trivial. For the three lucky 7s, just iterate over the visible squares and count the 7s. Same for checking for a diamond. If we let h be the number of rows and w be the number of columns, this operation is O(hw*), which for practically sized slot machines is pretty low.

The paylines, though, are more interesting. Theoretically the number of paylines (m from here on out) is much, much larger than *h ** w*; before throwing out illegal paylines that jump m = h^w which is much larger than *h ** w. More importantly, they appear to share a lot of similarity. For example, line 2 and line 6 in your example both require matching top left and top middle-left squares. If these two don't match, then you can't win on either line 2 or line 6.

To represent paylines, I'm going to use length w arrays of integers in the range [1, h], such that payline[i] = the index in the column (1 indexed) of row i in the solution. For example, payline 1 is [1, 1, 1, 1, 1], and payline 17 is [3, 3, 2, 1, 2].

To this end, a suffix tree seems like an applicable data structure that can vastly improve your running time of checking all of the paylines against a given board state. Consider the following algorithm to construct a suffix tree that encapsulates all paylines.

Initialize: 
    Create a root node at column 0 (off-screen, non-column part of all solutions)
    root node.length = 0
    root node.terminal = false
    Add all paylines (in the form of length w arrays of integers ranging from 1 to h) to the root nodes' "toDistribute set"
    Create a toWork queue, add the root node to it

Iterate: while toWork not empty:
    let node n = toWork.pop()
    if n.length < w
        create children of n with length n.length + 1 and terminal = (n.length + 1 == w).
    for payline p in n.toDistribute
        remove p from n.toDistribute
        if(p.length > 1)
            add p.subArray(1, end) to child of n as applicable.
    add children of n to toWork

Running this construction algorithm on your example for lines 1-11 gives a tree that looks like this:

enter image description here

The computation of this tree is fairly intensive; it involves the creation of sum i = 1 to w of h ^ i nodes. The size of the tree depends only on the size of the board (height and width), not the number of paylines, which is the main advantage of this approach. Another advantage is that it's all pre-processing; you can have this tree built long before a player ever sits down to pull the lever.

Once the tree is built, you can give each node a field for each match criteria (same symbol, same color, etc). Then, when evaluating a board state, you can dfs the tree, and at every new node, ask (for each critera) if it matches its parent node. If so, mark that criteria as true and continue. Else, mark it as false and do not search the children for that criteria. For example, if you're looking specifically for identical tokens on the sub array [1, 1, ...] and find that column 1's row 1 and column 2's row 1 don't match, then any payline that includes [1, 1, ...] (2, 6, 16, 20) all can't be won, and you don't have to dfs that part of the tree.

It's hard to have a thorough algorithmic analysis of how much more efficient this dfs approach is than individually checking each payline, because such an analysis would require knowing how much left-side overlap (on average) there is between paylines. It's certainly no worse, and at least for your example is a good deal better. Moreover, the more paylines you add to a board, the greater the overlap, and the greater the time savings for checking all paylines by using this method.

Mshnik
  • 7,032
  • 1
  • 25
  • 38
1

In order to calculate RTP you should have full slot machine information. The most important part are reels strips. Monte-Carlo is usually done in order to get statistics needed. For example: https://raw.githubusercontent.com/VelbazhdSoftwareLLC/BugABoomSimulator/master/Main.cs

Paytable info:

    private static int[][] paytable = {
        new int[]{0,0,0,0,0,0,0,0,0,0,0,0,0},
        new int[]{0,0,0,0,0,0,0,0,0,0,0,0,0},
        new int[]{0,0,0,0,0,0,0,0,2,2,2,10,2},
        new int[]{5,5,5,10,10,10,15,15,25,25,50,250,5},
        new int[]{25,25,25,50,50,50,75,75,125,125,250,2500,0},
        new int[]{125,125,125,250,250,250,500,500,750,750,1250,10000,0},
    };

Betting lines:

    private static int[][] lines = {
        new int[]{1,1,1,1,1},
        new int[]{0,0,0,0,0},
        new int[]{2,2,2,2,2},
        new int[]{0,1,2,1,0},
        new int[]{2,1,0,1,2},
        new int[]{0,0,1,2,2},
        new int[]{2,2,1,0,0},
        new int[]{1,0,1,2,1},
        new int[]{1,2,1,0,1},
        new int[]{1,0,0,1,0},
        new int[]{1,2,2,1,2},
        new int[]{0,1,0,0,1},
        new int[]{2,1,2,2,1},
        new int[]{0,2,0,2,0},
        new int[]{2,0,2,0,2},
        new int[]{1,0,2,0,1},
        new int[]{1,2,0,2,1},
        new int[]{0,1,1,1,0},
        new int[]{2,1,1,1,2},
        new int[]{0,2,2,2,0},
    };

Reels strips:

    private static int[][] baseReels = {
        new int[]{0,4,11,1,3,2,5,9,0,4,2,7,8,0,5,2,6,10,0,5,1,3,9,4,2,7,8,0,5,2,6,9,0,5,2,4,10,0,5,1,7,9,2,5},
        new int[]{4,1,11,2,7,0,9,5,1,3,8,4,2,6,12,4,0,3,1,8,4,2,6,0,10,4,1,3,2,12,4,0,7,1,8,2,4,0,9,1,6,2,8,0},
        new int[]{1,7,11,5,1,7,8,6,0,3,12,4,1,6,9,5,2,7,10,1,3,2,8,1,3,0,9,5,1,3,10,6,0,3,8,7,1,6,12,3,2,5,9,3},
        new int[]{5,2,11,3,0,6,1,5,12,2,4,0,10,3,1,7,3,2,11,5,4,6,0,5,12,1,3,7,2,4,8,0,3,6,1,4,12,2,5,7,0,4,9,1},
        new int[]{7,0,11,4,6,1,9,5,10,2,7,3,8,0,4,9,1,6,5,10,2,8,3},
    };

    private static int[][] freeReels = {
        new int[]{2,4,11,0,3,7,1,4,8,2,5,6,0,5,9,1,3,7,2,4,10,0,3,1,8,4,2,5,6,0,4,1,10,5,2,3,7,0,5,9,1,3,6},
        new int[]{4,2,11,0,5,2,12,1,7,0,9,2,3,0,12,2,4,0,5,8,2,6,0,12,2,7,1,3,10,6,0},
        new int[]{1,4,11,2,7,8,1,5,12,0,3,9,1,7,8,1,5,12,2,6,10,1,4,9,3,1,8,0,12,6,9},
        new int[]{6,4,11,2,7,3,9,1,6,5,12,0,4,10,2,3,8,1,7,5,12,0},
        new int[]{3,4,11,0,6,5,3,8,1,7,4,9,2,5,10,0,3,8,1,4,10,2,5,9},
    };

The spin function, which should be called many times in order to calculate RTP:

    private static void spin(int[][] reels) {
        for (int i = 0, r, u, d; i < view.Length && i < reels.Length; i++) {
            if (bruteForce == true) {
                u = reelsStops [i];
                r = u + 1;
                d = u + 2;
            } else {
                u = prng.Next (reels [i].Length);
                r = u + 1;
                d = u + 2;
            }

            r = r % reels[i].Length;
            d = d % reels[i].Length;

            view[i][0] = reels[i][u];
            view[i][1] = reels[i][r];
            view[i][2] = reels[i][d];
        }
    }

After each spin all wins should be calculated.

Todor Balabanov
  • 376
  • 3
  • 6
  • 17