aya_poker/lib.rs
1//! AyaPoker is a Rust library for fast poker hand evaluation based on the
2//! [OMPEval C++ hand evaluator](https://github.com/zekyll/OMPEval), with
3//! support for most popular poker variants.
4//!
5//! # Features
6//!
7//! - Can be used to rank hands from standard poker, ace-to-five lowball,
8//! deuce-to-seven lowball, six-or-better (short-deck), Omaha, Omaha Hi/Lo,
9//! Badugi or Baduci.
10//! - Can evaluate hands with 0 to 7 cards, with the missing cards counting as
11//! the worst possible kickers, allowing for use in stud poker games.
12//! - Uses compile-time generated perfect hash function lookup tables for
13//! excellent runtime performance and fast initialization.
14//! - Has extensive suite of tests to ensure correct implementation of the hand
15//! ranking rules for each variant.
16//!
17//! # Flags
18//!
19//! This crate has the following Cargo features:
20//!
21//! - `std`: By default, `aya_poker` is a `!#[no_std]` crate, but can be
22//! compiled with the `std` feature in order to allow the initialization of
23//! `Deck`s with system-generated random seeds.
24//! - `colored`: Use [`colored`](https://crates.io/crates/colored) to display
25//! cards and hands in color.
26//! - `colored-4color`: Same as `colored`, but using a four-color deck.
27//!
28//! # Example
29//!
30//! ```
31//! use std::cmp::Ordering;
32//!
33//! use aya_poker::{base::*, deck::Deck, poker_rank};
34//!
35//! const SEED: u64 = 42;
36//! const SAMPLE_SIZE: usize = 100_000;
37//!
38//! fn main() -> Result<(), ParseError> {
39//! // We can initialize cards by specifying the rank and suit,
40//! // and then use collect to combine the cards into a Hand
41//! let player = [
42//! Card::new(Rank::King, Suit::Hearts),
43//! Card::new(Rank::King, Suit::Clubs),
44//! ]
45//! .iter()
46//! .collect();
47//!
48//! // Or, first parse the cards from strings
49//! let opponent = ["Js".parse::<Card>(), "Qs".parse::<Card>()]
50//! .iter()
51//! .copied()
52//! .collect::<Result<Hand, ParseError>>()?;
53//!
54//! // Or, parse a space-separated string of cards as a Hand
55//! let board = "Ah 5s Ts".parse()?;
56//!
57//! let equity = equity_calculator(&player, &opponent, &board);
58//! println!(
59//! "{} has {:.1}% equity on {} against {}.",
60//! player,
61//! 100.0 * equity,
62//! board,
63//! opponent
64//! );
65//!
66//! Ok(())
67//! }
68//!
69//! fn equity_calculator(
70//! player: &Hand,
71//! opponent: &Hand,
72//! board: &Hand,
73//! ) -> f64 {
74//! // To simulate board run-outs, we begin by preparing a deck
75//! // that doesn't contain the already dealt-out cards
76//! let available_cards = CARDS
77//! .iter()
78//! .filter(|c| !player.contains(c))
79//! .filter(|c| !opponent.contains(c))
80//! .filter(|c| !board.contains(c));
81//! let mut deck = Deck::with_seed(available_cards, SEED);
82//!
83//! let mut pots_won = 0.0;
84//! for _ in 0..SAMPLE_SIZE {
85//! // Then, for each run we draw cards to complete the board
86//! deck.reset();
87//! let missing = 5 - board.len();
88//! let complete_board = board
89//! .iter()
90//! .chain(deck.deal(missing).unwrap().iter())
91//! .collect::<Hand>();
92//!
93//! // Evaluate the player's hand given the completed board
94//! let mut player_hand = *player;
95//! player_hand.extend(complete_board.iter());
96//! let player_rank = poker_rank(&player_hand);
97//!
98//! // Evaluate the opponent's hand
99//! let mut opponent_hand = *opponent;
100//! opponent_hand.extend(complete_board.iter());
101//! let opponent_rank = poker_rank(&opponent_hand);
102//!
103//! // And record the player's share of the pot for the run
104//! match player_rank.cmp(&opponent_rank) {
105//! Ordering::Greater => pots_won += 1.0,
106//! Ordering::Less => {}
107//! Ordering::Equal => pots_won += 0.5,
108//! };
109//! }
110//!
111//! pots_won / SAMPLE_SIZE as f64
112//! }
113//! ```
114
115#![cfg_attr(not(any(std, test)), no_std)]
116
117use quickdiv::DivisorU64;
118
119mod ace_five;
120mod baduci;
121mod badugi;
122mod deuce_seven;
123mod display;
124mod omaha;
125mod short_deck;
126mod standard;
127
128/// Basic types for playing card games.
129pub mod base {
130 pub use aya_base::{Card, Hand, ParseError, Rank, Suit, CARDS};
131}
132
133/// Deck types optimized for fast shuffling suitable for use in simulators.
134pub mod deck {
135 pub use aya_base::{Deck, FullDeck, ShortDeck};
136}
137
138pub use ace_five::{ace_five_rank, AceFiveHandRank};
139pub use baduci::{baduci_rank, BaduciHandRank};
140pub use badugi::{badugi_rank, BadugiHandRank};
141pub use deuce_seven::{deuce_seven_rank, DeuceSevenHandRank};
142pub use omaha::{omaha_lo_rank, omaha_rank};
143pub use short_deck::{short_deck_rank, ShortDeckHandRank};
144pub use standard::{poker_rank, PokerHandRank};
145
146struct MiniPhf {
147 buckets_len: DivisorU64,
148 len: DivisorU64,
149 values: &'static [u16],
150 pilots: &'static [u32],
151}
152
153impl MiniPhf {
154 pub const fn new(values: &'static [u16], pilots: &'static [u32]) -> MiniPhf {
155 let buckets_len = DivisorU64::new(pilots.len() as u64);
156 let len = DivisorU64::new(values.len() as u64);
157 MiniPhf {
158 buckets_len,
159 len,
160 values,
161 pilots,
162 }
163 }
164
165 #[inline]
166 pub fn get(&self, key: u64) -> u16 {
167 let pilot = self.pilots[(key % self.buckets_len) as usize] as u64;
168 let idx = ((key ^ pilot) % self.len) as usize;
169 self.values[idx]
170 }
171}
172
173/// A poker hand-ranking category, i.e. a straight, a flush, etc.
174///
175/// Note we do not implement [`PartialOrd`] since we use the same ranking
176/// categories for both regular and lowball poker variants.
177#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)]
178pub enum PokerRankCategory {
179 /// A hand without a valid ranking, for example a 9-high in a 8 or better
180 /// lowball game.
181 Ineligible,
182 /// A valid hand that does not fall into any of the other categories.
183 HighCard,
184 /// Two cards of one rank, and three cards of three other ranks.
185 Pair,
186 /// Two cards of one rank, two cards of another rank and a fifth card of
187 /// a different, third rank.
188 TwoPair,
189 /// Three cards of the same rank, and two cards of two other ranks.
190 ThreeOfAKind,
191 /// Five cards of sequential rank, with at least two different suits.
192 Straight,
193 /// Five cards of the same suit, but without sequential rank.
194 Flush,
195 /// Three cards of one rank and two cards of another rank.
196 FullHouse,
197 /// Four cards of the same rank and one card of another rank.
198 FourOfAKind,
199 /// Five cards of sequential rank, all of the same suit, excluding an
200 /// ace-high sequence.
201 StraightFlush,
202 /// The sequence A-K-Q-J-T all of the same suit, i.e. an ace-high
203 /// straight flush.
204 RoyalFlush,
205}
206
207impl core::fmt::Display for PokerRankCategory {
208 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
209 match self {
210 PokerRankCategory::Ineligible => write!(f, "Ineligible"),
211 PokerRankCategory::HighCard => write!(f, "High Card"),
212 PokerRankCategory::Pair => write!(f, "Pair"),
213 PokerRankCategory::TwoPair => write!(f, "Two Pair"),
214 PokerRankCategory::ThreeOfAKind => write!(f, "Three of a Kind"),
215 PokerRankCategory::Straight => write!(f, "Straight"),
216 PokerRankCategory::Flush => write!(f, "Flush"),
217 PokerRankCategory::FullHouse => write!(f, "Full House"),
218 PokerRankCategory::FourOfAKind => write!(f, "Four of a Kind"),
219 PokerRankCategory::StraightFlush => write!(f, "Straight Flush"),
220 PokerRankCategory::RoyalFlush => write!(f, "Royal Flush"),
221 }
222 }
223}
224
225/// A Badugi/Baduci hand-ranking category corresponding to the size
226/// of the made hand.
227#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash)]
228pub enum BadugiRankCategory {
229 /// A single card.
230 OneCard,
231 /// Two cards with different suits and ranks.
232 TwoCards,
233 /// Three cards with three distinct ranks and suits.
234 ThreeCards,
235 /// Four cards with four distinct ranks and suits.
236 FourCards,
237}
238
239impl core::fmt::Display for BadugiRankCategory {
240 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
241 match self {
242 BadugiRankCategory::OneCard => write!(f, "One Card"),
243 BadugiRankCategory::TwoCards => write!(f, "Two Cards"),
244 BadugiRankCategory::ThreeCards => write!(f, "Three Cards"),
245 BadugiRankCategory::FourCards => write!(f, "Four Cards"),
246 }
247 }
248}
249
250fn insert_cards<'a>(hand: &base::Hand, dest: &'a mut [base::Card]) -> &'a [base::Card] {
251 let n = hand.len();
252 for (i, &card) in hand.iter().enumerate() {
253 dest[i] = card;
254 }
255 &dest[..n]
256}