Skip to content

Latest commit

 

History

93 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GamblingSim

A Martingale-strategy casino simulator, rewritten from a Vue 2 web app into a native C# / WPF desktop application (feature/native-rewrite). The app sweeps a grid of (bet multiplier × start bank) strategies against a configurable game (win odds, payout rate, min bet), renders the results as a live 3D heatmap, and — as of the latest work — can skip simulation entirely and plot the exact closed-form solution of the strategy surface.

.NET 8 WPF License

This is a math/statistics playground, not gambling advice. One of the things the analytic model proves is that no cell on the surface is EV-positive while the game has a house edge — see The analytic model below.

Running

Requires the .NET 8 SDK on Windows.

run.cmd
# or
dotnet run --project src\GamblingSim\GamblingSim.csproj -c Release

What changed in the overhaul

The original web app (still in index.html / JS/ / CSS.css for reference) ran a single-threaded simulation in the browser. The rewrite keeps the simulation semantics and CSV schema 1:1 compatible, then goes considerably further:

Engine (Simulation/CasinoEngine.cs)

  • Pure C# port of the game loop: Martingale bet ladder (bet = max(minBet, bet × multi)), win-skim above max bank into take-home, roll-only-safe episode ending, respawn on bust.
  • Welford online accumulators per grid cell with a confidence-interval stopping rule — cells run until their net-% CI half-width falls under a configurable target (floor 256 episodes, checked every 64, capped) instead of a fixed episode count.
  • Hot loop optimized for throughput (inlined safe-bet check, no per-roll allocation).

Turbo sweep (Simulation/TurboSweep.cs)

  • Fully parallel additive sweep across all cores: the whole heatmap grid is dispatched to workers, results drain into the UI incrementally. Sustains on the order of hundreds of millions of rolls per second on a desktop CPU.
  • Additive "Add Rows" mode extends an existing surface upward in bet-multiplier without recomputing finished rows.

UI (WPF, MVVM)

  • Tabbed dark-theme main window: game settings, casino rules, heatmap/sweep config, leaderboard, model card. Live status bar with roll rate and progress.
  • 3D surface heatmap (HelixToolkit): turntable rotation about the model center, Plotly-style hover labels naming the exact (multi, bank) cell, height-normalized gradient coloring, resizable via a layout splitter.
  • Leaderboard of best cells found so far, with per-entry Report buttons.
  • Report windows (ScottPlot): watchable single-strategy replays — bank, take-home, and net-profit traces over time, driven by a shared engine config serialized to JSON.
  • CSV export/import of surfaces, schema-compatible with the old web app.

Real-world roulette presets

The Settings card has a Presets dropdown (European / American roulette, red-black) that fills the casino and bank settings with real table values — exact win odds (18/37 and 18/38), 1× payout, $10 outside minimum, $1,000 table maximum, and a real-dollar bank scale — then restarts the sweep.

The table max is deliberately not enforced in simulation: the sandbox stays uncapped so any regime can be explored. Instead, a "casino best on grid" readout runs the analytic model with the table limit applied to the bet ladder and shows the best cell a real table would allow, next to the uncapped optimum. A neat corollary the model makes obvious: since every dollar wagered loses the house edge (E[net] = −edge · E[wagered]), a table limit acts as a forced stop-loss — the capped optimum is always less negative than the uncapped one. Casinos cap bets to protect themselves from variance, not from Martingale expectation; the limit actually shrinks the player's expected loss.

The analytic model (Simulation/AnalyticModel.cs)

The heatmap's characteristic "wave" pattern turned out to be exactly derivable. With roll-only-safe play and max bank locked to the start bank, every win pins the bank back to exactly the start bank, so play decomposes into i.i.d. cycles ending in one bust of deterministic ladder depth K (the number of bets a losing streak can fit before the bank can't cover the next one). With win probability p, payout rate R, min bet b, multiplier m, and cumulative ladder losses Lj = b·m(mj−1)/(m−1):

q      = (1−p)^K                      (bust probability per cycle)
E[net] = (1/q) · Σᵢ₌₁..K (1−p)^(i−1) · p · (R·b·mⁱ − L₍ᵢ₋₁₎)  −  L_K
net %  = E[net] / startBank · 100
  • The waves are constant-K bands; the cliffs are where K increments. K is pure ladder geometry — the win rate only scales the surface vertically, which is why the shape is identical at any winrate.
  • K is computed with the simulator's exact floating-point operation sequence, so boundary cells can't disagree by rounding.
  • Evaluate/Surface/Optimum take an optional max-bet cap: the ladder stops where the casino would refuse the next raise. Uncapped callers are untouched (bit-identical); the cap powers the "casino best on grid" readout.
  • Globally, E[net] = −(1 − p(1+R)) · E[total wagered]: with any house edge, every strategy on the surface is provably EV-negative. There is no crest that "tops out" positive.

Validated against the simulator at scale: a 250 × 2000 grid backtest (497,690 cells, ~845 billion simulated rolls) landed 94.96% of cells within ±1.96σ (expected ≈95%) and 99.73% within ±3σ (expected ≈99.7%), mean z ≈ −0.01, max |z| 4.83 vs ~4.9 expected for that many samples — i.e. the model is exact and the residuals are pure sampling noise. The in-app Validate Model button reruns this comparison on any grid and writes a CSV report.

Surface view modes

The heatmap header has a View dropdown:

View What it shows
Simulated The live/turbo Monte-Carlo surface (default).
Analytic The closed-form surface, computed instantly for any grid size; hover shows the model value and ladder depth K.
Analytic (Fast) Same surface without hover hit-testing, for very large renders.
Residual Sim − model per cell — flat noise around zero when the model holds.

In the analytic views the background simulation pauses (nothing to feed), the model evaluates in parallel across all cores, and very large grids are decimated to ~1.5 samples per screen pixel (grid increments rescale so hover labels stay exact), so render cost is bounded by your monitor rather than the grid — there is no row cap.

Project layout

src/GamblingSim/
  Simulation/
    CasinoEngine.cs     game rules + Welford/CI cell accumulators
    TurboSweep.cs       parallel additive grid sweep
    AnalyticModel.cs    closed-form surface, optimum finder, house-edge readout
    CasinoPresets.cs    real-world roulette table presets
    Leaderboard.cs      best-cell tracking
    PlotCsv.cs          web-app-compatible CSV export/import
    ReportConfig.cs     JSON config handoff to report windows
  ViewModels/           MVVM layer (MainViewModel drives everything)
  MainWindow.xaml(.cs)  tabs, 3D viewport, hover, mesh build
  ReportWindow.xaml(.cs) ScottPlot strategy replay
index.html / JS/ / CSS.css   the original Vue 2 web app (reference only)

About

Solving the Martingale Problem

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages