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#[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 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 }, 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 }
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#[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 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 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 loop {
464 let front = upd_players.remove(0);
465 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 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 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}