Skip to content

Latest commit

 

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PAR Sheet Calculator — Slot Math Engine in C#

A production-grade RTP calculator for probability-based reward systems.
Exact enumeration · Parallel execution · Monte Carlo bonus simulation · Variance analysis


What This Is

This repository implements a PAR sheet calculator (Probability and Accounting Report) — the mathematical engine used to design and verify slot machines, gacha systems, and loot box mechanics.

Given a set of reel strips and a pay table, it computes:

  • RTP (Return to Player) — exact, not simulated
  • Hit Frequency — fraction of spins with at least one win
  • Variance — mathematical measure of volatility
  • Bonus RTP — Free Spins contribution via Monte Carlo
  • MaxWin impact — how much RTP the win cap removes

New to PAR sheet math?
Before diving into the code, read the theoretical foundation:
📐 Slot Mathematical Model — PAR Sheet Explained
It covers reel strip design, combination counting, and how RTP is derived analytically.


Features

  • ✅ Exact enumeration of all stop combinations (no sampling)
  • ✅ Parallel.For over reel 0 — linear scaling with CPU cores
  • ✅ Weighted symbol system — control probability via stop counts
  • ✅ Wild substitution with full chain evaluation
  • ✅ 20 configurable paylines (straight rows, V-shapes, zigzags)
  • ✅ MaxWin cap with RTP loss tracking
  • ✅ Scatter trigger probability (analytical, not sampled)
  • ✅ Free Spins Monte Carlo with retrigger support
  • ✅ Real-time progress bar with live RTP estimate

Quick Start

git clone https://github.com/Akubet/RTPsim
dotnet run --project RTPsim

Output:

