{
  "markdown": "# cardpack.rs\n\n[![Build and Test](https://github.com/ImperialBower/cardpack.rs/actions/workflows/CI.yaml/badge.svg)](https://github.com/ImperialBower/cardpack.rs/actions/workflows/CI.yaml)\n[![codecov](https://codecov.io/gh/ImperialBower/cardpack.rs/branch/main/graph/badge.svg)](https://codecov.io/gh/ImperialBower/cardpack.rs)\n[![Crates.io Version](https://img.shields.io/crates/v/cardpack.svg)](https://crates.io/crates/cardpack)\n[![Rustdocs](https://docs.rs/cardpack/badge.svg)](https://docs.rs/cardpack/)\n\nGeneric pack of cards library written in Rust. The goals of the library include:\n\n* Various types of decks of cards.\n* Internationalization support.\n* Ability to create custom sorts for a specific pack of cards.\n\n**UPDATE:** This is a complete rewrite of the library taking advantage of generics\nin order to make the code cleaner, and easier to extend. \n\n## Setup\n\nBuild and run common tasks with [GNU make](https://www.gnu.org/software/make/):\n\n```shell\nmake\n```\n\nRun `make help` to see all available targets.\n\n## Usage\n\n```rust\nuse cardpack::prelude::*;\n\nfn main() {\n  let mut pack = Standard52::deck();\n\n  // Deterministic shuffle — works in the pure, `no_std` default build.\n  // With the `std` feature you can call `pack.shuffle()` for a thread-RNG shuffle.\n  pack.shuffle_with_seed(42);\n\n  // Deal no-limit hold'em hands for two players:\n  let small_blind = pack.draw(2).unwrap().sorted_by_rank();\n  let big_blind = pack.draw(2).unwrap().sorted_by_rank();\n\n  println!(\"small blind: {}\", small_blind.to_string());\n  println!(\"big blind:   {}\", big_blind.to_string());\n\n  let flop = pack.draw(3).unwrap();\n  let turn = pack.draw(1).unwrap();\n  let river = pack.draw(1).unwrap();\n\n  println!();\n  println!(\"flop : {}\", flop.to_string());\n  println!(\"turn : {}\", turn.to_string());\n  println!(\"river: {}\", river.to_string());\n\n  // Now, let's validate that the cards when collected back together are a valid Standard52\n  // deck of cards.\n  let reconstituted_pile =\n          Pile::<Standard52>::pile_on(&*vec![pack, small_blind, big_blind, flop, turn, river]);\n  assert!(Standard52::deck().same(&reconstituted_pile));\n}\n```\n\n## Details\n\nThe goal of this library is to be able to support the creation of card\ndecks of various sizes and suits. Out of the box, the library supports:\n\n* [French Deck](https://en.wikipedia.org/wiki/French_playing_cards)\n  * [Pinochle](https://en.wikipedia.org/wiki/Pinochle#Deck)\n  * [Spades](https://en.wikipedia.org/wiki/Spades_(card_game)#General_overview) with [Jokers](https://en.wikipedia.org/wiki/Joker_(playing_card))\n  * [Standard 52](https://en.wikipedia.org/wiki/Standard_52-card_deck)\n  * [Canasta](https://en.wikipedia.org/wiki/Canasta#Cards_and_deal)\n    * [Hand and Foot](https://www.pagat.com/rummy/handfoot.html)\n  * [Euchre](https://en.wikipedia.org/wiki/Euchre)\n* [Ganjifa](https://en.wikipedia.org/wiki/Ganjifa) with per-suit inverted pip ranking\n  * Mughal (8 suits × 12 = 96 cards)\n  * Dashavatara (10 suits × 12 = 120 cards)\n* [Short Deck](https://en.wikipedia.org/wiki/Six-plus_hold_'em)\n* [Skat](https://en.wikipedia.org/wiki/Skat_(card_game)#Deck)\n* [Tarot](https://en.wikipedia.org/wiki/Tarot#Tarot_gaming_decks) with [Major](https://en.wikipedia.org/wiki/Major_Arcana) and [Minor](https://en.wikipedia.org/wiki/Minor_Arcana) Arcana\n\nThe project takes advantage of [Project Fluent](https://www.projectfluent.org/)'s\n[Rust](https://github.com/projectfluent/fluent-rs) support to offer\ninternationalization. Current languages supported are\n[English](src/localization/locales/en-US/french.ftl),\n[German](src/localization/locales/de/french.ftl),\n[French](src/localization/locales/fr/french.ftl),\n[Latin](src/localization/locales/la/french.ftl), and\n[Klingon](src/localization/locales/tlh/french.ftl).\n\n## Cargo features\n\n`cardpack` is **pure by default**: a bare dependency is an `alloc`-only,\n`no_std`, no-I/O domain kernel. Every dependency-bearing or I/O-bearing\ncapability is gated behind a Cargo feature, so consumers opt in to exactly\nwhat they need:\n\n| Feature           | Default | Pulls in           | What it turns on                                              |\n|-------------------|---------|--------------------|---------------------------------------------------------------|\n| `full`            | no      | everything below   | Umbrella turning on `std` + `i18n` + `colored-display` + `yaml` + `serde` |\n| `std`             | no      | libstd             | `std`-only APIs (thread-RNG shuffle, `draw_random`, etc.)     |\n| `i18n`            | no      | `fluent-templates` | `FluentName`, `Named`, `Card::fluent_name*`, `localization`   |\n| `colored-display` | no      | `colored`          | `Color`, `Colorize`, `Card::color*`, `Pile::to_color_*`       |\n| `yaml`            | no      | `serde_norway`     | Full deck ↔ YAML round-tripping (pure, in-memory) — see [Decks as YAML](#decks-as-yaml); plus the `Razz` deck |\n| `serde`           | no      | `serde`            | `Serialize`/`Deserialize` derives on `Pip`/`Card`/`Pile` etc. |\n| `std-io`          | no      | —                  | `BasicCard::cards_from_yaml_file` — reads decks from YAML *files* (`std::fs`). The crate's one filesystem seam; **not** in `full` |\n| `funky`           | no      | `std`              | The Balatro-style engine — see [Funky](#funky--balatro-style-cards) below |\n| `seal-test-double`| no      | —                  | `PlaintextSeal` (**no security**) and the `seal_roundtrip` conformance helper for testing a `Seal` backend; **not** in `full` |\n| `commit-reveal`   | no      | `sha2`             | Provably-fair shuffles: `ShuffleRound`, `Commitment`/`Contribution`, `CombinedSeed`, `commit_pile`, `Pile::shuffled_by_round` — see [Provably-fair shuffles](#provably-fair-shuffles); **not** in `full` |\n| `seal-aead`       | no      | `chacha20poly1305`, `hkdf`, `sha2`, `zeroize` | Holder-key seal: `HolderKeySeal`, `DealKey`/`CardKey`, `SealedBytes`, `Custody` — see [Sealed cards](#sealed-cards-holder-key-seal); **not** in `full` |\n| `crypto`          | no      | = both above       | Umbrella over `commit-reveal` + `seal-aead`; **not** in `full` |\n\nTo get the previous \"batteries-included\" behavior, opt into `full`:\n\n```toml\n# Full convenience stack (i18n, colored display, YAML, serde):\ncardpack = { version = \"0.8\", features = [\"full\"] }\n\n# Or trim to just what you need — e.g. the pure kernel plus serde:\ncardpack = { version = \"0.8\", features = [\"serde\"] }\n\n# Or the pure, no_std, alloc-only with no extra deps at all:\ncardpack = \"0.8\"\n```\n\n`yaml` implies `serde` (it deserializes into the serde-derived structs).\n`std-io` implies `yaml` and adds the filesystem reader on top of it; it is the\nonly feature that lets the crate touch `std::fs`, and it is intentionally left\nout of `full` so the pure kernel and the convenience stack both stay I/O-free.\n\n**Sealed Decks** ([EPIC-04](docs/EPIC-04_Sealed_Decks.md)) are always\non and dependency-free: \n\n- `Ordinal`/`Codebook` (a canonical card ↔ number bijection per deck)\n- `Permutation` (a shuffle as data)\n- `SlotPile` (a shoe of card *names* that shuffles, cuts and deals with no knowledge)\n- `Revealed` (the only slot → card map), and the five-item `Seal` adapter. \n\nNo kernel type holds ciphertext or is generic over a scheme. Real crypto backends are\nplanned as opt-in features outside `full`.\n\n### Provably-fair shuffles\n\nThe `commit-reveal` feature ([EPIC-04a](docs/EPIC-04a_Commit_Reveal_Shuffle.md))\nadds one dependency, `sha2`, and lets every participant in a game prove the\nshuffle was fair. Each participant commits to secret entropy, then everyone\nreveals; the combined seed fixes the shuffle through a frozen SHA-256\nderivation that any verifier, in any language, can reproduce from the\npublic transcript alone:\n\n```rust,ignore\n// needs `--features commit-reveal`; the same example is a compiled doctest in `src/seal/commit/mod.rs`\nuse cardpack::prelude::*;\n\nlet (dealer, player) = (ParticipantId(1), ParticipantId(2));\nlet a = Contribution::from_bytes([0x11; 32]); // Contribution::random(&mut rng) in real code\nlet b = Contribution::from_bytes([0x22; 32]);\n\nlet mut round = ShuffleRound::new([dealer, player])?;\nround.commit(dealer, a.commit())?;\nround.commit(player, b.commit())?;         // nobody may reveal before this point\nround.reveal(dealer, a)?;\nround.reveal(player, b)?;\n\nlet shuffled = Standard52::deck().shuffled_by_round(&round)?;\n# Ok::<(), CardError>(())\n```\n\n`commit_pile` / `verify_pile` let a dealer publish a blind commitment to a\nconcrete deck order before dealing and opening it later. Run\n`cargo ex provably_fair` for a two-party round end to end. This hides the\n*shuffle*, not the *cards*. Hiding cards is the next feature.\n\n### Sealed cards (holder-key seal)\n\nThe `seal-aead` feature ([EPIC-04b](docs/EPIC-04b_Holder_Key_Seal.md)) is the\nfirst real `Seal` backend: a trusted dealer seals every card under its own\nHKDF-derived key (XChaCha20-Poly1305, 42 public bytes per card), and a holder\nturns one card up by publishing one 32-byte token. A spectator with no secret\nverifies it through `Revealed::reveal_with`; the token opens nothing else.\n\n```rust,ignore\n// needs `--features seal-aead`; the same flow is a compiled doctest in `src/seal/aead/mod.rs`\nuse cardpack::prelude::*;\n\nlet dealer = HolderKeySeal::<Standard52>::dealer(DealKey::random(&mut rng), b\"table-7/hand-12\");\nlet (mut shoe, custody) = dealer.deal(&Standard52::deck(), &mut rng)?;   // SlotPile + Custody\nlet hole = shoe.draw(2).unwrap();                                        // slot names, no values\nlet tokens = dealer.tokens_for(hole.slots().iter().copied())?;\n\n// Holder publishes (slot, token); anyone verifies:\nlet spectator = HolderKeySeal::<Standard52>::verifier(b\"table-7/hand-12\");\nlet mut revealed = Revealed::<Standard52>::new();\nlet card = revealed.reveal_with(slot, custody.get(slot).unwrap(), &spectator, &token)?;\n```\n\nThree plain values — `SlotPile` (order), `Custody` (bytes), `Revealed`\n(values) — and a scheme that lives inside none of them. The RNG you pass\n**must** be a CSPRNG. Run `cargo ex holder_seal` for the flow end to end. The\n`crypto` feature turns on both backends; none of them is in `full`.\n\n## Decks as YAML\n\nWith `yaml`, every deck round-trips `deck → YAML → deck`. Documents use a\nself-describing **envelope** that carries the deck's identity — `version`,\n`name`, `fluent_deck_key`, `count`, `cards` — rather than a bare card list, so\na document can be checked against the deck it claims to be. The reader still\naccepts the legacy bare sequence, so the new format is a strict superset of\nwhat `BasicCard::cards_from_yaml_str` always took.\n\n```rust,ignore\n// This README is included in the crate docs, so its code blocks are compiled\n// as doctests. Ignored because it needs the `yaml` feature, which is off by\n// default; the executable versions live on the `YamlDecked` methods.\nuse cardpack::prelude::*;\n\n// Any DeckedBase implementor — including a deck you wrote — gets this free\n// via the blanket `YamlDecked` trait:\nlet yaml = French::to_yaml().unwrap();\nassert_eq!(French::deck_from_yaml(&yaml).unwrap(), French::base_vec());\n\n// A well-formed document describing the wrong deck is still rejected:\nassert!(Tarot::validate_yaml(&yaml).is_err());\n\n// `Pile` serialization preserves order, so hands and shuffles survive intact:\nlet shuffled = Pile::<Standard52>::deck().shuffled_with_seed(42);\nlet restored = Pile::<Standard52>::from_yaml(&shuffled.to_yaml().unwrap()).unwrap();\nassert_eq!(restored, shuffled);\n```\n\n`DeckKind::to_yaml` / `DeckKind::from_yaml` cover the non-generic path, for\ndecks known only at runtime. Golden fixtures for all shipped decks live in\n`tests/fixtures/yaml/` and are regenerated with `make yaml-fixtures`.\n\n## Funky — Balatro-style cards\n\nThe `funky` feature is a result of having my mind blown🤯 playing the \namazing solitare game [Balatro](https://www.playbalatro.com/). It honestly\nchanged the way I look at playing cards. Suddenly, suits and ranks are \njust two of an infinite possible number of pips that can be attached to a\n\"playing card\". I started realizing that there is little difference between\na French Deck of cards and creating heros in the\n[Evercraft Kata](https://github.com/guyroyse/evercraft-kata).\n\nThe goal of the feature is to see how hard I need to push the architecture of\nthis library to support decks such as those in Balatro. I guess the big idea\nwas the `MPip`, a sort of functional version of a pip on a card. \n\nTBH, this experiment demonstrates the rational behind designing games in flexible\nlanguages such as [Lua](https://www.lua.org/), over tyrannical ones such as my\nbeloved Rust.\n\nThere are a couple of use cases that are in the back of my mind for something like this.\nOne is a Balatro score solver, as a way to teach the math mechanics behind the game. The\nother is a library that would be able to create modded Balatro decks from simple yaml\nconfiguration files, similar to what the library already supports in simpler decks.\n\nIt is still very much a work in progress, which is documented here: \n[`docs/EPIC-01_Funky.md`](docs/EPIC-01_Funky.md).\n\nThere are two examples to see it in action:\n\n```shell\n# The four-phase scoring pipeline, phase by phase:\ncargo ex buffoon\n\n# A seeded four-act tour — round loop, editions, shop & vouchers, spectrals:\ncargo ex funky_tour\n```\n\n## WebAssembly\n\ncardpack compiles cleanly to `wasm32-unknown-unknown` (browser WASM)\nwith every feature combination. See [`docs/wasm.md`](docs/wasm.md) for\nthe consumer-side `getrandom` backend setup, recommended feature\ncombos, and runtime gotchas. A working example lives at\n[`examples/wasm.rs`](examples/wasm.rs).\n\n## Responsibilities\n\n* Represent a specific type of card deck.\n* Validate that a collection of cards is valid for that type of deck.\n* Create a textual representation of a deck that can be serialized and deserialized.\n* Shuffle a deck\n\n## Examples\n\nThe library has several examples programs, including `demo` which shows you the different decks\navailable.\n\nRun them with **`cargo ex <name>`**. Because cardpack is pure by default\n(`default = []`, see [Cargo features](#cargo-features)), most examples need\n`--features` to compile; `cargo ex` is an alias in\n[`.cargo/config.toml`](.cargo/config.toml) that supplies them for you, so\n`cargo ex demo` beats `cargo run --features full,funky --example demo`.\n\nFor the traditional 54 card French Deck with Jokers:\n\n```shell\n❯ cargo ex demo -- --french -v\n    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.06s\n     Running `target/debug/examples/demo --french -v`\n\nFrench Deck:          B🃟 L🃟 A♠ K♠ Q♠ J♠ T♠ 9♠ 8♠ 7♠ 6♠ 5♠ 4♠ 3♠ 2♠ A♥ K♥ Q♥ J♥ T♥ 9♥ 8♥ 7♥ 6♥ 5♥ 4♥ 3♥ 2♥ A♦ K♦ Q♦ J♦ T♦ 9♦ 8♦ 7♦ 6♦ 5♦ 4♦ 3♦ 2♦ A♣ K♣ Q♣ J♣ T♣ 9♣ 8♣ 7♣ 6♣ 5♣ 4♣ 3♣ 2♣\nFrench Deck Index:    BJ LJ AS KS QS JS TS 9S 8S 7S 6S 5S 4S 3S 2S AH KH QH JH TH 9H 8H 7H 6H 5H 4H 3H 2H AD KD QD JD TD 9D 8D 7D 6D 5D 4D 3D 2D AC KC QC JC TC 9C 8C 7C 6C 5C 4C 3C 2C\nFrench Deck Shuffled: K♣ 7♦ 8♣ Q♥ 6♠ J♦ 4♦ J♥ K♠ 9♥ 6♥ T♥ 2♦ 3♦ 3♣ J♣ 3♥ Q♣ 5♥ Q♦ 3♠ T♣ 7♥ 4♥ K♦ 5♦ 2♠ 6♦ T♠ 8♥ T♦ 7♠ 8♠ 2♣ Q♠ 7♣ A♣ 5♠ A♥ 9♣ 2♥ 9♦ 9♠ 4♠ K♥ 8♦ 5♣ A♦ L🃟 B🃟 A♠ 6♣ 4♣ J♠\n\n  English                  | German                   | French                   | Latin                    | Klingon\n  ------------------------ | ------------------------ | ------------------------ | ------------------------ | ------------------------\n  Joker Full-Color         | Joker Großer             | Joker Grand              | Joker Magnus             | Joker qoH'a'\n  Joker One-Color          | Joker Kleiner            | Joker Petit              | Joker Parvus             | Joker qoHHom\n  Ace of Spades            | Ass Spaten               | As de Pique              | As Spathae               | wa'DIch yan\n  King of Spades           | König Spaten             | Roi de Pique             | Rex Spathae              | ta' yan\n  Queen of Spades          | Dame Spaten              | Dame de Pique            | Regina Spathae           | ta'be' yan\n  Jack of Spades           | Bube Spaten              | Valet de Pique           | Famulus Spathae          | toy'wI' yan\n  Ten of Spades            | Zhen Spaten              | Dix de Pique             | Decem Spathae            | wa'maH yan\n  ...\n```\n\nDisplay a hand of [Bridge](https://en.wikipedia.org/wiki/Contract_bridge):\n\n```shell\n❯ cargo ex bridge                                                          \n    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.33s\n     Running `target/debug/examples/bridge`\nFirst, let's deal out a random bridge hand.\n\nHere it is in Portable Bridge Notation:\n    W:KJT.JT63.K8.QJT9 A75.KQ9874.65.AK Q6432.5.AJ74.853 98.A2.QT932.7642\n\nHow does it look as a traditional compass?\n               NORTH\n            ♠ A 7 5\n            ♥ K Q 9 8 7 4\n            ♦ 6 5\n            ♣ A K\n\n       WEST              EAST\n    ♠ K J T           ♠ Q 6 4 3 2\n    ♥ J T 6 3         ♥ 5\n    ♦ K 8             ♦ A J 7 4\n    ♣ Q J T 9         ♣ 8 5 3\n\n                SOUTH\n             ♠ 9 8\n             ♥ A 2\n             ♦ Q T 9 3 2\n             ♣ 7 6 4 2\n\nNow, let's take a PBN Deal String and convert it into a bridge hand.\nHere's the original' Portable Bridge Notation:\n    S:Q42.Q52.AQT943.Q 97.AT93.652.T743 AJT85.J76.KJ.A65 K63.K84.87.KJ982\n\nAs a bridge compass:\n\n                NORTH\n             ♠ A J T 8 5\n             ♥ J 7 6\n             ♦ K J\n             ♣ A 6 5\n\n       WEST              EAST\n    ♠ 9 7             ♠ K 6 3\n    ♥ A T 9 3         ♥ K 8 4\n    ♦ 6 5 2           ♦ 8 7\n    ♣ T 7 4 3         ♣ K J 9 8 2\n\n               SOUTH\n            ♠ Q 4 2\n            ♥ Q 5 2\n            ♦ A Q T 9 4 3\n            ♣ Q\n\n```\n\nOther decks in the demo program are `canasta`, `euchre`, `short`, `pinochle`, `skat`, `spades`,\n`standard`, `tarot`, `mughal`, and `dashavatara`.\n\nOther examples are:\n\n- `cargo ex handandfoot` - Shows how to support more than one decks like in the game [Hand and Foot](https://www.wikihow.com/Play-Hand-and-Foot).\n- `cargo ex poker` - A random heads up [no-limit Poker](https://en.wikipedia.org/wiki/Texas_hold_%27em) deal.\n- `cargo ex poker_eval` - Scores a Texas Hold'em board via the [`ckc-rs`](https://crates.io/crates/ckc-rs) evaluator, picking each player's best 5-card hand from their 7.\n- `cargo ex range` - Prints a 13×13 starting-hand range chart.\n- `cargo ex buffoon` - The Balatro four-phase scoring pipeline, phase by phase (see [Funky](#funky--balatro-style-cards)).\n- `cargo ex funky_tour` - A seeded tour of the funky engine: round loop, editions, shop & vouchers, spectral cards.\n- `cargo build --target wasm32-unknown-unknown --example wasm` - Minimal browser-WASM build showing wasm-friendly API patterns (seeded shuffle, no filesystem). See [`docs/wasm.md`](docs/wasm.md).\n\n## References\n\n* [Card games in Germany](https://www.pagat.com/national/germany.html)\n* [Playing cards in Unicode](https://en.wikipedia.org/wiki/Playing_cards_in_Unicode)\n* [Balatro](https://www.playbalatro.com/)\n  * [balatrowiki.org](https://balatrowiki.org/)\n  * [balatrogame.fandom.com](https://balatrogame.fandom.com/)\n  * [Balatro Modding Guide](https://steamcommunity.com/sharedfiles/filedetails/?id=3400691352)\n\n### Other Deck of Cards Libraries\n\n* [ascclemens/cards](https://github.com/ascclemens/cards)\n* [locka99/deckofcards-rs](https://github.com/locka99/deckofcards-rs)\n* [vsupalov/cards-rs](https://github.com/vsupalov/cards-rs)\n* [droundy/bridge-cards](https://github.com/droundy/bridge-cards)\n* Tarot Libraries\n  * [lawreka/ascii-tarot](https://github.com/lawreka/ascii-tarot)\n  * [pietdaniel/tarot](https://github.com/pietdaniel/tarot)\n  * [jeremytarling/ruby-tarot](https://github.com/jeremytarling/ruby-tarot)\n\n## Dependencies\n\n* [Colored](https://github.com/colored-rs/colored)\n* [Fluent Templates](https://github.com/XAMPPRocky/fluent-templates)\n  * [Project Fluent](https://www.projectfluent.org/)\n* [itertools](https://github.com/rust-itertools/itertools)\n* [log](https://github.com/rust-lang/log)\n* [rand](https://github.com/rust-random/rand)\n* [serde](https://github.com/serde-rs/serde)\n  * [serde_norway](https://crates.io/crates/serde_norway)\n* [thiserror](https://github.com/dtolnay/thiserror)\n\n## Dev Dependencies\n\n* [term-table](https://github.com/RyanBluth/term-table-rs)\n* [rstest](https://github.com/la10736/rstest) - Fixture-based test framework for Rust\n\n## TODO\n\n* [Hanafuda](https://en.wikipedia.org/wiki/Hanafuda)\n  * [고스톱 (Go-Stop)](https://en.wikipedia.org/wiki/Go-Stop)\n    * [Go-Stop - The Cards](https://www.sloperama.com/gostop/cards.html)\n    * [nbry/go-stop-rust](https://github.com/nbry/go-stop-rust)\n  * [Sakura](https://en.wikipedia.org/wiki/Sakura_(card_game))\n* [Cinch](https://en.wikipedia.org/wiki/Cinch_(card_game))\n* [Zwickern](https://en.wikipedia.org/wiki/Zwickern)\n* [Beggar-my-neighbour](https://en.wikipedia.org/wiki/Beggar-my-neighbour)\n",
  "bytes": 20854,
  "sha": "d57d3ad4589f99b7f9b8cd4e54373ddf69bf0d455ab787536c306c0d26100c52",
  "repo_slug": "imperialbower/cardpack.rs",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/okf_imperialbower_cardpack_rs_okf_index_md_78cc7910/readme"
}