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.

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.