Skip to content

Commit

Permalink
Merge pull request #17 from bkushigian/issue16-refactor-bet_size.rs
Browse files Browse the repository at this point in the history
Refactor bet_size.rs for issue 16

Closes #16
  • Loading branch information
bkushigian authored Oct 16, 2024
2 parents 04d40ab + 003668e commit 4ee82b8
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 75 deletions.
6 changes: 3 additions & 3 deletions src/action_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,7 @@ impl ActionTree {
actions.push(Action::Check);

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

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

if !info.allin_flag {
// raise
for &bet_size in &bet_options[player as usize].raise {
for &bet_size in bet_options[player as usize].raises() {
match bet_size {
BetSize::PotRelative(ratio) => {
let amount = prev_amount + (pot as f64 * ratio).round() as i32;
Expand Down
238 changes: 169 additions & 69 deletions src/bet_size.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#[cfg(feature = "bincode")]
use bincode::{Decode, Encode};
use serde::{Deserialize, Serialize};
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};

/// Bet size options for the first bets and raises.
///
Expand All @@ -27,7 +27,7 @@ use serde::{Deserialize, Serialize};
/// let bet_size = BetSizeOptions::try_from(("50%, 100c, 2e, a", "2.5x")).unwrap();
///
/// assert_eq!(
/// bet_size.bet,
/// bet_size.bets(),
/// vec![
/// PotRelative(0.5),
/// Additive(100, 0),
Expand All @@ -36,16 +36,20 @@ use serde::{Deserialize, Serialize};
/// ]
/// );
///
/// assert_eq!(bet_size.raise, vec![PrevBetRelative(2.5)]);
/// assert_eq!(bet_size.raises(), vec![PrevBetRelative(2.5)]);
/// ```
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "bincode", derive(Decode, Encode))]
pub struct BetSizeOptions {
/// Bet size options for first bet.
pub bet: Vec<BetSize>,
#[serde(deserialize_with = "deserialize_bet_sizes", default)]
#[serde(serialize_with = "serialize_bet_sizes")]
bets: Vec<BetSize>,

/// Bet size options for raise.
pub raise: Vec<BetSize>,
#[serde(deserialize_with = "deserialize_bet_sizes", default)]
#[serde(serialize_with = "serialize_bet_sizes")]
raises: Vec<BetSize>,
}

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

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

impl TryFrom<(&str, &str)> for BetSizeOptions {
type Error = String;

/// Attempts to convert comma-separated strings into bet sizes.
impl BetSizeOptions {
/// Tries to create a `BetSizeOptions` from two `BetSize` vecs.
///
/// See the [`BetSizeOptions`] struct for the description and examples.
fn try_from((bet_str, raise_str): (&str, &str)) -> Result<Self, Self::Error> {
let mut bet_sizes = bet_str.split(',').map(str::trim).collect::<Vec<_>>();
let mut raise_sizes = raise_str.split(',').map(str::trim).collect::<Vec<_>>();
/// # Errors
///
/// Returns `Err` when:
/// - `bets` contains a `BetSize::Relative` bet size
/// - `bets` contains an `BetSize::Additive(_, cap)` with non-zero `cap`
pub fn try_from_sizes(bets: Vec<BetSize>, raises: Vec<BetSize>) -> Result<Self, String> {
Ok(BetSizeOptions {
bets: BetSizeOptions::as_valid_bets(bets)?,
raises,
})
}

if bet_sizes.last().unwrap().is_empty() {
bet_sizes.pop();
/// Check `bets` for well-formedness (no sizes relative to previous bet and
/// no raise caps) and return it. Return an `Err` if:
/// - `bets` contains a `BetSize::Relative` bet size
/// - `bets` contains an `BetSize::Additive(_, cap)` with non-zero `cap`
pub fn as_valid_bets(bets: Vec<BetSize>) -> Result<Vec<BetSize>, String> {
for bs in bets.iter() {
match &bs {
BetSize::PrevBetRelative(_) => {
let err_msg = "bets cannot contain `BetSize::PrevBetRelative".to_string();
return Err(err_msg);
}
BetSize::Additive(_, cap) => {
if cap != &0 {
let err_msg =
"bets cannot contain additive bet sizes with non-zero raise caps"
.to_string();
return Err(err_msg);
}
}
_ => (),
}
}
Ok(bets)
}

if raise_sizes.last().unwrap().is_empty() {
raise_sizes.pop();
}
pub fn bets(&self) -> &[BetSize] {
&self.bets
}

let mut bet = Vec::new();
let mut raise = Vec::new();
pub fn raises(&self) -> &[BetSize] {
&self.raises
}
}

for bet_size in bet_sizes {
bet.push(bet_size_from_str(bet_size, false)?);
}
impl TryFrom<&str> for BetSize {
type Error = String;

for raise_size in raise_sizes {
raise.push(bet_size_from_str(raise_size, true)?);
}
fn try_from(s: &str) -> Result<Self, Self::Error> {
bet_size_from_str(s)
}
}

impl TryFrom<(&str, &str)> for BetSizeOptions {
type Error = String;

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

Ok(BetSizeOptions { bet, raise })
impl DonkSizeOptions {
pub fn donks(&self) -> &[BetSize] {
&self.donks
}
}

Expand All @@ -124,21 +169,39 @@ impl TryFrom<&str> for DonkSizeOptions {
///
/// See the [`BetSizeOptions`] struct for the description and examples.
fn try_from(donk_str: &str) -> Result<Self, Self::Error> {
let mut donk_sizes = donk_str.split(',').map(str::trim).collect::<Vec<_>>();

if donk_sizes.last().unwrap().is_empty() {
donk_sizes.pop();
}

let mut donk = Vec::new();
let donks = bet_sizes_from_str(donk_str)?;
let donks = BetSizeOptions::as_valid_bets(donks)?;
Ok(DonkSizeOptions { donks })
}
}

for donk_size in donk_sizes {
donk.push(bet_size_from_str(donk_size, false)?);
impl From<BetSize> for String {
fn from(bet_size: BetSize) -> Self {
match bet_size {
BetSize::PotRelative(x) => format!("{}%", 100.0 * x),
BetSize::PrevBetRelative(x) => format!("{}x", x),
BetSize::Additive(c, r) => {
if r != 0 {
format!("{}c{}r", c, r)
} else {
format!("{}c", c)
}
}
BetSize::Geometric(n, r) => {
if n == 0 {
if r == f64::INFINITY {
"e".to_string()
} else {
format!("e{}", r * 100.0)
}
} else if r == f64::INFINITY {
format!("{}e", n)
} else {
format!("{}e{}", n, r)
}
}
BetSize::AllIn => "a".to_string(),
}

donk.sort_unstable_by(|l, r| l.partial_cmp(r).unwrap());

Ok(DonkSizeOptions { donk })
}
}

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

fn bet_size_from_str(s: &str, is_raise: bool) -> Result<BetSize, String> {
fn bet_sizes_from_str(bets_str: &str) -> Result<Vec<BetSize>, String> {
let mut bet_sizes = bets_str.split(',').map(str::trim).collect::<Vec<_>>();

if bet_sizes.last().unwrap().is_empty() {
bet_sizes.pop();
}

let mut bets = Vec::new();

for bet_size in bet_sizes {
bets.push(bet_size_from_str(bet_size)?);
}

bets.sort_unstable_by(|l, r| l.partial_cmp(r).unwrap());

Ok(bets)
}

fn deserialize_bet_sizes<'de, D>(deserializer: D) -> Result<Vec<BetSize>, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
let bet_sizes = bet_sizes_from_str(&s);
bet_sizes.map_err(de::Error::custom)
}

fn bet_size_from_str(s: &str) -> Result<BetSize, String> {
let s_lower = s.to_lowercase();
let err_msg = format!("Invalid bet size: {s}");

if let Some(prev_bet_rel) = s_lower.strip_suffix('x') {
// Previous bet relative
if !is_raise {
let err_msg = format!("Relative size to the previous bet is not allowed: {s}");
let float = parse_float(prev_bet_rel).ok_or(&err_msg)?;
if float <= 1.0 {
let err_msg = format!("Multiplier must be greater than 1.0: {s}");
Err(err_msg)
} else {
let float = parse_float(prev_bet_rel).ok_or(&err_msg)?;
if float <= 1.0 {
let err_msg = format!("Multiplier must be greater than 1.0: {s}");
Err(err_msg)
} else {
Ok(BetSize::PrevBetRelative(float))
}
Ok(BetSize::PrevBetRelative(float))
}
} else if s_lower.contains('c') {
// Additive
Expand All @@ -185,10 +270,6 @@ fn bet_size_from_str(s: &str, is_raise: bool) -> Result<BetSize, String> {
let cap = if cap_str.is_empty() {
0
} else {
if !is_raise {
let err_msg = format!("Raise cap is not allowed: {s}");
return Err(err_msg);
}
let float_str = cap_str.strip_suffix('r').ok_or(&err_msg)?;
let float = parse_float(float_str).ok_or(&err_msg)?;
if float.trunc() != float || float == 0.0 {
Expand Down Expand Up @@ -251,6 +332,23 @@ fn bet_size_from_str(s: &str, is_raise: bool) -> Result<BetSize, String> {
}
}

pub fn bet_size_to_string(bs: &BetSize) -> String {
String::from(*bs)
}

pub fn serialize_bet_sizes<S>(bs: &[BetSize], s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
s.serialize_str(
bs.iter()
.map(|b| String::from(*b))
.collect::<Vec<String>>()
.join(",")
.as_str(),
)
}

#[cfg(test)]
mod tests {
use super::BetSize::*;
Expand Down Expand Up @@ -278,7 +376,7 @@ mod tests {
];

for (s, expected) in tests {
assert_eq!(bet_size_from_str(s, true), Ok(expected));
assert_eq!(bet_size_from_str(s), Ok(expected));
}

let error_tests = [
Expand All @@ -288,7 +386,7 @@ mod tests {
];

for s in error_tests {
assert!(bet_size_from_str(s, true).is_err());
assert!(bet_size_from_str(s).is_err());
}
}

Expand All @@ -298,18 +396,20 @@ mod tests {
(
"40%, 70%",
"",
BetSizeOptions {
bet: vec![PotRelative(0.4), PotRelative(0.7)],
raise: Vec::new(),
},
BetSizeOptions::try_from_sizes(
vec![PotRelative(0.4), PotRelative(0.7)],
Vec::new(),
)
.unwrap(),
),
(
"50c, e, a,",
"25%, 2.5x, e200%",
BetSizeOptions {
bet: vec![Additive(50, 0), Geometric(0, f64::INFINITY), AllIn],
raise: vec![PotRelative(0.25), PrevBetRelative(2.5), Geometric(0, 2.0)],
},
BetSizeOptions::try_from_sizes(
vec![Additive(50, 0), Geometric(0, f64::INFINITY), AllIn],
vec![PotRelative(0.25), PrevBetRelative(2.5), Geometric(0, 2.0)],
)
.unwrap(),
),
];

Expand All @@ -330,13 +430,13 @@ mod tests {
(
"40%, 70%",
DonkSizeOptions {
donk: vec![PotRelative(0.4), PotRelative(0.7)],
donks: vec![PotRelative(0.4), PotRelative(0.7)],
},
),
(
"50c, e, a,",
DonkSizeOptions {
donk: vec![Additive(50, 0), Geometric(0, f64::INFINITY), AllIn],
donks: vec![Additive(50, 0), Geometric(0, f64::INFINITY), AllIn],
},
),
];
Expand Down
Loading

0 comments on commit 4ee82b8

Please sign in to comment.