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')featureparameter - Base game and freespin entries are tagged with afeaturefield in the output filesfeatureAwards/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:
- Picks a base game entry (where
feature === 'basegame') using the weighted distribution. - If the entry has
featureAwardswithfeature: '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):
| win | weight | metaTags | featureAwards |
|---|---|---|---|
| 0 | ~600K | ['no-win'] | - |
| 50 | ~24K | ['big-win'] | - |
| 0 | ~500 | ['freespin-trigger'] | 10 spins → 'freespin' entries |
freespin entries (in entries.jsonl):
| win | weight | metaTags | featureAwards |
|---|---|---|---|
| 0 | ~600K | ['no-win'] | - |
| 30 | ~24K | ['big-win'] | - |
All features are combined in the same files, distinguished by the feature field on each entry.