| Calculating PAR sheet...
  [####################] 100%
  Combinations : 55,566,000 / 55,566,000
  Hits so far  : 7,451,340
  RTP so far   : 90.39%

╔══════════════════════════════════════════════════╗
║              PAR SHEET — FINAL RESULTS           ║
╠══════════════════════════════════════════════════╣
║  Base Game RTP   :   90.60 %                     ║
║  Hit Frequency   :   13.11 %                     ║
║  Variance        :   571.83                      ║
╠══════════════════════════════════════════════════╣
║  Trigger Freq    :  1 in    84 spins             ║
║  Avg Bonus Win   :    76.40x                     ║
║  Bonus RTP       :    5.40 %                     ║
╠══════════════════════════════════════════════════╣
║  TOTAL RTP       :   96.00 %                     ║
╚══════════════════════════════════════════════════╝

How It Works

Reel Strips and Weighted Symbols

Probability is not assigned directly — it emerges from symbol counts on the reel strip.
A Diamond×1 on a 35-stop reel = 2.86% probability. A Wild×3 = 8.57%.

var weightedReels = new WeightedSymbol[][]
{
    // Reel 1
    [
        new(Symbol.Diamond,  2),
        new(Symbol.Ruby,     3),
        new(Symbol.Emerald,  2),
        new(Symbol.Gold,     3),
        new(Symbol.Silver,   3),
        new(Symbol.Ace,      4),
        new(Symbol.King,     4),
        new(Symbol.Queen,    5),
        new(Symbol.Jack,     5),
        new(Symbol.Wild,     2),
        new(Symbol.Scatter,  2),
    ],
    // Reel 3 — center reel, more Wilds for near-miss tension
    [
        // ...
        new(Symbol.Wild,     3),  // higher Wild count on center reel is intentional
        // ...
    ],
};

Pay Table

var payTable = new PayTable(new()
{
    [Symbol.Diamond] = new() { [3] = 80m,  [4] = 300m, [5] = 1000m },
    [Symbol.Ruby]    = new() { [3] = 40m,  [4] = 150m, [5] = 500m  },
    [Symbol.Emerald] = new() { [3] = 25m,  [4] = 100m, [5] = 300m  },
    [Symbol.Gold]    = new() { [3] = 15m,  [4] = 50m,  [5] = 150m  },
    [Symbol.Silver]  = new() { [3] = 10m,  [4] = 35m,  [5] = 100m  },
    [Symbol.Ace]     = new() { [3] = 6m,   [4] = 18m,  [5] = 60m   },
    [Symbol.King]    = new() { [3] = 6m,   [4] = 18m,  [5] = 60m   },
    [Symbol.Queen]   = new() { [3] = 4m,   [4] = 12m,  [5] = 40m   },
    [Symbol.Jack]    = new() { [3] = 4m,   [4] = 12m,  [5] = 40m   },
    [Symbol.Wild]    = new() { [5] = 200m },
});

Running the Calculator

var config     = new GameConfig(weightedReels, payLines, payTable, MaxWin: 5000m);
var calculator = new ParSheetCalculator(config);

// Full PAR sheet: base game + bonus
var result = calculator.CalculateFull();

Console.WriteLine($"Total RTP: {result.TotalRTP:P2}");

RTP Tuning Guide

Reaching a target RTP (e.g. 96%) requires iterative tuning. The recommended workflow:

Step Action Speed Accuracy
1 Monte Carlo 500K samples ~0.5 sec ±0.2%
2 Adjust Wild weights, rerun ~0.5 sec ±0.2%
3 Exact enumeration ~35 sec exact
4 Tune bonus multiplier ~3 sec ±0.1%
5 Final exact + bonus ~40 sec exact

Sensitivity reference (approximate, varies by pay table):

Change RTP delta
Wild weight Reel3: +1 stop +3–5%
Wild weight Reel1,2,4,5: +1 stop +1–2% each
Diamond weight: +1 stop per reel +1–2%
Ruby weight: +1 stop per reel +0.5–1%
Bonus multiplier: +0.1 +0.28%

Wild on the center reel (Reel 3) is the most sensitive lever because more paylines pass through the center. A single stop difference can swing RTP by 3–5%.

Target breakdown for a 96% game:

Base game RTP  : 89–91%
Bonus RTP      : 5–7%
─────────────────────
Total RTP      : 96%

Architecture

RTPsim/
├── Core/
│   ├── ParSheetCalculator.cs   ← main engine
│   ├── GameConfig.cs           ← configuration records
│   ├── PayTable.cs             ← payout lookup
│   ├── PayLine.cs              ← payline definitions
│   └── ReelConfig/
│       └── WeightedSymbol.cs   ← weighted stop model
├── Configs/
│   └── DefaultConfig.cs        ← sample 96% RTP config
└── Program.cs

Key Concepts

Why Exact Enumeration?

Monte Carlo gives ~±0.2% accuracy at 1M samples. For casino certification, regulators require exact RTP. Exact enumeration is the only method that guarantees correctness — and at 52M combinations it completes in ~35 seconds with parallelisation.

Variance vs Volatility

Variance is the mathematical signature of volatility: E[X²] - (E[X])².

Variance Profile Example games
< 50 Low Classic fruit machines
50–150 Medium Most video slots
150–400 Medium-High This engine's target
400–800 High Book of Ra, Dead or Alive
> 800 Very High Money Train, Razor Shark

Two games with identical 96% RTP but different variance have completely different player experiences. Variance is a design choice, not a side effect.

RTP Is Dominated by Low-Tier Wins

Counterintuitively, QUEEN 3× contributes more total RTP than DIAMOND 5× — because probability × payout, and probability always wins. The jackpot feels important but the math is driven by frequent small wins.


Further Reading


License

MIT — free to use in commercial and open source projects.


Built for developers who want to understand how probability-based reward systems actually work — not just use them.

About

PAR sheet calculator for slot math — exact RTP enumeration, Hit Frequency, Variance, and Monte Carlo bonus simulation. Weighted reel strips, Wild substitution, 20 paylines. C# / .NET 10.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages