use thiserror::Error; use strum::IntoEnumIterator; use strum_macros::EnumIter; use std::fmt; use rand::seq::SliceRandom; use rand::rng; #[derive(PartialEq, Debug, EnumIter, Copy, Clone)] pub enum Suit { Heart, Diamond, Club, Spade } #[derive(PartialEq, Debug)] pub enum Colour { Black, Red, } impl Suit { pub fn colour(&self) -> Colour { match *self { Self::Heart | Suit::Diamond => Colour::Red, Self::Club | Suit::Spade => Colour::Black, } } } impl fmt::Display for Suit { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::Heart => write!(f, "♥"), Self::Diamond => write!(f, "♦"), Self::Club => write!(f, "♣"), Self::Spade => write!(f, "♠"), } } } #[derive(PartialEq, Debug, EnumIter, Copy, Clone)] pub enum Value { Ace, Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten, Jack, Queen, King } impl Value { fn indexed_values(&self) -> u8 { // It might also make sense for Ace to be high... depends on context match self { Self::Ace => 1, Self::Two => 2, Self::Three => 3, Self::Four => 4, Self::Five => 5, Self::Six => 6, Self::Seven => 7, Self::Eight => 8, Self::Nine => 9, Self::Ten => 10, Self::Jack => 11, Self::Queen => 12, Self::King => 13, } } } impl fmt::Display for Value { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::Ace => write!(f, "A"), Self::Two => write!(f, "2"), Self::Three => write!(f, "3"), Self::Four => write!(f, "4"), Self::Five => write!(f, "5"), Self::Six => write!(f, "6"), Self::Seven => write!(f, "7"), Self::Eight => write!(f, "8"), Self::Nine => write!(f, "9"), Self::Ten => write!(f, "T"), Self::Jack => write!(f, "J"), Self::Queen => write!(f, "Q"), Self::King => write!(f, "K"), } } } #[derive(PartialEq, Debug, Copy, Clone)] pub struct Card { pub suit: Suit, pub value: Value, pub visible: bool, } impl fmt::Display for Card { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}{}", self.value, self.suit) } } impl Default for Card { fn default() -> Self { Card { suit: Suit::Spade, value: Value::Ace, // If you like to gamble... visible: false, } } } #[derive(Error, Debug, PartialEq)] pub enum StackingError { #[error("Trying to stack the same coloured suit")] SameColour, #[error("{0} is not \"next\" to {1}")] NotAdjacent(String, String), } impl Card { pub fn can_be_placed_on_top(&self, top: &Card) -> Result<(), StackingError> { // Can't be the same Colour if self.suit.colour() == top.suit.colour() { return Err(StackingError::SameColour); } // Needs to be adjacent if self.value == Value::King || self.value.indexed_values() + 1 != top.value.indexed_values() { return Err(StackingError::NotAdjacent(self.to_string(), top.to_string())); } Ok(()) } } #[derive(Debug)] pub struct Deck { pub cards: Vec } impl Default for Deck { fn default() -> Self { let mut array = Vec::new(); for suit in Suit::iter() { for value in Value::iter() { array.push( Card { suit, value, ..Default::default() } ); } } Deck { cards: array, } } } enum Seed { Unseeded, SeedVal(u64), } impl Deck { pub fn shuffle(&mut self, seed: Seed) { match seed { Seed::SeedVal(_s) => unimplemented!("Not yet"), Seed::Unseeded => { let mut rng = rng(); self.cards.shuffle(&mut rng); } } } } #[derive(Debug)] enum NumPassesThroughDeck { Unlimited, Limited(u64), } pub const NUM_PILES_KLONDIKE: usize = 7; #[derive(Debug)] pub struct Klondike { pub piles: [Vec; NUM_PILES_KLONDIKE], pub deck: Vec, pub waste: Vec, pub foundation: [Vec; 4], // 4 = len of num suits max_num_passes_through_deck: NumPassesThroughDeck, current_num_passes_through_deck: u64, num_cards_turned: u8, } impl Default for Klondike { fn default() -> Self { let mut deck = Deck::default(); deck.shuffle(Seed::Unseeded); let mut piles: [Vec; NUM_PILES_KLONDIKE] = [Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new()]; for pile in 0..NUM_PILES_KLONDIKE { for num in 0..pile+1 { let mut c = deck.cards.pop().unwrap(); if num == pile { c.visible = true; } piles[pile].push(c); } } Self { piles, deck: deck.cards, waste: Vec::new(), foundation: [Vec::new(), Vec::new(), Vec::new(), Vec::new()], // is this really the best way? max_num_passes_through_deck: NumPassesThroughDeck::Unlimited, current_num_passes_through_deck: 0, num_cards_turned: 3, } } } #[cfg(test)] mod tests { use super::*; #[test] fn basic_card_stacking() { let testing_card = Card { suit: Suit::Heart, value: Value::Five, ..Default::default() }; let bad_same_suit = Card { suit: Suit::Heart, value: Value::Six, ..Default::default() }; assert_eq!(testing_card.can_be_placed_on_top(&bad_same_suit), Err(StackingError::SameColour)); let bad_same_colour = Card { suit: Suit::Diamond, value: Value::Six, ..Default::default() }; assert_eq!(testing_card.can_be_placed_on_top(&bad_same_colour), Err(StackingError::SameColour)); let should_stack_card = Card { suit: Suit::Club, value: Value::Six, ..Default::default() }; assert_eq!(testing_card.can_be_placed_on_top(&should_stack_card), Ok(())); let value_too_high = Card { suit: Suit::Club, value: Value::Seven, ..Default::default() }; let not_adj_error = testing_card.can_be_placed_on_top(&value_too_high); if let Err(e) = not_adj_error { match e { StackingError::NotAdjacent(_, _) => assert!(true), StackingError::SameColour => assert!(false, "Colour is different - incorrect error"), } } else { assert!(false, "Cards are not adjacent - should be an error") } } #[test] fn get_a_whole_deck() { let d = Deck::default(); assert_eq!(d.cards.len(), 52); // Probably should test whether all cards are in... eh println!("{:#?}", d); // A "manual" review looks alright } #[test] fn klondike() { let k = Klondike::default(); println!("{:#?}", k); } }