Skip to content

mwhooo/wildwinner

Repository files navigation

wildwinner

Blazor slot machine project (Wild Spinner).

wildwinner — Wild Spinner (Blazor)

This is a Blazor Server demo slot machine app ("Wild Spinner"). The README below explains how the game mechanics work, where the payout and free-spin logic lives, and how to run/tune the project and simulator.

Quick start

  1. Build locally:
dotnet build -c Debug
  1. Run the app (Blazor Server):
dotnet run --project "WildWinner.csproj"
  1. Simulator

There's a small RTP simulator under tools/rtp-sim. Example:

# from repo root
dotnet run --project "tools/rtp-sim" -- 100 --seed 12345

Use the in-app admin UI to Export Config for Simulator which writes tools/rtp-sim/config.json. The simulator can then use the exact runtime config for more accurate RTP sweeps.

How the slot works (high level)

  • Layout: 6 reels; each reel has a variable row count (Megaways-style) between 2 and 6 rows per spin.
  • Symbols: configured in Services/SlotConfig.cs (field Symbols).
  • Symbol weights (rarity), baseline multipliers and floor multipliers are all configurable in Services/SlotConfig.cs.
  • Wilds: images/wilds.PNG act as substitutes for other symbols when evaluating streaks.
  • Diamonds: special bonus symbol used to award Free Spins (see below).

Payout evaluation (detailed)

The core payout calculation is implemented in Services/SlotPayoutEvaluator.cs and mirrored in Components/Pages/SlotMachine.razor for UI logic. Steps:

  1. For each non-wild symbol S examine reels left-to-right and count the streak (number of consecutive reels from reel 0 containing S or a Wild). Streaks of 3 or more are eligible.
  2. Baseline multiplier: use the symbol's baseline (e.g. 0.40× for banana) for a 3-match. Larger streaks grow exponentially using a growth factor of 2.5 per extra reel: growth = 2.5^(streak-3).
  3. Raw line payout = betSize × lineMultiplier (baseline × growth).
  4. Combination count: for each reel in the streak multiply the count of matching symbols + wilds on that reel → comboCount. This accounts for multiple symbol occurrences per reel.
  5. Scaled payout = rawLinePayout × payoutScale × max(1, comboCount). payoutScale is a global tuning scalar (default in SlotConfig ≈ 0.129).
  6. Minimum floor: ensure scaled payout is at least betSize × MinMultiplierByStreak[streak] × comboCount × payoutScale (floors live in SlotConfig).
  7. Special-case: diamonds have a deterministic cash prize tied to the free-spin bonus (see below) — calculated differently than normal symbol payouts.

Important files/values

  • Services/SlotConfig.cs — primary tuning surface (symbols, SymbolMultipliers, SymbolWeights, MinMultiplierByStreak, FreeSpinWeightMultiplier, PayoutScale).
  • Services/SlotPayoutEvaluator.cs — canonical evaluation function used by the UI to decide payouts and wins.
  • Components/Pages/SlotMachine.razor — full gameplay UI and orchestration (spin loop, visuals, free-spins runner, admin UI hooks).
  • tools/rtp-sim — command-line simulator for large-scale RTP testing.

Free spins (how they work)

  • Trigger: if a spin finishes with 3 or more distinct reels containing the diamond symbol images/diamond.png, the player is awarded free spins.
  • Award formula: 3 diamonds → 10 free spins; each additional diamond adds +5 free spins (4→15, 5→20, 6→25). Award is capped at 25.
  • Pending state: awarded free spins are stored in pendingFreeSpins and the UI shows a confirmation modal Free Spins Awarded. The user must confirm to start them (this prevents unexpected autospin interruptions).
  • Free spin mode rules:
    • Free spins do NOT deduct the player's balance.
    • Free spins use a slightly boosted symbol weight table (see FreeSpinWeightMultiplier in SlotConfig) so wins are more likely.
    • Each free spin increases a progressive multiplier: starts at 1× for the first free spin and increases by +1 for each subsequent free spin. The multiplier is applied to the payouts during the free-spin session when accumulating the session total.
    • Wins during free spins are accumulated into freeSpinAccumulated and added to the player's balance at the end of the session.

