Skip to content

Commit 4ee82b8

Browse files
authored
Merge pull request #17 from bkushigian/issue16-refactor-bet_size.rs
Refactor bet_size.rs for issue 16 Closes #16
2 parents 04d40ab + 003668e commit 4ee82b8

File tree

4 files changed

+179
-75
lines changed

4 files changed

+179
-75
lines changed

src/action_tree.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,7 @@ impl ActionTree {
599599
actions.push(Action::Check);
600600

601601
// donk bet
602-
for &donk_size in &donk_options.as_ref().unwrap().donk {
602+
for &donk_size in donk_options.as_ref().unwrap().donks() {
603603
match donk_size {
604604
BetSize::PotRelative(ratio) => {
605605
let amount = (pot as f64 * ratio).round() as i32;
@@ -631,7 +631,7 @@ impl ActionTree {
631631
actions.push(Action::Check);
632632

633633
// bet
634-
for &bet_size in &bet_options[player as usize].bet {
634+
for &bet_size in bet_options[player as usize].bets() {
635635
match bet_size {
636636
BetSize::PotRelative(ratio) => {
637637
let amount = (pot as f64 * ratio).round() as i32;
@@ -664,7 +664,7 @@ impl ActionTree {
664664

665665
if !info.allin_flag {
666666
// raise
667-
for &bet_size in &bet_options[player as usize].raise {
667+
for &bet_size in bet_options[player as usize].raises() {
668668
match bet_size {
669669
BetSize::PotRelative(ratio) => {
670670
let amount = prev_amount + (pot as f64 * ratio).round() as i32;

src/bet_size.rs

+169-69
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#[cfg(feature = "bincode")]
22
use bincode::{Decode, Encode};
3-
use serde::{Deserialize, Serialize};
3+
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
44

55
/// Bet size options for the first bets and raises.
66
///
@@ -27,7 +27,7 @@ use serde::{Deserialize, Serialize};
2727
/// let bet_size = BetSizeOptions::try_from(("50%, 100c, 2e, a", "2.5x")).unwrap();
2828
///
2929
/// assert_eq!(
30-
/// bet_size.bet,
30+
/// bet_size.bets(),
3131
/// vec![
3232
/// PotRelative(0.5),
3333
/// Additive(100, 0),
@@ -36,16 +36,20 @@ use serde::{Deserialize, Serialize};
3636
/// ]
3737
/// );
3838
///
39-
/// assert_eq!(bet_size.raise, vec![PrevBetRelative(2.5)]);
39+
/// assert_eq!(bet_size.raises(), vec![PrevBetRelative(2.5)]);
4040
/// ```
4141
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
4242
#[cfg_attr(feature = "bincode", derive(Decode, Encode))]
4343
pub struct BetSizeOptions {
4444
/// Bet size options for first bet.
45-
pub bet: Vec<BetSize>,
45+
#[serde(deserialize_with = "deserialize_bet_sizes", default)]
46+
#[serde(serialize_with = "serialize_bet_sizes")]
47+
bets: Vec<BetSize>,
4648

4749
/// Bet size options for raise.
48-
pub raise: Vec<BetSize>,
50+
#[serde(deserialize_with = "deserialize_bet_sizes", default)]
51+
#[serde(serialize_with = "serialize_bet_sizes")]
52+
raises: Vec<BetSize>,
4953
}
5054

5155
/// Bet size options for the donk bets.
@@ -54,12 +58,15 @@ pub struct BetSizeOptions {
5458
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
5559
#[cfg_attr(feature = "bincode", derive(Decode, Encode))]
5660
pub struct DonkSizeOptions {
57-
pub donk: Vec<BetSize>,
61+
#[serde(deserialize_with = "deserialize_bet_sizes", default)]
62+
#[serde(serialize_with = "serialize_bet_sizes")]
63+
donks: Vec<BetSize>,
5864
}
5965

6066
/// Bet size specification.
6167
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
6268
#[cfg_attr(feature = "bincode", derive(Decode, Encode))]
69+
#[serde(try_from = "&str")]
6370
pub enum BetSize {
6471
/// Bet size relative to the current pot size.
6572
PotRelative(f64),
@@ -81,39 +88,77 @@ pub enum BetSize {
8188
AllIn,
8289
}
8390

84-
impl TryFrom<(&str, &str)> for BetSizeOptions {
85-
type Error = String;
86-
87-
/// Attempts to convert comma-separated strings into bet sizes.
91+
impl BetSizeOptions {
92+
/// Tries to create a `BetSizeOptions` from two `BetSize` vecs.
8893
///
89-
/// See the [`BetSizeOptions`] struct for the description and examples.
90-
fn try_from((bet_str, raise_str): (&str, &str)) -> Result<Self, Self::Error> {
91-
let mut bet_sizes = bet_str.split(',').map(str::trim).collect::<Vec<_>>();
92-
let mut raise_sizes = raise_str.split(',').map(str::trim).collect::<Vec<_>>();
94+
/// # Errors
95+
///
96+
/// Returns `Err` when:
97+
/// - `bets` contains a `BetSize::Relative` bet size
98+
/// - `bets` contains an `BetSize::Additive(_, cap)` with non-zero `cap`
99+
pub fn try_from_sizes(bets: Vec<BetSize>, raises: Vec<BetSize>) -> Result<Self, String> {
100+
Ok(BetSizeOptions {
101+
bets: BetSizeOptions::as_valid_bets(bets)?,
102+
raises,
103+
})
104+
}
93105

94-
if bet_sizes.last().unwrap().is_empty() {
95-
bet_sizes.pop();
106+
/// Check `bets` for well-formedness (no sizes relative to previous bet and
107+
/// no raise caps) and return it. Return an `Err` if:
108+
/// - `bets` contains a `BetSize::Relative` bet size
109+
/// - `bets` contains an `BetSize::Additive(_, cap)` with non-zero `cap`
110+
pub fn as_valid_bets(bets: Vec<BetSize>) -> Result<Vec<BetSize>, String> {
111+
for bs in bets.iter() {
112+
match &bs {
113+
BetSize::PrevBetRelative(_) => {
114+
let err_msg = "bets cannot contain `BetSize::PrevBetRelative".to_string();
115+
return Err(err_msg);
116+
}
117+
BetSize::Additive(_, cap) => {
118+
if cap != &0 {
119+
let err_msg =
120+
"bets cannot contain additive bet sizes with non-zero raise caps"
121+
.to_string();
122+
return Err(err_msg);
123+
}
124+
}
125+
_ => (),
126+
}
96127
}
128+
Ok(bets)
129+
}
97130

98-
if raise_sizes.last().unwrap().is_empty() {
99-
raise_sizes.pop();
100-
}
131+
pub fn bets(&self) -> &[BetSize] {
132+
&self.bets
133+
}
101134

102-
let mut bet = Vec::new();
103-
let mut raise = Vec::new();
135+
pub fn raises(&self) -> &[BetSize] {
136+
&self.raises
137+
}
138+
}
104139

105-
for bet_size in bet_sizes {
106-
bet.push(bet_size_from_str(bet_size, false)?);
107-
}
140+
impl TryFrom<&str> for BetSize {
141+
type Error = String;
108142

109-
for raise_size in raise_sizes {
110-
raise.push(bet_size_from_str(raise_size, true)?);
111-
}
143+
fn try_from(s: &str) -> Result<Self, Self::Error> {
144+
bet_size_from_str(s)
145+
}
146+
}
147+
148+
impl TryFrom<(&str, &str)> for BetSizeOptions {
149+
type Error = String;
112150

113-
bet.sort_unstable_by(|l, r| l.partial_cmp(r).unwrap());
114-
raise.sort_unstable_by(|l, r| l.partial_cmp(r).unwrap());
151+
/// Attempts to convert comma-separated strings into bet sizes.
152+
///
153+
/// See the [`BetSizeOptions`] struct for the description and examples.
154+
fn try_from((bet_str, raise_str): (&str, &str)) -> Result<Self, Self::Error> {
155+
Self::try_from_sizes(bet_sizes_from_str(bet_str)?, bet_sizes_from_str(raise_str)?)
156+
}
157+
}
115158

116-
Ok(BetSizeOptions { bet, raise })
159+
impl DonkSizeOptions {
160+
pub fn donks(&self) -> &[BetSize] {
161+
&self.donks
117162
}
118163
}
119164

@@ -124,21 +169,39 @@ impl TryFrom<&str> for DonkSizeOptions {
124169
///
125170
/// See the [`BetSizeOptions`] struct for the description and examples.
126171
fn try_from(donk_str: &str) -> Result<Self, Self::Error> {
127-
let mut donk_sizes = donk_str.split(',').map(str::trim).collect::<Vec<_>>();
128-
129-
if donk_sizes.last().unwrap().is_empty() {
130-
donk_sizes.pop();
131-
}
132-
133-
let mut donk = Vec::new();
172+
let donks = bet_sizes_from_str(donk_str)?;
173+
let donks = BetSizeOptions::as_valid_bets(donks)?;
174+
Ok(DonkSizeOptions { donks })
175+
}
176+
}
134177

135-
for donk_size in donk_sizes {
136-
donk.push(bet_size_from_str(donk_size, false)?);
178+
impl From<BetSize> for String {
179+
fn from(bet_size: BetSize) -> Self {
180+
match bet_size {
181+
BetSize::PotRelative(x) => format!("{}%", 100.0 * x),
182+
BetSize::PrevBetRelative(x) => format!("{}x", x),
183+
BetSize::Additive(c, r) => {
184+
if r != 0 {
185+
format!("{}c{}r", c, r)
186+
} else {
187+
format!("{}c", c)
188+
}
189+
}
190+
BetSize::Geometric(n, r) => {
191+
if n == 0 {
192+
if r == f64::INFINITY {
193+
"e".to_string()
194+
} else {
195+
format!("e{}", r * 100.0)
196+
}
197+
} else if r == f64::INFINITY {
198+
format!("{}e", n)
199+
} else {
200+
format!("{}e{}", n, r)
201+
}
202+
}
203+
BetSize::AllIn => "a".to_string(),
137204
}
138-
139-
donk.sort_unstable_by(|l, r| l.partial_cmp(r).unwrap());
140-
141-
Ok(DonkSizeOptions { donk })
142205
}
143206
}
144207

@@ -150,23 +213,45 @@ fn parse_float(s: &str) -> Option<f64> {
150213
}
151214
}
152215

153-
fn bet_size_from_str(s: &str, is_raise: bool) -> Result<BetSize, String> {
216+
fn bet_sizes_from_str(bets_str: &str) -> Result<Vec<BetSize>, String> {
217+
let mut bet_sizes = bets_str.split(',').map(str::trim).collect::<Vec<_>>();
218+
219+
if bet_sizes.last().unwrap().is_empty() {
220+
bet_sizes.pop();
221+
}
222+
223+
let mut bets = Vec::new();
224+
225+
for bet_size in bet_sizes {
226+
bets.push(bet_size_from_str(bet_size)?);
227+
}
228+
229+
bets.sort_unstable_by(|l, r| l.partial_cmp(r).unwrap());
230+
231+
Ok(bets)
232+
}
233+
234+
fn deserialize_bet_sizes<'de, D>(deserializer: D) -> Result<Vec<BetSize>, D::Error>
235+
where
236+
D: Deserializer<'de>,
237+
{
238+
let s = String::deserialize(deserializer)?;
239+
let bet_sizes = bet_sizes_from_str(&s);
240+
bet_sizes.map_err(de::Error::custom)
241+
}
242+
243+
fn bet_size_from_str(s: &str) -> Result<BetSize, String> {
154244
let s_lower = s.to_lowercase();
155245
let err_msg = format!("Invalid bet size: {s}");
156246

157247
if let Some(prev_bet_rel) = s_lower.strip_suffix('x') {
158248
// Previous bet relative
159-
if !is_raise {
160-
let err_msg = format!("Relative size to the previous bet is not allowed: {s}");
249+
let float = parse_float(prev_bet_rel).ok_or(&err_msg)?;
250+
if float <= 1.0 {
251+
let err_msg = format!("Multiplier must be greater than 1.0: {s}");
161252
Err(err_msg)
162253
} else {
163-
let float = parse_float(prev_bet_rel).ok_or(&err_msg)?;
164-
if float <= 1.0 {
165-
let err_msg = format!("Multiplier must be greater than 1.0: {s}");
166-
Err(err_msg)
167-
} else {
168-
Ok(BetSize::PrevBetRelative(float))
169-
}
254+
Ok(BetSize::PrevBetRelative(float))
170255
}
171256
} else if s_lower.contains('c') {
172257
// Additive
@@ -185,10 +270,6 @@ fn bet_size_from_str(s: &str, is_raise: bool) -> Result<BetSize, String> {
185270
let cap = if cap_str.is_empty() {
186271
0
187272
} else {
188-
if !is_raise {
189-
let err_msg = format!("Raise cap is not allowed: {s}");
190-
return Err(err_msg);
191-
}
192273
let float_str = cap_str.strip_suffix('r').ok_or(&err_msg)?;
193274
let float = parse_float(float_str).ok_or(&err_msg)?;
194275
if float.trunc() != float || float == 0.0 {
@@ -251,6 +332,23 @@ fn bet_size_from_str(s: &str, is_raise: bool) -> Result<BetSize, String> {
251332
}
252333
}
253334

335+
pub fn bet_size_to_string(bs: &BetSize) -> String {
336+
String::from(*bs)
337+
}
338+
339+
pub fn serialize_bet_sizes<S>(bs: &[BetSize], s: S) -> Result<S::Ok, S::Error>
340+
where
341+
S: Serializer,
342+
{
343+
s.serialize_str(
344+
bs.iter()
345+
.map(|b| String::from(*b))
346+
.collect::<Vec<String>>()
347+
.join(",")
348+
.as_str(),
349+
)
350+
}
351+
254352
#[cfg(test)]
255353
mod tests {
256354
use super::BetSize::*;
@@ -278,7 +376,7 @@ mod tests {
278376
];
279377

280378
for (s, expected) in tests {
281-
assert_eq!(bet_size_from_str(s, true), Ok(expected));
379+
assert_eq!(bet_size_from_str(s), Ok(expected));
282380
}
283381

284382
let error_tests = [
@@ -288,7 +386,7 @@ mod tests {
288386
];
289387

290388
for s in error_tests {
291-
assert!(bet_size_from_str(s, true).is_err());
389+
assert!(bet_size_from_str(s).is_err());
292390
}
293391
}
294392

@@ -298,18 +396,20 @@ mod tests {
298396
(
299397
"40%, 70%",
300398
"",
301-
BetSizeOptions {
302-
bet: vec![PotRelative(0.4), PotRelative(0.7)],
303-
raise: Vec::new(),
304-
},
399+
BetSizeOptions::try_from_sizes(
400+
vec![PotRelative(0.4), PotRelative(0.7)],
401+
Vec::new(),
402+
)
403+
.unwrap(),
305404
),
306405
(
307406
"50c, e, a,",
308407
"25%, 2.5x, e200%",
309-
BetSizeOptions {
310-
bet: vec![Additive(50, 0), Geometric(0, f64::INFINITY), AllIn],
311-
raise: vec![PotRelative(0.25), PrevBetRelative(2.5), Geometric(0, 2.0)],
312-
},
408+
BetSizeOptions::try_from_sizes(
409+
vec![Additive(50, 0), Geometric(0, f64::INFINITY), AllIn],
410+
vec![PotRelative(0.25), PrevBetRelative(2.5), Geometric(0, 2.0)],
411+
)
412+
.unwrap(),
313413
),
314414
];
315415

@@ -330,13 +430,13 @@ mod tests {
330430
(
331431
"40%, 70%",
332432
DonkSizeOptions {
333-
donk: vec![PotRelative(0.4), PotRelative(0.7)],
433+
donks: vec![PotRelative(0.4), PotRelative(0.7)],
334434
},
335435
),
336436
(
337437
"50c, e, a,",
338438
DonkSizeOptions {
339-
donk: vec![Additive(50, 0), Geometric(0, f64::INFINITY), AllIn],
439+
donks: vec![Additive(50, 0), Geometric(0, f64::INFINITY), AllIn],
340440
},
341441
),
342442
];

0 commit comments

Comments
 (0)