Gambling… and Rust#
Happy New Year! In this post I will present a gambling game I designed for AP Stats. And Rust.
Background#
Ok. Let me explain.
Recently, I just finished basic probability theory in AP Stats. One of my assignments was to design a game of chance [1] that consistently delivers a profit to the dealer. I also calculated the expected value and standard deviation of the outcome.
Coincidentally, I have also been learning about the Rust programming language. That’s when I came up with an idea:
Why not simulate my game in Rust?
And so I did.
The Game#
Note
The following description is directly adapted from my assignment for AP Stats.
To celebrate the mathematical significance of prime numbers, this game involves choosing your favorite prime number from a deck of cards!
Given a standard 52-card deck, you are free to randomly select one card from the deck (without seeing its front side). There are four possible outcomes.
You selected a prime number (2, 3, 5, or 7): Hooray! You get to double your money.
You selected a composite number (4, 6, 8, 9, or 10): Unfortunately, you lose your money.
You selected 1 (an ace of any color): Although one is neither prime nor composite, it is a special number in math. You also get to double your money.
You selected a face card (J, Q, or K):
Place your card back into the deck, because now you get to choose your own number!
Designate your favorite prime number to be either 2, 3, 5, or 7. You will then choose another card from the deck.
If the new card has the same prime number, you get paid 5 to 1.
If the new card has any other prime number, you double your money.
If the new card has composite numbers, aces, or is a face card, you lose your money.
Outcome |
Payout |
---|---|
2, 3, 5, 7, A |
1 : 1 |
4, 6, 8, 9, 10 |
lose |
J, Q, K |
same prime => 5 : 1 |
other prime => 1 : 1 |
|
all other => lose |
The Math#
Assume that the player pays \(10\) dollars for the game.
Outcome \(x\) |
Probability \(p\) |
---|---|
\(10\) |
\(\frac{5}{13}\) |
\(-10\) |
\(\frac{5}{13}\) |
\(50\) |
\(\frac{3}{169}\) |
\(10\) |
\(\frac{9}{169}\) |
\(-10\) |
\(\frac{27}{169}\) |
The Code#
Short. Sweet. Amazingly functional.
1use rand::prelude::*;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
4pub enum Card {
5 Ace,
6 J,
7 Q,
8 K,
9 Number(u8),
10}
11
12#[derive(Debug)]
13pub struct Deck {
14 deck: Vec<Card>,
15}
16
17impl Deck {
18 pub fn new() -> Self {
19 let mut deck = vec![Card::Ace, Card::J, Card::Q, Card::K];
20 for i in 2..=10 {
21 deck.push(Card::Number(i));
22 }
23 Self {
24 deck: deck.iter().copied().cycle().take(52).collect(),
25 }
26 }
27
28 pub fn shuffle(&mut self) {
29 let mut rng = thread_rng();
30 self.deck.shuffle(&mut rng);
31 }
32
33 pub fn choose(&self) -> Card {
34 let mut rng = thread_rng();
35 *self.deck.choose(&mut rng).unwrap()
36 }
37}
1use prime::{Card, Deck};
2use rand::prelude::*;
3
4fn simulate(deck: &Deck) -> i64 {
5 match deck.choose() {
6 Card::Number(2 | 3 | 5 | 7) => 20,
7 Card::Number(_) => 0,
8 Card::Ace => 20,
9 _ => {
10 let prime = *[2, 3, 5, 7].choose(&mut thread_rng()).unwrap();
11 match deck.choose() {
12 Card::Number(x) if x == prime => 60,
13 Card::Number(2 | 3 | 5 | 7) => 20,
14 _ => 0,
15 }
16 }
17 }
18}
19
20fn stats(iter: impl Iterator<Item = i64>) -> (f64, f64) {
21 let v = iter.collect::<Vec<_>>();
22 let mean = v.iter().sum::<i64>() as f64 / v.len() as f64;
23 let mut stdev = v.iter().map(|&x| (x as f64 - mean).powi(2)).sum::<f64>();
24 stdev = (stdev / (v.len() - 1) as f64).sqrt();
25 (mean, stdev)
26}
27
28fn main() {
29 let mut deck = Deck::new();
30 deck.shuffle();
31
32 let len = 1000000i64;
33 // let mean = (0..len).map(|_| simulate(&deck) - 10).sum::<i64>() as f64 / len as f64;
34 let (mean, stdev) = stats((0..len).map(|_| simulate(&deck) - 10));
35
36 println!("{mean}, {stdev}");
37}
Reflection#
IMO, the most exhilarating part is definitely writing the code in Rust. Not only does the program produce a consistent result with my mathematical calculations:
-0.1794, 11.946612840602265
but it also demonstrates the unique characteristics of Rust.
Within 80 lines of code, I made use of structs, enums, traits, closures, iterators, and perhaps most
importantly, match
expressions. While I definitely overcomplicated things a bit (especially with
respect to the iterator soup in fn stats
:P), the joy of learning a new programming language never
ceases to inspire me.
However,
I did not make use of lifetimes or Rust memory management. I will do that—soon.