Admin / debug tuning

  • Toggle the debug/admin panel in the app (click the ⚙ icon) to access live tuning:
    • Payout Scale slider — change payoutScale at runtime to affect RTP.
    • Export Config for Simulator — writes tools/rtp-sim/config.json for simulator use.
    • Several debug helper buttons exist (force test spins, export debug reels, etc.) to aid testing.

Simulator guidance

  • For large RTP sweeps use the simulator project tools/rtp-sim. Export the live config from the app and place it at tools/rtp-sim/config.json (admin UI provides that export). Then run the simulator with the desired spin count.

Example:

# export config from the app (admin panel) then:
dotnet run --project "tools/rtp-sim" -- 1000000

Notes / gotchas

  • The payoutScale tuned in SlotConfig is intentionally conservative; the in-app slider allows faster experimentation (simulator provides the best statistical view).
  • The UI shows smaller wins inline and larger wins in a modal with animations; autospin waits for large-win displays unless interrupted.
  • The tease animation for diamond bonus is purely cosmetic and does not change the final reels/outcome.

Contributing

  • If you change tuning values in Services/SlotConfig.cs you can export the runtime config from the app and run the simulator to re-check RTP.

Commit & push

If you'd like me to commit the README changes and push them, say so and I will run the git commands for you.

License / Contact

This project is a small demo — adapt as needed. If you want additional documentation or diagrams (probability trees, prize tables), I can add them.

Simulator results & charts

I ran several large simulator sweeps using the bundled simulator (tools/rtp-sim) after tuning PayoutScale to target an RTP ≈ 0.98. The generated charts are committed under tools/rtp-sim/plots and a short summary follows.

  • Latest tuned PayoutScale used by the app and simulator: 0.0578 (committed in Services/SlotConfig.cs).
  • Representative 1,000,000-spin runs:
    • seed 22222: RTP = 0.9801 (98.01%) — 1,000,000 spins. See plots below.
    • seed 12345: RTP = 2.1383 (213.83%) — earlier run showing the pre-tuning behavior.

Plots (generated by tools/rtp-sim/plot_sim.py):

Bucket counts

Bucket share

Summary file:

tools/rtp-sim/plots/summary.txt

If you'd like, I can copy these images into a top-level docs/ folder and add a dedicated docs/simulator-results.md page with the full set of run output files (CSV, detailed stats), or generate higher-resolution SVG plots for embedding.

Probability, Odds & Prize Table

Releases

  • Tag format: use vMAJOR.MINOR.PATCH (for example: v1.0.0).
  • Pushing a tag will trigger the Release workflow and create a GitHub Release with an attached publish.tar.gz containing the published app.
  • Release notes: they are auto-generated from commits between the previous tag and the pushed tag. Prefer concise commit messages (one-line summary) to produce clean release notes.

Example:

git tag v1.0.0
git push origin v1.0.0

Below are approximate probabilities and a prize table to help reason about expected outcomes. These are computed from the runtime tuning in Services/SlotConfig.cs (weights, multipliers and floors). Important notes:

  • These are approximate: symbol selection is weighted and some runtime rules alter exact probabilities (e.g. diamonds are limited to at most one per reel, wilds are excluded from reel 0, and free-spin mode temporarily boosts certain weights).
  • Values are shown per 1€ bet for clarity (multiply by your betSize to get actual payout). The runtime payoutScale (default: 0.129) is applied.

Symbol selection (approx per-row)

Total base weight (all reels): 57

Symbol Weight Approx per-row probability
apple 6 10.53%
banana 5 8.77%
bars 6 10.53%
bells 4 7.02%
cherry 6 10.53%
crown 3 5.26%
grapes 5 8.77%
lemon 5 8.77%
melon 5 8.77%
orange 5 8.77%
sevens 3 5.26%
wilds 2 3.51%
jackpot.png 1 1.75%
diamond.png 1 1.75%

Special-case notes:

  • Reel 0 excludes wilds (so per-row probabilities on reel 0 are computed from total weight = 55).
  • Diamonds cannot appear more than once on the same reel (generation removes diamond from choices after the first diamond on a reel).
  • In free-spin mode the FreeSpinWeightMultiplier (default 1.15) is applied to weights and wilds gain an extra +2 weight, increasing win frequency.

