Skip to main content
kld.dev

Writing a slot machine game: Rules, paylines, and symbols

This is the first in a series of articles about building a slot machine game for the browser. In this article, I will discuss what makes a proper slot machine and how I have attempted to model some of that in a JavaScript-based game I have been tinkering with for several years.

Basic rules

The gameplay of a slot machine is extremely simple. At its core, you are pressing a single button, and the machine then tells you to what degree you have won or lost. The rest is pure showmanship.

A beautiful complexity can be found in the rules that a modern slot machine follows to determine a player’s winnings (if any). Determining the payout of a single game mostly consists of:

  • rotating the reels (looping columns of “symbols”) to a random stop,
  • checking all of the “paylines” for repeated symbols,
  • and calculating the total winnings based on the player’s bet and all the winning paylines.

Paylines

Below is a hypothetical outcome of a five-reel game.

Five reels with three visible rows of symbols. The first reel is all cherries, and the other four reels are all bells. You win nothing from this.

At a glance, you might think this would be a huge win. However, for most games, in order to have a four-of-a-kind, the sequence must start on the first reel on the left. There are some games that pay “two ways” or “all ways”, but they are not as common as one-way pay machines.

On the other hand, the below screen could be a decent win.

Five reels showing a zig-zag pattern of bells across all reels. All other symbols are fruit.

There is a bell visible on every reel, and in fact, it is a five-of-a-kind as long as one of the game’s paylines follows the below pattern.

A payline that follows the same zig-zag path as the bells.

A modern video slot machine will often have at least 20 paylines, if not 50 or more. When you press the “Spin” button, you are placing a bet on every one of these paylines. After determining the random stops for each reel, the game will check each one of those paylines to see if there is a winning sequence of symbols and award the player for each one.

Five paylines shown all at once.

paylines.ts

Let’s look at that payline from above again.

A payline that crosses the rows in a "1, 0, 1, 2, 1" pattern.

In order for our game to check this particular pattern for repeating symbols, it needs to know which row to check on each reel (column). In other words, this payline can be represented as an array of five row numbers (where the first row number is 0).

[1, 0, 1, 2, 1];

My game has 25 paylines. I didn’t trust myself to enter those numbers correctly for all 25 patterns, so I instead figured out a more visual, human-friendly notation using asterisks for the line’s location. It’s like ASCII art.

-*---
*-*-*
---*-

This notation is much more human friendly. I just had to write some code to convert that string into an array of row indexes.

// paylines.ts

const NOTATED_LINES = [
  `
  -----
  *****
  -----
  `, // Payline 1
  `
  *****
  -----
  -----
  `, // Payline 2
  // and so on...
];

// Convert human-friendly paylines into sequences of row indices
const paylines: number[][] = NOTATED_LINES.map(convertToRowSequence);

/** Convert a single payline into an array of row indices */
function convertToRowSequence(line: string): number[] {
  const rows = getRows(line);
  const columns = rows[0].length;
  const sequence = [];
  for (let column = 0; column < columns; column++) {
    sequence.push(rows.findIndex((row) => row[column] === '*'));
  }
  return sequence;
}

/** Split the notated payline into an array of trimmed rows */
function getRows(line: string): string[] {
  return line
    .split('\n')
    .map((row) => row.trim())
    .filter(Boolean); // Remove any empty lines that may remain
}

export default paylines;

At runtime, paylines.ts will iterate over each payline I’ve diagrammed and find the row index for each asterisk. It will export an array of paylines, where each payline itself is an array of row indices. Perfect!

// resulting paylines.ts export:
[
  [1, 1, 1, 1, 1],
  [0, 0, 0, 0, 0],
  [2, 2, 2, 2, 2],
  [0, 1, 2, 1, 0],
  // and so on...
];

Symbols

We have our paylines coded, but we can’t score a game until we have our reels, and we can’t have reels without symbols.

In many slot games, you’ll find that there are “character symbols” that have higher values. These could be characters from a movie, Egyptian pharaohs, dolphins, etc. Symbols with lower values are often theme-related objects like fruits, pyramids, or seashells.

This is similar to how playing cards have face cards and number cards, and in fact many slot games use playing card symbols. For the sake of keeping my code theme-agnostic, I have used both numbers and letters like 'A', 'K', 'Q', and 'J' as symbols, but these are not shown to the player in the final game.

// types.ts

export const SLOT_SYMBOLS = [
  'W', // Wild
  'A',
  'K',
  'Q',
  'J',
  '4',
  '3',
  '2',
  '1',
  'B', // Bonus
] as const;

export type SlotSymbol = (typeof SLOT_SYMBOLS)[number];

This code gives us a SlotSymbol type, so we can add type safety to arrays like this.

const lineSymbols: SlotSymbol[] = ['1', '2', 'K', 'Q', 'A'];

I’m also exporting a SLOT_SYMBOLS array that I can iterate over later when I generate the reels.

You’ll notice a few special symbols in that code, the first being the Wild symbol ('W'). This can act as any other symbol in order to create a winning payline. It can also have its own value if a player gets three or more on a payline.

The second special symbol is the Bonus symbol ('B'). This is sometimes called a “scatter” symbol. It is a special symbol that can trigger special features, which I will discuss later. Just know that these symbols do not have to fall on a payline in order to trigger an award (they can be scattered across the reels).

In the next article, I will dive into generating reels from the symbols and how the game logic can spin the reels, so we can make something that begins to resemble a game.

Webmentions

Kevin Lee Drum
@splendorr In both cases so far, it was for the learning experience. Although I like JSX, there's a lot I don't like about (p)react. The latest iteration also uses framer motion, and when I try to update all my dependencies from two years ago, my animations break. For those reasons, I kinda just want to do another restart and write about it as I progress. Zustand is pretty great, though!
nick splendorr ✨
@kevinleedrum oh yeah, zustand and jotai are made the same person. I used and liked Jotai in my last project, though I had a heck of a time getting other devs to buy in to its slightly-odd way of thinking
nick splendorr ✨
@kevinleedrum Who knows!! The tools only matter if they help us get there. And a side project like that can be a great excuse to learn new tools ???? Have you migrated to different tools just to see? Or based on specific frustrations with Vue? I’m using Vue because it’s what my current day job uses, so it’s learning alongside. I was excited to get out of React after a few years, and am now in the valley of learning Vue’s quirks, especially with the (less intuitive but maybe more useful) Comp API
Kevin Lee Drum
@splendorr The blackjack game uses the old vue 2 API and vuex instead of pinia, but maybe it's still a bit relevant. I'll be on the lookout for your game. ???? I started the slot machine in vue 2, then migrated to vue 3, and then to preact+zustand two years ago. It's like 90% playable, but I'm still not happy with it. Maybe 2024 will be its year. Maybe I'll migrate it to svelte, who knows haha.
nick splendorr ✨
@kevinleedrum Oh, thanks!! ???? I’ve gotten to work on some really fun projects. Been a couple of years of corporate consulting work that doesn’t go in the portfolio. But I’m making a game prototype in Vue, so was very interested to see your blackjack implementation! Thanks for sharing!
Kevin Lee Drum
@splendorr Same here! I'm super impressed by your portfolio site.
nick splendorr ✨
@kevinleedrum I really enjoyed reading your slot machine post! And your mrmo portraits are really fun. Nice to meetcha! ????