Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .claude/settings.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"allow": [
"Bash(find /Users/gaoler/src/github.com/folkengine/pkcore/src/analysis/store -type f -name \"*.rs\" -exec wc -l {} +)",
"Bash(cargo check:*)",
"Bash(cargo test:*)"
"Bash(cargo test:*)",
"Bash(cargo build:*)"
]
}
}
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "pkcore"
description = "Prototype core poker library."
version = "0.0.26"
version = "0.0.27"
rust-version = "1.91.0"
edition = "2024"
authors = ["electronicpanopticon <gaoler@electronicpanopticon.com>"]
Expand Down Expand Up @@ -38,6 +38,7 @@ wincounter = "0.1.5"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
rusqlite = { version = "0.34", features = ["bundled"] } # 0.35 breaks HUPResult https://github.com/rusqlite/rusqlite/releases#reactions--reaction_button_component-a0702b
termion = "4.0"
zstd = "0.13"

[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom_v2 = { package = "getrandom", version = "0.2", features = ["js"] }
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# pkcore AKA Spawn of [Fudd](https://github.com/ImperialBower/fudd)

[![Crates.io](https://img.shields.io/crates/v/pkcore.svg)](https://crates.io/crates/pkcore)

🚧 **Work In Progress** 🚧

[Rust](https://www.rust-lang.org/) poker library. Code inspired by [Cactus Kev's](https://suffe.cool)
Expand Down
10 changes: 4 additions & 6 deletions examples/generate_bcm.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
use pkcore::analysis::store::bcm::binary_card_map::SevenFiveBCM;

/// Creates a pregenerated file that makes 7 cards to the best five cards and to the score NLH
/// Cactus Kev score for the hand. The file generated is a little under 5GB in size.
///
/// While it takes a over 10 minutes to load, once it's in memory, doing combo calculations is
/// much faster.
/// Creates a pregenerated zstd-compressed binary file mapping 7-card combinations to the best
/// five-card hand and its Cactus Kev score. The file is typically ~300–600 MB, compared to the
/// ~5 GB CSV equivalent.
///
/// RUST_LOG=trace cargo run --example generate_bcm
fn main() {
let now = std::time::Instant::now();
env_logger::init();

SevenFiveBCM::generate_csv("generated/bcm.csv").expect("TODO: panic message");
SevenFiveBCM::generate_bin("generated/bcm.zst").expect("Failed to generate bcm binary");

println!("Elapsed: {:.2?}", now.elapsed());
}
78 changes: 65 additions & 13 deletions src/analysis/store/bcm/binary_card_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,35 @@ use crate::bard::Bard;
use crate::card::Card;
use crate::cards::Cards;
use crate::{PKError, Pile};
use csv::Reader;
use csv::WriterBuilder;
use rusqlite::{Connection, named_params};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::error::Error;
use std::fs::File;
use std::io::{BufReader, BufWriter, Read, Write};

/// This code is brutal, heavy, and wonderful. It is an optimization that makes things much slower
/// in the short term, and MUCH faster in the long term. Eventually, we will want containers that
/// have all this stuff loaded for bear. We're not there yet.
///
/// Reads from a zstd-compressed binary file (`generated/bcm.zst`) containing 18-byte records:
/// 8 bytes bc (u64 LE) + 8 bytes best (u64 LE) + 2 bytes rank (u16 LE).
///
/// TODO TD: Add logging
#[allow(clippy::unwrap_used)]
pub static BC_RANK_HASHMAP: std::sync::LazyLock<HashMap<Bard, FiveBCM>> = std::sync::LazyLock::new(|| {
let mut m = HashMap::new();
let file = File::open(SevenFiveBCM::get_csv_filepath()).unwrap();
let mut rdr = Reader::from_reader(file);

for result in rdr.deserialize() {
let bcm: SevenFiveBCM = result.unwrap();
m.insert(bcm.bc, FiveBCM::from(bcm));
let file = File::open(SevenFiveBCM::get_filepath()).unwrap();
let decoder = zstd::stream::read::Decoder::new(file).unwrap();
let mut reader = BufReader::new(decoder);

let mut buf = [0u8; 18];
while reader.read_exact(&mut buf).is_ok() {
let bc = Bard::from(u64::from_le_bytes(buf[0..8].try_into().unwrap()));
let best = Bard::from(u64::from_le_bytes(buf[8..16].try_into().unwrap()));
let rank = u16::from_le_bytes([buf[16], buf[17]]);
m.insert(bc, FiveBCM::new(best, rank));
}
m
});
Expand Down Expand Up @@ -132,20 +139,65 @@ pub struct SevenFiveBCM {
}

impl SevenFiveBCM {
/// This file is a little under 5GB in size. Please ask the author for a link
/// if you don't want to generate it yourself with the `examples/generate_bcm.rs` utility.
/// TBH, I don't remember how long it took to generate.
/// Default path for the zstd-compressed binary BCM file (~300–600 MB).
/// Override with the `PKCORE_75BCM_PATH` environment variable.
pub const DEFAULT_PKCORE_75BCM_PATH: &'static str = "generated/bcm.zst";

/// Default path for the legacy CSV BCM file (~5 GB).
/// Override with the `PKCORE_75BCM_CSV_PATH` environment variable.
pub const DEFAULT_PKCORE_75BCM_CSV_PATH: &'static str = "generated/bcm.csv";

/// Returns the path to the binary BCM file, using `PKCORE_75BCM_PATH` if set.
#[must_use]
pub fn get_filepath() -> String {
std::env::var("PKCORE_75BCM_PATH").unwrap_or_else(|_| SevenFiveBCM::DEFAULT_PKCORE_75BCM_PATH.to_string())
}

/// Returns the path to the legacy CSV BCM file, using `PKCORE_75BCM_CSV_PATH` if set.
#[must_use]
pub fn get_csv_filepath() -> String {
std::env::var("PKCORE_75BCM_CSV_PATH")
.unwrap_or_else(|_| SevenFiveBCM::DEFAULT_PKCORE_75BCM_CSV_PATH.to_string())
}

/// OK, this is the old school way of generating serialized data. Next step
/// is to try to do the same with an embedded DB like
/// [sled](https://github.com/spacejam/sled).
/// Generates a zstd-compressed binary BCM file at `path`.
///
/// Each record is 18 bytes: 8 bytes `bc` (u64 LE) + 8 bytes `best` (u64 LE) +
/// 2 bytes `rank` (u16 LE). The resulting file is typically ~300–600 MB, compared
/// to ~5 GB for the equivalent CSV.
///
/// # Errors
///
/// Returns an error if the file cannot be created or if card combinations are invalid.
pub fn generate_bin(path: &str) -> Result<(), Box<dyn Error>> {
let file = File::create(path)?;
let mut encoder = zstd::stream::write::Encoder::new(BufWriter::new(file), 0)?;

let deck = Cards::deck();

for b in deck.combinations(5) {
if let Ok(bcm) = SevenFiveBCM::try_from(b) {
encoder.write_all(&bcm.bc.as_u64().to_le_bytes())?;
encoder.write_all(&bcm.best.as_u64().to_le_bytes())?;
encoder.write_all(&bcm.rank.to_le_bytes())?;
}
}

for b in deck.combinations(7) {
if let Ok(bcm) = SevenFiveBCM::try_from(b) {
encoder.write_all(&bcm.bc.as_u64().to_le_bytes())?;
encoder.write_all(&bcm.best.as_u64().to_le_bytes())?;
encoder.write_all(&bcm.rank.to_le_bytes())?;
}
}

encoder.finish()?;
Ok(())
}

/// Generates a CSV BCM file at `path` (~5 GB).
///
/// Prefer [`SevenFiveBCM::generate_bin`] for a much smaller zstd-compressed output.
///
/// # Errors
///
Expand Down