Approx chance to award Free Spins

Using the per-row diamond weight above and the runtime behavior (2–6 rows per reel, equal probability), the approximate probability that any single reel contains at least one diamond is ~6.9% (averaged across 2..6 rows). Treating reels as independent for a rough estimate, the probability of finding diamonds on 3 or more distinct reels (the condition that awards Free Spins) is approximately 0.48% per spin (about 1 in 208 spins). This is an approximation; exact simulator runs are recommended for precise RTP/odds.

Prize table (per 1€ bet) — typical payouts

The table below shows the payout per 1€ bet for a single-line match (combo multiplicity = 1). The runtime evaluator applies a global payoutScale (default: 0.129) and also enforces minimum floor multipliers per streak length (these floors cause many lower baselines to use the floor values for streaks >3). Diamonds are a special-case (they award a fixed cash prize tied to the free-spin award): 3→10×, 4→15×, 5→20×, 6→25× (per bet).

Symbol Baseline (3‑match ×bet) Payout (3-match) Payout (4-match) Payout (5-match) Payout (6-match)
banana 0.40× 0.194 € 1.290 € 2.580 € 8.000 €
cherry 0.60× 0.194 € 1.290 € 2.580 € 8.000 €
grapes 0.80× 0.194 € 1.290 € 2.580 € 8.000 €
lemon 1.00× 0.194 € 1.290 € 2.580 € 8.000 €
apple 1.50× 0.194 € 1.290 € 2.580 € 8.000 €
melon 2.00× 0.258 € 1.290 € 2.580 € 8.000 €
orange 2.40× 0.310 € 1.290 € 2.580 € 8.000 €
bars 3.20× 0.413 € 1.290 € 2.580 € 8.000 €
crown 3.80× 0.490 € 1.290 € 3.064 € 8.000 €
bells 4.50× 0.581 € 1.451 € 3.626 € 9.070 €
wilds 5.50× 0.710 € 1.774 € 4.434 € 11.086 €
sevens 8.00× 1.032 € 2.580 € 6.450 € 16.125 €
jackpot.png 14.00× 1.806 € 4.515 € 11.288 € 28.219 €
diamond.png (special) 1.20× (special) 10.00 €* 15.00 €* 20.00 €* 25.00 €*

*Diamonds use a deterministic cash prize tied to the free-spin award (see Free Spins section) rather than the standard scaling rules above.

How to interpret the table

  • The numbers are for a single-line win where each reel in the streak contributes one matching symbol (combo multiplicity = 1). If a reel contains multiple matching symbols or wilds, the evaluator multiplies payouts by the combination count (product of counts on each reel), which can increase payouts.
  • The payoutScale acts as a global tuning scalar; raising it increases all payouts proportionally (and affects RTP). Use the in-app admin slider for quick experimentation and the simulator for large-sample verification.

Suggested additions (next steps)

  • Generate a full prize matrix that includes common combo multiplicities for 2–6 rows per reel (more work but useful for exact expected-value tables).
  • Add an SVG/PNG probability chart exported from the simulator results for visual RTP breakdown.

Diagrams (flow)

Spin flow (simplified):

flowchart TD
	A[User clicks Spin] --> B[Deduct bet]
	B --> C[Generate reels 2to6]
	C --> D[Optional tease]
	D --> E[Evaluate wins]
	E --> F{Payout}
	F -->|Yes| G[Show win overlay]
	F -->|No| H[No win]
	E --> I{Free spins pending}
	I -->|Yes| J[Show Free Spins modal]
	G --> K[Add payout]
	J --> K
	K --> L[Ready]

Loading

Free-spin flow:

flowchart TD
	A[User confirms Free Spins] --> B[Set freeSpinCount]
	B --> C[Disable turbo and set multiplier=1]
	C --> D[Run free spin loop]
	D --> E[Use boosted weights and accumulate payout]
	E --> F[Increment multiplier and decrement count]
	F --> G{freeSpinCount == 0}
	G -->|Yes| H[Show summary and add total to balance]
	G -->|No| D

Loading

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

No packages published