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.
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.
Requires the .NET 8 SDK on Windows.
run.cmd
# or
dotnet run --project src\GamblingSim\GamblingSim.csproj -c Release
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:
- 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).
- 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.
- 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.
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 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/Optimumtake 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.
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.
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)