Skip to content

Instantly share code, notes, and snippets.

@haritonch
Last active August 6, 2025 23:50
Show Gist options
  • Select an option

  • Save haritonch/1e793a68f574c5410e1efd632b484390 to your computer and use it in GitHub Desktop.

Select an option

Save haritonch/1e793a68f574c5410e1efd632b484390 to your computer and use it in GitHub Desktop.
Monte Carlo simulation for Tzoker
use rand::{seq::SliceRandom, Rng};
use rayon::prelude::*;
use rust_decimal::{prelude::FromPrimitive, Decimal};
use std::time::Instant;
use std::{cmp::min, str::FromStr};
const EXPECTED_TOTAL_COUPONS_PLAYED: u32 = 7_000_000;
const N_SIMULATIONS: u32 = 10_000;
const JACKPOT: u32 = 22_000_000;
const SECOND_CATEGORY_POT: u32 = 2_000_000;
type Five = [u8; 5];
type One = u8;
struct Coupon {
pub five: Five,
pub one: One,
}
fn random_one() -> One {
let mut rng = rand::rng();
rng.random_range(1..=20)
}
fn random_five() -> Five {
let mut numbers: Vec<u8> = (1..=45).collect();
let mut rng = rand::rng();
numbers.shuffle(&mut rng);
let five: Five = numbers[..5].try_into().unwrap();
five
}
fn random_coupon() -> Coupon {
let five = random_five();
let one = random_one();
Coupon { five, one }
}
fn n_matches(five: &Five, other_five: &Five) -> u32 {
let mut matches = 0;
for &num in five {
if other_five.contains(&num) {
matches += 1;
}
}
matches
}
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
enum WinningCategory {
First,
Second,
Third,
Fourth,
Fifth,
Sixth,
Seventh,
Eighth,
Ninth,
None,
}
impl From<&WinningCategory> for Decimal {
fn from(value: &WinningCategory) -> Self {
match value {
WinningCategory::First => Decimal::from_u32(JACKPOT).unwrap(),
WinningCategory::Second => Decimal::from_str("100000").unwrap(),
WinningCategory::Third => Decimal::from_str("2500").unwrap(),
WinningCategory::Fourth => Decimal::from_str("50").unwrap(),
WinningCategory::Fifth => Decimal::from_str("50").unwrap(),
WinningCategory::Sixth => Decimal::from_str("2").unwrap(),
WinningCategory::Seventh => Decimal::from_str("2").unwrap(),
WinningCategory::Eighth => Decimal::from_str("1.5").unwrap(),
WinningCategory::Ninth => Decimal::from_str("1").unwrap(),
WinningCategory::None => Decimal::ZERO,
}
}
}
impl WinningCategory {
fn payout(&self) -> Decimal {
Decimal::from(self)
}
}
fn winning_category(coupon: &Coupon, winning_coupon: &Coupon) -> WinningCategory {
let five_matches = n_matches(&coupon.five, &winning_coupon.five);
let one_match = if coupon.one == winning_coupon.one {
1
} else {
0
};
match (five_matches, one_match) {
(5, 1) => WinningCategory::First,
(5, 0) => WinningCategory::Second,
(4, 1) => WinningCategory::Third,
(4, 0) => WinningCategory::Fourth,
(3, 1) => WinningCategory::Fifth,
(3, 0) => WinningCategory::Sixth,
(2, 1) => WinningCategory::Seventh,
(1, 1) => WinningCategory::Eighth,
(2, 0) => WinningCategory::Ninth,
_ => WinningCategory::None,
}
}
fn num_category_winners(
played_coupons: &[Coupon],
winning_coupon: &Coupon,
target_category: WinningCategory,
) -> u32 {
let mut count = 0;
for coupon in played_coupons {
let coupon_category = winning_category(coupon, winning_coupon);
if coupon_category == target_category {
count += 1;
}
}
count
}
// Returns the round payout
fn round_payout() -> Decimal {
let my_coupon = random_coupon();
let played_coupons: Vec<Coupon> = (0..EXPECTED_TOTAL_COUPONS_PLAYED)
.map(|_| random_coupon())
.collect();
let winning_coupon = random_coupon();
let my_category = winning_category(&my_coupon, &winning_coupon);
match my_category {
c @ WinningCategory::First => {
let num_category_winners =
Decimal::from_u32(num_category_winners(&played_coupons, &winning_coupon, c))
.unwrap();
println!("Winner!!!");
c.payout().checked_div(num_category_winners).unwrap()
}
c @ WinningCategory::Second => {
let num_category_winners =
Decimal::from_u32(num_category_winners(&played_coupons, &winning_coupon, c))
.unwrap();
min(
c.payout(),
Decimal::from_u32(SECOND_CATEGORY_POT)
.unwrap()
.checked_div(num_category_winners)
.unwrap(),
)
}
c => c.payout(),
}
}
fn main() {
let start = Instant::now();
let total_payout = (0..N_SIMULATIONS)
.into_par_iter()
.map(|_| round_payout())
.reduce(|| Decimal::ZERO, |a, b| a.checked_add(b).unwrap());
let expected_payout = total_payout
.checked_div(Decimal::from_u32(N_SIMULATIONS).unwrap())
.unwrap();
println!("Average payout after {N_SIMULATIONS} simulations: {expected_payout}");
let duration = start.elapsed();
println!("Execution time: {:.2?}", duration);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment