In the previous article, I discussed the concepts of paylines and symbols. The next thing we need are reels. In a traditional, physical slot machine, reels are long plastic loops that run vertically through the game window.
Symbols per reel
How many of each symbol should I place on my reels? That is a complicated question that slot machine manufacturers spend a lot of time considering and testing when creating a game since it is a key factor to a game’s RTP (Return to Player) payout percentage. Slot machine manufacturers document all of this in what is called a PAR sheet (Probability and Accounting Report).
I personally am not very interested in doing probability formulations myself. I’d rather just imitate an existing game and move on to the fun stuff. Thankfully, some PAR sheet information has been made public.
Symbol | Symbols per reel | Payout (credits) | ||||||
---|---|---|---|---|---|---|---|---|
×3 | ×4 | ×5 | ||||||
WS (wild) | 4 | 2 | 1 | 4 | 2 | 100 | 500 | 10,000 |
LM Lobstermania | 4 | 4 | 3 | 4 | 3 | 40 | 200 | 1,000 |
BU Buoy | 4 | 4 | 5 | 4 | 4 | 25 | 100 | 500 |
BO Boat | 5 | 4 | 4 | 5 | 4 | 25 | 100 | 500 |
LH Light House | 5 | 4 | 4 | 6 | 4 | 10 | 50 | 500 |
TU Tuna | 6 | 4 | 5 | 6 | 7 | 10 | 50 | 250 |
CL Clam | 7 | 6 | 6 | 5 | 6 | 5 | 30 | 200 |
SG Sea Gull | 5 | 5 | 5 | 5 | 5 | 5 | 30 | 200 |
SF Star Fish | 8 | 5 | 5 | 5 | 5 | 5 | 30 | 150 |
LO (bonus) | 0 | 2 | 5 | 5 | 3 | 331 | n/a | n/a |
LT (scatter) | 2 | 2 | 2 | 2 | 2 | 5 | 25 | 200 |
Total | 50 | 47 | 46 | 48 | 50 |
Since I am building a game that has five reels and three rows, I am going to reference a game with the same format called Lucky Larry’s Lobstermania. It also has a wild symbol, eight regular symbols, as well two distinct bonus and scatter symbols. I currently do not have an additional scatter symbol, so I will leave that off my reels for now. This change will make my game have a slightly higher payout percentage, but that’s probably a good thing for a game that doesn’t offer the thrill of winning real money.
Here is how I represent the symbols-per-reel in my TypeScript code to match Lobstermania.
// reels.ts
import { SlotSymbol, SLOT_SYMBOLS } from './types';
const SYMBOLS_PER_REEL: { [K in SlotSymbol]: number[] } = {
W: [2, 2, 1, 4, 2],
A: [4, 4, 3, 4, 4],
K: [4, 4, 5, 4, 5],
Q: [6, 4, 4, 4, 4],
J: [5, 4, 6, 6, 7],
'4': [6, 4, 5, 6, 7],
'3': [6, 6, 5, 6, 6],
'2': [5, 6, 5, 6, 6],
'1': [5, 5, 6, 8, 7],
B: [2, 0, 5, 0, 6],
};
Each array above has five numbers that represent that symbol’s count for each reel. The first reel has two Wilds, four Aces, four Kings, six Queens, and so on. A keen reader may notice that the Bonus should be [2, 5, 6, 0, 0]
, but I have used [2, 0, 5, 0, 6]
. This is purely for aesthetics because I like seeing the Bonus symbols spread across the screen rather than just on the three left reels. This probably affects the payout percentage as well, but for hobby purposes, I’m sure it’s negligible.
Generating reel sequences
Each reel can be simply represented as an array of symbols (['A', '1', 'K', 'K', 'W', ...]
). I just need to make sure I use the above SYMBOLS_PER_REEL
to add the right number of each symbol to each of the five reel arrays.
// Something like this...
const reels = new Array(5).fill(null).map((_, reelIndex) => {
const reel: SlotSymbol[] = [];
SLOT_SYMBOLS.forEach((symbol) => {
for (let i = 0; i < SYMBOLS_PER_REEL[symbol][reelIndex]; i++) {
reel.push(symbol);
}
});
return reel;
});
The above code would generate five reels that each look like this:
[
'W', 'W', 'A', 'A', 'A', 'A', 'K', 'K', // and so on...
]
This would technically work, but the symbols are grouped together like a brand new deck of cards. I need to shuffle the symbols to make the game more realistic.
/** Generate five shuffled reels */
function generateReels(symbolsPerReel: {
[K in SlotSymbol]: number[];
}): SlotSymbol[][] {
return new Array(5).fill(null).map((_, reelIndex) => {
const reel = generateReel(reelIndex, symbolsPerReel);
let shuffled: SlotSymbol[];
let bonusesTooClose: boolean;
// Ensure bonuses are at least two symbols apart
do {
shuffled = shuffleReel(reel);
bonusesTooClose = /B.{0,1}B/.test(shuffled.concat(shuffled).join(''));
} while (bonusesTooClose);
return shuffled;
});
}
/** Generate a single unshuffled reel */
function generateReel(
reelIndex: number,
symbolsPerReel: {
[K in SlotSymbol]: number[];
},
): SlotSymbol[] {
const reel: SlotSymbol[] = [];
SLOT_SYMBOLS.forEach((symbol) => {
for (let i = 0; i < symbolsPerReel[symbol][reelIndex]; i++) {
reel.push(symbol);
}
});
return reel;
}
/** Return a shuffled copy of a reel array */
function shuffleReel(reel: SlotSymbol[]) {
const shuffled = reel.slice();
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
return shuffled;
}
That’s quite a bit more code, but it ensures that the reels are shuffled randomly. I’ve factored out a generateReel
function to keep the generateReels
function to a reasonable size. The shuffleReel
function is a Fisher-Yates shuffle. I’m also ensuring that bonus symbols are spread at least two symbols apart. This is optional, though; I’ve seen real games with bonus symbols right on top of each other.
Stopping each reel
Now that I have five reels, the core mechanism of the game is to stop each reel on a random position. That is simple enough.
function pickRandomStop(reel: SlotSymbol[]): number {
return Math.floor(Math.random() * reel.length);
}
Keep in mind that a machine with physical reels may spin each reel end-to-end multiple times before actually stopping in order to build suspense. A video slot machine simply has to transition from one grid of symbols on the screen to another with animation.
Determining the symbols in play
I need to use my random stop position to determine the symbols to show in the game’s window. These in-play symbols will then be compared to the game’s paylines to determine the outcome of the bet.
A naive approach to getting the visible portion of the reel would be something like reel.slice(position, position + 3)
(since I need to fill three rows starting at that position). However, if the reel stops on one of the last two symbols, this would only return one or two symbols. I could change the pickRandomStop
function to exclude the last two symbols, but then they would be under-represented in the possible outcomes (as would the first two symbols on the reel).
The correct approach is to start looping back over the start of the reel if it stops on one of the last two positions. A one-liner based on the previous naive approach would be reel.concat(reel.slice(0, 2)).slice(position, position + 3)
. Here is a more efficient way, though:
/** Return the three-symbol sequence starting at the randomly picked position */
function getVisibleSymbols(reel: SlotSymbol[], position: number): SlotSymbol[] {
let slice = [];
for (let i = 0; i < 3; i++) {
slice.push(reel[(position + i) % reel.length]);
}
return slice;
}
I always feel fancy when I get to use the modulus (%
) operator. If reel.length
is 52
, and position + i
is 52
, then (position + i) % reel.length
will evaluate to index 0
. Likewise, 53 % 52
will equal 1
, but 5 % 52
will remain 5
since 5 is less than 52. In other words, the modulus and for
loop take care of looping the index without having to create another array in memory. I’m sure the performance difference is extremely small, but let’s just do the right thing and not be wasteful.
Putting it all together, below is a live example of spinning reels (minus the animation).
Another approach
If we’re making a game that does not have to map to physical reels, then why bother spinning the same reels over and over again? Why not just run generateReels
for every spin and then pick the first three symbols from each array? In theory, that should result in the same payout percentage over time (I think). However, I decided to go with the approach that does less math per spin. If anyone reading this has done professional work on slot machine software, I’d love to know which approach is closer to games currently found in casinos.
What’s left to make a working slot machine? The next thing the game needs to do after spinning is to check each payline and determine the payout for the symbols in play. That’s where I’ll pick up in the next article.
Webmentions
Kurau :tokyo: and Programming Feed reposted this.