Skip to main content

Crate cardpack

Crate cardpack 

Source
Expand description

Cardpack is a library to represent various decks of playing cards. The library is designed to support the following features:

§Overview

The structure of the library is the following:

  • Pile - A generic collection of Cards that implement the DeckedBase trait
    • Card - A generic wrapper around BasicCard that implements the DeckedBase trait.
      • BasicCard - The basic data of a Card without any generic constraints. Made up of a Rank and Suit Pip.
        • Pip - The basic data of a Rank and Suit, used for sorting, evaluating, and displaying Cards.

The library supports the following decks:

§French Deck

The French deck is the foundation Deck of playing cards. It is made up of a collection of 54 Cards with 13 ranks in each of the four suits, and two jokers. Most of the other decks are made up on the French BasicCards.

// ignored under cargo test --no-default-features (uses fluent_name* / FluentName at the bottom)
use cardpack::prelude::*;

let mut french_deck = Pile::<French>::deck();

// It's also possible to call the deck method directly on the specific generic implementing type:
let mut french_deck = French::deck();

assert_eq!(french_deck.len(), 54);
assert_eq!(
    french_deck.to_string(),
    "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♣"
);
assert!(french_deck.contains(&Card::<French>::new(FrenchBasicCard::ACE_SPADES)));

let shuffled = french_deck.shuffled();

// Use the `french_cards!` macro to parse the shuffled deck as a string:
let parsed = french_cards!(shuffled.to_string().as_str());

// Verify that the cards, in any order, are the same:
assert!(french_deck.same(&parsed));

// When sorted, they should be exactly the same:
assert_eq!(parsed.sorted(), french_deck);

// For a joker card's index string, `B` stands for the Big or Full-Color Joker and `L` for the
// Little or One-Color Joker, with `🃟` being the symbol character for the joker suit.
let jokers = french_deck.draw(2).unwrap();
assert_eq!(jokers.to_string(), "B🃟 L🃟");

let royal_flush = french_deck.draw(5).unwrap();
assert_eq!(royal_flush.to_string(), "A♠ K♠ Q♠ J♠ T♠");
assert_eq!(royal_flush.index(), "AS KS QS JS TS");

// The original deck should now have five cards less:
assert_eq!(french_deck.len(), 47);

// Cards can provide a longer description in English and German:
assert_eq!(Card::<French>::new(FrenchBasicCard::ACE_SPADES).fluent_name_default(), "Ace of Spades");
assert_eq!(Card::<French>::new(FrenchBasicCard::QUEEN_HEARTS).fluent_name(&FluentName::DEUTSCH), "Dame Herzen");

At some point I would love to add support for more languages.

§Standard 52 Card Deck

A Standard52 deck is a French deck without the two jokers.

use cardpack::prelude::*;

let mut standard52_deck = Pile::<Standard52>::deck();

assert_eq!(standard52_deck.len(), 52);
assert_eq!(
    standard52_deck.to_string(),
    "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♣"
);

// It includes the card! and cards! macros for easy Standard52 card creation:
assert_eq!(card!(AS), Card::<Standard52>::new(FrenchBasicCard::ACE_SPADES));
assert_eq!(cards!("AS KS QS JS TS"), standard52_deck.draw(5).unwrap());

By default, a Deck displays the suit symbols when you display the values. It also has the ability to return the letter values, or what are called “index strings”.

use cardpack::prelude::*;

assert_eq!(
    Pile::<Standard52>::deck().index(),
    "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"
);

An important thing to remember about the decks is that the cards have their weight inside them to facilitate sorting. If you wanted a deck for a game of poker where the lowest hand wins, you would need to create a separate deck file with the card’s Rank weights inverted. The Razz deck (basic::decks::razz::Razz, behind yaml) is an example of this. It is also an example of how you can create a Deck where the BasicCard for the deck are generated programmatically in YAML instead using the power of Serde

// ignored under cargo test --no-default-features (Razz needs the `yaml` feature)
use cardpack::prelude::*;
assert_eq!(Pile::<Razz>::deck().draw(5).unwrap().to_string(), "A♠ 2♠ 3♠ 4♠ 5♠");
assert_eq!(Pile::<Standard52>::deck().draw(5).unwrap().to_string(), "A♠ K♠ Q♠ J♠ T♠");

The raw YAML that was used to create the Razz deck (basic::decks::razz::Razz, behind yaml) is available in the source code.

Other decks include:

  • Canasta - 2 Modern decks with the red 3s made jokers.
  • Euchre24 - A 24 card version of a Euchre deck.
  • Euchre32 - A 32 card version of a Euchre deck.
  • ShortDeck - A 36 card deck with ranks 6 through Ace.
  • Pinochle - A 48 card deck with two copies of the 9 through Ace ranks.
  • Skat - A 32 card German card game with different suits and ranks.
  • Spades - A Modern deck with the 2 of Clubs and 2 of Diamonds removed.
  • Tarot - A 78 card deck with 22 Major Arcana and 56 Minor Arcana cards.

In past versions of the library there was a Hand and Foot deck. This has been removed because it can simply be created using a French and what functionality is available in the Decked trait:

use cardpack::prelude::*;

let hand_and_foot_4players = French::decks(4);
assert_eq!(hand_and_foot_4players.len(), 216);

let hand_and_foot_5players = French::decks(5);
assert_eq!(hand_and_foot_5players.len(), 270);

§Custom Deck example:

Here’s a very simple example where we create a tiny deck with only the ace and kink ranks, and only the spades and hearts suits. Just for fun, we’ll include a tiny! macro for one Tiny card.

// ignored under cargo test --no-default-features (uses colored::Color directly)
use std::collections::HashMap;
use colored::Color;
use cardpack::prelude::*;

#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Tiny {}

impl Tiny {
   pub const DECK_SIZE: usize = 4;

    pub const DECK: [BasicCard; Tiny::DECK_SIZE] = [
        FrenchBasicCard::ACE_SPADES,
        FrenchBasicCard::KING_SPADES,
        FrenchBasicCard::ACE_HEARTS,
        FrenchBasicCard::KING_HEARTS,
    ];
}

impl DeckedBase for Tiny {
    fn base_vec() -> Vec<BasicCard> {
        Tiny::DECK.to_vec()
    }

    fn colors() -> HashMap<Pip, Color> {
        Standard52::colors()
    }

    fn deck_name() -> String {
        "Tiny".to_string()
    }

    fn fluent_deck_key() -> String {
        FLUENT_KEY_BASE_NAME_FRENCH.to_string()
    }
}

// Let's you call Decked methods directly on the Tiny type:
impl Decked<Tiny> for Tiny {}

macro_rules! tiny {
    (AS) => {
        Card::<Tiny>::new(FrenchBasicCard::ACE_SPADES)
    };
    (KS) => {
        Card::<Tiny>::new(FrenchBasicCard::KING_SPADES)
    };
    (AH) => {
        Card::<Tiny>::new(FrenchBasicCard::ACE_HEARTS)
    };
    (KH) => {
        Card::<Tiny>::new(FrenchBasicCard::KING_HEARTS)
    };
    (__) => {
        Card::<Tiny>::default()
    };
}

let mut deck = Tiny::deck();

assert_eq!(deck.to_string(), "A♠ K♠ A♥ K♥");

// Every deck comes with the Ranged trait automatically:
assert_eq!(deck.combos(2).to_string(), "A♠ K♠, A♠ A♥, A♠ K♥, K♠ K♥, A♥ K♠, A♥ K♥");

// Deal from the top of the deck:
assert_eq!(deck.draw_first().unwrap().to_string(), "A♠");

// Deal from the bottom of the deck:
assert_eq!(deck.draw_last().unwrap().to_string(), "K♥");

// Should be two cards remaining:
assert_eq!(deck.len(), 2);
assert_eq!(deck.index(), "KS AH");

// Draw a remaining card:
assert_eq!(deck.draw_first().unwrap(), tiny!(KS));

// Draw the last card:
assert_eq!(deck.draw_last().unwrap(), tiny!(AH));

// And now the deck is empty:
assert!(deck.draw_first().is_none());
assert!(deck.draw_last().is_none());

// Of all the tests you could use to make sure that your deck is setup correctly, the most
// fundamental is the validate method.
assert!(Tiny::validate());

§Decks as YAML

With the yaml feature every deck round-trips through YAML — deck → YAML → deck — using a self-describing envelope that carries the deck’s identity alongside its cards, rather than a bare card list:

version: 1
name: Tiny
fluent_deck_key: french
count: 4
cards:
- suit: { weight: 3, pip_type: Suit, index: 'S', symbol: '♠', value: 4 }
  rank: { weight: 12, pip_type: Rank, index: 'A', symbol: 'A', value: 11 }
# ...

There are three entry points, one per layer:

  • YamlDecked (behind yaml) — blanket-implemented for every DeckedBase type, so the Tiny deck defined above gets to_yaml, deck_from_yaml, and validate_yaml for free, exactly like the decks shipped in this crate.
  • DeckKind — the non-generic path: serialize a deck you only know at runtime, and parse a document back into the registry variant that produced it.
  • Pile<T> — the instance path. Order is preserved, so a shuffled deck or a dealt hand round-trips as-is. Unlike the deck-level paths, a Pile may legitimately be empty (fully drawn) and need not hold every card in the deck; membership, not cardinality, is the rule.
// ignored under cargo test --no-default-features (needs the `yaml` feature)
use cardpack::prelude::*;

// Type level: any DeckedBase implementor, including your own.
let yaml = French::to_yaml().unwrap();
assert_eq!(French::deck_from_yaml(&yaml).unwrap(), French::base_vec());

// A well-formed document describing the *wrong* deck is still an error:
assert!(Tarot::validate_yaml(&yaml).is_err());

// Registry level: round-trips through the deck's identity.
for kind in DeckKind::all() {
    assert_eq!(DeckKind::from_yaml(&kind.to_yaml().unwrap()).unwrap(), *kind);
}

// Instance level: order survives, and so does a partial hand.
let shuffled = Pile::<Standard52>::deck().shuffled_with_seed(42);
let restored = Pile::<Standard52>::from_yaml(&shuffled.to_yaml().unwrap()).unwrap();
assert_eq!(restored, shuffled);

The reader accepts the legacy bare-sequence form as well — the format BasicCard::cards_from_yaml_str has always taken, and the one src/basic/decks/yaml/razz.yaml is written in — so envelope support is a strict superset of what came before.

§cardpack.rs

Build and Test codecov Crates.io Version Rustdocs

Generic pack of cards library written in Rust. The goals of the library include:

  • Various types of decks of cards.
  • Internationalization support.
  • Ability to create custom sorts for a specific pack of cards.

UPDATE: This is a complete rewrite of the library taking advantage of generics in order to make the code cleaner, and easier to extend.

§Setup

Build and run common tasks with GNU make:

make

Run make help to see all available targets.

§Usage

use cardpack::prelude::*;

fn main() {
  let mut pack = Standard52::deck();

  // Deterministic shuffle — works in the pure, `no_std` default build.
  // With the `std` feature you can call `pack.shuffle()` for a thread-RNG shuffle.
  pack.shuffle_with_seed(42);

  // Deal no-limit hold'em hands for two players:
  let small_blind = pack.draw(2).unwrap().sorted_by_rank();
  let big_blind = pack.draw(2).unwrap().sorted_by_rank();

  println!("small blind: {}", small_blind.to_string());
  println!("big blind:   {}", big_blind.to_string());

  let flop = pack.draw(3).unwrap();
  let turn = pack.draw(1).unwrap();
  let river = pack.draw(1).unwrap();

  println!();
  println!("flop : {}", flop.to_string());
  println!("turn : {}", turn.to_string());
  println!("river: {}", river.to_string());

  // Now, let's validate that the cards when collected back together are a valid Standard52
  // deck of cards.
  let reconstituted_pile =
          Pile::<Standard52>::pile_on(&*vec![pack, small_blind, big_blind, flop, turn, river]);
  assert!(Standard52::deck().same(&reconstituted_pile));
}

§Details

The goal of this library is to be able to support the creation of card decks of various sizes and suits. Out of the box, the library supports:

The project takes advantage of Project Fluent’s Rust support to offer internationalization. Current languages supported are English, German, French, Latin, and Klingon.

§Cargo features

cardpack is pure by default: a bare dependency is an alloc-only, no_std, no-I/O domain kernel. Every dependency-bearing or I/O-bearing capability is gated behind a Cargo feature, so consumers opt in to exactly what they need:

