summaryrefslogtreecommitdiffstats
path: root/src/roulette.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/roulette.rs')
-rw-r--r--src/roulette.rs132
1 files changed, 132 insertions, 0 deletions
diff --git a/src/roulette.rs b/src/roulette.rs
new file mode 100644
index 0000000..b59f92e
--- /dev/null
+++ b/src/roulette.rs
@@ -0,0 +1,132 @@
1//! European roulette rules shared by the server and client.
2
3use serde::{Deserialize, Serialize};
4
5/// European wheel clockwise from zero.
6pub const WHEEL: [u8; 37] = [
7 0, 32, 15, 19, 4, 21, 2, 25, 17, 34, 6, 27, 13, 36, 11, 30, 8, 23, 10, 5,
8 24, 16, 33, 1, 20, 14, 31, 9, 22, 18, 29, 7, 28, 12, 35, 3, 26,
9];
10
11pub const RED_NUMBERS: [u8; 18] = [
12 1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, 36,
13];
14
15/// Total return on a winning straight (35:1 plus the stake).
16pub const STRAIGHT_RETURN: u64 = 36;
17/// Total return on a winning outside bet (1:1 plus the stake).
18pub const EVEN_MONEY_RETURN: u64 = 2;
19
20pub fn is_red(number: u8) -> bool {
21 RED_NUMBERS.contains(&number)
22}
23
24pub fn is_black(number: u8) -> bool {
25 number != 0 && !is_red(number)
26}
27
28pub fn color(number: u8) -> &'static str {
29 if number == 0 {
30 "green"
31 } else if is_red(number) {
32 "red"
33 } else {
34 "black"
35 }
36}
37
38pub fn is_even(number: u8) -> bool {
39 number != 0 && number % 2 == 0
40}
41
42pub fn is_odd(number: u8) -> bool {
43 number != 0 && number % 2 == 1
44}
45
46pub fn rtp() -> f64 {
47 36.0 / 37.0
48}
49
50#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
51pub struct NumberBet {
52 pub number: u8,
53 pub amount: u64,
54}
55
56#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
57pub struct Bets {
58 #[serde(default)]
59 pub red: u64,
60 #[serde(default)]
61 pub black: u64,
62 #[serde(default)]
63 pub even: u64,
64 #[serde(default)]
65 pub odd: u64,
66 #[serde(default)]
67 pub numbers: Vec<NumberBet>,
68}
69
70impl Bets {
71 pub fn total(&self) -> u64 {
72 let mut total = self
73 .red
74 .saturating_add(self.black)
75 .saturating_add(self.even)
76 .saturating_add(self.odd);
77 for bet in &self.numbers {
78 total = total.saturating_add(bet.amount);
79 }
80 total
81 }
82
83 pub fn is_empty(&self) -> bool {
84 self.total() == 0
85 }
86
87 /// Total returned for a winning number, including returned stakes.
88 pub fn winnings(&self, number: u8) -> u64 {
89 let mut winnings = 0u64;
90
91 if number != 0 {
92 if is_red(number) {
93 winnings = winnings.saturating_add(self.red.saturating_mul(EVEN_MONEY_RETURN));
94 } else {
95 winnings =
96 winnings.saturating_add(self.black.saturating_mul(EVEN_MONEY_RETURN));
97 }
98 if is_even(number) {
99 winnings = winnings.saturating_add(self.even.saturating_mul(EVEN_MONEY_RETURN));
100 } else {
101 winnings = winnings.saturating_add(self.odd.saturating_mul(EVEN_MONEY_RETURN));
102 }
103 }
104
105 for bet in &self.numbers {
106 if bet.number == number {
107 winnings = winnings
108 .saturating_add(bet.amount.saturating_mul(STRAIGHT_RETURN));
109 }
110 }
111
112 winnings
113 }
114
115 pub fn validate(&self, balance: u64) -> Result<(), String> {
116 if self.is_empty() {
117 return Err("Place at least one bet".to_string());
118 }
119 if self.total() > balance {
120 return Err("Insufficient balance".to_string());
121 }
122 for bet in &self.numbers {
123 if bet.number > 36 {
124 return Err(format!("Invalid number: {}", bet.number));
125 }
126 if bet.amount == 0 {
127 return Err("A bet amount must be positive".to_string());
128 }
129 }
130 Ok(())
131 }
132}