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

```rust
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`:

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

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

Cards use a compact `u8` representation:

```rust
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:

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

The supported rank symbols are:

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

The supported suit symbols are:

```text
c d h s
```

These represent clubs, diamonds, hearts, and spades.

### Parsing

Cards can be parsed from their two-character representation:

```rust
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:

```rust
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:

```rust
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

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

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

### Removing a card

```rust
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

```rust
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`:

```rust
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:

```rust
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]`:

```rust
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:

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

let deck = Deck::kuhn();

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

The deck contains:

```text
Kc Qc Jc
```

## Leduc Hold'em

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

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

let deck = Deck::leduc();

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

The deck contains:

```text
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:

```rust
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:

```rust
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.