FeatureDefaultPulls inWhat it turns on
fullnoeverything belowUmbrella turning on std + i18n + colored-display + yaml + serde
stdnolibstdstd-only APIs (thread-RNG shuffle, draw_random, etc.)
i18nnofluent-templatesFluentName, Named, Card::fluent_name*, localization
colored-displaynocoloredColor, Colorize, Card::color*, Pile::to_color_*
yamlnoserde_norwayFull deck ↔ YAML round-tripping (pure, in-memory) — see Decks as YAML; plus the Razz deck
serdenoserdeSerialize/Deserialize derives on Pip/Card/Pile etc.
std-ionoBasicCard::cards_from_yaml_file — reads decks from YAML files (std::fs). The crate’s one filesystem seam; not in full
funkynostdThe Balatro-style engine — see Funky below
seal-test-doublenoPlaintextSeal (no security) and the seal_roundtrip conformance helper for testing a Seal backend; not in full
commit-revealnosha2Provably-fair shuffles: ShuffleRound, Commitment/Contribution, CombinedSeed, commit_pile, Pile::shuffled_by_round — see Provably-fair shuffles; not in full
seal-aeadnochacha20poly1305, hkdf, sha2, zeroizeHolder-key seal: HolderKeySeal, DealKey/CardKey, SealedBytes, Custody — see Sealed cards; not in full
cryptono= both aboveUmbrella over commit-reveal + seal-aead; not in full

To get the previous “batteries-included” behavior, opt into full:

# Full convenience stack (i18n, colored display, YAML, serde):
cardpack = { version = "0.8", features = ["full"] }

# Or trim to just what you need — e.g. the pure kernel plus serde:
cardpack = { version = "0.8", features = ["serde"] }

# Or the pure, no_std, alloc-only kernel with no extra deps at all:
cardpack = "0.8"

yaml implies serde (it deserializes into the serde-derived structs). std-io implies yaml and adds the filesystem reader on top of it; it is the only feature that lets the crate touch std::fs, and it is intentionally left out of full so the pure kernel and the convenience stack both stay I/O-free.

The sealed-deck kernel (EPIC-04) is always on and dependency-free: Ordinal/Codebook (a canonical card ↔ number bijection per deck), Permutation (a shuffle as data), SlotPile (a shoe of card names that shuffles, cuts and deals with no knowledge), Revealed (the only slot → card map), and the five-item Seal adapter. No kernel type holds ciphertext or is generic over a scheme. Real crypto backends are planned as opt-in features outside full.

§Provably-fair shuffles

The commit-reveal feature (EPIC-04a) adds one dependency, sha2, and lets every participant in a game prove the shuffle was fair. Each participant commits to secret entropy, then everyone reveals; the combined seed fixes the shuffle through a frozen SHA-256 derivation that any verifier — in any language — can reproduce from the public transcript alone:

// needs `--features commit-reveal`; the same example is a compiled doctest in `src/seal/commit/mod.rs`
use cardpack::prelude::*;

let (dealer, player) = (ParticipantId(1), ParticipantId(2));
let a = Contribution::from_bytes([0x11; 32]); // Contribution::random(&mut rng) in real code
let b = Contribution::from_bytes([0x22; 32]);

let mut round = ShuffleRound::new([dealer, player])?;
round.commit(dealer, a.commit())?;
round.commit(player, b.commit())?;         // nobody may reveal before this point
round.reveal(dealer, a)?;
round.reveal(player, b)?;

let shuffled = Standard52::deck().shuffled_by_round(&round)?;

commit_pile / verify_pile let a dealer publish a blind commitment to a concrete deck order before dealing and open it after. Run cargo ex provably_fair for a two-party round end to end. This hides the shuffle, not the cards; hiding cards is the next feature.

§Sealed cards (holder-key seal)

The seal-aead feature (EPIC-04b) is the first real Seal backend: a trusted dealer seals every card under its own HKDF-derived key (XChaCha20-Poly1305, 42 public bytes per card), and a holder turns one card up by publishing one 32-byte token. A spectator with no secret verifies it through Revealed::reveal_with; the token opens nothing else.

// needs `--features seal-aead`; the same flow is a compiled doctest in `src/seal/aead/mod.rs`
use cardpack::prelude::*;

let dealer = HolderKeySeal::<Standard52>::dealer(DealKey::random(&mut rng), b"table-7/hand-12");
let (mut shoe, custody) = dealer.deal(&Standard52::deck(), &mut rng)?;   // SlotPile + Custody
let hole = shoe.draw(2).unwrap();                                        // slot names, no values
let tokens = dealer.tokens_for(hole.slots().iter().copied())?;

// Holder publishes (slot, token); anyone verifies:
let spectator = HolderKeySeal::<Standard52>::verifier(b"table-7/hand-12");
let mut revealed = Revealed::<Standard52>::new();
let card = revealed.reveal_with(slot, custody.get(slot).unwrap(), &spectator, &token)?;

Three plain values — SlotPile (order), Custody (bytes), Revealed (values) — and a scheme that lives inside none of them. The RNG you pass must be a CSPRNG. Run cargo ex holder_seal for the flow end to end. The crypto feature turns on both backends; none of them is in full.

§Decks as YAML

With yaml, every deck round-trips deck → YAML → deck. Documents use a self-describing envelope that carries the deck’s identity — version, name, fluent_deck_key, count, cards — rather than a bare card list, so a document can be checked against the deck it claims to be. The reader still accepts the legacy bare sequence, so the new format is a strict superset of what BasicCard::cards_from_yaml_str always took.

// This README is included in the crate docs, so its code blocks are compiled
// as doctests. Ignored because it needs the `yaml` feature, which is off by
// default; the executable versions live on the `YamlDecked` methods.
use cardpack::prelude::*;

// Any DeckedBase implementor — including a deck you wrote — gets this free
// via the blanket `YamlDecked` trait:
let yaml = French::to_yaml().unwrap();
assert_eq!(French::deck_from_yaml(&yaml).unwrap(), French::base_vec());

// A well-formed document describing the wrong deck is still rejected:
assert!(Tarot::validate_yaml(&yaml).is_err());

// `Pile` serialization preserves order, so hands and shuffles survive intact:
let shuffled = Pile::<Standard52>::deck().shuffled_with_seed(42);
let restored = Pile::<Standard52>::from_yaml(&shuffled.to_yaml().unwrap()).unwrap();
assert_eq!(restored, shuffled);

DeckKind::to_yaml / DeckKind::from_yaml cover the non-generic path, for decks known only at runtime. Golden fixtures for all shipped decks live in tests/fixtures/yaml/ and are regenerated with make yaml-fixtures.

§Funky — Balatro-style cards

The funky feature is a result of having my mind blown🤯 playing the amazing solitare game Balatro. It honestly changed the way I look at playing cards. Suddenly, suits and ranks are just two of an infinite possible number of pips that can be attached to a “playing card”. I started realizing that there is little difference between a French Deck of cards and creating heros in the Evercraft Kata.

The goal of the feature is to see how hard I need to push the architecture of this library to support decks such as those in Balatro. I guess the big idea was the MPip, a sort of functional version of a pip on a card.

TBH, this experiment demonstrates the rational behind designing games in flexible languages such as Lua, over tyrannical ones such as my beloved Rust.

There are a couple of use cases that are in the back of my mind for something like this. One is a Balatro score solver, as a way to teach the math mechanics behind the game. The other is a library that would be able to create modded Balatro decks from simple yaml configuration files, similar to what the library already supports in simpler decks.

It is still very much a work in progress, which is documented here: docs/EPIC-01_Funky.md.

There are two examples to see it in action:

# The four-phase scoring pipeline, phase by phase:
cargo ex buffoon

# A seeded four-act tour — round loop, editions, shop & vouchers, spectrals:
cargo ex funky_tour

§WebAssembly

cardpack compiles cleanly to wasm32-unknown-unknown (browser WASM) with every feature combination. See docs/wasm.md for the consumer-side getrandom backend setup, recommended feature combos, and runtime gotchas. A working example lives at examples/wasm.rs.

§Responsibilities

  • Represent a specific type of card deck.
  • Validate that a collection of cards is valid for that type of deck.
  • Create a textual representation of a deck that can be serialized and deserialized.
  • Shuffle a deck

§Examples

The library has several examples programs, including demo which shows you the different decks available.

