Skip to content

Slot Game

A complete example showing how to use tags, feature awards, and feature tables to generate config for a slot game with a freespins bonus feature.

The Game

  • 3 reels, 3 rows with a single payline (middle row)
  • Symbols: 0–5 (regular, low-to-high) and 6 (scatter)
  • Win: 3 of a kind on the payline pays symbol × 10
  • Freespins: 3+ scatters anywhere triggers 10 freespins

Key Concepts

This example demonstrates:

  • metaTags / meta_tags - Classify spin outcomes ('no-win', 'big-win', 'freespin-trigger')
  • feature parameter - Base game and freespin entries are tagged with a feature field in the output files
  • featureAwards / feature_awards - Tell the hizi engine to award bonus spins referencing a feature name

Full Example

typescript
import { HiziEngineGenerator, TFeaturesAwarded } from '@hizi.io/engine-generator';

const SYMBOLS = [0, 1, 2, 3, 4, 5, 6]; // 6 = scatter
const SCATTER = 6;
const NUM_REELS = 3;
const NUM_ROWS = 3;

function randomSymbol(): number {
  return SYMBOLS[Math.floor(Math.random() * SYMBOLS.length)];
}

function simulateSpin() {
  // Generate the grid
  const grid: number[][] = [];
  let scatterCount = 0;
  for (let row = 0; row < NUM_ROWS; row++) {
    const rowSymbols: number[] = [];
    for (let reel = 0; reel < NUM_REELS; reel++) {
      const sym = randomSymbol();
      rowSymbols.push(sym);
      if (sym === SCATTER) scatterCount++;
    }
    grid.push(rowSymbols);
  }

  // Check payline (middle row, index 1)
  const payline = [grid[1][0], grid[1][1], grid[1][2]];
  let win = 0;
  if (payline[0] === payline[1] && payline[1] === payline[2] && payline[0] !== SCATTER) {
    win = payline[0] * 10;
  }

  return { grid, win, scatterCount };
}

async function main() {
  const NUM_BASE_SPINS = 1_000_000;
  const NUM_FREESPIN_SPINS = 1_000_000;

  const generator = new HiziEngineGenerator();
  await generator.start('./output/');

  // ── Base game feature ──
  for (let i = 0; i < NUM_BASE_SPINS; i++) {
    const base = simulateSpin();
    const triggersFreespins = base.scatterCount >= 3;

    // Meta tags
    const metaTags: string[] = [];
    if (base.win === 0 && !triggersFreespins) metaTags.push('no-win');
    if (base.win >= 30) metaTags.push('big-win');
    if (triggersFreespins) metaTags.push('freespin-trigger');

    // Feature awards config
    let featureAwards: TFeaturesAwarded | undefined;
    if (triggersFreespins) {
      featureAwards = {
        type: 'randomChoice',
        awards: [
          {
            count: 10,
            feature: 'freespin', // Use the 'freespin' feature tables
          },
        ],
      };
    }

    // Record base game spin
    generator.addResult(
      { grid: base.grid },
      {
        feature: 'basegame',
        win: base.win,
        metaTags,
        featureAwards,
      },
    );
  }

  // ── Freespin feature ──
  for (let i = 0; i < NUM_FREESPIN_SPINS; i++) {
    const free = simulateSpin();

    const fsMetaTags: string[] = [];
    if (free.win === 0) fsMetaTags.push('no-win');
    if (free.win >= 30) fsMetaTags.push('big-win');

    // Record freespin result into the freespin tables
    generator.addResult(
      { grid: free.grid },
      {
        feature: 'freespin',
        win: free.win,
        metaTags: fsMetaTags,
      },
    );
  }

  await generator.end();
  console.log('Done!');
}

main();
python
import random
from hizi_engine_generator import (
    HiziEngineGenerator,
    FeaturesAwardedRandomChoice,
    RandomChoiceFeatureAward,
)

SYMBOLS = [0, 1, 2, 3, 4, 5, 6]  # 6 = scatter
SCATTER = 6
NUM_REELS = 3
NUM_ROWS = 3


def simulate_spin():
    grid = []
    scatter_count = 0
    for _ in range(NUM_ROWS):
        row = []
        for _ in range(NUM_REELS):
            sym = random.choice(SYMBOLS)
            row.append(sym)
            if sym == SCATTER:
                scatter_count += 1
        grid.append(row)

    # Check payline (middle row, index 1)
    payline = grid[1]
    win = 0
    if payline[0] == payline[1] == payline[2] and payline[0] != SCATTER:
        win = payline[0] * 10

    return grid, win, scatter_count


def main():
    NUM_BASE_SPINS = 1_000_000
    NUM_FREESPIN_SPINS = 1_000_000

    gen = HiziEngineGenerator()
    gen.start('./output/')

    # ── Base game feature ──
    for _ in range(NUM_BASE_SPINS):
        grid, win, scatter_count = simulate_spin()
        triggers_freespins = scatter_count >= 3

        # Meta tags
        tags = []
        if win == 0 and not triggers_freespins:
            tags.append('no-win')
        if win >= 30:
            tags.append('big-win')
        if triggers_freespins:
            tags.append('freespin-trigger')

        # Feature awards config
        feature_awards = None
        if triggers_freespins:
            feature_awards = FeaturesAwardedRandomChoice(awards=[
                RandomChoiceFeatureAward(count=10, feature='freespin'),
            ])

        gen.add_result(
            {'grid': grid},
            feature='basegame',
            win=win,
            meta_tags=tags or None,
            feature_awards=feature_awards,
        )

    # ── Freespin feature ──
    for _ in range(NUM_FREESPIN_SPINS):
        grid, win, _ = simulate_spin()

        tags = []
        if win == 0:
            tags.append('no-win')
        if win >= 30:
            tags.append('big-win')

        gen.add_result(
            {'grid': grid},
            feature='freespin',
            win=win,
            meta_tags=tags or None,
        )

    gen.end()
    print('Done!')


if __name__ == '__main__':
    main()

How Features and Spins Work Together

The flow during simulation:

entries.jsonl
  {"feature":"basegame","id":0,"weight":...,"cumulativeWeight":...,"win":0,"metaTags":["no-win"]}
  {"feature":"basegame","id":1,"weight":...,"cumulativeWeight":...,"win":50,"metaTags":["big-win"]}
  {"feature":"basegame","id":2,"weight":...,"cumulativeWeight":...,"win":0,"metaTags":["freespin-trigger"],
      "featureAwards":{"type":"randomChoice","awards":[{"count":10,"feature":"freespin"}]}}
  {"feature":"freespin","id":3,"weight":...,"cumulativeWeight":...,"win":20}
  {"feature":"freespin","id":4,"weight":...,"cumulativeWeight":...,"win":0,"metaTags":["no-win"]}
  {"feature":"freespin","id":5,"weight":...,"cumulativeWeight":...,"win":50,"metaTags":["big-win"]}

At runtime, the hizi engine:

  1. Picks a base game entry (where feature === 'basegame') using the weighted distribution.
  2. If the entry has featureAwards with feature: 'freespin', it runs that many spins using freespin entries.

Output Structure

Two output files are produced — entries.jsonl and scenarios.jsonl:

basegame entries (in entries.jsonl):

winweightmetaTagsfeatureAwards
0~600K['no-win']-
50~24K['big-win']-
0~500['freespin-trigger']10 spins → 'freespin' entries

freespin entries (in entries.jsonl):

winweightmetaTagsfeatureAwards
0~600K['no-win']-
30~24K['big-win']-

All features are combined in the same files, distinguished by the feature field on each entry.