Files
mini_projects/card_stuffs/src/lib.rs
Arthur Roberts 0ae0bf73b2 Added deck and the start of the waste
I think I'm going to need some mode advanced widget that I make myself though
2025-02-26 19:26:17 +00:00

294 lines
7.4 KiB
Rust

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<Card>
}
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<Card>; NUM_PILES_KLONDIKE],
pub deck: Vec<Card>,
pub waste: Vec<Card>,
pub foundation: [Vec<Card>; 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<Card>; 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);
}
}