# 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.