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.

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

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

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());

§cardpack.rs

Build and Test 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.

§Usage

use cardpack::prelude::*;

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

  pack.shuffle();

  // 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 and German.

§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
  • Verify that a specific card is playable given a set of discards.

§Examples

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

For the traditional 54 card French Deck with Jokers:

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

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♠

Long in English and German:
  Joker Full-Color 
  Joker One-Color 
  Ace of Spades 
  King of Spades 
  Queen of Spades 
  ...

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

Other examples are:

  • cargo run --example bridge - Prints out a Bridge deal.
  • cargo run --example handandfoot - Shows how to support more than one decks like in the game Hand and Foot.
  • cargo run --example poker - A random heads up no-limit Poker deal.

§References

§Other Deck of Cards Libraries

§Dependencies

§Dev Dependencies

§TODO

Modules§

basic
common
localization
prelude

Macros§

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. If the passed in string isn’t valid, it will simply return an empty pile.