Run them with cargo ex <name>. Because cardpack is pure by default (default = [], see Cargo features), most examples need --features to compile; cargo ex is an alias in .cargo/config.toml that supplies them for you, so cargo ex demo beats cargo run --features full,funky --example demo.

For the traditional 54 card French Deck with Jokers:

❯ cargo ex demo -- --french -v
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.06s
     Running `target/debug/examples/demo --french -v`

French 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♣
French 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
French 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♠

  English                  | German                   | French                   | Latin                    | Klingon
  ------------------------ | ------------------------ | ------------------------ | ------------------------ | ------------------------
  Joker Full-Color         | Joker Großer             | Joker Grand              | Joker Magnus             | Joker qoH'a'
  Joker One-Color          | Joker Kleiner            | Joker Petit              | Joker Parvus             | Joker qoHHom
  Ace of Spades            | Ass Spaten               | As de Pique              | As Spathae               | wa'DIch yan
  King of Spades           | König Spaten             | Roi de Pique             | Rex Spathae              | ta' yan
  Queen of Spades          | Dame Spaten              | Dame de Pique            | Regina Spathae           | ta'be' yan
  Jack of Spades           | Bube Spaten              | Valet de Pique           | Famulus Spathae          | toy'wI' yan
  Ten of Spades            | Zhen Spaten              | Dix de Pique             | Decem Spathae            | wa'maH yan
  ...

Display a hand of Bridge:

❯ cargo ex bridge                                                          
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.33s
     Running `target/debug/examples/bridge`
First, let's deal out a random bridge hand.

Here it is in Portable Bridge Notation:
    W:KJT.JT63.K8.QJT9 A75.KQ9874.65.AK Q6432.5.AJ74.853 98.A2.QT932.7642

How does it look as a traditional compass?
               NORTH
            ♠ A 7 5
            ♥ K Q 9 8 7 4
            ♦ 6 5
            ♣ A K

       WEST              EAST
    ♠ K J T           ♠ Q 6 4 3 2
    ♥ J T 6 3         ♥ 5
    ♦ K 8             ♦ A J 7 4
    ♣ Q J T 9         ♣ 8 5 3

                SOUTH
             ♠ 9 8
             ♥ A 2
             ♦ Q T 9 3 2
             ♣ 7 6 4 2

Now, let's take a PBN Deal String and convert it into a bridge hand.
Here's the original' Portable Bridge Notation:
    S:Q42.Q52.AQT943.Q 97.AT93.652.T743 AJT85.J76.KJ.A65 K63.K84.87.KJ982

As a bridge compass:

                NORTH
             ♠ A J T 8 5
             ♥ J 7 6
             ♦ K J
             ♣ A 6 5

       WEST              EAST
    ♠ 9 7             ♠ K 6 3
    ♥ A T 9 3         ♥ K 8 4
    ♦ 6 5 2           ♦ 8 7
    ♣ T 7 4 3         ♣ K J 9 8 2

               SOUTH
            ♠ Q 4 2
            ♥ Q 5 2
            ♦ A Q T 9 4 3
            ♣ Q

Other decks in the demo program are canasta, euchre, short, pinochle, skat, spades, standard, tarot, mughal, and dashavatara.

Other examples are:

  • cargo ex handandfoot - Shows how to support more than one decks like in the game Hand and Foot.
  • cargo ex poker - A random heads up no-limit Poker deal.
  • cargo ex poker_eval - Scores a Texas Hold’em board via the ckc-rs evaluator, picking each player’s best 5-card hand from their 7.
  • cargo ex range - Prints a 13×13 starting-hand range chart.
  • cargo ex buffoon - The Balatro four-phase scoring pipeline, phase by phase (see Funky).
  • cargo ex funky_tour - A seeded tour of the funky engine: round loop, editions, shop & vouchers, spectral cards.
  • 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.

§References

§Other Deck of Cards Libraries

§Dependencies

§Dev Dependencies

§TODO

Modules§

basic
common
funky
localization
prelude
preludes
seal
Cards you cannot see.

Macros§

basic
basic_cell
bcard
bcards
card
This macro is to allow for quick and easy generation of individual cards from the most common Standard52 deck.
cards
A macro to create a Pile of Standard52 cards from a string.
french_cards
A macro to create a Pile of French Deck cards from a string.