//! European roulette rules shared by the server and client. use serde::{Deserialize, Serialize}; /// European wheel clockwise from zero. pub const WHEEL: [u8; 37] = [ 0, 32, 15, 19, 4, 21, 2, 25, 17, 34, 6, 27, 13, 36, 11, 30, 8, 23, 10, 5, 24, 16, 33, 1, 20, 14, 31, 9, 22, 18, 29, 7, 28, 12, 35, 3, 26, ]; pub const RED_NUMBERS: [u8; 18] = [ 1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, 36, ]; /// Total return on a winning straight (35:1 plus the stake). pub const STRAIGHT_RETURN: u64 = 36; /// Total return on a winning outside bet (1:1 plus the stake). pub const EVEN_MONEY_RETURN: u64 = 2; pub fn is_red(number: u8) -> bool { RED_NUMBERS.contains(&number) } pub fn is_black(number: u8) -> bool { number != 0 && !is_red(number) } pub fn color(number: u8) -> &'static str { if number == 0 { "green" } else if is_red(number) { "red" } else { "black" } } pub fn is_even(number: u8) -> bool { number != 0 && number % 2 == 0 } pub fn is_odd(number: u8) -> bool { number != 0 && number % 2 == 1 } pub fn rtp() -> f64 { 36.0 / 37.0 } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct NumberBet { pub number: u8, pub amount: u64, } #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct Bets { #[serde(default)] pub red: u64, #[serde(default)] pub black: u64, #[serde(default)] pub even: u64, #[serde(default)] pub odd: u64, #[serde(default)] pub numbers: Vec, } impl Bets { pub fn total(&self) -> u64 { let mut total = self .red .saturating_add(self.black) .saturating_add(self.even) .saturating_add(self.odd); for bet in &self.numbers { total = total.saturating_add(bet.amount); } total } pub fn is_empty(&self) -> bool { self.total() == 0 } /// Total returned for a winning number, including returned stakes. pub fn winnings(&self, number: u8) -> u64 { let mut winnings = 0u64; if number != 0 { if is_red(number) { winnings = winnings.saturating_add(self.red.saturating_mul(EVEN_MONEY_RETURN)); } else { winnings = winnings.saturating_add(self.black.saturating_mul(EVEN_MONEY_RETURN)); } if is_even(number) { winnings = winnings.saturating_add(self.even.saturating_mul(EVEN_MONEY_RETURN)); } else { winnings = winnings.saturating_add(self.odd.saturating_mul(EVEN_MONEY_RETURN)); } } for bet in &self.numbers { if bet.number == number { winnings = winnings .saturating_add(bet.amount.saturating_mul(STRAIGHT_RETURN)); } } winnings } pub fn validate(&self, balance: u64) -> Result<(), String> { if self.is_empty() { return Err("Place at least one bet".to_string()); } if self.total() > balance { return Err("Insufficient balance".to_string()); } for bet in &self.numbers { if bet.number > 36 { return Err(format!("Invalid number: {}", bet.number)); } if bet.amount == 0 { return Err("A bet amount must be positive".to_string()); } } Ok(()) } }