Skip to main content

ceelo_core/
lib.rs

1use std::{io::Write, rc::Rc};
2
3use either::Either;
4use rand::random_range;
5
6pub enum WagerData {
7    PlayerWager,
8    BankWager,
9}
10pub trait Wager {
11    fn wager(&self, data: WagerData) -> u32;
12}
13
14#[derive(Eq, Hash, PartialEq, Clone, Debug)]
15pub struct Player {
16    name: String,
17}
18
19impl Player {
20    #[must_use]
21    pub const fn new(name: String) -> Self {
22        Self { name }
23    }
24}
25
26#[derive(Clone)]
27pub struct PlayerData {
28    points: u32,
29    wager: Rc<dyn Wager>,
30}
31
32impl PlayerData {
33    pub const fn new(wager: Rc<dyn Wager>, points: u32) -> Self {
34        Self { points, wager }
35    }
36}
37
38impl std::fmt::Debug for PlayerData {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        f.debug_struct("PlayerData")
41            .field("points", &self.points)
42            .field(
43                "wager",
44                &"Wager is `Rc<dyn Wager>` and therefore not `Debug`able",
45            )
46            .finish()
47    }
48}
49
50pub type Bet = u32;
51
52// return type is (faded_bank_bet, player_left_over_bet)
53#[must_use]
54pub const fn fade_bet(bank_bet: u32, player_bet: u32) -> (u32, u32) {
55    let leftovers = bank_bet.saturating_sub(player_bet);
56
57    (
58        leftovers,
59        if leftovers == 0 { bank_bet } else { player_bet },
60    )
61}
62
63#[must_use]
64pub fn fade_bets(players: Vec<(Player, PlayerData)>) -> Vec<(Player, PlayerData, Bet)> {
65    // call bank and other players' wager function progressively "updating" wagers
66    let (bank, players) = {
67        let mut players = players;
68        (players.remove(0), players)
69    };
70    let original_bank_wager = bank.1.wager.wager(WagerData::BankWager);
71    let mut bank_wager = original_bank_wager;
72    let players: Vec<(Player, PlayerData, Bet)> = players
73        .into_iter()
74        .map(|(player, player_data)| {
75            let faded_wager = fade_bet(bank_wager, player_data.wager.wager(WagerData::PlayerWager));
76            bank_wager = faded_wager.0;
77            (player, player_data, faded_wager.1)
78        })
79        .collect();
80    let mut new_players = players;
81    let (bank, bank_data) = bank;
82    new_players.insert(0, (bank, bank_data, original_bank_wager));
83    new_players
84}
85
86#[must_use]
87pub fn roll_dice() -> (u8, u8, u8) {
88    (
89        random_range(1..6 + 1),
90        random_range(1..6 + 1),
91        random_range(1..6 + 1),
92    )
93}
94
95#[derive(PartialEq, Debug, Eq, Clone)]
96pub enum Rolls {
97    InstantWin { multiplier: u8 },
98    Point { point: u8, other: u8 }, // point is never 6 because that's instant win tho
99    Loss,
100}
101
102impl std::fmt::Display for Rolls {
103    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104        match self {
105            Self::InstantWin { multiplier } => write!(f, "Instant Win: {multiplier}"),
106            Self::Point { point, other } => write!(f, "Set Point of {point}, other: {other}"),
107            Self::Loss => write!(f, "Loss"),
108        }
109        // write!(f, "{}", )
110    }
111}
112
113#[derive(Debug)]
114pub struct Reroll;
115
116impl TryFrom<(u8, u8, u8)> for Rolls {
117    type Error = Reroll;
118
119    fn try_from(value: (u8, u8, u8)) -> Result<Self, Self::Error> {
120        match (value.0 == value.1, value.1 == value.2) {
121            (true, true) => Ok(Self::InstantWin {
122                multiplier: if value.0 == 1 { 5 } else { 3 },
123            }),
124            (true, false) => {
125                if value.2 == 6 {
126                    Ok(Self::InstantWin { multiplier: 1 })
127                } else if value.2 == 1 {
128                    Ok(Self::Loss)
129                } else {
130                    Ok(Self::Point {
131                        point: value.2,
132                        other: value.0,
133                    })
134                }
135            }
136            (false, true) => {
137                if value.0 == 6 {
138                    Ok(Self::InstantWin { multiplier: 1 })
139                } else if value.0 == 1 {
140                    Ok(Self::Loss)
141                } else {
142                    Ok(Self::Point {
143                        point: value.0,
144                        other: value.1,
145                    })
146                }
147            }
148            (false, false) => {
149                if value.0 == value.2 {
150                    if value.1 == 6 {
151                        Ok(Self::InstantWin { multiplier: 1 })
152                    } else if value.1 == 1 {
153                        Ok(Self::Loss)
154                    } else {
155                        Ok(Self::Point {
156                            point: value.1,
157                            other: value.0,
158                        })
159                    }
160                } else {
161                    let p = [value.0, value.1, value.2];
162                    if p.contains(&4) && p.contains(&5) && p.contains(&6) {
163                        Ok(Self::InstantWin { multiplier: 2 })
164                    } else if p.contains(&1) && p.contains(&2) && p.contains(&3) {
165                        Ok(Self::Loss)
166                    } else {
167                        Err(Reroll)
168                    }
169                }
170            }
171        }
172    }
173}
174
175#[must_use]
176pub fn always_roll() -> ((u8, u8, u8), Rolls) {
177    let mut raw = roll_dice();
178    loop {
179        if let Ok(res) = Rolls::try_from(raw) {
180            return (raw, res);
181        }
182        raw = roll_dice();
183    }
184}
185
186#[must_use]
187pub fn everyone_roll(
188    players: Vec<(Player, PlayerData, Bet)>,
189) -> Vec<(Player, PlayerData, Bet, (u8, u8, u8), Rolls)> {
190    let mut rolls = vec![];
191    for (player, player_data, wager) in players {
192        let (roll_raw, roll) = always_roll();
193        rolls.push((player, player_data, wager, roll_raw, roll));
194    }
195    rolls
196}
197
198#[derive(Clone)]
199pub enum WinOrLose {
200    InstWin(u8),
201    Win,
202    Tie,
203    Lose(u8),
204}
205
206// it's sufficent to track the players besides the bank
207#[must_use]
208pub fn wins_and_losses(
209    players: Vec<(Player, PlayerData, Bet, (u8, u8, u8), Rolls)>,
210) -> Vec<(
211    Player,
212    PlayerData,
213    Bet,
214    (u8, u8, u8),
215    Rolls,
216    Option<WinOrLose>,
217)> {
218    let (bank, players) = {
219        let mut players = players;
220        (players.remove(0), players)
221    };
222
223    match bank.4 {
224        // bank gets all the money
225        Rolls::InstantWin { multiplier } => vec![players
226            .into_iter()
227            .map(|(player, pd, b, rr, r)| (player, pd, b, rr, r, Some(WinOrLose::Lose(multiplier))))
228            .collect::<Vec<(
229                Player,
230                PlayerData,
231                Bet,
232                (u8, u8, u8),
233                Rolls,
234                Option<WinOrLose>,
235            )>>()]
236        .into_iter()
237        .fold(std::iter::empty().collect(), |_, x| {
238            let mut unpushed = x;
239            let (bpl, bpd, bbet, brr, br) = &bank;
240
241            unpushed.insert(0, (bpl.clone(), bpd.clone(), *bbet, *brr, br.clone(), None));
242            unpushed
243        }),
244        Rolls::Point {
245            point: bank_point,
246            other: _,
247        } => {
248            let mut win_and_losses = Vec::new();
249            for (player, pd, b, rr, player_roll) in players {
250                match player_roll {
251                    Rolls::InstantWin { multiplier } => {
252                        win_and_losses.push((
253                            player,
254                            pd,
255                            b,
256                            rr,
257                            player_roll,
258                            Some(WinOrLose::InstWin(multiplier)),
259                        ));
260                    }
261                    Rolls::Point {
262                        point: player_point,
263                        other: _,
264                    } => match player_point.cmp(&bank_point) {
265                        std::cmp::Ordering::Less => {
266                            win_and_losses.push((
267                                player,
268                                pd,
269                                b,
270                                rr,
271                                player_roll,
272                                Some(WinOrLose::Lose(1)),
273                            ));
274                        }
275                        std::cmp::Ordering::Equal => {
276                            win_and_losses.push((
277                                player,
278                                pd,
279                                b,
280                                rr,
281                                player_roll,
282                                Some(WinOrLose::Tie),
283                            ));
284                        }
285                        std::cmp::Ordering::Greater => {
286                            win_and_losses.push((
287                                player,
288                                pd,
289                                b,
290                                rr,
291                                player_roll,
292                                Some(WinOrLose::Win),
293                            ));
294                        }
295                    },
296                    Rolls::Loss => {
297                        win_and_losses.push((
298                            player,
299                            pd,
300                            b,
301                            rr,
302                            player_roll,
303                            Some(WinOrLose::Lose(1)),
304                        ));
305                    }
306                }
307            }
308            let (pl, pd, bet, rr, rolls) = bank;
309            let new_bank = (pl, pd, bet, rr, rolls, None);
310            win_and_losses.insert(0, new_bank);
311            win_and_losses
312        }
313        // bank pays out
314        Rolls::Loss => {
315            let mut handled: Vec<(
316                Player,
317                PlayerData,
318                u32,
319                (u8, u8, u8),
320                Rolls,
321                Option<WinOrLose>,
322            )> = players
323                .into_iter()
324                .map(|(player, pd, b, rr, r)| (player, pd, b, rr, r, Some(WinOrLose::InstWin(1))))
325                .collect();
326            let (pl, pd, bet, rr, rolls) = bank;
327            let new_bank = (pl, pd, bet, rr, rolls, Some(WinOrLose::Lose(1)));
328            handled.insert(0, new_bank);
329            handled
330        }
331    }
332}
333
334impl WinOrLose {
335    #[must_use]
336    pub const fn is_win(&self) -> bool {
337        match self {
338            Self::InstWin(_) | Self::Win | Self::Tie => true,
339            Self::Lose(_) => false,
340        }
341    }
342}
343
344#[must_use]
345pub fn wins_and_losses_no_bank(
346    players: Vec<(
347        Player,
348        PlayerData,
349        u32,
350        (u8, u8, u8),
351        Rolls,
352        Option<WinOrLose>,
353    )>,
354) -> (
355    (
356        Player,
357        PlayerData,
358        u32,
359        (u8, u8, u8),
360        Rolls,
361        Option<WinOrLose>,
362    ),
363    Vec<(Player, PlayerData, u32, (u8, u8, u8), Rolls, WinOrLose)>,
364) {
365    let mut the_players = players;
366    let bank = the_players.remove(0);
367    let some_players = the_players
368        .into_iter()
369        .filter_map(|(pl, pd, bet, rr, roll, won_or_lost)| {
370            won_or_lost.map(|wol| (pl, pd, bet, rr, roll, wol))
371        })
372        .collect();
373    (bank, some_players)
374}
375
376#[must_use]
377pub fn payout(
378    players: Vec<(
379        Player,
380        PlayerData,
381        Bet,
382        (u8, u8, u8),
383        Rolls,
384        Option<WinOrLose>,
385    )>,
386) -> Vec<(Player, PlayerData, Bet, (u8, u8, u8), Rolls)> {
387    let (mut bank, the_players) = wins_and_losses_no_bank(players);
388    let losers_then_winners = {
389        let (mut losers, mut winners): (
390            Vec<(Player, PlayerData, u32, (u8, u8, u8), Rolls, WinOrLose)>,
391            _,
392        ) = the_players
393            .into_iter()
394            .partition(|player| player.5.is_win());
395
396        losers.append(&mut winners);
397        losers
398    };
399    let mut upd_players = vec![];
400    let mut winners = 0usize;
401    let mut instant_won = None;
402    for (player, pd, bet, rr, roll, win_or_lose) in losers_then_winners {
403        match win_or_lose {
404            WinOrLose::InstWin(mult) => {
405                let (bank_leftover, player_left_over) =
406                    fade_bet(bank.1.points, bet.saturating_mul(mult.into()));
407                bank.1.points = bank_leftover;
408                let new_player = (
409                    player.clone(),
410                    PlayerData {
411                        points: pd.points.saturating_add(player_left_over),
412                        wager: pd.wager,
413                    },
414                    bet,
415                    rr,
416                    roll,
417                );
418                instant_won = Some(new_player.clone());
419                upd_players.push(new_player);
420            }
421            WinOrLose::Win => {
422                winners = winners.saturating_add(1);
423                let (bank_leftover, player_leftover) = fade_bet(bank.1.points, bet);
424                bank.1.points = bank_leftover;
425                upd_players.push((
426                    player,
427                    PlayerData {
428                        points: pd.points.saturating_add(player_leftover),
429                        wager: pd.wager,
430                    },
431                    bet,
432                    rr,
433                    roll,
434                ));
435            }
436            WinOrLose::Tie => upd_players.push((player, pd, bet, rr, roll)),
437            WinOrLose::Lose(mult) => {
438                let (player_leftover, player_loss) =
439                    fade_bet(pd.points, bet.saturating_mul(mult.into()));
440                bank.1.points = bank.1.points.saturating_add(player_loss);
441
442                upd_players.push((
443                    player,
444                    PlayerData {
445                        points: player_leftover,
446                        wager: pd.wager,
447                    },
448                    bet,
449                    rr,
450                    roll,
451                ));
452            }
453        }
454    }
455    let new_bank = {
456        let (bank_player, bank_player_data, bbet, brr, brolls, ..) = bank;
457        (bank_player, bank_player_data, bbet, brr, brolls)
458    };
459
460    if let Some(next_bank) = instant_won {
461        upd_players.push(new_bank);
462        // remove from front until front = next_bank if names match
463        loop {
464            let front = upd_players.remove(0);
465            // make player the bank
466            if front.0 == next_bank.0 {
467                upd_players.insert(0, front);
468                break;
469            }
470            upd_players.push(front);
471        }
472    } else if winners == upd_players.len().saturating_add(1) || bank.5.is_some() {
473        upd_players.push(new_bank);
474    } else {
475        upd_players.insert(0, new_bank);
476    }
477    upd_players
478}
479
480#[must_use]
481pub fn winner(
482    players: Vec<(Player, PlayerData, Bet, (u8, u8, u8), Rolls)>,
483) -> Either<
484    (Player, PlayerData, Bet, (u8, u8, u8), Rolls),
485    Vec<(Player, PlayerData, Bet, (u8, u8, u8), Rolls)>,
486> {
487    match players {
488        mut lone if lone.len() == 1 => Either::Left(lone.remove(0)),
489        multiple => either::Right(multiple),
490    }
491}
492
493pub type History = Vec<Vec<(Player, PlayerData, Bet, (u8, u8, u8), Rolls)>>;
494
495#[must_use]
496pub fn play_until_winner(
497    mut players: Vec<(Player, PlayerData)>,
498) -> ((Player, PlayerData, Bet, (u8, u8, u8), Rolls), History) {
499    let mut history = vec![];
500    loop {
501        let payed = payout(wins_and_losses(everyone_roll(fade_bets(players.clone()))));
502        history.push(payed.clone());
503        // for (player, pd, bet, (r1, r2, r3), roll) in payed.clone() {
504        //     println!("<{player}> <{pd}> <{bet}> <{roll}> <({r1}, {r2}, {r3})>");
505        // }
506        let maybe_winner = winner(payed);
507        let mut the_winner: Option<(Player, PlayerData, Bet, (u8, u8, u8), Rolls)> = None;
508        match maybe_winner {
509            Either::Left(winner) => the_winner = Some(winner),
510            Either::Right(not_winners) => {
511                // println!("no winner");
512                players = not_winners
513                    .into_iter()
514                    .map(|(player, pd, ..)| (player, pd))
515                    .collect();
516                players.retain(|player| player.1.points != 0);
517            }
518        }
519        if let Some(winner) = the_winner {
520            return (winner, history);
521        }
522    }
523}
524
525pub struct WagerN(u32);
526impl Wager for WagerN {
527    fn wager(&self, _: WagerData) -> u32 {
528        self.0
529    }
530}
531
532pub struct WagerConsole;
533impl Wager for WagerConsole {
534    fn wager(&self, _data: WagerData) -> u32 {
535        loop {
536            let mut buffer = String::new();
537            print!("Wager Here => ");
538            let _ = std::io::stdout().flush();
539            if std::io::stdin().read_line(&mut buffer).is_ok() {
540                if let Ok(i) = buffer.trim().parse::<u32>() {
541                    return i;
542                }
543                println!("Failed to parse");
544            }
545        }
546    }
547}
548
549impl<T: Fn(WagerData) -> u32> Wager for T {
550    fn wager(&self, data: WagerData) -> u32 {
551        self(data)
552    }
553}
554
555#[must_use]
556pub const fn add(left: u64, right: u64) -> u64 {
557    left.saturating_add(right)
558}
559
560#[cfg(test)]
561mod tests {
562    use super::*;
563
564    #[test]
565    fn it_works() {
566        let result = add(2, 2);
567        assert_eq!(result, 4);
568    }
569}