1
1
#[ cfg( feature = "bincode" ) ]
2
2
use bincode:: { Decode , Encode } ;
3
- use serde:: { Deserialize , Serialize } ;
3
+ use serde:: { de , Deserialize , Deserializer , Serialize , Serializer } ;
4
4
5
5
/// Bet size options for the first bets and raises.
6
6
///
@@ -27,7 +27,7 @@ use serde::{Deserialize, Serialize};
27
27
/// let bet_size = BetSizeOptions::try_from(("50%, 100c, 2e, a", "2.5x")).unwrap();
28
28
///
29
29
/// assert_eq!(
30
- /// bet_size.bet ,
30
+ /// bet_size.bets() ,
31
31
/// vec![
32
32
/// PotRelative(0.5),
33
33
/// Additive(100, 0),
@@ -36,16 +36,20 @@ use serde::{Deserialize, Serialize};
36
36
/// ]
37
37
/// );
38
38
///
39
- /// assert_eq!(bet_size.raise , vec![PrevBetRelative(2.5)]);
39
+ /// assert_eq!(bet_size.raises() , vec![PrevBetRelative(2.5)]);
40
40
/// ```
41
41
#[ derive( Debug , Clone , Default , PartialEq , Serialize , Deserialize ) ]
42
42
#[ cfg_attr( feature = "bincode" , derive( Decode , Encode ) ) ]
43
43
pub struct BetSizeOptions {
44
44
/// 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 > ,
46
48
47
49
/// 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 > ,
49
53
}
50
54
51
55
/// Bet size options for the donk bets.
@@ -54,12 +58,15 @@ pub struct BetSizeOptions {
54
58
#[ derive( Debug , Clone , Default , PartialEq , Serialize , Deserialize ) ]
55
59
#[ cfg_attr( feature = "bincode" , derive( Decode , Encode ) ) ]
56
60
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 > ,
58
64
}
59
65
60
66
/// Bet size specification.
61
67
#[ derive( Debug , Clone , Copy , PartialEq , PartialOrd , Serialize , Deserialize ) ]
62
68
#[ cfg_attr( feature = "bincode" , derive( Decode , Encode ) ) ]
69
+ #[ serde( try_from = "&str" ) ]
63
70
pub enum BetSize {
64
71
/// Bet size relative to the current pot size.
65
72
PotRelative ( f64 ) ,
@@ -81,39 +88,77 @@ pub enum BetSize {
81
88
AllIn ,
82
89
}
83
90
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.
88
93
///
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
+ }
93
105
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
+ }
96
127
}
128
+ Ok ( bets)
129
+ }
97
130
98
- if raise_sizes . last ( ) . unwrap ( ) . is_empty ( ) {
99
- raise_sizes . pop ( ) ;
100
- }
131
+ pub fn bets ( & self ) -> & [ BetSize ] {
132
+ & self . bets
133
+ }
101
134
102
- let mut bet = Vec :: new ( ) ;
103
- let mut raise = Vec :: new ( ) ;
135
+ pub fn raises ( & self ) -> & [ BetSize ] {
136
+ & self . raises
137
+ }
138
+ }
104
139
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 ;
108
142
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 ;
112
150
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
+ }
115
158
116
- Ok ( BetSizeOptions { bet, raise } )
159
+ impl DonkSizeOptions {
160
+ pub fn donks ( & self ) -> & [ BetSize ] {
161
+ & self . donks
117
162
}
118
163
}
119
164
@@ -124,21 +169,39 @@ impl TryFrom<&str> for DonkSizeOptions {
124
169
///
125
170
/// See the [`BetSizeOptions`] struct for the description and examples.
126
171
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
+ }
134
177
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 ( ) ,
137
204
}
138
-
139
- donk. sort_unstable_by ( |l, r| l. partial_cmp ( r) . unwrap ( ) ) ;
140
-
141
- Ok ( DonkSizeOptions { donk } )
142
205
}
143
206
}
144
207
@@ -150,23 +213,45 @@ fn parse_float(s: &str) -> Option<f64> {
150
213
}
151
214
}
152
215
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 > {
154
244
let s_lower = s. to_lowercase ( ) ;
155
245
let err_msg = format ! ( "Invalid bet size: {s}" ) ;
156
246
157
247
if let Some ( prev_bet_rel) = s_lower. strip_suffix ( 'x' ) {
158
248
// 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}" ) ;
161
252
Err ( err_msg)
162
253
} 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) )
170
255
}
171
256
} else if s_lower. contains ( 'c' ) {
172
257
// Additive
@@ -185,10 +270,6 @@ fn bet_size_from_str(s: &str, is_raise: bool) -> Result<BetSize, String> {
185
270
let cap = if cap_str. is_empty ( ) {
186
271
0
187
272
} else {
188
- if !is_raise {
189
- let err_msg = format ! ( "Raise cap is not allowed: {s}" ) ;
190
- return Err ( err_msg) ;
191
- }
192
273
let float_str = cap_str. strip_suffix ( 'r' ) . ok_or ( & err_msg) ?;
193
274
let float = parse_float ( float_str) . ok_or ( & err_msg) ?;
194
275
if float. trunc ( ) != float || float == 0.0 {
@@ -251,6 +332,23 @@ fn bet_size_from_str(s: &str, is_raise: bool) -> Result<BetSize, String> {
251
332
}
252
333
}
253
334
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
+
254
352
#[ cfg( test) ]
255
353
mod tests {
256
354
use super :: BetSize :: * ;
@@ -278,7 +376,7 @@ mod tests {
278
376
] ;
279
377
280
378
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) ) ;
282
380
}
283
381
284
382
let error_tests = [
@@ -288,7 +386,7 @@ mod tests {
288
386
] ;
289
387
290
388
for s in error_tests {
291
- assert ! ( bet_size_from_str( s, true ) . is_err( ) ) ;
389
+ assert ! ( bet_size_from_str( s) . is_err( ) ) ;
292
390
}
293
391
}
294
392
@@ -298,18 +396,20 @@ mod tests {
298
396
(
299
397
"40%, 70%" ,
300
398
"" ,
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 ( ) ,
305
404
) ,
306
405
(
307
406
"50c, e, a," ,
308
407
"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 ( ) ,
313
413
) ,
314
414
] ;
315
415
@@ -330,13 +430,13 @@ mod tests {
330
430
(
331
431
"40%, 70%" ,
332
432
DonkSizeOptions {
333
- donk : vec ! [ PotRelative ( 0.4 ) , PotRelative ( 0.7 ) ] ,
433
+ donks : vec ! [ PotRelative ( 0.4 ) , PotRelative ( 0.7 ) ] ,
334
434
} ,
335
435
) ,
336
436
(
337
437
"50c, e, a," ,
338
438
DonkSizeOptions {
339
- donk : vec ! [ Additive ( 50 , 0 ) , Geometric ( 0 , f64 :: INFINITY ) , AllIn ] ,
439
+ donks : vec ! [ Additive ( 50 , 0 ) , Geometric ( 0 , f64 :: INFINITY ) , AllIn ] ,
340
440
} ,
341
441
) ,
342
442
] ;
0 commit comments