open_pql/functions/
mod.rs

1#![allow(clippy::wildcard_imports)]
2
3use std::{
4    iter::Filter,
5    str::{Chars, FromStr},
6    sync::LazyLock,
7};
8
9use itertools::Itertools;
10use open_pql_macro::{pqlfn, pqlfn_fromstr};
11use rustc_hash::FxHashMap;
12use speedy::Readable;
13use vm::{VmBuffer, VmStack, VmStackValue, VmStore, VmStoreVarIdx, VmValue, *};
14
15use crate::{
16    Board, Card, Card64, DeadCards, Flop, Hand, HandType, PQLBoardRange,
17    PQLBoolean, PQLCard, PQLCardCount, PQLGame, PQLHiRating, PQLInteger,
18    PQLRange, PQLRank, PQLRankSet, PQLStreet, Rank, Rank16, RuntimeError, Suit,
19    Suit4, eval_holdem7, eval_omaha9, eval_shortdeck7,
20    prim::eval::{ARR_STRAIGHT, ARR_STRAIGHT_SHORT, get_card_count},
21    *,
22};
23
24mod outs_info;
25
26mod cast;
27pub use cast::*;
28
29mod best_hi_rating;
30mod board_in_range;
31mod board_ranks;
32mod board_suit_count;
33mod duplicated_board_ranks;
34mod duplicated_hand_ranks;
35mod equity;
36mod exact_flop_hand_category;
37mod exact_hand_type;
38mod five_card_hi_hand_number;
39mod flop_hand_category;
40mod flushing_board;
41mod four_flush;
42mod hand_board_intersections;
43mod hand_ranks;
44mod hand_type;
45mod has_second_board_rank;
46mod has_top_board_rank;
47mod hi_rating;
48mod in_range;
49mod intersecting_hand_ranks;
50mod max_rank;
51mod min_flop_hand_category;
52mod min_hand_type;
53mod min_hi_rating;
54mod min_outs_to_hand_type;
55mod min_rank;
56mod monotone_board;
57mod nonintersecting_hand_ranks;
58mod nth_rank;
59mod nut_hi;
60mod nut_hi_for_hand_type;
61mod nut_hi_outs;
62mod outs_to_hand_type;
63mod overpair;
64mod paired_board;
65mod pocket_pair;
66mod rainbow_board;
67mod rank_count;
68mod rate_hi_hand;
69mod river_card;
70mod scoops;
71mod straight_board;
72mod three_flush;
73mod ties_hi;
74mod turn_card;
75mod twotone_board;
76mod winning_hand_type;
77mod wins_hi;
78
79pub use best_hi_rating::best_hi_rating;
80pub use board_in_range::board_in_range;
81pub use board_ranks::board_ranks;
82pub use board_suit_count::board_suit_count;
83pub use duplicated_board_ranks::duplicated_board_ranks;
84pub use duplicated_hand_ranks::duplicated_hand_ranks;
85pub use equity::equity;
86pub use exact_flop_hand_category::exact_flop_hand_category;
87pub use exact_hand_type::exact_hand_type;
88pub use five_card_hi_hand_number::five_card_hi_hand_number;
89pub use flop_hand_category::flop_hand_category;
90pub use flushing_board::flushing_board;
91pub use four_flush::four_flush;
92pub use hand_board_intersections::hand_board_intersections;
93pub use hand_ranks::hand_ranks;
94pub use hand_type::hand_type;
95pub use has_second_board_rank::has_second_board_rank;
96pub use has_top_board_rank::has_top_board_rank;
97pub use hi_rating::hi_rating;
98pub use in_range::in_range;
99pub use intersecting_hand_ranks::intersecting_hand_ranks;
100pub use max_rank::max_rank;
101pub use min_flop_hand_category::min_flop_hand_category;
102pub use min_hand_type::min_hand_type;
103pub use min_hi_rating::min_hi_rating;
104pub use min_outs_to_hand_type::min_outs_to_hand_type;
105pub use min_rank::min_rank;
106pub use monotone_board::monotone_board;
107pub use nonintersecting_hand_ranks::nonintersecting_hand_ranks;
108pub use nth_rank::nth_rank;
109pub use nut_hi::nut_hi;
110pub use nut_hi_for_hand_type::nut_hi_for_hand_type;
111pub use nut_hi_outs::nut_hi_outs;
112pub use outs_to_hand_type::outs_to_hand_type;
113pub use overpair::overpair;
114pub use paired_board::paired_board;
115pub use pocket_pair::pocket_pair;
116pub use rainbow_board::rainbow_board;
117pub use rank_count::rank_count;
118pub use rate_hi_hand::rate_hi_hand;
119pub use river_card::river_card;
120pub use scoops::scoops;
121pub use straight_board::straight_board;
122pub use three_flush::three_flush;
123pub use ties_hi::ties_hi;
124pub use turn_card::turn_card;
125pub use twotone_board::twotone_board;
126pub use winning_hand_type::winning_hand_type;
127pub use wins_hi::wins_hi;
128
129/// # Panics
130/// board is always non-empty
131#[inline]
132fn max_rank_of_board(street: PQLStreet, board: Board) -> PQLRank {
133    max_rank(board_ranks(street, board)).unwrap()
134}
135
136/// # Panics
137/// board is always non-empty
138#[inline]
139fn fill_ratings(
140    street: PQLStreet,
141    (game, board, player_hands, ratings): (
142        PQLGame,
143        Board,
144        &PlayerHands,
145        &mut BufferRatings,
146    ),
147) {
148    for (hand, rating) in player_hands.iter().zip(ratings.iter_mut()) {
149        *rating = hi_rating(hand, street, (game, board));
150    }
151}
152
153/// Trait for functions to be used in vm
154///
155/// Generated by `fn_macro::pqlfn`
156/// Example:
157/// ```ignore
158/// #[pqlfn(arg, rtn, eval)]
159/// pub fn hand_ranks(hand: &Hand, _street: PQLStreet) -> PQLRankSet;
160///
161/// pub trait PQLFnArgs {
162///     fn arg_types(&self) -> &[PQLType] {
163///         &[PQLType::Player, PQLType::Street]
164///     }
165/// }
166///
167/// pub trait PQLFnRtn {
168///     fn rtn_type(&self) -> PQLType {
169///         PQLType::RankSet
170///     }
171/// }
172///
173/// pub trait PQLFnEval {
174///     fn evaluate(
175///         &self,
176///         buffer: &mut VmBuffer,
177///         store: &mut VmStore,
178///         stack: &mut VmStack,
179///     ) -> Result<VmStackValue, PQLError> {
180///         Ok(self(arg_range(buffer, store, stack), (&*buffer).into()).into())
181///     }
182/// }
183/// ```
184///
185/// each pqlfn also generates a match arm for `FromStr` of &dyn `PQLFn`
186/// ```ignore
187///   "handranks" => Ok(&(hand_ranks as fn(&Hand, PQLStreet) -> PQLRankSet)),
188/// ```
189pub trait PQLFn:
190    fmt::Debug + Send + Sync + PQLFnRtn + PQLFnArgs + PQLFnEval
191{
192}
193
194impl<T> PQLFn for T where
195    T: fmt::Debug + Send + Sync + PQLFnRtn + PQLFnArgs + PQLFnEval
196{
197}
198
199pub trait PQLFnArgs {
200    fn arg_types(&self) -> &[PQLType];
201}
202
203pub trait PQLFnRtn {
204    fn rtn_type(&self) -> PQLType;
205}
206
207pub trait PQLFnEval {
208    fn evaluate(
209        &self,
210        buffer: &mut VmBuffer,
211        store: &mut VmStore,
212        stack: &mut VmStack,
213    ) -> Result<VmStackValue, PQLError>;
214}
215
216impl FromStr for &dyn PQLFn {
217    type Err = ParseError;
218
219    pqlfn_fromstr!();
220}
221
222fn arg_player<'b>(
223    buffer: &'b VmBuffer,
224    _store: &mut VmStore,
225    stack: &mut VmStack,
226) -> &'b Hand {
227    let player: PQLPlayer = stack.downcast_pop().unwrap();
228
229    let hand: &Hand = buffer.player_hand(player);
230
231    hand
232}
233
234fn arg_str<'b>(
235    _buffer: &VmBuffer,
236    store: &'b VmStore,
237    stack: &mut VmStack,
238) -> &'b PQLString {
239    let idx: VmStoreVarIdx = stack.downcast_pop().unwrap();
240    let s: &PQLString = store.downcast_get(idx).unwrap();
241
242    s
243}
244
245fn arg_range<'b, R>(
246    _buffer: &VmBuffer,
247    store: &'b mut VmStore,
248    stack: &mut VmStack,
249) -> &'b mut R
250where
251    &'b mut R: TryFrom<&'b mut VmValue>,
252{
253    let range_idx: VmStoreVarIdx = stack.downcast_pop().unwrap();
254
255    store.downcast_get_mut(range_idx).unwrap()
256}
257
258const fn decompose_omaha(
259    cards: Card64,
260) -> (Card64, Card64, Card64, Card64, Card64, Card64) {
261    let (v0, v1, v2, v3, v4, v5) = eval::decompose_omaha(cards.to_u64());
262
263    (
264        Card64::from_u64(v0),
265        Card64::from_u64(v1),
266        Card64::from_u64(v2),
267        Card64::from_u64(v3),
268        Card64::from_u64(v4),
269        Card64::from_u64(v5),
270    )
271}
272
273#[cfg(test)]
274mod tests {
275    use super::*;
276    use crate::*;
277
278    #[quickcheck]
279    fn test_decompose_omaha(cards: CardN<4>) {
280        let vec: Vec<_> = cards.clone().into();
281        let hands: [_; 6] = decompose_omaha(cards.into()).into();
282
283        for cs in vec.into_iter().combinations(2) {
284            assert!(hands.contains(&Card64::from(cs.as_ref())));
285        }
286    }
287}