fair_baccarat/
lib.rs

1//! # provably fair baccarat
2//!
3//! Deterministically simulates a game of baccarat. Assumes an inifinite amount of card decks.
4
5/*
6use std::env;
7use std::error::Error; use std::fs;
8*/
9
10mod card;
11mod rng;
12mod wasm;
13
14use crate::card::Card;
15use crate::rng::ProvablyFairRNG;
16use crate::rng::ProvablyFairRNGFloat;
17
18use std::cmp::Ordering;
19use std::fmt;
20use BaccaratCardRecipient::*;
21
22#[derive(Debug, PartialEq, Eq)]
23enum Outcome {
24    Banker,
25    Player,
26    Tie,
27}
28
29impl fmt::Display for Outcome {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        let s = match self {
32            Outcome::Banker => "Banker won",
33            Outcome::Player => "Player won",
34            Outcome::Tie => "It's a tie",
35        };
36        write!(f, "{}", s)
37    }
38}
39
40#[derive(Debug)]
41struct SimulationResultTotals {
42    player: u32,
43    banker: u32,
44}
45
46#[derive(Debug)]
47struct Step(BaccaratCardRecipient, Card);
48
49#[derive(Debug)]
50pub struct SimulationResult {
51    outcome: Outcome,
52    totals: SimulationResultTotals,
53    steps: Vec<Step>,
54}
55
56impl SimulationResult {
57    fn from_steps(steps: Vec<Step>) -> SimulationResult {
58        let totals = SimulationResultTotals {
59            player: sum_cards_player(&steps),
60            banker: sum_cards_banker(&steps),
61        };
62        let outcome = match totals.player.cmp(&totals.banker) {
63            Ordering::Less => Outcome::Banker,
64            Ordering::Greater => Outcome::Player,
65            Ordering::Equal => Outcome::Tie,
66        };
67        SimulationResult {
68            outcome,
69            totals,
70            steps,
71        }
72    }
73}
74impl fmt::Display for SimulationResult {
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        fn pretty_print_steps(recipient: &BaccaratCardRecipient, steps: &Vec<Step>) -> String {
77            let step_str = steps
78                .iter()
79                .filter_map(|Step(r, c)| {
80                    if r == recipient {
81                        Some(c.to_string())
82                    } else {
83                        None
84                    }
85                })
86                .collect::<Vec<String>>()
87                .join(" - ");
88            let total = sum_cards(recipient, steps);
89            format!("{} ({}): {}", recipient, total, step_str)
90        }
91        let banker = pretty_print_steps(&BANKER, &self.steps);
92        let player = pretty_print_steps(&PLAYER, &self.steps);
93
94        write!(f, "{}\n\n{}\n{}", self.outcome, player, banker)
95    }
96}
97
98#[derive(Debug, PartialEq, Eq)]
99enum BaccaratCardRecipient {
100    BANKER,
101    PLAYER,
102}
103impl fmt::Display for BaccaratCardRecipient {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        match self {
106            BANKER => f.write_str("Banker"),
107            PLAYER => f.write_str("Player"),
108        }
109    }
110}
111
112fn baccarat_add(left: u32, right: u32) -> u32 {
113    (left + right) % 10
114}
115
116fn sum_cards(for_recipient: &BaccaratCardRecipient, steps: &Vec<Step>) -> u32 {
117    steps
118        .iter()
119        .filter(|Step(recipient, _)| recipient == for_recipient)
120        .fold(0, |acc, Step(_, card)| {
121            baccarat_add(acc, card.to_baccarat_value() as u32)
122        })
123}
124
125fn sum_cards_player(steps: &Vec<Step>) -> u32 {
126    sum_cards(&PLAYER, &steps)
127}
128fn sum_cards_banker(steps: &Vec<Step>) -> u32 {
129    sum_cards(&BANKER, &steps)
130}
131
132/// Simulates a game of baccarat.
133///
134/// # Example
135///
136/// ```
137///
138/// let client_seed = "some client seed";
139/// let server_seed = "some server seed";
140/// let nonce = 1;
141/// let result = fair_baccarat::simulate(
142///   client_seed,
143///   server_seed,
144///   nonce,
145/// );
146/// // assert_eq!(result, vec!["todo", "todo"]);
147/// ```
148///
149pub fn simulate(client_seed: &str, server_seed: &str, nonce: u64) -> SimulationResult {
150    let mut rng = ProvablyFairRNGFloat::new(ProvablyFairRNG::new(client_seed, server_seed, nonce));
151
152    // keep track of drawn cards
153    let mut steps: Vec<Step> = vec![];
154
155    steps.push(Step(PLAYER, Card::random(&mut rng)));
156    steps.push(Step(PLAYER, Card::random(&mut rng)));
157    steps.push(Step(BANKER, Card::random(&mut rng)));
158    steps.push(Step(BANKER, Card::random(&mut rng)));
159
160    // If either The player or banker or both achieve a total of 8 or 9
161    // at this stage, the coup is finished and the result is announced:
162    // a player win, a banker win, or tie.
163    if sum_cards_banker(&steps) >= 8 || sum_cards_player(&steps) >= 8 {
164        // This is called a "natural win"
165        return SimulationResult::from_steps(steps);
166    }
167
168    // If neither hand has eight or nine, the drawing rules are applied
169    // to determine whether the player should receive a third card.
170
171    // If the player has an initial total of 6 or 7, he stands pat.
172    if sum_cards_player(&steps) > 5 {
173        // If the Player stands pat (or draws no new cards), the Banker draws with
174        // a hand total of 0-5 and stays pat with a hand total of 6 or 7.
175        if sum_cards_banker(&steps) <= 5 {
176            steps.push(Step(BANKER, Card::random(&mut rng)));
177        }
178        return SimulationResult::from_steps(steps);
179    }
180
181    // If the player has an initial total of 0–5, he draws a third card.
182    let player_third_card = Card::random(&mut rng);
183    steps.push(Step(PLAYER, player_third_card));
184
185    fn banker_should_draw_third_card(banker_total: u32, player_third_card: Card) -> bool {
186        let rank = player_third_card.to_baccarat_value();
187
188        match banker_total {
189            0 | 1 | 2 => true,
190            3 => rank != 8,
191            4 => match rank {
192                2 | 3 | 4 | 5 | 6 | 7 => true,
193                _ => false,
194            },
195            5 => match rank {
196                4 | 5 | 6 | 7 => true,
197                _ => false,
198            },
199            6 => match rank {
200                6 | 7 => true,
201                _ => false,
202            },
203            7 => false,
204            _ => {
205                panic!(
206                    "got an impossible value \"{}\" for banker total (>7), something with library!",
207                    banker_total
208                );
209            }
210        }
211    }
212
213    if banker_should_draw_third_card(sum_cards_banker(&steps), player_third_card) {
214        steps.push(Step(BANKER, Card::random(&mut rng)));
215    }
216
217    SimulationResult::from_steps(steps)
218}
219
220#[cfg(test)]
221mod test {
222    use super::*;
223
224    fn pretty_print_steps(steps: &Vec<Step>) -> Vec<String> {
225        steps
226            .iter()
227            .map(|Step(recipient, card)| format!("{}: {}", recipient, card))
228            .collect::<Vec<String>>()
229    }
230
231    #[test]
232    fn simulate_five_cards_drawn() {
233        let client_seed = "some client seed";
234        let server_seed = "some server seed";
235        let nonce = 2;
236        let result = simulate(client_seed, server_seed, nonce);
237        // println!("{:?}", result);
238        assert_eq!(result.outcome, Outcome::Banker);
239
240        assert_eq!(
241            pretty_print_steps(&result.steps),
242            vec![
243                "Player: ♥Q",
244                "Player: ♣Q",
245                "Banker: ♥4",
246                "Banker: ♥3",
247                "Player: ♣10"
248            ]
249        );
250    }
251
252    #[test]
253    fn simulate_four_cards_drawn() {
254        let client_seed = "some client seed";
255        let server_seed = "some server seed";
256        let nonce = 1;
257        let result = simulate(client_seed, server_seed, nonce);
258        // println!("{:?}", result);
259        assert_eq!(result.totals.player, 9);
260        assert_eq!(result.totals.banker, 9);
261        assert_eq!(result.outcome, Outcome::Tie);
262
263        assert_eq!(
264            pretty_print_steps(&result.steps),
265            vec!["Player: ♠9", "Player: ♠Q", "Banker: ♦4", "Banker: ♠5"]
266        );
267    }
268}