diff --git a/src/card_game.rs b/src/card_game.rs index 09122b0..1f8e49f 100644 --- a/src/card_game.rs +++ b/src/card_game.rs @@ -42,6 +42,9 @@ impl CardValue { pub const JACK: Self = CardValue(11); pub const QUEEN: Self = CardValue(12); pub const KING: Self = CardValue(13); + pub fn get(self) -> u8 { + self.0 + } pub fn checked_add(self, offset: u8) -> Option { let new_value = self.0.checked_add(offset)?; if 13 < new_value { @@ -192,6 +195,9 @@ where history: Vec::new(), } } + pub fn state(&self) -> &G { + &self.state + } pub fn history(&self) -> &[G::Instruction] { &self.history } diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..aa5ef14 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,138 @@ +mod card_game; +mod klondike; + +pub type Rng = rand::rngs::ThreadRng; + +use card_game::{Card, Game, Session, Suit}; +use klondike::{Klondike, KlondikeInstruction, KlondikePileId}; + +use std::fmt::Display; + +impl Display for Card { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.value().get() { + 1 => write!(f, "A"), + 11 => write!(f, "J"), + 12 => write!(f, "Q"), + 13 => write!(f, "K"), + other => write!(f, "{other}"), + }?; + match self.suit() { + Suit::Spades => write!(f, "♠"), + Suit::Hearts => write!(f, "♥️"), + Suit::Clubs => write!(f, "♣"), + Suit::Diamonds => write!(f, "♦"), + } + } +} + +struct OptionalCard<'a>(Option<&'a Card>); +impl Display for OptionalCard<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + OptionalCard(Some(card)) => write!(f, "{card}"), + OptionalCard(None) => write!(f, "None"), + } + } +} + +impl Display for Klondike { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // Stock + let stock_count = self.pile(KlondikePileId::Stock).face_down().len(); + writeln!(f, "Stock: {stock_count}")?; + + // Hand + let hand = self.pile(KlondikePileId::Stock).face_up().last(); + writeln!(f, "Hand: {}", OptionalCard(hand))?; + + // Foundations + write!( + f, + "Foundations: {} {} {} {}", + OptionalCard(self.pile(KlondikePileId::Foundation0).face_up().last()), + OptionalCard(self.pile(KlondikePileId::Foundation1).face_up().last()), + OptionalCard(self.pile(KlondikePileId::Foundation2).face_up().last()), + OptionalCard(self.pile(KlondikePileId::Foundation3).face_up().last()) + )?; + writeln!(f)?; + + for (i, tableau) in [ + KlondikePileId::Tableau0, + KlondikePileId::Tableau1, + KlondikePileId::Tableau2, + KlondikePileId::Tableau3, + KlondikePileId::Tableau4, + KlondikePileId::Tableau5, + KlondikePileId::Tableau6, + KlondikePileId::Tableau7, + ] + .into_iter() + .enumerate() + { + write!(f, "T{i} ")?; + let pile = self.pile(tableau); + for _ in pile.face_down() { + write!(f, "]")?; + } + for card in pile.face_up() { + write!(f, "{card}")?; + } + writeln!(f)?; + } + + Ok(()) + } +} + +#[derive(Debug)] +struct Invalid; +struct Parsed(T); +impl core::str::FromStr for Parsed { + type Err = Invalid; + fn from_str(s: &str) -> Result { + let Parsed(src) = s.get(0..2).ok_or(Invalid)?.parse()?; + let Parsed(dst) = s.get(3..5).ok_or(Invalid)?.parse()?; + Ok(Parsed(KlondikeInstruction { src, dst })) + } +} +impl core::str::FromStr for Parsed { + type Err = Invalid; + fn from_str(s: &str) -> Result { + Ok(Parsed(match s { + "ST" => KlondikePileId::Stock, + "T0" => KlondikePileId::Tableau0, + "T1" => KlondikePileId::Tableau1, + "T2" => KlondikePileId::Tableau2, + "T3" => KlondikePileId::Tableau3, + "T4" => KlondikePileId::Tableau4, + "T5" => KlondikePileId::Tableau5, + "T6" => KlondikePileId::Tableau6, + "T7" => KlondikePileId::Tableau7, + "F0" => KlondikePileId::Foundation0, + "F1" => KlondikePileId::Foundation1, + "F2" => KlondikePileId::Foundation2, + "F3" => KlondikePileId::Foundation3, + _ => return Err(Invalid), + })) + } +} + +fn main() -> Result<(), std::io::Error> { + let mut session = Session::new(Klondike::new_random_default()); + loop { + // display game + println!("{}", session.state()); + + // parse input + let mut input = String::new(); + std::io::stdin().read_line(&mut input)?; + let Ok(Parsed(instruction)) = input.trim().parse() else { + println!("Invalid move!"); + continue; + }; + + // perform move + session.process_instruction(instruction); + } +}