diff --git a/Cargo.toml b/Cargo.toml index bf10896..cfc653b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "cardpack" description = "Generic Deck of Cards" -version = "0.4.7" +version = "0.4.8" authors = ["folkengine "] repository = "https://github.com/ContractBridge/cardpack.rs.git" homepage = "https://github.com/ContractBridge/cardpack.rs" diff --git a/clippy.toml b/clippy.toml index 0c30ee2..6a1c995 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,3 +1,4 @@ msrv = "1.56.0" cognitive-complexity-threshold = 30 + diff --git a/examples/standard52.rs b/examples/standard52.rs index fd41a61..d064dad 100644 --- a/examples/standard52.rs +++ b/examples/standard52.rs @@ -1,3 +1,5 @@ +use cardpack::Named; + fn main() { println!("Let's create a Standard 52 deck of cards:"); let standard52 = cardpack::Standard52::default(); @@ -9,6 +11,11 @@ fn main() { println!("Let's display it with its short suit index:"); println!("{}\n", standard52.deck.short_suit_indexes_to_string()); + println!("Let's print each card out with the count:"); + for card in standard52.pack.cards().clone().into_iter() { + println!("{} {}", card.count(), card.index_default()); + } + println!("Let's create a shuffled Standard 52 deck of cards:"); let standard52 = cardpack::Standard52::new_shuffled(); println!("{}\n", standard52); diff --git a/src/cards/card.rs b/src/cards/card.rs index 2313b6e..2f97257 100644 --- a/src/cards/card.rs +++ b/src/cards/card.rs @@ -9,13 +9,14 @@ use crate::Named; pub const BLANK: &str = "blank"; /// `Card` is the core struct in the library. A Card is made up of a Rank, -/// a Suit and weight, which is an integer that controls how a card is sorted -/// in a Pile or as a part of a Vector. +/// a `Suit`, `weight`, which is an integer that controls how a card is sorted +/// in a `Pile` or as a part of a `Vector`, and index, which is a short `String` +/// representation of the card, suitable for serialization in text format. /// #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct Card { /// Used by the Pile struct to sort Cards. - pub weight: isize, + pub weight: u32, /// The identity indicator in the corner of a playing card, such as `AS` for ace of spades. pub index: String, pub suit: Suit, @@ -23,12 +24,9 @@ pub struct Card { } impl Card { - /// Instantiates a new Card with the default weight as defined in the fluent - /// templates. + /// Instantiates a Card with the weight determined by the passed in Rank and Suit. #[must_use] - pub fn new(rank: &'static str, suit: &'static str) -> Card { - let suit = Suit::new(suit); - let rank = Rank::new(rank); + pub fn new(rank: Rank, suit: Suit) -> Card { let weight = Card::determine_weight(&suit, &rank); let index = Card::determine_index(&suit, &rank); Card { @@ -39,18 +37,10 @@ impl Card { } } - /// Instantiates a Card with the weight determined by the passed in Rank and - /// Suit. + /// Instantiates a new Card with the default weight as defined in the fluent templates. #[must_use] - pub fn new_from_structs(rank: Rank, suit: Suit) -> Card { - let weight = Card::determine_weight(&suit, &rank); - let index = Card::determine_index(&suit, &rank); - Card { - weight, - index, - suit, - rank, - } + pub fn from_index_strings(rank: &'static str, suit: &'static str) -> Card { + Card::new(Rank::new(rank), Suit::new(suit)) } /// Returns a Symbol String for the Card. @@ -76,7 +66,15 @@ impl Card { #[must_use] pub fn blank_card() -> Card { - Card::new(BLANK, BLANK) + Card::from_index_strings(BLANK, BLANK) + } + + /// A unique index of a `Card` relative to other cards in a `Pile` prioritized by `Rank` and + /// then by `Suit`, such that a 2 of spades is lower than a 3 of clubs. While Card.weight + /// prioritizes by `Suit` and then by `Rank`. + #[must_use] + pub fn count(&self) -> u32 { + (self.suit.weight - 1) + (self.rank.weight * 4) + 1 } /// A valid Card is one where the Rank and Suit are not blank. @@ -94,14 +92,15 @@ impl Card { } /// Prioritizes sorting by Suit and then by Rank. - fn determine_weight(suit: &Suit, rank: &Rank) -> isize { + fn determine_weight(suit: &Suit, rank: &Rank) -> u32 { (suit.weight * 1000) + rank.weight } } +/// Defaults to a blank `Card`. impl Default for Card { fn default() -> Card { - Card::new(BLANK, BLANK) + Card::from_index_strings(BLANK, BLANK) } } @@ -128,7 +127,7 @@ impl Named for Card { format!("{} {}", rank, suit) } - fn default_weight(&self) -> isize { + fn default_weight(&self) -> u32 { Card::determine_weight(&self.suit, &self.rank) } } @@ -138,7 +137,7 @@ impl Named for Card { mod card_tests { use super::*; use crate::fluent::named::{GERMAN, US_ENGLISH}; - use crate::{ACE, BLANK_RANK, BLANK_SUIT, CLUBS, HEARTS, JACK, QUEEN, SPADES}; + use crate::{ACE, BLANK_RANK, BLANK_SUIT, CLUBS, DIAMONDS, HEARTS, JACK, QUEEN, SPADES, TWO}; use std::cell::Cell; // region impl tests @@ -146,70 +145,76 @@ mod card_tests { #[test] fn new() { let expected = Card { - weight: 4014, + weight: 4012, index: "AS".to_string(), rank: Rank::new(ACE), suit: Suit::new(SPADES), }; - assert_eq!(expected, Card::new(ACE, SPADES)); + assert_eq!(expected, Card::from_index_strings(ACE, SPADES)); } #[test] fn new_from_structs() { let expected = Card { - weight: 4014, + weight: 4012, index: "AS".to_string(), rank: Rank::new(ACE), suit: Suit::new(SPADES), }; - assert_eq!( - expected, - Card::new_from_structs(Rank::new(ACE), Suit::new(SPADES)) - ); + assert_eq!(expected, Card::new(Rank::new(ACE), Suit::new(SPADES))); + } + + #[test] + fn count() { + assert_eq!(52, Card::from_index_strings(ACE, SPADES).count()); + assert_eq!(1, Card::from_index_strings(TWO, CLUBS).count()); + assert_eq!(2, Card::from_index_strings(TWO, DIAMONDS).count()); } #[test] fn index() { - let card = Card::new(QUEEN, CLUBS); + let card = Card::from_index_strings(QUEEN, CLUBS); assert_eq!(card.index(&GERMAN), "DK".to_string()); } #[test] fn symbol() { - let card = Card::new(QUEEN, HEARTS); + let card = Card::from_index_strings(QUEEN, HEARTS); assert_eq!(card.symbol(&GERMAN), "D♥".to_string()); } #[test] fn symbol_colorized() { - let card = Card::new(QUEEN, HEARTS); + let card = Card::from_index_strings(QUEEN, HEARTS); assert_eq!(card.symbol_colorized(&GERMAN), "D♥".red().to_string()); } #[test] fn is_valid() { - assert!(Card::new(QUEEN, CLUBS).is_valid()) + assert!(Card::from_index_strings(QUEEN, CLUBS).is_valid()) } #[test] fn is_valid__false() { - assert!(!Card::new("", "").is_valid()); - assert!(!Card::new(QUEEN, BLANK_SUIT).is_valid()); - assert!(!Card::new(BLANK_RANK, CLUBS).is_valid()); - assert!(!Card::new(BLANK_RANK, BLANK_SUIT).is_valid()); - assert!(!Card::new(" ", BLANK_SUIT).is_valid()); + assert!(!Card::from_index_strings("", "").is_valid()); + assert!(!Card::from_index_strings(QUEEN, BLANK_SUIT).is_valid()); + assert!(!Card::from_index_strings(BLANK_RANK, CLUBS).is_valid()); + assert!(!Card::from_index_strings(BLANK_RANK, BLANK_SUIT).is_valid()); + assert!(!Card::from_index_strings(" ", BLANK_SUIT).is_valid()); } #[test] fn default() { let card = Card::default(); - assert_eq!(-1001, card.weight); + // println!("{:?}", card); + + assert_eq!(0, card.weight); assert_eq!("__".to_string(), card.index); assert_eq!("__".to_string(), card.index_default()); assert_eq!("__".to_string(), card.symbol(&US_ENGLISH)); @@ -223,15 +228,15 @@ mod card_tests { #[test] fn named__name() { - let jack = Card::new(JACK, SPADES); + let jack = Card::from_index_strings(JACK, SPADES); assert_eq!(&"JS".to_string(), jack.name()); } #[test] fn named__default_weight() { - let original = Card::new(ACE, SPADES); - let mut ace = Card::new(ACE, SPADES); + let original = Card::from_index_strings(ACE, SPADES); + let mut ace = Card::from_index_strings(ACE, SPADES); assert_eq!(ace.weight, ace.default_weight()); let weight = ace.weight; @@ -243,7 +248,7 @@ mod card_tests { #[test] fn named__index() { - let jack = Card::new(JACK, SPADES); + let jack = Card::from_index_strings(JACK, SPADES); assert_eq!("JS".to_string(), jack.index(&US_ENGLISH)); assert_eq!("BS".to_string(), jack.index(&GERMAN)); @@ -252,7 +257,7 @@ mod card_tests { #[test] fn named__long() { - let ace = Card::new(ACE, SPADES); + let ace = Card::from_index_strings(ACE, SPADES); assert_eq!("Ace Spades".to_string(), ace.long(&US_ENGLISH)); assert_eq!("Ass Spaten".to_string(), ace.long(&GERMAN)); @@ -263,13 +268,13 @@ mod card_tests { #[test] fn card_cell() { - let ace_of_spades = Card::new(ACE, SPADES); + let ace_of_spades = Card::from_index_strings(ACE, SPADES); let blank = Card::default(); let cell = Cell::new(ace_of_spades.clone()); let aces = cell.take(); - assert_eq!(Card::new(ACE, SPADES), aces); + assert_eq!(Card::from_index_strings(ACE, SPADES), aces); assert_eq!(blank, cell.take()); assert_eq!(blank, cell.take()); @@ -277,7 +282,7 @@ mod card_tests { let aces = cell.take(); - assert_eq!(Card::new(ACE, SPADES), aces); + assert_eq!(Card::from_index_strings(ACE, SPADES), aces); assert_eq!(blank, cell.take()); assert_eq!(blank, cell.take()); } diff --git a/src/cards/card_error.rs b/src/cards/card_error.rs new file mode 100644 index 0000000..f38d5d4 --- /dev/null +++ b/src/cards/card_error.rs @@ -0,0 +1,8 @@ +#[derive(Debug, PartialEq)] +pub enum CardError { + InvalidCard, + InvalidCardCount, + InvalidIndex, + NotEnoughCards, + TooManyCards, +} diff --git a/src/cards/decks/bridge.rs b/src/cards/decks/bridge.rs index ff6a8d1..00c4670 100644 --- a/src/cards/decks/bridge.rs +++ b/src/cards/decks/bridge.rs @@ -238,7 +238,7 @@ impl BridgeBoard { .map(|s| self.pack.cards().card_by_index(s.as_str()).unwrap().clone()) .collect(); - Pile::new_from_vector(coll) + Pile::from_vector(coll) } fn splice_suit_in(s: &str, suit: char) -> Vec { diff --git a/src/cards/decks/deck_error.rs b/src/cards/decks/deck_error.rs index 9928964..7e07109 100644 --- a/src/cards/decks/deck_error.rs +++ b/src/cards/decks/deck_error.rs @@ -1,5 +1,6 @@ -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum DeckError { + DuplicateCard, InvalidIndex, Incomplete, PilePackMismatch, diff --git a/src/cards/decks/mod.rs b/src/cards/decks/mod.rs index 642c2d0..2f40a92 100644 --- a/src/cards/decks/mod.rs +++ b/src/cards/decks/mod.rs @@ -1,3 +1,4 @@ pub mod bridge; pub mod deck_error; pub mod standard52; +pub mod standard52_set; diff --git a/src/cards/decks/standard52.rs b/src/cards/decks/standard52.rs index 8660ace..14a9e0c 100644 --- a/src/cards/decks/standard52.rs +++ b/src/cards/decks/standard52.rs @@ -1,10 +1,15 @@ +use std::collections::HashMap; use std::fmt; use crate::cards::decks::deck_error::DeckError; +use crate::cards::decks::standard52_set::Standard52Set; use crate::cards::rank::{Rank, BLANK_RANK}; use crate::cards::suit::Suit; use crate::{Card, Pack, Pile}; +/// `Standard52` is a representation of a deck of cards used to play +/// most versions of poker. It is useful to determine if a `Card` belongs +/// in the deck and to deserialize Cards, Piles and decks from index strings. #[derive(Clone, Debug, Hash, PartialEq)] pub struct Standard52 { pub pack: Pack, @@ -40,14 +45,9 @@ impl Standard52 { /// /// Will return `DeckError::InvalidIndex` if passed in index is incomplete. pub fn from_index(card_str: &'static str) -> Result { - let mut pile = Pile::default(); - for index in card_str.split_whitespace() { - pile.add(Standard52::card_from_index(index)); - } - let standard52 = Standard52 { pack: Pack::french_deck(), - deck: pile, + deck: Standard52::pile_from_index(card_str)?, }; if standard52.is_complete() { @@ -57,6 +57,83 @@ impl Standard52 { } } + /// # Errors + /// + /// Will return `DeckError::InvalidIndex` if passed in index is invalid. + pub fn pile_from_index(card_str: &'static str) -> Result { + let mut pile = Pile::default(); + for index in card_str.split_whitespace() { + let card = Standard52::card_from_index(index); + + if card.is_valid() { + pile.push(card); + } else { + return Err(DeckError::InvalidIndex); + } + } + Ok(pile) + } + + /// Validating method that takes a `Standard52` index string and returns a `Pile`, + /// making sure that there are no duplicate valid cards in the string. + /// + /// This method is doing a lot :-P + /// + /// # Errors + /// + /// Will return `DeckError::InvalidIndex` if passed in index is invalid. + /// Will return `DeckError::DuplicateCard` if the index has the same `Card` more + /// than once. + /// + /// # Panics + /// + /// Should not be possible. + #[allow(clippy::question_mark)] + pub fn pile_from_index_validated(card_str: &'static str) -> Result { + let mut set = Standard52Set::default(); + let pile = Standard52::pile_from_index(card_str); + if pile.is_err() { + return pile; + } + + for card in pile.unwrap() { + let inserted = set.insert(card); + if !inserted { + return Err(DeckError::DuplicateCard); + } + } + + Ok(set.to_pile()) + } + + /// # Errors + /// + /// Will return `DeckError::PilePackMismatch` if `Pile` passed contains a card that isn't + /// in the `Standard52` deck. + pub fn pile_from_pile(&self, pile: Pile) -> Result { + let mut r = Pile::default(); + for card in pile { + if self.is_valid_card(&card) { + r.push(card); + } else { + return Err(DeckError::PilePackMismatch); + } + } + Ok(r) + } + + pub fn draw(&mut self, x: usize) -> Option { + if x > self.deck.len() || x < 1 { + None + } else { + let mut cards = Pile::default(); + for _ in 0..x { + cards.push(self.deck.draw_first()?); + } + Some(cards) + } + } + #[must_use] pub fn to_index(&self) -> String { self.deck.to_index() @@ -87,10 +164,15 @@ impl Standard52 { if rank.is_blank() || suit.is_blank() { Card::blank_card() } else { - Card::new_from_structs(rank, suit) + Card::new(rank, suit) } } + #[must_use] + pub fn is_valid_card(&self, card: &Card) -> bool { + self.pack.contains(card) + } + fn rank_str_from_index(card_str: &'static str) -> &'static str { if card_str.len() < 2 { return BLANK_RANK; @@ -104,6 +186,40 @@ impl Standard52 { } card_str.char_indices().nth(1).unwrap().1 } + + // Suit HashMap Functions + + /// Returns `HashMap` of Piles of Cards sorted by the Standard52 Suits. + /// + /// + /// + #[must_use] + pub fn sort_by_suit(pile: &Pile) -> HashMap { + let mut sorted: HashMap = HashMap::new(); + + for suit in Suit::generate_french_suits() { + let cards_by_suit = pile.cards_by_suit(suit); + if !cards_by_suit.is_empty() { + sorted.insert(suit, Pile::from_vector(cards_by_suit)); + } + } + + sorted + } + + /// Returns true if five or more cards in a `Pile` are of the same `Suit`. + /// + /// NOTE: This method is non optimal and is primarily for verification purposes. + #[must_use] + pub fn is_flush(pile: &Pile) -> bool { + let hash_map = Standard52::sort_by_suit(pile); + for c in hash_map.values() { + if c.len() > 4 { + return true; + } + } + false + } } impl Default for Standard52 { @@ -126,7 +242,7 @@ impl fmt::Display for Standard52 { #[allow(non_snake_case)] mod standard52_tests { use super::*; - use crate::{DIAMONDS, FIVE, FOUR, SPADES, THREE, TWO}; + use crate::{CLUBS, DIAMONDS, FIVE, FOUR, HEARTS, KING, QUEEN, SPADES, TEN, THREE, TWO}; use rstest::rstest; #[test] @@ -136,11 +252,11 @@ mod standard52_tests { let mut standard52 = Standard52::from_index(index_string).unwrap(); assert_eq!( - Card::new(TWO, SPADES), + Card::from_index_strings(TWO, SPADES), standard52.deck.draw_first().unwrap() ); assert_eq!( - Card::new(THREE, DIAMONDS), + Card::from_index_strings(THREE, DIAMONDS), standard52.deck.draw_first().unwrap() ); } @@ -192,12 +308,40 @@ mod standard52_tests { } #[test] - fn from_index_shuffled__invalid_symbol_index() { + fn from_index__invalid_index__invalid_index_error() { let index = "8 4♣ K♥ Q♦ K♦ 8♥ 5♦ T♣ 9♦ J♣ T♠ 2♠ 4♥ 2♦ 3♠ 5♥ 3♦ A♣ T♥ 7♠ 4♠ K♠ 5♠ 7♣ A♥ K♣ J♠ A♠ Q♥ 2♣ 6♦ J♦ 6♠ 8♠ T♦ 9♠ 7♦ 8♦ 7♥ Q♣ 4♦ 9♣ J♥ 3♣ 6♥ 5♣ A♦ 3♥ 6♣ Q♠ 2♥ 9♥"; - let standard52 = Standard52::from_index(index); + let actual_error = Standard52::from_index(index).unwrap_err(); + + assert_eq!(actual_error, DeckError::InvalidIndex); + } + + #[test] + fn pile_from_index() { + let index_string = "2S 3D QS KH 3C 3S TC"; + + let pile = Standard52::pile_from_index(index_string); + + assert!(pile.is_ok()); + let pile = pile.unwrap(); + assert_eq!(pile.cards().len(), 7); + assert!(pile.contains(&Card::from_index_strings(TWO, SPADES))); + assert!(pile.contains(&Card::from_index_strings(THREE, DIAMONDS))); + assert!(pile.contains(&Card::from_index_strings(QUEEN, SPADES))); + assert!(pile.contains(&Card::from_index_strings(KING, HEARTS))); + assert!(pile.contains(&Card::from_index_strings(THREE, CLUBS))); + assert!(pile.contains(&Card::from_index_strings(THREE, SPADES))); + assert!(pile.contains(&Card::from_index_strings(TEN, CLUBS))); + } + + /// + #[test] + fn pile_from_index__invalid_index__invalid_index_error() { + let index = "2S 3D QS K 3C 3S TC"; + + let actual_error = Standard52::pile_from_index(index).unwrap_err(); - assert!(standard52.is_err()); + assert_eq!(actual_error, DeckError::InvalidIndex); } #[test] @@ -243,15 +387,15 @@ mod standard52_tests { } #[rstest] - #[case("2S", Card::new(TWO, SPADES))] - #[case("2s", Card::new(TWO, SPADES))] - #[case("2♠", Card::new(TWO, SPADES))] - #[case("3S", Card::new(THREE, SPADES))] - #[case("3♠", Card::new(THREE, SPADES))] - #[case("4♠", Card::new(FOUR, SPADES))] - #[case("4S", Card::new(FOUR, SPADES))] - #[case("5♠", Card::new(FIVE, SPADES))] - #[case("5S", Card::new(FIVE, SPADES))] + #[case("2S", Card::from_index_strings(TWO, SPADES))] + #[case("2s", Card::from_index_strings(TWO, SPADES))] + #[case("2♠", Card::from_index_strings(TWO, SPADES))] + #[case("3S", Card::from_index_strings(THREE, SPADES))] + #[case("3♠", Card::from_index_strings(THREE, SPADES))] + #[case("4♠", Card::from_index_strings(FOUR, SPADES))] + #[case("4S", Card::from_index_strings(FOUR, SPADES))] + #[case("5♠", Card::from_index_strings(FIVE, SPADES))] + #[case("5S", Card::from_index_strings(FIVE, SPADES))] fn card_from_index(#[case] input: &'static str, #[case] expected: Card) { assert_eq!(expected, Standard52::card_from_index(input)); } @@ -266,4 +410,42 @@ mod standard52_tests { fn card_from_index__invalid_index(#[case] input: &'static str) { assert_eq!(Card::blank_card(), Standard52::card_from_index(input)); } + + #[test] + fn sort_by_suit() { + let pile = Standard52::pile_from_index("2S 3S 9S TS QS JH Ac").unwrap(); + + let sorted = Standard52::sort_by_suit(&pile); + + assert!(sorted.contains_key(&Suit::new(SPADES))); + assert!(sorted.contains_key(&Suit::new(HEARTS))); + assert!(sorted.contains_key(&Suit::new(CLUBS))); + assert!(!sorted.contains_key(&Suit::new(DIAMONDS))); + } + + #[rstest] + #[case("2S 3S 9S TS QS")] + fn to_a_flush(#[case] input: &'static str) { + let pile = Standard52::pile_from_index(input).unwrap(); + + let _sorted = Standard52::sort_by_suit(&pile); + } + + #[rstest] + #[case("2S 3S 9S TS QS")] + #[case("2S 3S 9S TS QS AH QD")] + fn is_flush(#[case] input: &'static str) { + assert!(Standard52::is_flush( + &Standard52::pile_from_index(input).unwrap() + )); + } + + #[rstest] + #[case("2S 3S 9D TS QS")] + #[case("2S 3S 9S TD QS AH QD")] + fn is_flush__false(#[case] input: &'static str) { + assert!(!Standard52::is_flush( + &Standard52::pile_from_index(input).unwrap() + )); + } } diff --git a/src/cards/decks/standard52_set.rs b/src/cards/decks/standard52_set.rs new file mode 100644 index 0000000..2696495 --- /dev/null +++ b/src/cards/decks/standard52_set.rs @@ -0,0 +1,281 @@ +use crate::{Card, Pile, Standard52}; +use std::collections::HashSet; + +#[derive(Clone, Debug, Default, PartialEq)] +pub struct Standard52Set(HashSet); + +impl Standard52Set { + #[must_use] + pub fn new(pile: Pile) -> Standard52Set { + let mut pile_set = Standard52Set::default(); + for card in pile { + pile_set.insert(card); + } + pile_set + } + + #[must_use] + pub fn standard52() -> Standard52Set { + Standard52Set::new(Pile::french_deck()) + } + + #[must_use] + pub fn contains(&self, card: &Card) -> bool { + self.0.contains(card) + } + + /// Returns true if a `Card` in the `PileSet` based on its `Standard52` index string is present. + /// + /// **WARNING:** This method will return false for indexes that aren't a part of the + /// `Standard52` deck. + #[must_use] + pub fn contains_by_index(&self, index: &'static str) -> bool { + let card = Standard52::card_from_index(index); + if !card.is_valid() { + return false; + } + self.0.contains(&card) + } + + #[must_use] + pub fn get(&self, card: &Card) -> Option<&Card> { + self.0.get(card) + } + + /// Inserts a `Card` into the `PileSet`. Returns true if it isn't already present. + pub fn insert(&mut self, card: Card) -> bool { + self.0.insert(card) + } + + /// Inserts a `Card` into the `PileSet` based on its `Standard52` index string. Returns + /// true if it isn't already present. + /// + /// **WARNING:** This method will return false for indexes that aren't a part of the + /// `Standard52` deck. + pub fn insert_by_index(&mut self, index: &'static str) -> bool { + let card = Standard52::card_from_index(index); + if !card.is_valid() { + return false; + } + self.0.insert(card) + } + + #[must_use] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + #[must_use] + pub fn len(&self) -> usize { + self.0.len() + } + + /// Removes a `Card` from the `PileSet`. + pub fn remove(&mut self, card: &Card) -> bool { + self.0.remove(card) + } + + /// Removes a `Card` from the `PileSet` based on its `Standard52` index string. + /// + /// **WARNING:** This method will return false for indexes that aren't a part of the + /// `Standard52` deck. + pub fn remove_by_index(&mut self, index: &'static str) -> bool { + let card = Standard52::card_from_index(index); + if !card.is_valid() { + return false; + } + self.remove(&card) + } + + /// Removes and returns a `Card` from the `PileSet`. + pub fn take(&mut self, card: &Card) -> Option { + self.0.take(card) + } + + /// Removes and returns a `Card` from the `PileSet` based on its `Standard52` index string. + /// + /// **WARNING:** This method will return false for indexes that aren't a part of the + /// `Standard52` deck. + pub fn take_by_index(&mut self, index: &'static str) -> Option { + let card = Standard52::card_from_index(index); + if !card.is_valid() { + return None; + } + self.take(&card) + } + + #[must_use] + pub fn to_pile(&self) -> Pile { + let mut pile: Pile = Pile::from_vector(self.clone().into_iter().collect::>()); + pile.sort_in_place(); + pile + } +} + +impl FromIterator for Standard52Set { + fn from_iter>(iter: T) -> Self { + let mut c = Standard52Set::default(); + for i in iter { + c.insert(i); + } + c + } +} + +impl IntoIterator for Standard52Set { + type Item = Card; + type IntoIter = std::collections::hash_set::IntoIter; + + fn into_iter(self) -> std::collections::hash_set::IntoIter { + self.0.into_iter() + } +} + +#[cfg(test)] +#[allow(non_snake_case)] +mod pile_set_tests { + use super::*; + use crate::{CLUBS, CUPS, HEARTS, QUEEN, THREE}; + + #[test] + fn new() { + let qclubs = Card::from_index_strings(QUEEN, CLUBS); + let qhearts = Card::from_index_strings(QUEEN, HEARTS); + let mut pile = Pile::from_vector(vec![qclubs.clone(), qhearts.clone()]); + pile.sort_in_place(); + + let actual = Standard52Set::new(pile.clone()); + + assert_eq!(actual.to_pile(), pile); + assert_eq!(actual.len(), 2); + } + + #[test] + fn standard52() { + let actual = Standard52Set::standard52(); + + assert_eq!(Pile::french_deck(), actual.to_pile()); + } + + #[test] + fn contains() { + let pile_set = Standard52Set::standard52(); + + assert!(pile_set.contains(&Card::from_index_strings(QUEEN, CLUBS))); + assert!(pile_set.contains(&Card::from_index_strings(QUEEN, HEARTS))); + assert!(!pile_set.contains(&Card::from_index_strings(THREE, CUPS))); + } + + #[test] + fn contains_by_index() { + let pile_set = Standard52Set::standard52(); + + assert!(pile_set.contains_by_index("QH")); + assert!(pile_set.contains_by_index("AS")); + assert!(!pile_set.contains_by_index("3🏆")); + } + + #[test] + fn get() { + let pile_set = Standard52Set::standard52(); + let qclubs = Card::from_index_strings(QUEEN, CLUBS); + + let gotten = pile_set.get(&qclubs); + + assert!(gotten.is_some()); + assert_eq!(gotten.unwrap(), &qclubs); + } + + #[test] + fn insert() { + let mut pile_set = Standard52Set::default(); + let qclubs = Card::from_index_strings(QUEEN, CLUBS); + + let inserted = pile_set.insert(qclubs.clone()); + + assert!(inserted); + // returns false if `Card` is already inserted. + assert!(!pile_set.insert(qclubs)); + } + + #[test] + fn insert_by_index() { + let mut pile_set = Standard52Set::default(); + + assert!(pile_set.insert_by_index("AH")); + assert!(pile_set.insert_by_index("2C")); + assert!(!pile_set.insert_by_index("AH")); + } + + #[test] + fn is_empty() { + let pile_set = Standard52Set::default(); + + assert!(pile_set.is_empty()); + } + + #[test] + fn is_empty__not_empty() { + let mut pile_set = Standard52Set::default(); + pile_set.insert(Card::from_index_strings(QUEEN, CLUBS)); + + assert!(!pile_set.is_empty()); + } + + #[test] + fn len() { + assert_eq!(Standard52Set::standard52().len(), 52); + } + + #[test] + fn remove() { + let mut pile_set = Standard52Set::standard52(); + let qclubs = Card::from_index_strings(QUEEN, CLUBS); + + assert!(pile_set.remove(&qclubs)); + assert!(!pile_set.remove(&qclubs)); + } + + #[test] + fn remove_by_index() { + let mut pile_set = Standard52Set::standard52(); + + assert!(pile_set.remove_by_index("KH")); + assert!(pile_set.remove_by_index("3♠")); + assert!(!pile_set.remove_by_index("3S")); + assert!(!pile_set.remove_by_index("KH")); + assert!(!pile_set.remove_by_index("8🏆")); + } + + #[test] + fn take() { + let mut pile_set = Standard52Set::standard52(); + let qclubs = Card::from_index_strings(QUEEN, CLUBS); + + let r = pile_set.take(&qclubs); + + assert!(r.is_some()); + assert_eq!(r.unwrap(), qclubs); + assert!(pile_set.take(&qclubs).is_none()); + } + + #[test] + fn take_by_index() { + let mut pile_set = Standard52Set::standard52(); + let qclubs = Card::from_index_strings(QUEEN, CLUBS); + + let r = pile_set.take_by_index("QC"); + + assert!(r.is_some()); + assert_eq!(r.unwrap(), qclubs); + assert!(pile_set.take_by_index("QC").is_none()); + } + + #[test] + fn to_pile() { + let pile_set = Standard52Set::standard52(); + let pile = Pile::french_deck(); + + assert_eq!(pile_set.to_pile(), pile); + } +} diff --git a/src/cards/mod.rs b/src/cards/mod.rs index 597a70c..ec3779e 100644 --- a/src/cards/mod.rs +++ b/src/cards/mod.rs @@ -1,4 +1,5 @@ pub mod card; +pub mod card_error; pub mod decks; pub mod pack; pub mod pile; diff --git a/src/cards/pack.rs b/src/cards/pack.rs index 455182c..d349a62 100644 --- a/src/cards/pack.rs +++ b/src/cards/pack.rs @@ -1,4 +1,5 @@ use crate::cards::pile::Pile; +use crate::Card; /// A Pack is an immutable pile of cards. Packs are designed to be a flexible representation of /// a deck, stack, discard pile, or hand. @@ -47,6 +48,12 @@ impl Pack { &self.cards } + /// Returns true if the passed in `Card` is a part of the `Pack`. + #[must_use] + pub fn contains(&self, card: &Card) -> bool { + self.cards.contains(card) + } + pub fn canasta_deck() -> Pack { let pile = Pile::pile_up(2, Pile::canasta_single_deck); let pile = pile.sort(); diff --git a/src/cards/pile.rs b/src/cards/pile.rs index 4b955f0..bc77f9a 100644 --- a/src/cards/pile.rs +++ b/src/cards/pile.rs @@ -18,18 +18,19 @@ use crate::Named; /// # Usage: /// ``` /// let mut pile = cardpack::Pile::default(); -/// let ace_of_spades = cardpack::Card::new(cardpack::ACE, cardpack::SPADES); -/// let ace_of_hearts = cardpack::Card::new(cardpack::ACE, cardpack::HEARTS); -/// pile.add(ace_of_spades); -/// pile.add(ace_of_hearts); +/// let ace_of_spades = cardpack::Card::from_index_strings(cardpack::ACE, cardpack::SPADES); +/// let ace_of_hearts = cardpack::Card::from_index_strings(cardpack::ACE, cardpack::HEARTS); +/// pile.push(ace_of_spades); +/// pile.push(ace_of_hearts); /// pile.shuffle(); +/// ``` #[derive(Clone, Debug, Hash, PartialEq)] pub struct Pile(Vec); impl Pile { #[must_use] - pub fn new_from_vector(v: Vec) -> Pile { + pub fn from_vector(v: Vec) -> Pile { Pile(v) } @@ -62,7 +63,7 @@ impl Pile { } /// Places the Card at the bottom (end) of the Pile. - pub fn add(&mut self, elem: Card) { + pub fn push(&mut self, elem: Card) { self.0.push(elem); } @@ -190,7 +191,7 @@ impl Pile { } else { let mut cards = Pile::default(); for _ in 0..x { - cards.add(self.draw_first()?); + cards.push(self.draw_first()?); } Some(cards) } @@ -218,7 +219,7 @@ impl Pile { fn fold_in(&mut self, suits: &[Suit], ranks: &[Rank]) { for (_, suit) in suits.iter().enumerate() { for (_, rank) in ranks.iter().enumerate() { - self.add(Card::new_from_structs(*rank, *suit)); + self.push(Card::new(*rank, *suit)); } } } @@ -248,6 +249,15 @@ impl Pile { self.0.len() } + #[must_use] + pub fn map_by_rank_count(&self) -> HashMap { + let mut mappie: HashMap = HashMap::new(); + for card in self.cards() { + *mappie.entry(card.rank).or_insert(0) += 1; + } + mappie + } + /// Takes a `Pile` and returns a `HashMap` with the key as each `Suit` in the `Pile` with the values /// as a `Pile` of the cards for that Suit. #[must_use] @@ -276,7 +286,7 @@ impl Pile { for index in indexes { let card = self.card_by_index(index); match card { - Some(c) => pile.add(c.clone()), + Some(c) => pile.push(c.clone()), _ => return None, } } @@ -340,7 +350,7 @@ impl Pile { #[must_use] pub fn short_index_for_suit(&self, suit: Suit) -> String { - let cards = Pile::new_from_vector(self.cards_by_suit(suit)); + let cards = Pile::from_vector(self.cards_by_suit(suit)); suit.symbol().as_str().to_owned() + " " + &cards.rank_indexes_with_separator(" ") } @@ -400,9 +410,9 @@ impl Pile { #[must_use] pub fn jokers() -> Pile { - let big_joker = Card::new(BIG_JOKER, TRUMP); - let little_joker = Card::new(LITTLE_JOKER, TRUMP); - Pile::new_from_vector(vec![big_joker, little_joker]) + let big_joker = Card::from_index_strings(BIG_JOKER, TRUMP); + let little_joker = Card::from_index_strings(LITTLE_JOKER, TRUMP); + Pile::from_vector(vec![big_joker, little_joker]) } #[must_use] @@ -420,20 +430,20 @@ impl Pile { pub fn canasta_single_deck() -> Pile { let mut cards: Pile = Pile::canasta_base_single_deck(); - cards.remove_card(&Card::new(THREE, HEARTS)); - cards.remove_card(&Card::new(THREE, DIAMONDS)); + cards.remove_card(&Card::from_index_strings(THREE, HEARTS)); + cards.remove_card(&Card::from_index_strings(THREE, DIAMONDS)); cards.prepend(&Pile::canasta_red_threes()); cards } fn canasta_red_threes() -> Pile { - let mut three_hearts = Card::new(THREE, HEARTS); - let mut three_diamonds = Card::new(THREE, DIAMONDS); + let mut three_hearts = Card::from_index_strings(THREE, HEARTS); + let mut three_diamonds = Card::from_index_strings(THREE, DIAMONDS); three_hearts.weight = 100_001; three_diamonds.weight = 100_000; - Pile::new_from_vector(vec![three_hearts, three_diamonds]) + Pile::from_vector(vec![three_hearts, three_diamonds]) } #[must_use] @@ -443,7 +453,9 @@ impl Pile { let mut cards: Pile = Pile::default(); cards.fold_in(&suits, &ranks); - cards.prepend(&Pile::new_from_vector(vec![Card::new(BIG_JOKER, TRUMP)])); + cards.prepend(&Pile::from_vector(vec![Card::from_index_strings( + BIG_JOKER, TRUMP, + )])); cards } @@ -501,8 +513,8 @@ impl Pile { #[must_use] pub fn spades_deck() -> Pile { let mut deck = Pile::french_deck(); - deck.remove_card(&Card::new(TWO, CLUBS)); - deck.remove_card(&Card::new(TWO, DIAMONDS)); + deck.remove_card(&Card::from_index_strings(TWO, CLUBS)); + deck.remove_card(&Card::from_index_strings(TWO, DIAMONDS)); let jokers = Pile::jokers(); deck.prepend(&jokers); @@ -523,13 +535,13 @@ impl Pile { // Generate Major Arcana for (_, rank) in major_arcana_ranks.iter().enumerate() { - cards.add(Card::new_from_structs(*rank, *major_arcana_suit)); + cards.push(Card::new(*rank, *major_arcana_suit)); } // Generate Minor Arcana for (_, suit) in arcana_suits_enumerator { for (_, rank) in minor_arcana_ranks.iter().enumerate() { - cards.add(Card::new_from_structs(*rank, *suit)); + cards.push(Card::new(*rank, *suit)); } } @@ -549,7 +561,7 @@ impl Pile { impl Default for Pile { fn default() -> Self { - Pile::new_from_vector(Vec::new()) + Pile::from_vector(Vec::new()) } } @@ -565,7 +577,7 @@ impl FromIterator for Pile { fn from_iter>(iter: T) -> Self { let mut c = Pile::default(); for i in iter { - c.add(i); + c.push(i); } c } @@ -582,19 +594,19 @@ impl IntoIterator for Pile { #[cfg(test)] #[allow(non_snake_case)] -mod card_deck_tests { +mod pile_tests { use super::*; use crate::cards::suit::{MAJOR_ARCANA, SPADES}; #[test] fn new_from_vector() { - let qclubs = Card::new(QUEEN, CLUBS); - let qhearts = Card::new(QUEEN, HEARTS); + let qclubs = Card::from_index_strings(QUEEN, CLUBS); + let qhearts = Card::from_index_strings(QUEEN, HEARTS); let mut expected = Pile::default(); - expected.add(qclubs.clone()); - expected.add(qhearts.clone()); + expected.push(qclubs.clone()); + expected.push(qhearts.clone()); - let actual = Pile::new_from_vector(vec![qclubs, qhearts]); + let actual = Pile::from_vector(vec![qclubs, qhearts]); assert_eq!(expected, actual); } @@ -611,13 +623,13 @@ mod card_deck_tests { #[test] fn append() { - let qclubs = Card::new(QUEEN, CLUBS); - let qhearts = Card::new(QUEEN, HEARTS); - let big_joker = Card::new(BIG_JOKER, TRUMP); - let little_joker = Card::new(LITTLE_JOKER, TRUMP); - let mut to_deck = Pile::new_from_vector(vec![qclubs.clone(), qhearts.clone()]); + let qclubs = Card::from_index_strings(QUEEN, CLUBS); + let qhearts = Card::from_index_strings(QUEEN, HEARTS); + let big_joker = Card::from_index_strings(BIG_JOKER, TRUMP); + let little_joker = Card::from_index_strings(LITTLE_JOKER, TRUMP); + let mut to_deck = Pile::from_vector(vec![qclubs.clone(), qhearts.clone()]); let from_deck = Pile::jokers(); - let expected = Pile::new_from_vector(vec![qclubs, qhearts, big_joker, little_joker]); + let expected = Pile::from_vector(vec![qclubs, qhearts, big_joker, little_joker]); to_deck.append(&from_deck); @@ -627,7 +639,7 @@ mod card_deck_tests { #[test] fn card_by_index() { let deck = Pile::spades_deck(); - let expected = Card::new(LITTLE_JOKER, TRUMP); + let expected = Card::from_index_strings(LITTLE_JOKER, TRUMP); let card = deck.card_by_index("JLT").unwrap(); @@ -637,7 +649,7 @@ mod card_deck_tests { #[test] fn card_by_index__ne() { let deck = Pile::spades_deck(); - let fool_index = Card::new(FOOL, MAJOR_ARCANA).index_default(); + let fool_index = Card::from_index_strings(FOOL, MAJOR_ARCANA).index_default(); // Verifies that the index for a card in the tarot deck isn't in a spades deck. assert!(deck.card_by_index(fool_index.as_str()).is_none()); @@ -645,10 +657,10 @@ mod card_deck_tests { #[test] fn cards() { - let qclubs = Card::new(QUEEN, CLUBS); - let qhearts = Card::new(QUEEN, HEARTS); + let qclubs = Card::from_index_strings(QUEEN, CLUBS); + let qhearts = Card::from_index_strings(QUEEN, HEARTS); let expected = vec![qclubs.clone(), qhearts.clone()]; - let pile = Pile::new_from_vector(expected.clone()); + let pile = Pile::from_vector(expected.clone()); let v = pile.cards(); @@ -657,13 +669,13 @@ mod card_deck_tests { #[test] fn cards_by_suit() { - let qh = Card::new(QUEEN, HEARTS); - let jh = Card::new(JACK, HEARTS); - let qc = Card::new(QUEEN, CLUBS); - let jc = Card::new(JACK, CLUBS); + let qh = Card::from_index_strings(QUEEN, HEARTS); + let jh = Card::from_index_strings(JACK, HEARTS); + let qc = Card::from_index_strings(QUEEN, CLUBS); + let jc = Card::from_index_strings(JACK, CLUBS); let expected = vec![qc.clone(), jc.clone()]; - let pile = Pile::new_from_vector(vec![jh.clone(), jc.clone(), qh.clone(), qc.clone()]); + let pile = Pile::from_vector(vec![jh.clone(), jc.clone(), qh.clone(), qc.clone()]); let v = pile.cards_by_suit(qc.suit); @@ -672,11 +684,11 @@ mod card_deck_tests { #[test] fn short_index_by_suit() { - let qh = Card::new(QUEEN, HEARTS); - let jh = Card::new(JACK, HEARTS); - let qc = Card::new(QUEEN, CLUBS); - let jc = Card::new(JACK, CLUBS); - let pile = Pile::new_from_vector(vec![jh.clone(), jc.clone(), qh.clone(), qc.clone()]); + let qh = Card::from_index_strings(QUEEN, HEARTS); + let jh = Card::from_index_strings(JACK, HEARTS); + let qc = Card::from_index_strings(QUEEN, CLUBS); + let jc = Card::from_index_strings(JACK, CLUBS); + let pile = Pile::from_vector(vec![jh.clone(), jc.clone(), qh.clone(), qc.clone()]); let expected = String::from("♥ Q J"); let actual = pile.short_index_for_suit(qh.suit); @@ -688,9 +700,9 @@ mod card_deck_tests { #[test] fn contains() { - let qclubs = Card::new(QUEEN, CLUBS); - let qhearts = Card::new(QUEEN, HEARTS); - let deck = Pile::new_from_vector(vec![qclubs.clone(), qhearts.clone()]); + let qclubs = Card::from_index_strings(QUEEN, CLUBS); + let qhearts = Card::from_index_strings(QUEEN, HEARTS); + let deck = Pile::from_vector(vec![qclubs.clone(), qhearts.clone()]); assert!(deck.contains(&qclubs)); assert!(deck.contains(&qhearts)); @@ -714,15 +726,14 @@ mod card_deck_tests { #[test] fn draw() { - let qclubs = Card::new(QUEEN, CLUBS); - let qhearts = Card::new(QUEEN, HEARTS); - let qspades = Card::new(QUEEN, SPADES); - let mut deck = - Pile::new_from_vector(vec![qclubs.clone(), qhearts.clone(), qspades.clone()]); + let qclubs = Card::from_index_strings(QUEEN, CLUBS); + let qhearts = Card::from_index_strings(QUEEN, HEARTS); + let qspades = Card::from_index_strings(QUEEN, SPADES); + let mut deck = Pile::from_vector(vec![qclubs.clone(), qhearts.clone(), qspades.clone()]); assert_eq!( deck.draw(2).unwrap(), - Pile::new_from_vector(vec![qclubs.clone(), qhearts.clone()]) + Pile::from_vector(vec![qclubs.clone(), qhearts.clone()]) ); assert_eq!(1, deck.len()); } @@ -746,9 +757,9 @@ mod card_deck_tests { #[test] fn draw_first() { - let qclubs = Card::new(QUEEN, CLUBS); - let qhearts = Card::new(QUEEN, HEARTS); - let mut deck = Pile::new_from_vector(vec![qclubs.clone(), qhearts.clone()]); + let qclubs = Card::from_index_strings(QUEEN, CLUBS); + let qhearts = Card::from_index_strings(QUEEN, HEARTS); + let mut deck = Pile::from_vector(vec![qclubs.clone(), qhearts.clone()]); assert_eq!(deck.draw_first().unwrap(), qclubs); assert_eq!(1, deck.len()); @@ -761,9 +772,9 @@ mod card_deck_tests { #[test] fn draw_last() { - let qclubs = Card::new(QUEEN, CLUBS); - let qhearts = Card::new(QUEEN, HEARTS); - let mut deck = Pile::new_from_vector(vec![qclubs.clone(), qhearts.clone()]); + let qclubs = Card::from_index_strings(QUEEN, CLUBS); + let qhearts = Card::from_index_strings(QUEEN, HEARTS); + let mut deck = Pile::from_vector(vec![qclubs.clone(), qhearts.clone()]); assert_eq!(deck.draw_last().unwrap(), qhearts); assert_eq!(1, deck.len()); @@ -776,9 +787,9 @@ mod card_deck_tests { #[test] fn first() { - let qclubs = Card::new(QUEEN, CLUBS); - let qhearts = Card::new(QUEEN, HEARTS); - let deck = Pile::new_from_vector(vec![qclubs.clone(), qhearts.clone()]); + let qclubs = Card::from_index_strings(QUEEN, CLUBS); + let qhearts = Card::from_index_strings(QUEEN, HEARTS); + let deck = Pile::from_vector(vec![qclubs.clone(), qhearts.clone()]); assert_eq!(deck.first().unwrap(), &qclubs); } @@ -790,9 +801,9 @@ mod card_deck_tests { #[test] fn get() { - let qclubs = Card::new(QUEEN, CLUBS); - let qhearts = Card::new(QUEEN, HEARTS); - let deck = Pile::new_from_vector(vec![qclubs.clone(), qhearts.clone()]); + let qclubs = Card::from_index_strings(QUEEN, CLUBS); + let qhearts = Card::from_index_strings(QUEEN, HEARTS); + let deck = Pile::from_vector(vec![qclubs.clone(), qhearts.clone()]); let gotten = deck.get(1); @@ -801,8 +812,8 @@ mod card_deck_tests { #[test] fn get_random() { - let qhearts = Card::new(QUEEN, HEARTS); - let deck = Pile::new_from_vector(vec![qhearts.clone()]); + let qhearts = Card::from_index_strings(QUEEN, HEARTS); + let deck = Pile::from_vector(vec![qhearts.clone()]); let gotten = deck.get_random(); @@ -812,9 +823,9 @@ mod card_deck_tests { #[test] fn last() { let zero = Pile::default(); - let qclubs = Card::new(QUEEN, CLUBS); - let qhearts = Card::new(QUEEN, HEARTS); - let deck = Pile::new_from_vector(vec![qclubs.clone(), qhearts.clone()]); + let qclubs = Card::from_index_strings(QUEEN, CLUBS); + let qhearts = Card::from_index_strings(QUEEN, HEARTS); + let deck = Pile::from_vector(vec![qclubs.clone(), qhearts.clone()]); assert!(zero.last().is_none()); assert_eq!(deck.last().unwrap(), &qhearts); @@ -823,14 +834,26 @@ mod card_deck_tests { #[test] fn len() { let zero = Pile::default(); - let qclubs = Card::new(QUEEN, CLUBS); - let qhearts = Card::new(QUEEN, HEARTS); - let deck = Pile::new_from_vector(vec![qclubs.clone(), qhearts.clone()]); + let qclubs = Card::from_index_strings(QUEEN, CLUBS); + let qhearts = Card::from_index_strings(QUEEN, HEARTS); + let deck = Pile::from_vector(vec![qclubs.clone(), qhearts.clone()]); assert_eq!(zero.len(), 0); assert_eq!(deck.len(), 2); } + #[test] + fn map_by_rank_count() { + let pile = Pile::french_deck() + .pile_by_index(&["QS", "9S", "QC", "QH", "QD"]) + .unwrap(); + let mappie = pile.map_by_rank_count(); + + assert_eq!(*mappie.get(&Rank::new(QUEEN)).unwrap(), 4); + assert_eq!(*mappie.get(&Rank::new(NINE)).unwrap(), 1); + assert!(mappie.get(&Rank::new(EIGHT)).is_none()); + } + #[test] fn map_by_suit() { let pile = Pile::french_deck() @@ -861,9 +884,9 @@ mod card_deck_tests { #[test] fn pile_by_index() { let deck = Pile::french_deck(); - let qclubs = Card::new(QUEEN, CLUBS); - let qhearts = Card::new(QUEEN, HEARTS); - let expected = Pile::new_from_vector(vec![qclubs.clone(), qhearts.clone()]); + let qclubs = Card::from_index_strings(QUEEN, CLUBS); + let qhearts = Card::from_index_strings(QUEEN, HEARTS); + let expected = Pile::from_vector(vec![qclubs.clone(), qhearts.clone()]); let actual = deck.pile_by_index(&["QC", "QH"]); @@ -882,9 +905,9 @@ mod card_deck_tests { #[test] fn position() { - let qclubs = Card::new(QUEEN, CLUBS); - let qhearts = Card::new(QUEEN, HEARTS); - let deck = Pile::new_from_vector(vec![qclubs.clone(), qhearts.clone()]); + let qclubs = Card::from_index_strings(QUEEN, CLUBS); + let qhearts = Card::from_index_strings(QUEEN, HEARTS); + let deck = Pile::from_vector(vec![qclubs.clone(), qhearts.clone()]); assert_eq!(0, deck.position(&qclubs).unwrap()); assert_eq!(1, deck.position(&qhearts).unwrap()); @@ -892,13 +915,13 @@ mod card_deck_tests { #[test] fn prepend() { - let qclubs = Card::new(QUEEN, CLUBS); - let qhearts = Card::new(QUEEN, HEARTS); - let big_joker = Card::new(BIG_JOKER, SPADES); - let little_joker = Card::new(LITTLE_JOKER, SPADES); - let mut to_deck = Pile::new_from_vector(vec![qclubs.clone(), qhearts.clone()]); - let from_deck = Pile::new_from_vector(vec![big_joker.clone(), little_joker.clone()]); - let expected = Pile::new_from_vector(vec![big_joker, little_joker, qclubs, qhearts]); + let qclubs = Card::from_index_strings(QUEEN, CLUBS); + let qhearts = Card::from_index_strings(QUEEN, HEARTS); + let big_joker = Card::from_index_strings(BIG_JOKER, SPADES); + let little_joker = Card::from_index_strings(LITTLE_JOKER, SPADES); + let mut to_deck = Pile::from_vector(vec![qclubs.clone(), qhearts.clone()]); + let from_deck = Pile::from_vector(vec![big_joker.clone(), little_joker.clone()]); + let expected = Pile::from_vector(vec![big_joker, little_joker, qclubs, qhearts]); to_deck.prepend(&from_deck); @@ -908,11 +931,11 @@ mod card_deck_tests { // todo #[test] fn ranks() { - let qc = Card::new(QUEEN, CLUBS); - let qh = Card::new(QUEEN, HEARTS); - let jh = Card::new(JACK, HEARTS); + let qc = Card::from_index_strings(QUEEN, CLUBS); + let qh = Card::from_index_strings(QUEEN, HEARTS); + let jh = Card::from_index_strings(JACK, HEARTS); let expected: Vec = vec![qc.clone().rank, jh.clone().rank]; - let deck = Pile::new_from_vector(vec![jh.clone(), qc.clone(), qh.clone()]); + let deck = Pile::from_vector(vec![jh.clone(), qc.clone(), qh.clone()]); assert_eq!(expected, deck.ranks()); } @@ -929,11 +952,11 @@ mod card_deck_tests { #[test] fn rank_indexes__shuffled() { - let qc = Card::new(QUEEN, CLUBS); - let qh = Card::new(QUEEN, HEARTS); - let jh = Card::new(JACK, HEARTS); + let qc = Card::from_index_strings(QUEEN, CLUBS); + let qh = Card::from_index_strings(QUEEN, HEARTS); + let jh = Card::from_index_strings(JACK, HEARTS); let expected = "QJ".to_string(); - let deck = Pile::new_from_vector(vec![jh.clone(), qc.clone(), qh.clone()]); + let deck = Pile::from_vector(vec![jh.clone(), qc.clone(), qh.clone()]); let actual = deck.rank_indexes(); @@ -952,9 +975,9 @@ mod card_deck_tests { #[test] fn remove() { - let qclubs = Card::new(QUEEN, CLUBS); - let qhearts = Card::new(QUEEN, HEARTS); - let mut deck = Pile::new_from_vector(vec![qclubs.clone(), qhearts.clone()]); + let qclubs = Card::from_index_strings(QUEEN, CLUBS); + let qhearts = Card::from_index_strings(QUEEN, HEARTS); + let mut deck = Pile::from_vector(vec![qclubs.clone(), qhearts.clone()]); let removed = deck.remove(0); @@ -964,9 +987,9 @@ mod card_deck_tests { #[test] fn remove_card() { - let qclubs = Card::new(QUEEN, CLUBS); - let qhearts = Card::new(QUEEN, HEARTS); - let mut deck = Pile::new_from_vector(vec![qclubs.clone(), qhearts.clone()]); + let qclubs = Card::from_index_strings(QUEEN, CLUBS); + let qhearts = Card::from_index_strings(QUEEN, HEARTS); + let mut deck = Pile::from_vector(vec![qclubs.clone(), qhearts.clone()]); let removed = deck.remove_card(&qclubs); @@ -1121,18 +1144,18 @@ mod card_deck_tests { fn short_deck() { let deck = Pile::short_deck(); - assert!(!deck.contains(&Card::new(FIVE, CLUBS))); - assert!(!deck.contains(&Card::new(TWO, CLUBS))); - assert!(!deck.contains(&Card::new(TWO, DIAMONDS))); - assert!(!deck.contains(&Card::new(TWO, SPADES))); + assert!(!deck.contains(&Card::from_index_strings(FIVE, CLUBS))); + assert!(!deck.contains(&Card::from_index_strings(TWO, CLUBS))); + assert!(!deck.contains(&Card::from_index_strings(TWO, DIAMONDS))); + assert!(!deck.contains(&Card::from_index_strings(TWO, SPADES))); } #[test] fn spades_deck() { let deck = Pile::spades_deck(); - assert!(!deck.contains(&Card::new(TWO, CLUBS))); - assert!(!deck.contains(&Card::new(TWO, DIAMONDS))); - assert!(deck.contains(&Card::new(TWO, SPADES))); + assert!(!deck.contains(&Card::from_index_strings(TWO, CLUBS))); + assert!(!deck.contains(&Card::from_index_strings(TWO, DIAMONDS))); + assert!(deck.contains(&Card::from_index_strings(TWO, SPADES))); } } diff --git a/src/cards/rank.rs b/src/cards/rank.rs index 334cde7..533afca 100644 --- a/src/cards/rank.rs +++ b/src/cards/rank.rs @@ -64,11 +64,12 @@ pub const BLANK_RANK: &str = "_"; /// ``` /// let ace = cardpack::Rank { /// weight: 1, +/// prime: 19, /// name: cardpack::fluent_name::FluentName::new(cardpack::ACE), /// }; /// ``` /// This gives you maximum flexibility. Since the value of the Ace is 1, it will be sorted -/// at the and of a Suit (unless there are any Cards with negative weights). +/// at the end of a Suit (unless there are any Cards with negative weights). /// /// # ``Rank::new()`` with a value string /// ``` @@ -90,10 +91,11 @@ pub const BLANK_RANK: &str = "_"; /// Returns a Vector of Ranks with their weights determined by the order they're passed in, high to /// low. This facilitates the easy creation of custom decks, such as pinochle. /// -#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct Rank { /// Used by the Pile struct to sort Cards by their Suit and Rank. - pub weight: isize, + pub weight: u32, + pub prime: u32, pub name: FluentName, } @@ -112,6 +114,7 @@ impl Rank { let name = FluentName::new(name); Rank { weight: name.default_weight(), + prime: name.default_prime(), name, } } @@ -124,9 +127,20 @@ impl Rank { /// let king = cardpack::Rank::new_with_weight(cardpack::QUEEN, 12); /// ``` #[must_use] - pub fn new_with_weight(name: &'static str, weight: isize) -> Rank { + pub fn new_with_weight(name: &'static str, weight: u32) -> Rank { + let name = FluentName::new(name); + Rank { + weight, + prime: name.default_prime(), + name, + } + } + + #[must_use] + pub fn new_with_weight_and_prime(name: &'static str, weight: u32, prime: u32) -> Rank { Rank { weight, + prime, name: FluentName::new(name), } } @@ -143,10 +157,14 @@ impl Rank { pub fn from_array(s: &[&'static str]) -> Vec { let mut v: Vec = Vec::new(); - #[allow(clippy::cast_possible_wrap, clippy::into_iter_on_ref)] + #[allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::into_iter_on_ref + )] for (i, &elem) in s.into_iter().enumerate() { - let weight = (s.len() + 1) - i; - v.push(Rank::new_with_weight(elem, weight as isize)); + let weight = (s.len() - 1) - i; + v.push(Rank::new_with_weight(elem, weight as u32)); } v } @@ -230,6 +248,27 @@ impl Rank { } } +/// Defaults to a blank `Rank`. +impl Default for Rank { + fn default() -> Rank { + Rank::new(BLANK_RANK) + } +} + +/// Allows for the Rank to be displayed as a binary value based upon it's prime field. +/// This will be used for Cactus Kev style hand evaluation. +/// ``` +/// let king = cardpack::Rank::new(cardpack::KING); +/// assert_eq!(format!("King as binary is: {:06b}", king), "King as binary is: 100101"); +/// ``` +impl fmt::Binary for Rank { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let val = self.prime; + + fmt::Binary::fmt(&val, f) + } +} + impl fmt::Display for Rank { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.name.index(&US_ENGLISH)) @@ -278,7 +317,8 @@ mod rank_tests { #[test] fn new() { let expected = Rank { - weight: 9, + weight: 7, + prime: 19, name: FluentName::new(NINE), }; @@ -307,8 +347,8 @@ mod rank_tests { #[test] fn to_vec() { let mut expected: Vec = Vec::new(); - expected.push(Rank::new_with_weight(KING, 3)); - expected.push(Rank::new_with_weight(QUEEN, 2)); + expected.push(Rank::new_with_weight(KING, 1)); + expected.push(Rank::new_with_weight(QUEEN, 0)); assert_eq!(expected, Rank::from_array(&[KING, QUEEN])); } @@ -341,19 +381,19 @@ mod rank_tests { #[test] fn generate_canasta_ranks() { let mut expected: Vec = Vec::new(); - expected.push(Rank::new_with_weight(TWO, 14)); - expected.push(Rank::new_with_weight(ACE, 13)); - expected.push(Rank::new_with_weight(KING, 12)); - expected.push(Rank::new_with_weight(QUEEN, 11)); - expected.push(Rank::new_with_weight(JACK, 10)); - expected.push(Rank::new_with_weight(TEN, 9)); - expected.push(Rank::new_with_weight(NINE, 8)); - expected.push(Rank::new_with_weight(EIGHT, 7)); - expected.push(Rank::new_with_weight(SEVEN, 6)); - expected.push(Rank::new_with_weight(SIX, 5)); - expected.push(Rank::new_with_weight(FIVE, 4)); - expected.push(Rank::new_with_weight(FOUR, 3)); - expected.push(Rank::new_with_weight(THREE, 2)); + expected.push(Rank::new_with_weight(TWO, 12)); + expected.push(Rank::new_with_weight(ACE, 11)); + expected.push(Rank::new_with_weight(KING, 10)); + expected.push(Rank::new_with_weight(QUEEN, 9)); + expected.push(Rank::new_with_weight(JACK, 8)); + expected.push(Rank::new_with_weight(TEN, 7)); + expected.push(Rank::new_with_weight(NINE, 6)); + expected.push(Rank::new_with_weight(EIGHT, 5)); + expected.push(Rank::new_with_weight(SEVEN, 4)); + expected.push(Rank::new_with_weight(SIX, 3)); + expected.push(Rank::new_with_weight(FIVE, 2)); + expected.push(Rank::new_with_weight(FOUR, 1)); + expected.push(Rank::new_with_weight(THREE, 0)); assert_eq!(expected, Rank::generate_canasta_ranks()); } @@ -361,12 +401,12 @@ mod rank_tests { #[test] fn generate_euchre_ranks() { let mut expected: Vec = Vec::new(); - expected.push(Rank::new_with_weight(ACE, 7)); - expected.push(Rank::new_with_weight(KING, 6)); - expected.push(Rank::new_with_weight(QUEEN, 5)); - expected.push(Rank::new_with_weight(JACK, 4)); - expected.push(Rank::new_with_weight(TEN, 3)); - expected.push(Rank::new_with_weight(NINE, 2)); + expected.push(Rank::new_with_weight(ACE, 5)); + expected.push(Rank::new_with_weight(KING, 4)); + expected.push(Rank::new_with_weight(QUEEN, 3)); + expected.push(Rank::new_with_weight(JACK, 2)); + expected.push(Rank::new_with_weight(TEN, 1)); + expected.push(Rank::new_with_weight(NINE, 0)); assert_eq!(expected, Rank::generate_euchre_ranks()); } @@ -394,12 +434,12 @@ mod rank_tests { #[test] fn generate_pinochle_ranks() { let mut expected: Vec = Vec::new(); - expected.push(Rank::new_with_weight(ACE, 7)); - expected.push(Rank::new_with_weight(TEN, 6)); - expected.push(Rank::new_with_weight(KING, 5)); - expected.push(Rank::new_with_weight(QUEEN, 4)); - expected.push(Rank::new_with_weight(JACK, 3)); - expected.push(Rank::new_with_weight(NINE, 2)); + expected.push(Rank::new_with_weight(ACE, 5)); + expected.push(Rank::new_with_weight(TEN, 4)); + expected.push(Rank::new_with_weight(KING, 3)); + expected.push(Rank::new_with_weight(QUEEN, 2)); + expected.push(Rank::new_with_weight(JACK, 1)); + expected.push(Rank::new_with_weight(NINE, 0)); assert_eq!(expected, Rank::generate_pinochle_ranks()); } @@ -423,15 +463,15 @@ mod rank_tests { #[test] fn generate_short_deck_ranks() { let mut expected: Vec = Vec::new(); - expected.push(Rank::new_with_weight(ACE, 10)); - expected.push(Rank::new_with_weight(KING, 9)); - expected.push(Rank::new_with_weight(QUEEN, 8)); - expected.push(Rank::new_with_weight(JACK, 7)); - expected.push(Rank::new_with_weight(TEN, 6)); - expected.push(Rank::new_with_weight(NINE, 5)); - expected.push(Rank::new_with_weight(EIGHT, 4)); - expected.push(Rank::new_with_weight(SEVEN, 3)); - expected.push(Rank::new_with_weight(SIX, 2)); + expected.push(Rank::new_with_weight(ACE, 8)); + expected.push(Rank::new_with_weight(KING, 7)); + expected.push(Rank::new_with_weight(QUEEN, 6)); + expected.push(Rank::new_with_weight(JACK, 5)); + expected.push(Rank::new_with_weight(TEN, 4)); + expected.push(Rank::new_with_weight(NINE, 3)); + expected.push(Rank::new_with_weight(EIGHT, 2)); + expected.push(Rank::new_with_weight(SEVEN, 1)); + expected.push(Rank::new_with_weight(SIX, 0)); assert_eq!(expected, Rank::generate_short_deck_ranks()); } @@ -439,10 +479,35 @@ mod rank_tests { #[test] fn revise_value() { let mut ace = Rank::new(ACE); - assert_eq!(14, ace.weight); + assert_eq!(12, ace.weight); ace.weight = 3; assert_eq!(3, ace.weight); } + + #[test] + fn fmt_binary() { + let king = Rank::new(KING); + let jack = Rank::new(JACK); + let five = Rank::new(FIVE); + + assert_eq!( + format!("King as binary is: {:08b}", king), + "King as binary is: 00100101" + ); + assert_eq!( + format!("Jack as binary is: {:08b}", jack), + "Jack as binary is: 00011101" + ); + assert_eq!( + format!("Five as binary is: {:08b}", five), + "Five as binary is: 00000111" + ); + } + + #[test] + fn default() { + assert_eq!(Rank::default(), Rank::new(BLANK_RANK)); + } } diff --git a/src/cards/suit.rs b/src/cards/suit.rs index 38e8fa4..d43a343 100644 --- a/src/cards/suit.rs +++ b/src/cards/suit.rs @@ -27,9 +27,9 @@ pub const BLANK_SUIT: &str = "blank"; /// Suit struct for a playing card. Made up of the suit's name, letter, and symbol. /// Supports internationalization through fluent template files. -#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct Suit { - pub weight: isize, + pub weight: u32, pub name: FluentName, } @@ -44,13 +44,25 @@ impl Suit { } #[must_use] - pub fn new_with_weight(name: &'static str, weight: isize) -> Suit { + pub fn new_with_weight(name: &'static str, weight: u32) -> Suit { Suit { weight, name: FluentName::new(name), } } + /// Used to generate `Card`'s binary signature. + #[must_use] + pub fn binary_signature(&self) -> u32 { + match self.weight { + 4 => 0x1000, + 3 => 0x2000, + 2 => 0x4000, + 1 => 0x8000, + _ => 0xF000, + } + } + #[must_use] pub fn is_blank(&self) -> bool { self.name.name() == BLANK_SUIT @@ -61,12 +73,12 @@ impl Suit { self.name.fluent_value(FLUENT_SYMBOL_SECTION, &US_ENGLISH) } - #[allow(clippy::cast_possible_wrap)] - fn top_down_value(len: usize, i: usize) -> isize { - (len - i) as isize + #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] + fn top_down_value(len: usize, i: usize) -> u32 { + (len - i) as u32 } - fn from_array_gen(s: &[&'static str], f: impl Fn(usize, usize) -> isize) -> Vec { + fn from_array_gen(s: &[&'static str], f: impl Fn(usize, usize) -> u32) -> Vec { let mut v: Vec = Vec::new(); #[allow(clippy::into_iter_on_ref)] @@ -85,10 +97,10 @@ impl Suit { #[must_use] pub fn from_french_deck_index(symbol: char) -> Suit { match symbol { - '♠' | 'S' | 's' => Suit::new(SPADES), - '♥' | 'H' | 'h' => Suit::new(HEARTS), - '♦' | 'D' | 'd' => Suit::new(DIAMONDS), - '♣' | 'C' | 'c' => Suit::new(CLUBS), + '♤' | '♠' | 'S' | 's' => Suit::new(SPADES), + '♡' | '♥' | 'H' | 'h' => Suit::new(HEARTS), + '♢' | '♦' | 'D' | 'd' => Suit::new(DIAMONDS), + '♧' | '♣' | 'C' | 'c' => Suit::new(CLUBS), '🃟' | 'T' | 't' => Suit::new(TRUMP), _ => Suit::new(BLANK_SUIT), } @@ -110,6 +122,13 @@ impl Suit { } } +/// Defaults to a blank `Suit`. +impl Default for Suit { + fn default() -> Suit { + Suit::new(BLANK_SUIT) + } +} + impl fmt::Display for Suit { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.symbol()) @@ -159,6 +178,14 @@ mod suit_tests { assert_eq!(expected, Suit::new_with_weight(SPADES, 4)); } + #[test] + fn binary_signature() { + assert_eq!(4096, Suit::new(SPADES).binary_signature()); + assert_eq!(8192, Suit::new(HEARTS).binary_signature()); + assert_eq!(16384, Suit::new(DIAMONDS).binary_signature()); + assert_eq!(32768, Suit::new(CLUBS).binary_signature()); + } + #[rstest] #[case('♠', Suit::new(SPADES))] #[case('S', Suit::new(SPADES))] @@ -182,7 +209,7 @@ mod suit_tests { } #[test] - fn part1ial_eq() { + fn partial_eq() { assert_ne!( Suit::new_with_weight(SPADES, 3), Suit::new_with_weight(SPADES, 4) @@ -269,4 +296,9 @@ mod suit_tests { assert_eq!(3, wands.weight); } + + #[test] + fn default() { + assert_eq!(Suit::default(), Suit::new(BLANK_SUIT)); + } } diff --git a/src/fluent/locales/core.ftl b/src/fluent/locales/core.ftl index 270bc5b..ccba287 100644 --- a/src/fluent/locales/core.ftl +++ b/src/fluent/locales/core.ftl @@ -1,6 +1,6 @@ # Default blank-symbol = _ -blank-weight = -1 +blank-weight = 0 blank-long = _____ blank-index = _ @@ -22,21 +22,37 @@ hearts-weight = 3 diamonds-weight = 2 clubs-weight = 1 -big-joker-weight = 100 -little-joker-weight = 50 -ace-weight = 14 -king-weight = 13 -queen-weight = 12 -jack-weight = 11 -ten-weight = 10 -nine-weight = 9 -eight-weight = 8 -seven-weight = 7 -six-weight = 6 -five-weight = 5 -four-weight = 4 -three-weight = 3 -two-weight = 2 +big-joker-weight = 14 +little-joker-weight = 13 +ace-weight = 12 +king-weight = 11 +queen-weight = 10 +jack-weight = 9 +ten-weight = 8 +nine-weight = 7 +eight-weight = 6 +seven-weight = 5 +six-weight = 4 +five-weight = 3 +four-weight = 2 +three-weight = 1 +two-weight = 0 + +big-joker-prime=47 +little-joker-prime=43 +ace-prime = 41 +king-prime = 37 +queen-prime = 31 +jack-prime = 29 +ten-prime = 23 +nine-prime = 19 +eight-prime = 17 +seven-prime = 13 +six-prime = 11 +five-prime = 7 +four-prime = 5 +three-prime = 3 +two-prime = 2 # Skat Deck eichel-symbol = ♣ diff --git a/src/fluent/named.rs b/src/fluent/named.rs index 2c9a42e..058111c 100644 --- a/src/fluent/named.rs +++ b/src/fluent/named.rs @@ -17,6 +17,7 @@ pub const FLUENT_INDEX_SECTION: &str = "index"; pub const FLUENT_LONG_SECTION: &str = "long"; pub const FLUENT_SYMBOL_SECTION: &str = "symbol"; pub const FLUENT_WEIGHT_SECTION: &str = "weight"; +pub const FLUENT_PRIME_SECTION: &str = "prime"; pub trait Named { fn name(&self) -> &str; @@ -81,8 +82,13 @@ pub trait Named { } /// Returns the default weight for a name. Weight is used to sort cards. - fn default_weight(&self) -> isize { + fn default_weight(&self) -> u32 { let weight = self.fluent_value(FLUENT_WEIGHT_SECTION, &US_ENGLISH); weight.parse().unwrap_or(0) } + + fn default_prime(&self) -> u32 { + let prime = self.fluent_value(FLUENT_PRIME_SECTION, &US_ENGLISH); + prime.parse().unwrap_or(0) + } } diff --git a/src/lib.rs b/src/lib.rs index 8ab5c23..9ba98c9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,12 +5,12 @@ extern crate rand; pub mod cards; pub mod fluent; -pub use cards::card::Card; -pub use cards::decks::bridge::BridgeBoard; -pub use cards::decks::standard52::Standard52; -pub use cards::pack::Pack; -pub use cards::pile::Pile; -pub use cards::rank::*; -pub use cards::suit::*; +pub use crate::cards::card::Card; +pub use crate::cards::decks::bridge::BridgeBoard; +pub use crate::cards::decks::standard52::Standard52; +pub use crate::cards::pack::Pack; +pub use crate::cards::pile::Pile; +pub use crate::cards::rank::*; +pub use crate::cards::suit::*; pub use fluent::named::*; pub use fluent::*;