use core::fmt;
use std::{
io::{stdout, Write},
thread::sleep,
time::Duration,
};
use anyhow::{ensure, Result};
use colored::*;
use crossterm::{cursor, terminal, QueueableCommand};
use inquire::{Select, Text};
use rand::{rng, Rng};
use crate::{money::Money, Casino};
pub fn play_craps() -> Result<()> {
let mut casino = Casino::from_filesystem()?;
let pass_bet = prompt_for_line_bets(&casino.bankroll);
let point = animate_roll();
if pass_bet.is_win(point) {
println!("A {}! Your {} bet wins!", point, pass_bet.kind);
let payout = pass_bet.payout(point);
casino.add_bankroll(payout);
println!("You receive {}. You now have {}", payout, casino.bankroll);
casino.save();
return Ok(());
}
if pass_bet.is_lose(point) {
println!("A {}! Your {} bet loses!", point, pass_bet.kind);
casino.subtract_bankroll(pass_bet.amount)?;
println!(
"You lose {}. You now have {}",
pass_bet.amount, casino.bankroll
);
casino.save();
return Ok(());
}
let mut bets = vec![];
loop {
let loop_options = vec!["Roll the dice again", "Place another bet"];
let loop_selection = Select::new("What do you do?", loop_options)
.prompt()
.unwrap();
match loop_selection {
"Place another bet" => {
let bet_kind_options = vec![BetKind::Come, BetKind::DontCome, BetKind::Field];
let come_bet_kind = Select::new(
"What kind of bet would you like to place?",
bet_kind_options,
)
.prompt()
.unwrap();
loop {
let bet_result = Text::new("How much will you bet?").prompt();
match bet_result {
Ok(bet_text) => {
let bet = bet_text.trim().parse::<Money>().unwrap();
if bet <= casino.bankroll {
let bet = Bet::new(come_bet_kind, bet);
bets.push(bet);
break;
} else {
println!("You can't bet that amount, try again.");
}
}
Err(_) => panic!("Error getting your answer."),
}
}
}
"Roll the dice again" => {}
_ => panic!("Unknown selection {loop_selection}"),
}
let roll = animate_roll();
let (wins, rest): (Vec<Bet>, Vec<Bet>) = bets.into_iter().partition(|b| b.is_win(roll));
let (losses, rest): (Vec<Bet>, Vec<Bet>) = rest.into_iter().partition(|b| b.is_lose(roll));
let (expired, rest): (Vec<Bet>, Vec<Bet>) = rest
.into_iter()
.partition(|b| b.duration() == BetDuration::SingleRoll);
for bet in wins.iter() {
println!("A {}! Your {} bet wins!", roll, bet.kind);
let payout = bet.payout(roll);
casino.add_bankroll(payout);
println!("You receive {}. You now have {}", payout, casino.bankroll);
}
for bet in losses.iter() {
println!("A {}! Your {} bet loses!", roll, bet.kind);
casino.subtract_bankroll(bet.amount)?;
println!("You lose {}. You now have {}", bet.amount, casino.bankroll);
}
for bet in expired.iter() {
println!("Your {} bet expires!", bet.kind);
casino.subtract_bankroll(bet.amount)?;
println!("You lose {}. You now have {}", bet.amount, casino.bankroll);
}
bets = rest;
if roll == point {
println!("A {roll}! The round is over!");
if !bets.is_empty() {
println!("Remaining bets lose:");
let mut lost_amount = Money::ZERO;
for bet in bets.iter() {
println!("- Your {} bet for {} loses.", bet.kind, bet.amount);
lost_amount += bet.amount;
}
if lost_amount > Money::ZERO {
casino.bankroll -= lost_amount;
println!("You lose {}. You now have {}", lost_amount, casino.bankroll);
casino.save();
}
}
break;
}
}
casino.check_for_mister_green();
casino.save();
Ok(())
}
#[derive(Clone, Debug, Eq, PartialEq)]
enum BetDuration {
MultiRoll,
SingleRoll,
}
enum BetKind {
PassLine,
DontPass,
Come,
DontCome,
Field,
}
impl fmt::Display for BetKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::PassLine => write!(f, "Pass Line"),
Self::DontPass => write!(f, "Don't Pass"),
Self::Come => write!(f, "Come"),
Self::DontCome => write!(f, "Don't Come"),
Self::Field => write!(f, "Field"),
}
}
}
struct Bet {
pub kind: BetKind,
pub amount: Money,
}
impl Bet {
pub fn new(kind: BetKind, amount: Money) -> Self {
Self { kind, amount }
}
pub fn is_win(&self, roll: u8) -> bool {
match self.kind {
BetKind::PassLine => [7, 11].contains(&roll),
BetKind::DontPass => [2, 3, 12].contains(&roll),
BetKind::Come => [7, 11].contains(&roll),
BetKind::DontCome => [2, 3, 12].contains(&roll),
BetKind::Field => [2, 3, 4, 9, 10, 11, 12].contains(&roll),
}
}
pub fn is_lose(&self, roll: u8) -> bool {
match self.kind {
BetKind::PassLine => [2, 3, 12].contains(&roll),
BetKind::DontPass => [7, 11].contains(&roll),
BetKind::Come => [2, 3, 12].contains(&roll),
BetKind::DontCome => [7, 11].contains(&roll),
BetKind::Field => [5, 6, 7, 8].contains(&roll),
}
}
pub fn payout(&self, roll: u8) -> Money {
match self.kind {
BetKind::PassLine => self.amount,
BetKind::DontPass => self.amount,
BetKind::Come => self.amount,
BetKind::DontCome => self.amount,
BetKind::Field => match roll {
2 | 12 => self.amount * 2i64,
_ => self.amount,
},
}
}
pub fn duration(&self) -> BetDuration {
match self.kind {
BetKind::PassLine => BetDuration::MultiRoll,
BetKind::DontPass => BetDuration::MultiRoll,
BetKind::Come => BetDuration::MultiRoll,
BetKind::DontCome => BetDuration::MultiRoll,
BetKind::Field => BetDuration::SingleRoll,
}
}
}
fn prompt_for_line_bets(max: &Money) -> Bet {
let bet_kind_options = vec![BetKind::PassLine, BetKind::DontPass];
let pass_bet_kind = Select::new(
"What kind of bet would you like to place?",
bet_kind_options,
)
.prompt()
.unwrap();
loop {
let bet_result = Text::new("How much will you bet?").prompt();
match bet_result {
Ok(bet_text) => {
let bet = bet_text.trim().parse::<Money>().unwrap();
if bet <= *max {
return Bet::new(pass_bet_kind, bet);
} else {
println!("You can't bet that amount, try again.");
}
}
Err(_) => panic!("Error getting your answer."),
}
}
}
pub struct Die(u8);
impl Die {
pub fn new(num: u8) -> Result<Self> {
ensure!((1..=6).contains(&num), "Number outside of die range");
Ok(Self(num))
}
pub fn roll() -> Self {
let mut rng = rng();
let num = rng.random_range(1..=6);
Self(num)
}
}
impl Die {
pub fn as_u8(&self) -> u8 {
self.0
}
}
impl fmt::Display for Die {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
1 => write!(f, "⚀"),
2 => write!(f, "⚁"),
3 => write!(f, "⚂"),
4 => write!(f, "⚃"),
5 => write!(f, "⚄"),
6 => write!(f, "⚅"),
_ => panic!("Bad die number"),
}
}
}
fn animate_roll() -> u8 {
println!("{}", "* You throw the dice.".dimmed());
println!();
let mut d1 = Die::roll();
let mut d2 = Die::roll();
let mut rng = rng();
let mut position = 0.0;
let mut velocity = rng.random_range(20.0..40.0);
let accel = rng.random_range(-30.0..-15.0);
let mut stdout = stdout();
while velocity > 0.0 {
if position >= 1.0 {
d1 = Die::roll();
d2 = Die::roll();
position -= 1.0;
}
stdout.queue(cursor::SavePosition).unwrap();
stdout.write_all(format!("\t{d1}{d2}").as_bytes()).unwrap();
stdout.queue(cursor::RestorePosition).unwrap();
stdout.flush().unwrap();
sleep(Duration::from_millis(16));
velocity += accel * (16.0 / 1000.0);
position += velocity * (16.0 / 1000.0);
stdout.queue(cursor::RestorePosition).unwrap();
stdout
.queue(terminal::Clear(terminal::ClearType::FromCursorDown))
.unwrap();
}
stdout.write_all(format!("\t{d1}{d2}").as_bytes()).unwrap();
sleep(Duration::from_millis(1_000));
println!();
println!();
d1.as_u8() + d2.as_u8()
}