Expand description
§pkcore
A comprehensive poker library for Texas Hold’em analysis, evaluation, and game simulation.
ASIDE: This documentation block was originally generated with AI. Required a lot of rework, but it did create an interesting overview.
§Overview
pkcore is a high-performance Rust library designed for serious poker analysis and game theory applications.
It provides tools for:
- Card and deck manipulation with efficient bit representations
- Hand evaluation (5-card, 7-card, and 8-or-better low hands)
- Heads-up preflop equity calculations with
SQLitepersistence - Game theory optimal (GTO) range analysis and combo breakdowns
- Full game simulation with multi-round betting and pot management
- Suit-shift analysis for distinct hand pattern recognition
§Core Types
§Cards and Ranks
-
card::Card- A single playing card with rank and suit- Represented internally as a
u8for efficient bitwise operations - Supports parsing from strings like “As” and “a♠” for (Ace of Spades) or “2h” for (Two of Hearts)
- Represented internally as a
-
cards::Cards- An ordered, deduplicated collection of cards (hands, boards, etc.)- Built on
IndexSetfor O(1) lookups and preservation of insertion order - Implements
Piletrait for common card collection operations
- Built on
-
deck::Deck- A shuffleable 52-card deck- Supports drawing cards and checking remaining inventory
-
rank::Rankandsuit::Suit- Card componentsrank::Rank: Ace through King with utility methods for comparisons and bit patternssuit::Suit: Spades, Hearts, Diamonds, Clubs with shift operations for distinct analysis
§Analysis Module
The analysis module provides sophisticated poker analysis tools:
§Hand Evaluation
-
analysis::evals::Evals- Complete hand evaluation results- 5-card hand rankings with Cactus Kev’s evaluator
- 7-card hand analysis (Texas Hold’em)
- 8-or-better low hand qualification
- Hand classifications (pair, two pair, trips, straight, flush, etc.)
-
analysis::the_nuts::TheNuts- Nut hand calculations- Determines the strongest possible hand given community cards
- Supports both high and low hand analysis
§Preflop Equity
analysis::store::db::hup::HUPResult- Heads-up preflop equity results- Precomputed matchups between any two starting hands
- Ability to compute odds against more than two players in a hand
SQLitepersistence with theSqlabletrait- Configurable database path via
HUPS_DB_PATHenvironment variable - Supports split pot scenarios
- Efficient querying and bulk insertion
§GTO Analysis (WIP)
analysis::gto::combo::Combo- Individual hand combinationsanalysis::gto::combo_pairs::ComboPairs- Grouped combinations by rank patternanalysis::gto::twos::Twos- Two-card hand explosions for range analysis- Range analysis with combo weighting and equity breakdowns
- See
examples/gto.rsfor a practical demonstration
§Game Simulation Module (WIP)
The games module provides complete game simulation infrastructure:
- Table Management: Multi-seat tables with position tracking
- Betting Rounds: Preflop, Flop, Turn, River with action tracking
- Pot Calculation: Side pots, main pots, and all-in scenarios
- Player State: Active, folded, all-in, or busted statuses
- Action History: Complete hand history tracking
§Key Traits
§Card Collection Operations
Pile- Common operations for card collections- Card containment checks
- Combination generation (remaining cards, enumeration)
- Rank and suit extraction
- Uniqueness validation
- Hand evaluation delegation
§Game State and Actions
-
Agency- Player action permissions based on game statecan_act()- Can player act at all?can_given()- Can player do action X given previous action Y?can_given_against()- Can player respond to opponent’s action?
-
Betting- Chip management and wageringall_in()- Wager all remaining chipsbet()- Wager specific amountwins()- Collect winnings- Stack size queries
§Hand and Range Analysis
GTO- Range explosion and combo analysisexplode()- Convert range to all two-card combinationscombo_pairs()- Group combos by rank pattern
§Suit Permutation Analysis
-
SuitShift- Suit rotation operationsshift_suit_up()/shift_suit_down()- Rotate suitsopposite()- Find suit-shifted equivalent- Essential for analyzing distinct hand patterns
-
Shifty- Comprehensive shift analysisshifts()- Generate all suit-shifted variantsother_shifts()- Get non-identity shiftsis_shift()- Check if two hands are suit-shifted versions
§Utility Traits
-
Forgiving- Graceful parsing with sensible defaultsforgiving_from_str()- Parse with fallback to default- Reduces error handling boilerplate
-
Plurable- Pluribus AI log format parsingfrom_pluribus()- Parse hand notation from Pluribus logs
-
SOK- Validation checkssalright()- Is this entity in a valid state?
§Constants and Metrics
The crate defines comprehensive constants for poker mathematics, based on Cactus Kev’s evaluator:
§Hand Classification Counts
| Hand Type | Unique | Distinct |
|---|---|---|
| Straight Flushes | 40 | 10 |
| Four of a Kind | 624 | 156 |
| Full Houses | 3,744 | 156 |
| Flushes | 5,108 | 1,277 |
| Straights | 10,200 | 10 |
| Three of a Kind | 54,912 | 858 |
| Two Pair | 123,552 | 858 |
| One Pair | 1,098,240 | 2,860 |
| High Card | 1,302,540 | 1,277 |
§Hold’em-Specific Counts
UNIQUE_5_CARD_HANDS= 2,598,960 - All possible 5-card handsDISTINCT_5_CARD_HANDS= 7,462 - Unique hand strengthsUNIQUE_2_CARD_HANDS= 1,326 - All starting hand combinationsDISTINCT_2_CARD_HANDS= 169 - Distinct starting hand patternsPOSSIBLE_UNIQUE_HOLDEM_HUP_MATCHUPS= 1,624,350 - Heads-up preflop scenarios
§Starting Hand Metrics
UNIQUE_POCKET_PAIRS= 78 - All pocket pair combinationsUNIQUE_SUITED_2_CARD_HANDS= 312 - All suited combinationsUNIQUE_PER_RANK_2_CARD_HANDS= 198 - Combinations per specific rankUNIQUE_PER_SUIT_2_CARD_HANDS= 585 - Combinations per specific suit
§Error Handling
The library uses a comprehensive PKError enum for all error conditions:
- Card Errors:
BlankCard,InvalidCard,DuplicateCard - Action Errors:
InvalidAction,ActionIsntFinished - Betting Errors:
InsufficientChips,Busted - Data Errors:
NotDealt,AlreadyDealt,TooManyCards - System Errors:
DBConnectionError,SqlError - Parsing Errors:
InvalidCardNumber,InvalidRangeIndex
All errors implement std::error::Error and can be converted from rusqlite::Error.
§Database Integration
Precomputed heads-up results can be stored in SQLite:
- Type:
analysis::store::db::hup::HUPResult - Trait: Implements
Sqlablefor insert/query operations - Configuration: Set
HUPS_DB_PATHenvironment variable (default:generated/hups.db) - Persistence: Efficient bulk loading and querying
- Features: Split pot tracking, win/loss counts
Add to .env:
HUPS_DB_PATH=generated/hups.db§Examples
§Parsing and Evaluating a Hand at the Turn:
cargo run --example simple_eval_example
use pkcore::prelude::*;
// 1st player has A♠ K♥ while 2nd player has 8♦ K♣
let hands = HoleCards::from_str("A♠ KH 8♦ K♣").unwrap();
let board = Board::from_str("A♣ 8♥ 7♥ 9♠").unwrap();
let game = Game::new(hands, board);
let case_evals = game.turn_case_evals();
let outs = Outs::from(&case_evals);
let player1_outs = outs.get(1).unwrap();
let player2_outs = outs.get(2).unwrap();
// Show the outs for each player
println!("Player #1 has {} outs: {}", player1_outs.len(), player1_outs);
assert_eq!("K♠ Q♠ J♠ T♠ 7♠ 6♠ 5♠ 4♠ 3♠ 2♠ A♥ Q♥ J♥ T♥ 9♥ 6♥ 5♥ 4♥ 3♥ 2♥ A♦ K♦ Q♦ J♦ T♦ 9♦ 7♦ 6♦ 5♦ 4♦ 3♦ 2♦ Q♣ J♣ T♣ 9♣ 7♣ 6♣ 5♣ 4♣ 3♣ 2♣", player1_outs.to_string());
println!("Player #2 has {} outs: {}", player2_outs.len(), player2_outs);
assert_eq!("8♠ 8♣", player2_outs.to_string());
// Show each players odds of winning against every possible card dealt as well
// as their best hand at the turn.
game.turn_display_odds().expect("TurnDisplayOdds");§Showing Remaining Card Combinations
cargo run --example simple_collections_example
use pkcore::prelude::*;
let hand: Cards = "As Ks".parse().unwrap();
let board: Cards = "Qs Js Ts".parse().unwrap();
// Get remaining cards
let remaining = hand.remaining_after(&board);
println!("Cards left in deck: {}", remaining.len());
assert_eq!(47, remaining.len());
// Generate combinations
for combo in hand.combinations_remaining(2) {
println!("Possible combo: {}", Cards::from(combo));
}§Suit-Shift Analysis
In order to speed up the brute force analysis of every possible result of two hands playing against each other, I came up with the idea of suit-shifting.
See docs/EPIC-07_Transposition.md for a detailed description of concept and rationale behind
suit shifting.
cargo run --example simple_suit_shift_example
use pkcore::prelude::*;
// Create a heads-up example where player 1 has A♠ K♠ and player 2 has A♥ K♥
let hand: SortedHeadsUp = "As Ks Ah kh".parse().unwrap();
// Find all suit-shifted variants
let all_shifts = hand.shifts();
println!("Total variants: {}", all_shifts.len());
assert_eq!(6, all_shifts.len());
for variant in all_shifts {
println!("{}", variant);
}returns:
Total variants: 6
A♠ K♠ - A♥ K♥
A♠ K♠ - A♦ K♦
A♥ K♥ - A♦ K♦
A♠ K♠ - A♣ K♣
A♦ K♦ - A♣ K♣
A♥ K♥ - A♣ K♣§Performance Characteristics
- Card Operations: O(1) lookups via bit representation
- Hand Evaluation: ~50ns per 5-card hand (Cactus Kev algorithm)
- Combination Generation: Lazy iteration without pre-allocation
- Memory: Minimal card representation (~4 bytes per card)
- Database: Pre-computed results enable instant lookups
§Compiler Features
The crate uses Clippy’s pedantic checking and forbids unsafe unwrap patterns:
#![warn(clippy::pedantic)]- Strict code quality checks#![warn(clippy::unwrap_used)]- Unsafe unwrap detection Thanks Cloudflare!#![warn(clippy::expect_used)]- Unsafe expect detection
Several allowances are configured for practical implementation:
non_upper_case_globals- Library constants use camelCaseclippy::upper_case_acronyms- Pragmatic acronym naming- Other pragmatic exceptions for macro expansions and inheritance patterns
§Philosophy
pkcore is built on principles of:
- Correctness: Comprehensive error handling and validation
- Performance: Bit-level optimizations where appropriate
- Clarity: Descriptive naming and extensive documentation
- Testability: Extensive testing, with strict GitHub actions validation
Modules§
- analysis
- arrays
- bard
- card
- card_
number - cards
- cards_
cell - casino
- deck
- games
- macros
- play
- prelude
- Prelude module for pkcore
- rank
- ranks
- suit
- util
Macros§
- boxed
- cards
- cc
- deck
- deck_
cell - range
- I want to get the tests right for this macro since it’s going to be the foundation for all of the range analysis work.
Enums§
Constants§
- DISTINCT_
2_ CARD_ HANDS - DISTINCT_
5_ CARD_ HANDS - DISTINCT_
FLUSH - DISTINCT_
FOUR_ OF_ A_ KIND - DISTINCT_
FULL_ HOUSES - DISTINCT_
HIGH_ CARD - DISTINCT_
ONE_ PAIR - DISTINCT_
PER_ RANK_ 2_ CARD_ HANDS - DISTINCT_
STRAIGHT - DISTINCT_
STRAIGHT_ FLUSHES - DISTINCT_
THREE_ OF_ A_ KIND - DISTINCT_
TWO_ PAIR - POSSIBLE_
UNIQUE_ HOLDEM_ HUP_ MATCHUPS - UNIQUE_
2_ CARD_ HANDS - UNIQUE_
5_ CARD_ HANDS - UNIQUE_
FLUSH - UNIQUE_
FOUR_ OF_ A_ KIND - UNIQUE_
FULL_ HOUSES - UNIQUE_
HIGH_ CARD - UNIQUE_
NON_ POCKET_ PAIRS - UNIQUE_
ONE_ PAIR - UNIQUE_
PER_ CARD_ 2_ CARD_ HANDS - UNIQUE_
PER_ RANK_ 2_ CARD_ HANDS - UNIQUE_
PER_ SUIT_ 2_ CARD_ HANDS - UNIQUE_
POCKET_ PAIRS - UNIQUE_
STRAIGHT - UNIQUE_
STRAIGHT_ FLUSHES - See Cactus Kev’s explanation of unique vs. distinct Poker hands. TODO: Write on demand tests (ignore) to validate these numbers against our code.
- UNIQUE_
SUITED_ 2_ CARD_ HANDS - UNIQUE_
THREE_ OF_ A_ KIND - UNIQUE_
TWO_ PAIR
Traits§
- Agency
- Agency Trait
- Betting
- Forgiving
- GTO
- Pile
- Plurable
- The name of this trait is a pun on pluribus, which is the name of the poker AI group.
- SOK
- The more I think about this, the more I feel like this is me avoiding the best practice
of returning
ResultandOption. I’m worried about speed, but that’s probably Knuth’s dreaded premature optimization. - Shifty
- Suit
Shift - Spades to Hearts to Diamonds to Clubs.