use std::{io::Write, rc::Rc};
use either::Either;
use rand::random_range;
pub enum WagerData {
PlayerWager,
BankWager,
}
pub trait Wager {
fn wager(&self, data: WagerData) -> u32;
}
#[derive(Eq, Hash, PartialEq, Clone, Debug)]
pub struct Player {
name: String,
}
impl Player {
#[must_use]
pub const fn new(name: String) -> Self {
Self { name }
}
}
#[derive(Clone)]
pub struct PlayerData {
points: u32,
wager: Rc<dyn Wager>,
}
impl PlayerData {
pub const fn new(wager: Rc<dyn Wager>, points: u32) -> Self {
Self { points, wager }
}
}
impl std::fmt::Debug for PlayerData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PlayerData")
.field("points", &self.points)
.field(
"wager",
&"Wager is `Rc<dyn Wager>` and therefore not `Debug`able",
)
.finish()
}
}
pub type Bet = u32;
#[must_use]
pub const fn fade_bet(bank_bet: u32, player_bet: u32) -> (u32, u32) {
let leftovers = bank_bet.saturating_sub(player_bet);
(
leftovers,
if leftovers == 0 { bank_bet } else { player_bet },
)
}
#[must_use]
pub fn fade_bets(players: Vec<(Player, PlayerData)>) -> Vec<(Player, PlayerData, Bet)> {
let (bank, players) = {
let mut players = players;
(players.remove(0), players)
};
let original_bank_wager = bank.1.wager.wager(WagerData::BankWager);
let mut bank_wager = original_bank_wager;
let players: Vec<(Player, PlayerData, Bet)> = players
.into_iter()
.map(|(player, player_data)| {
let faded_wager = fade_bet(bank_wager, player_data.wager.wager(WagerData::PlayerWager));
bank_wager = faded_wager.0;
(player, player_data, faded_wager.1)
})
.collect();
let mut new_players = players;
let (bank, bank_data) = bank;
new_players.insert(0, (bank, bank_data, original_bank_wager));
new_players
}
#[must_use]
pub fn roll_dice() -> (u8, u8, u8) {
(
random_range(1..6 + 1),
random_range(1..6 + 1),
random_range(1..6 + 1),
)
}
#[derive(PartialEq, Debug, Eq, Clone)]
pub enum Rolls {
InstantWin { multiplier: u8 },
Point { point: u8, other: u8 }, Loss,
}
#[derive(Debug)]
pub struct Reroll;
impl TryFrom<(u8, u8, u8)> for Rolls {
type Error = Reroll;
fn try_from(value: (u8, u8, u8)) -> Result<Self, Self::Error> {
match (value.0 == value.1, value.1 == value.2) {
(true, true) => Ok(Self::InstantWin {
multiplier: if value.0 == 1 { 5 } else { 3 },
}),
(true, false) => {
if value.2 == 6 {
Ok(Self::InstantWin { multiplier: 1 })
} else if value.2 == 1 {
Ok(Self::Loss)
} else {
Ok(Self::Point {
point: value.2,
other: value.0,
})
}
}
(false, true) => {
if value.0 == 6 {
Ok(Self::InstantWin { multiplier: 1 })
} else if value.0 == 1 {
Ok(Self::Loss)
} else {
Ok(Self::Point {
point: value.0,
other: value.1,
})
}
}
(false, false) => {
if value.0 == value.2 {
if value.1 == 6 {
Ok(Self::InstantWin { multiplier: 1 })
} else if value.1 == 1 {
Ok(Self::Loss)
} else {
Ok(Self::Point {
point: value.1,
other: value.0,
})
}
} else {
let p = [value.0, value.1, value.2];
if p.contains(&4) && p.contains(&5) && p.contains(&6) {
Ok(Self::InstantWin { multiplier: 2 })
} else if p.contains(&1) && p.contains(&2) && p.contains(&3) {
Ok(Self::Loss)
} else {
Err(Reroll)
}
}
}
}
}
}
#[must_use]
pub fn always_roll() -> ((u8, u8, u8), Rolls) {
let mut raw = roll_dice();
loop {
if let Ok(res) = Rolls::try_from(raw) {
return (raw, res);
}
raw = roll_dice();
}
}
#[must_use]
pub fn everyone_roll(
players: Vec<(Player, PlayerData, Bet)>,
) -> Vec<(Player, PlayerData, Bet, (u8, u8, u8), Rolls)> {
let mut rolls = vec![];
for (player, player_data, wager) in players {
let (roll_raw, roll) = always_roll();
rolls.push((player, player_data, wager, roll_raw, roll));
}
rolls
}
#[derive(Clone)]
pub enum WinOrLose {
InstWin(u8),
Win,
Tie,
Lose(u8),
}
#[must_use]
pub fn wins_and_losses(
players: Vec<(Player, PlayerData, Bet, (u8, u8, u8), Rolls)>,
) -> Vec<(
Player,
PlayerData,
Bet,
(u8, u8, u8),
Rolls,
Option<WinOrLose>,
)> {
let (bank, players) = {
let mut players = players;
(players.remove(0), players)
};
match bank.4 {
Rolls::InstantWin { multiplier } => vec![players
.into_iter()
.map(|(player, pd, b, rr, r)| (player, pd, b, rr, r, Some(WinOrLose::Lose(multiplier))))
.collect::<Vec<(
Player,
PlayerData,
Bet,
(u8, u8, u8),
Rolls,
Option<WinOrLose>,
)>>()]
.into_iter()
.fold(std::iter::empty().collect(), |_, x| {
let mut unpushed = x;
let (bpl, bpd, bbet, brr, br) = &bank;
unpushed.insert(0, (bpl.clone(), bpd.clone(), *bbet, *brr, br.clone(), None));
unpushed
}),
Rolls::Point {
point: bank_point,
other: _,
} => {
let mut win_and_losses = Vec::new();
for (player, pd, b, rr, player_roll) in players {
match player_roll {
Rolls::InstantWin { multiplier } => {
win_and_losses.push((
player,
pd,
b,
rr,
player_roll,
Some(WinOrLose::InstWin(multiplier)),
));
}
Rolls::Point {
point: player_point,
other: _,
} => match player_point.cmp(&bank_point) {
std::cmp::Ordering::Less => {
win_and_losses.push((
player,
pd,
b,
rr,
player_roll,
Some(WinOrLose::Lose(1)),
));
}
std::cmp::Ordering::Equal => {
win_and_losses.push((
player,
pd,
b,
rr,
player_roll,
Some(WinOrLose::Tie),
));
}
std::cmp::Ordering::Greater => {
win_and_losses.push((
player,
pd,
b,
rr,
player_roll,
Some(WinOrLose::Win),
));
}
},
Rolls::Loss => {
win_and_losses.push((
player,
pd,
b,
rr,
player_roll,
Some(WinOrLose::Lose(1)),
));
}
}
}
let (pl, pd, bet, rr, rolls) = bank;
let new_bank = (pl, pd, bet, rr, rolls, None);
win_and_losses.insert(0, new_bank);
win_and_losses
}
Rolls::Loss => {
let mut handled: Vec<(
Player,
PlayerData,
u32,
(u8, u8, u8),
Rolls,
Option<WinOrLose>,
)> = players
.into_iter()
.map(|(player, pd, b, rr, r)| (player, pd, b, rr, r, Some(WinOrLose::InstWin(1))))
.collect();
let (pl, pd, bet, rr, rolls) = bank;
let new_bank = (pl, pd, bet, rr, rolls, Some(WinOrLose::Lose(1)));
handled.insert(0, new_bank);
handled
}
}
}
impl WinOrLose {
#[must_use]
pub const fn is_win(&self) -> bool {
match self {
Self::InstWin(_) | Self::Win | Self::Tie => true,
Self::Lose(_) => false,
}
}
}
#[must_use]
pub fn wins_and_losses_no_bank(
players: Vec<(
Player,
PlayerData,
u32,
(u8, u8, u8),
Rolls,
Option<WinOrLose>,
)>,
) -> (
(
Player,
PlayerData,
u32,
(u8, u8, u8),
Rolls,
Option<WinOrLose>,
),
Vec<(Player, PlayerData, u32, (u8, u8, u8), Rolls, WinOrLose)>,
) {
let mut the_players = players;
let bank = the_players.remove(0);
let some_players = the_players
.into_iter()
.filter_map(|(pl, pd, bet, rr, roll, won_or_lost)| {
won_or_lost.map(|wol| (pl, pd, bet, rr, roll, wol))
})
.collect();
(bank, some_players)
}
#[must_use]
pub fn payout(
players: Vec<(
Player,
PlayerData,
Bet,
(u8, u8, u8),
Rolls,
Option<WinOrLose>,
)>,
) -> Vec<(Player, PlayerData, Bet, (u8, u8, u8), Rolls)> {
let (mut bank, the_players) = wins_and_losses_no_bank(players);
let losers_then_winners = {
let (mut losers, mut winners): (
Vec<(Player, PlayerData, u32, (u8, u8, u8), Rolls, WinOrLose)>,
_,
) = the_players
.into_iter()
.partition(|player| player.5.is_win());
losers.append(&mut winners);
losers
};
let mut upd_players = vec![];
let mut winners = 0usize;
let mut instant_won = None;
for (player, pd, bet, rr, roll, win_or_lose) in losers_then_winners {
match win_or_lose {
WinOrLose::InstWin(mult) => {
let (bank_leftover, player_left_over) =
fade_bet(bank.1.points, bet.saturating_mul(mult.into()));
bank.1.points = bank_leftover;
let new_player = (
player.clone(),
PlayerData {
points: pd.points.saturating_add(player_left_over),
wager: pd.wager,
},
bet,
rr,
roll,
);
instant_won = Some(new_player.clone());
upd_players.push(new_player);
}
WinOrLose::Win => {
winners = winners.saturating_add(1);
let (bank_leftover, player_leftover) = fade_bet(bank.1.points, bet);
bank.1.points = bank_leftover;
upd_players.push((
player,
PlayerData {
points: pd.points.saturating_add(player_leftover),
wager: pd.wager,
},
bet,
rr,
roll,
));
}
WinOrLose::Tie => upd_players.push((player, pd, bet, rr, roll)),
WinOrLose::Lose(mult) => {
let (player_leftover, player_loss) =
fade_bet(pd.points, bet.saturating_mul(mult.into()));
bank.1.points = bank.1.points.saturating_add(player_loss);
upd_players.push((
player,
PlayerData {
points: player_leftover,
wager: pd.wager,
},
bet,
rr,
roll,
));
}
}
}
let new_bank = {
let (bank_player, bank_player_data, bbet, brr, brolls, ..) = bank;
(bank_player, bank_player_data, bbet, brr, brolls)
};
if let Some(next_bank) = instant_won {
upd_players.push(new_bank);
loop {
let front = upd_players.remove(0);
if front.0 == next_bank.0 {
upd_players.insert(0, front);
break;
}
upd_players.push(front);
}
} else if winners == upd_players.len().saturating_add(1) || bank.5.is_some() {
upd_players.push(new_bank);
} else {
upd_players.insert(0, new_bank);
}
upd_players
}
#[must_use]
pub fn winner(
players: Vec<(Player, PlayerData, Bet, (u8, u8, u8), Rolls)>,
) -> Either<
(Player, PlayerData, Bet, (u8, u8, u8), Rolls),
Vec<(Player, PlayerData, Bet, (u8, u8, u8), Rolls)>,
> {
match players {
mut lone if lone.len() == 1 => Either::Left(lone.remove(0)),
multiple => either::Right(multiple),
}
}
pub type History = Vec<Vec<(Player, PlayerData, Bet, (u8, u8, u8), Rolls)>>;
#[must_use]
pub fn play_until_winner(
mut players: Vec<(Player, PlayerData)>,
) -> ((Player, PlayerData, Bet, (u8, u8, u8), Rolls), History) {
let mut history = vec![];
loop {
let payed = payout(wins_and_losses(everyone_roll(fade_bets(players.clone()))));
history.push(payed.clone());
let maybe_winner = winner(payed);
let mut the_winner: Option<(Player, PlayerData, Bet, (u8, u8, u8), Rolls)> = None;
match maybe_winner {
Either::Left(winner) => the_winner = Some(winner),
Either::Right(not_winners) => {
players = not_winners
.into_iter()
.map(|(player, pd, ..)| (player, pd))
.collect();
players.retain(|player| player.1.points != 0);
}
}
if let Some(winner) = the_winner {
return (winner, history);
}
}
}
pub struct WagerN(
pub u32,
);
impl Wager for WagerN {
fn wager(&self, _: WagerData) -> u32 {
self.0
}
}
pub struct WagerConsole;
impl Wager for WagerConsole {
fn wager(&self, _data: WagerData) -> u32 {
loop {
let mut buffer = String::new();
print!("Wager Here => ");
let _ = std::io::stdout().flush();
if std::io::stdin().read_line(&mut buffer).is_ok() {
if let Ok(i) = buffer.trim().parse::<u32>() {
return i;
}
println!("Failed to parse");
}
}
}
}
impl<T: Fn(WagerData) -> u32> Wager for T {
fn wager(&self, data: WagerData) -> u32 {
self(data)
}
}
#[must_use]
pub const fn add(left: u64, right: u64) -> u64 {
left.saturating_add(right)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}