rs_poker 5.0.0

A library to help with any Rust code dealing with poker. This includes card values, suits, hands, hand ranks, 5 card hand strength calculation, 7 card hand strength calulcation, and monte carlo game simulation helpers.
Documentation
use super::{GameState, action::Action};
use async_trait::async_trait;
use thiserror::Error;

/// Identifies which historian-owned lock was poisoned.
///
/// Used by [`HistorianError::LockPoisoned`] to carry typed context about
/// where the poisoning occurred, instead of a free-form string.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HistorianLock {
    /// Read lock on the shared `StatsStorage`.
    StatsStorageRead,
    /// Write lock on the shared `StatsStorage`.
    StatsStorageWrite,
    /// Lock on the shared `VecHistorian` records storage.
    VecRecords,
    /// Lock on a CFR `HandLog` tail buffer.
    HandLog,
}

impl std::fmt::Display for HistorianLock {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::StatsStorageRead => write!(f, "StatsStorage read"),
            Self::StatsStorageWrite => write!(f, "StatsStorage write"),
            Self::VecRecords => write!(f, "VecHistorian records"),
            Self::HandLog => write!(f, "HandLog tail"),
        }
    }
}

/// HistorianError is the error type for historian implementations.
#[derive(Error, Debug)]
pub enum HistorianError {
    #[error("Unable to record action")]
    UnableToRecordAction,
    #[error("IO Error: {0}")]
    IOError(#[from] std::io::Error),
    /// A shared lock on historian state was poisoned by a panicking thread.
    /// The `lock` field identifies which lock to aid debugging.
    #[error("{lock} lock poisoned by a panicking thread")]
    LockPoisoned { lock: HistorianLock },
    #[cfg(any(test, feature = "serde"))]
    #[error("JSON Error: {0}")]
    JSONError(#[from] serde_json::Error),
    #[error("Unexpected CFR Node: {0}")]
    CFRUnexpectedNode(String),
    #[error("Expected Node not found in tree")]
    CFRNodeNotFound,
    #[cfg(all(feature = "open-hand-history", feature = "arena"))]
    #[error("OHH Conversion Error: {0}")]
    OHHConversion(#[from] crate::arena::errors::OHHConversionError),
}

/// Historians are a way for the simulation to record or notify of
/// actions while the game is progressing. This is useful for
/// logging, debugging, or even for implementing a replay system.
/// However it's also useful for CFR+ as each action
/// moves the game along the nodes.
///
/// Async so an implementation may await IO (stream to disk or network).
/// The `Send` bound lets a simulation (and its owned historians) be spawned
/// onto the tokio runtime.
#[async_trait]
pub trait Historian: Send {
    /// This method is called by the simulation when an action is received.
    ///
    /// # Arguments
    /// - `id` - The id of the simulation that the action was received on.
    /// - `game_state` - The game state after the action was played
    /// - `action` - The action that was played
    ///
    /// # Returns
    /// - `Ok(())` if the action was recorded successfully
    /// - `Err(HistorianError)` if there was an error recording the action.
    ///
    /// Returning an error will cause the historian to be dropped from the
    /// `Simulation`.
    async fn record_action(
        &mut self,
        id: u128,
        game_state: &GameState,
        action: &Action,
    ) -> Result<(), HistorianError>;
}

/// `HistorianGenerator` is a trait that is used to build historians
/// for tournaments where each simulation needs a new historian.
pub trait HistorianGenerator {
    /// This method is called before each game to build a new historian.
    fn generate(&self, game_state: &GameState) -> Box<dyn Historian>;
}

pub trait CloneHistorian: Historian {
    fn clone_box(&self) -> Box<dyn Historian>;
}

impl<T> CloneHistorian for T
where
    T: 'static + Historian + Clone,
{
    fn clone_box(&self) -> Box<dyn Historian> {
        Box::new(self.clone())
    }
}

pub struct CloneHistorianGenerator<T> {
    historian: T,
}

impl<T> CloneHistorianGenerator<T>
where
    T: CloneHistorian,
{
    pub fn new(historian: T) -> Self {
        CloneHistorianGenerator { historian }
    }
}

impl<T> HistorianGenerator for CloneHistorianGenerator<T>
where
    T: CloneHistorian,
{
    fn generate(&self, _game_state: &GameState) -> Box<dyn Historian> {
        self.historian.clone_box()
    }
}

mod failing;
mod fn_historian;
mod null;
mod stats_tracking;
mod vec;

#[cfg(any(test, feature = "serde"))]
mod directory_historian;

#[cfg(feature = "open-hand-history")]
mod open_hand_history;

pub use failing::FailingHistorian;
pub use fn_historian::FnHistorian;
pub use null::NullHistorian;
pub use vec::HistoryRecord;
pub use vec::SharedHistoryStorage;
pub use vec::VecHistorian;

#[cfg(any(test, feature = "serde"))]
pub use directory_historian::DirectoryHistorian;

pub use stats_tracking::{SharedStatsStorage, StatsStorage, StatsTrackingHistorian};

#[cfg(feature = "open-hand-history")]
pub use open_hand_history::OpenHandHistoryHistorian;
#[cfg(feature = "open-hand-history")]
pub use open_hand_history::OpenHandHistoryVecHistorian;