card-core 0.1.1

Core playing-card types and abstractions
Documentation

card-cores

Reusable playing-card traits and concrete card/deck implementations for Rust.

The crate currently provides:

  • Generic Card and Deck traits
  • A compact standard playing-card representation
  • A traditional 52-card deck
  • Parsing and display for ranks, suits, and cards
  • Drawing, shuffling, removal, iteration, and fixed-size combinations
  • Convenience decks for Kuhn Poker and Leduc Hold'em

Modules

  • card-cores::traits contains the generic Card and Deck interfaces.
  • card-cores::traditional contains the standard rank, suit, card, and deck types.

Usage

use card-cores::{
    traditional::{Card, Deck, Rank, Suit},
    traits::{Card as _, Deck as _},
};

let ace_of_spades = Card::from((Rank::Ace, Suit::Spades));

assert_eq!(ace_of_spades.rank(), Rank::Ace);
assert_eq!(ace_of_spades.suit(), Suit::Spades);

let mut deck = Deck::new();
assert_eq!(deck.len(), 52);

deck.shuffle();
let drawn = deck.draw();

assert!(drawn.is_some());
assert_eq!(deck.len(), 51);

Cards

A traditional card is constructed from a Rank and Suit:

use card-cores::traditional::{Card, Rank, Suit};

let card = Card::from((Rank::Queen, Suit::Hearts));

Cards use a compact u8 representation:

use card-cores::traditional::{Card, Rank, Suit};

let card = Card::from((Rank::Ace, Suit::Spades));
let value: u8 = card.into();

assert_eq!(value, 51);

Cards are encoded in rank-major order:

2c 2d 2h 2s 3c 3d 3h 3s ... Ac Ad Ah As

The supported rank symbols are:

2 3 4 5 6 7 8 9 T J Q K A

The supported suit symbols are:

c d h s

These represent clubs, diamonds, hearts, and spades.

Parsing

Cards can be parsed from their two-character representation:

use card-cores::traditional::Card;

let ace_of_spades = Card::try_from("As")?;
let ten_of_diamonds = Card::try_from("Td")?;

# Ok::<(), card-cores::traditional::ParseCardError>(())

Rank parsing is case-insensitive, and suit parsing accepts either uppercase or lowercase letters.

Decks

Create a complete 52-card deck through the Deck trait:

use card-cores::{traditional::Deck, traits::Deck as _};

let deck = Deck::new();

assert_eq!(deck.len(), 52);
assert!(!deck.is_empty());

Drawing

draw removes and returns the final card in the deck's current ordering:

use card-cores::{traditional::Deck, traits::Deck as _};

let mut deck = Deck::new();
let card = deck.draw();

assert!(card.is_some());
assert_eq!(deck.len(), 51);

Shuffling

use card-cores::{traditional::Deck, traits::Deck as _};

let mut deck = Deck::new();
deck.shuffle();

Removing a card

use card-cores::{
    traditional::{Card, Deck, Rank, Suit},
    traits::Deck as _,
};

let mut deck = Deck::new();
let ace_of_spades = Card::from((Rank::Ace, Suit::Spades));

assert_eq!(deck.remove(&ace_of_spades), Some(ace_of_spades));
assert_eq!(deck.remove(&ace_of_spades), None);
assert_eq!(deck.len(), 51);

Removal uses swap removal, so it does not preserve the ordering of the remaining cards.

Iterating without consuming the deck

use card-cores::{traditional::Deck, traits::Deck as _};

let deck = Deck::new();

for card in deck.iter() {
    println!("{card}");
}

A deck can also be consumed through IntoIterator:

use card-cores::{traditional::Deck, traits::Deck as _};

let deck = Deck::new();

for card in deck {
    println!("{card}");
}

Card combinations

Deck::combinations returns every unordered combination of N cards as an array:

use card-cores::{traditional::Deck, traits::Deck as _};

let deck = Deck::new();
let number_of_two_card_hands = deck.combinations::<2>().count();

assert_eq!(number_of_two_card_hands, 1_326);

Because N is a const generic parameter, each result has the type [Card; N]:

use card-cores::{traditional::Deck, traits::Deck as _};

let deck = Deck::new();

for [first, second] in deck.combinations::<2>() {
    println!("{first} {second}");
}

Kuhn Poker

Deck::kuhn creates a three-card deck containing one jack, queen, and king:

use card-cores::{traditional::Deck, traits::Deck as _};

let deck = Deck::kuhn();

assert_eq!(deck.len(), 3);

The deck contains:

Kc Qc Jc

Leduc Hold'em

Deck::leduc creates a six-card deck containing two cards of each rank:

use card-cores::{traditional::Deck, traits::Deck as _};

let deck = Deck::leduc();

assert_eq!(deck.len(), 6);

The deck contains:

Kc Qc Jc Kd Qh Js

The suits distinguish cards with equal ranks; standard Leduc hand strength depends on rank rather than suit.

Generic interfaces

The traits in card-cores::traits allow card-game implementations to operate independently of the traditional card representation.

Card

A card implementation defines associated rank and suit types and exposes accessors for each:

use core::{fmt::Display, hash::Hash};

pub trait Card:
    Copy
    + Display
    + Eq
    + Hash
    + Ord
    + Send
    + Sync
    + 'static
    + From<(Self::Rank, Self::Suit)>
    + Into<u8>
    + for<'a> TryFrom<&'a str>
{
    type Rank: Copy + Eq + Hash + Ord + Send + Sync + 'static;
    type Suit: Copy + Eq + Hash + Ord + Send + Sync + 'static;

    fn rank(self) -> Self::Rank;
    fn suit(self) -> Self::Suit;
}

Deck

A deck owns cards and provides the operations commonly needed by card games:

pub trait Deck: Clone + Default + Eq + IntoIterator<Item = Self::Card> {
    type Card: Card;

    fn combinations<const N: usize>(
        &self,
    ) -> impl Iterator<Item = [Self::Card; N]> + '_;

    fn draw(&mut self) -> Option<Self::Card>;
    fn is_empty(&self) -> bool;
    fn iter(&self) -> impl Iterator<Item = &Self::Card>;
    fn len(&self) -> usize;
    fn new() -> Self;
    fn remove(&mut self, card: &Self::Card) -> Option<Self::Card>;
    fn shuffle(&mut self);
}

Custom card games can implement these traits without depending on the traditional 52-card deck.

License

This project is licensed under the MIT License. See the LICENSE file for details.