From 68e891d3b735d6643c49597eff5a84ff5470297e Mon Sep 17 00:00:00 2001 From: Rhys Lloyd Date: Sat, 16 May 2026 10:04:32 -0700 Subject: [PATCH] minify state & respect InstructionSrc spec --- src/card_game.rs | 55 ++++++++---- src/klondike.rs | 227 ++++++++++++++++++++++++++++++++++++----------- src/main.rs | 42 ++++----- 3 files changed, 236 insertions(+), 88 deletions(-) diff --git a/src/card_game.rs b/src/card_game.rs index 1594d0f..12b225d 100644 --- a/src/card_game.rs +++ b/src/card_game.rs @@ -1,3 +1,5 @@ +use core::ops::RangeBounds; + // TODO: pub struct ValidInstruction(I); pub trait Game { type Instruction; @@ -103,8 +105,11 @@ impl Card { #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct Stack(arrayvec::ArrayVec); impl Stack { - pub fn new() -> Self { - Self(arrayvec::ArrayVec::new()) + pub const fn new() -> Self { + Self(arrayvec::ArrayVec::new_const()) + } + pub fn take_range>(&mut self, range: R) -> Self { + Stack::from_iter(self.drain(range)) } } impl Stack<52> { @@ -124,6 +129,11 @@ impl From> for Stack { Self(value) } } +impl FromIterator for Stack { + fn from_iter>(iter: T) -> Self { + Self(arrayvec::ArrayVec::from_iter(iter)) + } +} impl core::ops::Deref for Stack { type Target = arrayvec::ArrayVec; fn deref(&self) -> &Self::Target { @@ -144,30 +154,23 @@ impl IntoIterator for Stack { } #[derive(Clone, Debug, Eq, Hash, PartialEq)] -pub struct Pile { - face_down: Stack, - face_up: Stack, +pub struct Pile { + face_down: Stack, + face_up: Stack, } -impl Pile { +impl Pile { pub fn new() -> Self { Self { face_down: Stack::new(), face_up: Stack::new(), } } - pub fn new_face_down(stack: Stack) -> Self { + pub fn new_face_down(stack: Stack) -> Self { Self { face_down: stack, face_up: Stack::new(), } } - pub fn flip_it_and_reverse_it(&mut self) { - self.swap_up_down(); - self.face_down.reverse(); - } - pub fn swap_up_down(&mut self) { - core::mem::swap(&mut self.face_up, &mut self.face_down); - } pub fn flip_up(&mut self) { if let Some(card) = self.face_down.pop() { self.face_up.push(card); @@ -179,16 +182,25 @@ impl Pile { pub fn pop(&mut self) -> Option { self.face_up.pop() } - pub fn pop_flip_up(&mut self) -> Option { - let card = self.pop()?; + pub fn take_range>(&mut self, range: R) -> Stack { + // if self.face_up.get(range).is_none() { + // return None; + // } + self.face_up.take_range(range) + } + pub fn take_range_flip_up>(&mut self, range: R) -> Stack { + let cards = self.take_range(range); if self.face_up.is_empty() { self.flip_up(); } - Some(card) + cards } pub fn push(&mut self, card: Card) { self.face_up.push(card); } + pub fn extend>(&mut self, cards: I) { + self.face_up.extend(cards); + } pub fn face_up(&self) -> &[Card] { &self.face_up } @@ -196,6 +208,15 @@ impl Pile { &self.face_down } } +impl Pile { + pub fn flip_it_and_reverse_it(&mut self) { + self.swap_up_down(); + self.face_down.reverse(); + } + pub fn swap_up_down(&mut self) { + core::mem::swap(&mut self.face_up, &mut self.face_down); + } +} #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct Session { diff --git a/src/klondike.rs b/src/klondike.rs index de74bf8..1e1fe73 100644 --- a/src/klondike.rs +++ b/src/klondike.rs @@ -1,5 +1,5 @@ use crate::Rng; -use crate::card_game::{CardValue, Game, Pile, Stack}; +use crate::card_game::{Card, CardValue, Game, Pile, Stack}; #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct KlondikeConfig {} @@ -260,8 +260,6 @@ impl KlondikeInstruction { } } -const STOCKS: usize = 1; -const FOUNDATIONS: usize = 4; const TABLEAUS: usize = 7; const fn sum(n: usize) -> usize { n * (n + 1) / 2 @@ -269,15 +267,145 @@ const fn sum(n: usize) -> usize { const MAX_STACK: usize = 52 - sum(TABLEAUS); #[derive(Clone, Debug, Eq, Hash, PartialEq)] -struct KlondikeState { - piles: [Pile; TABLEAUS + FOUNDATIONS + STOCKS], +pub struct KlondikeState { + stock: Pile, + foundations: [Stack<13>; 4], + tableau1: Pile<0, 13>, + tableau2: Pile<1, 13>, + tableau3: Pile<2, 13>, + tableau4: Pile<3, 13>, + tableau5: Pile<4, 13>, + tableau6: Pile<5, 13>, + tableau7: Pile<6, 13>, } impl KlondikeState { - fn pile(&self, index: KlondikePileId) -> &Pile { - &self.piles[index as usize] + pub const fn stock(&self) -> &Pile { + &self.stock } - fn pile_mut(&mut self, index: KlondikePileId) -> &mut Pile { - &mut self.piles[index as usize] + pub const fn foundation1(&self) -> &Stack<13> { + &self.foundations[1 - 1] + } + pub const fn foundation2(&self) -> &Stack<13> { + &self.foundations[2 - 1] + } + pub const fn foundation3(&self) -> &Stack<13> { + &self.foundations[3 - 1] + } + pub const fn foundation4(&self) -> &Stack<13> { + &self.foundations[4 - 1] + } + pub const fn tableau1(&self) -> &Pile<0, 13> { + &self.tableau1 + } + pub const fn tableau2(&self) -> &Pile<1, 13> { + &self.tableau2 + } + pub const fn tableau3(&self) -> &Pile<2, 13> { + &self.tableau3 + } + pub const fn tableau4(&self) -> &Pile<3, 13> { + &self.tableau4 + } + pub const fn tableau5(&self) -> &Pile<4, 13> { + &self.tableau5 + } + pub const fn tableau6(&self) -> &Pile<5, 13> { + &self.tableau6 + } + pub const fn tableau7(&self) -> &Pile<6, 13> { + &self.tableau7 + } + fn src_card(&self, src: InstructionSrc) -> Option<&Card> { + match src.into_spec() { + KlondikePileStack::Tableau1(skip_cards) => { + self.tableau1.face_up().get(skip_cards as usize) + } + KlondikePileStack::Tableau2(skip_cards) => { + self.tableau2.face_up().get(skip_cards as usize) + } + KlondikePileStack::Tableau3(skip_cards) => { + self.tableau3.face_up().get(skip_cards as usize) + } + KlondikePileStack::Tableau4(skip_cards) => { + self.tableau4.face_up().get(skip_cards as usize) + } + KlondikePileStack::Tableau5(skip_cards) => { + self.tableau5.face_up().get(skip_cards as usize) + } + KlondikePileStack::Tableau6(skip_cards) => { + self.tableau6.face_up().get(skip_cards as usize) + } + KlondikePileStack::Tableau7(skip_cards) => { + self.tableau7.face_up().get(skip_cards as usize) + } + KlondikePileStack::Foundation1 => self.foundations[1 - 1].last(), + KlondikePileStack::Foundation2 => self.foundations[2 - 1].last(), + KlondikePileStack::Foundation3 => self.foundations[3 - 1].last(), + KlondikePileStack::Foundation4 => self.foundations[4 - 1].last(), + KlondikePileStack::Stock => self.stock.face_up().last(), + } + } + fn take_src_cards(&mut self, src: InstructionSrc) -> Stack<13> { + match src.into_spec() { + KlondikePileStack::Tableau1(skip_cards) => { + self.tableau1.take_range_flip_up(skip_cards as usize..) + } + KlondikePileStack::Tableau2(skip_cards) => { + self.tableau2.take_range_flip_up(skip_cards as usize..) + } + KlondikePileStack::Tableau3(skip_cards) => { + self.tableau3.take_range_flip_up(skip_cards as usize..) + } + KlondikePileStack::Tableau4(skip_cards) => { + self.tableau4.take_range_flip_up(skip_cards as usize..) + } + KlondikePileStack::Tableau5(skip_cards) => { + self.tableau5.take_range_flip_up(skip_cards as usize..) + } + KlondikePileStack::Tableau6(skip_cards) => { + self.tableau6.take_range_flip_up(skip_cards as usize..) + } + KlondikePileStack::Tableau7(skip_cards) => { + self.tableau7.take_range_flip_up(skip_cards as usize..) + } + KlondikePileStack::Foundation1 => Stack::from_iter(self.foundations[1 - 1].pop()), + KlondikePileStack::Foundation2 => Stack::from_iter(self.foundations[2 - 1].pop()), + KlondikePileStack::Foundation3 => Stack::from_iter(self.foundations[3 - 1].pop()), + KlondikePileStack::Foundation4 => Stack::from_iter(self.foundations[4 - 1].pop()), + KlondikePileStack::Stock => Stack::from_iter(self.stock.pop()), + } + } + fn dst_card(&self, dst: KlondikePileId) -> Option<&Card> { + match dst { + KlondikePileId::Tableau1 => self.tableau1.face_up().last(), + KlondikePileId::Tableau2 => self.tableau2.face_up().last(), + KlondikePileId::Tableau3 => self.tableau3.face_up().last(), + KlondikePileId::Tableau4 => self.tableau4.face_up().last(), + KlondikePileId::Tableau5 => self.tableau5.face_up().last(), + KlondikePileId::Tableau6 => self.tableau6.face_up().last(), + KlondikePileId::Tableau7 => self.tableau7.face_up().last(), + KlondikePileId::Foundation1 => self.foundations[1 - 1].last(), + KlondikePileId::Foundation2 => self.foundations[2 - 1].last(), + KlondikePileId::Foundation3 => self.foundations[3 - 1].last(), + KlondikePileId::Foundation4 => self.foundations[4 - 1].last(), + KlondikePileId::Stock => None, + } + } + fn extend_dst_pile(&mut self, dst: KlondikePileId, cards: Stack<13>) { + match dst { + KlondikePileId::Tableau1 => self.tableau1.extend(cards), + KlondikePileId::Tableau2 => self.tableau2.extend(cards), + KlondikePileId::Tableau3 => self.tableau3.extend(cards), + KlondikePileId::Tableau4 => self.tableau4.extend(cards), + KlondikePileId::Tableau5 => self.tableau5.extend(cards), + KlondikePileId::Tableau6 => self.tableau6.extend(cards), + KlondikePileId::Tableau7 => self.tableau7.extend(cards), + KlondikePileId::Foundation1 => self.foundations[1 - 1].extend(cards), + KlondikePileId::Foundation2 => self.foundations[2 - 1].extend(cards), + KlondikePileId::Foundation3 => self.foundations[3 - 1].extend(cards), + KlondikePileId::Foundation4 => self.foundations[4 - 1].extend(cards), + KlondikePileId::Stock => (), + } } fn is_instruction_valid(&self, instruction: KlondikeInstruction) -> bool { match instruction { @@ -287,7 +415,7 @@ impl KlondikeState { dst: KlondikePileId::Stock, } => { // cannot move stock when stock is empty - !self.pile(KlondikePileId::Stock).is_empty() + !self.stock.is_empty() } // cannot move cards to stock @@ -307,8 +435,8 @@ impl KlondikeState { ) => { // get the top cards - if let Some(src_card) = self.pile(src.into()).face_up().last() { - match self.pile(dst).face_up().last() { + if let Some(src_card) = self.src_card(src) { + match self.dst_card(dst) { // destination card exists Some(dst_card) => { // suit matches? @@ -326,8 +454,8 @@ impl KlondikeState { // other = move to tableau KlondikeInstruction { src, dst } => { // get the top cards - if let Some(src_card) = self.pile(src.into()).face_up().last() { - match self.pile(dst).face_up().last() { + if let Some(src_card) = self.src_card(src) { + match self.dst_card(dst) { // destination card exists Some(dst_card) => { // red-ness is opposite? @@ -385,41 +513,38 @@ impl Klondike { let mut deck = deck.into_iter(); // generate tableaus - let [t0, t1, t2, t3, t4, t5, t6] = core::array::from_fn(|i| { - let stack = arrayvec::ArrayVec::from_iter((&mut deck).take(i)).into(); + fn pile(deck: &mut arrayvec::IntoIter) -> Pile { + let stack = arrayvec::ArrayVec::from_iter(deck.take(DN)).into(); let mut pile = Pile::new_face_down(stack); pile.push(deck.next().unwrap()); pile - }); + } + let tableau1 = pile(&mut deck); + let tableau2 = pile(&mut deck); + let tableau3 = pile(&mut deck); + let tableau4 = pile(&mut deck); + let tableau5 = pile(&mut deck); + let tableau6 = pile(&mut deck); + let tableau7 = pile(&mut deck); // stock is remaining cards let stock = Pile::new_face_down(arrayvec::ArrayVec::from_iter(deck).into()); let state = KlondikeState { - piles: [ - t0, - t1, - t2, - t3, - t4, - t5, - t6, - Pile::new(), - Pile::new(), - Pile::new(), - Pile::new(), - stock, - ], + stock, + foundations: core::array::from_fn(|_| Stack::new()), + tableau1, + tableau2, + tableau3, + tableau4, + tableau5, + tableau6, + tableau7, }; Self { config, state } } - #[inline] - pub fn pile(&self, index: KlondikePileId) -> &Pile { - self.state.pile(index) - } - #[inline] - fn pile_mut(&mut self, index: KlondikePileId) -> &mut Pile { - self.state.pile_mut(index) + pub const fn state(&self) -> &KlondikeState { + &self.state } } @@ -439,25 +564,27 @@ impl Game for Klondike { src: InstructionSrc::STOCK, dst: KlondikePileId::Stock, } => { - if self.pile(KlondikePileId::Stock).face_down().is_empty() { - self.pile_mut(KlondikePileId::Stock) - .flip_it_and_reverse_it(); + if self.state.stock.face_down().is_empty() { + self.state.stock.flip_it_and_reverse_it(); } else { - self.pile_mut(KlondikePileId::Stock).flip_up(); + self.state.stock.flip_up(); } } KlondikeInstruction { src, dst } => { - if let Some(card) = self.pile_mut(src.into()).pop_flip_up() { - self.pile_mut(dst).push(card); - } else { - println!("Attempted to move from an empty src"); - dbg!(instruction); - } + let cards = self.state.take_src_cards(src); + self.state.extend_dst_pile(dst, cards); } } } fn is_win(&self) -> bool { - // assuming only valid moves, tableau empty and stock empty means win - self.state.piles[0..9].iter().all(|pile| pile.is_empty()) + // all face down cards empty means win + self.state.stock.face_down().is_empty() + && self.state.tableau1.face_down().is_empty() + && self.state.tableau2.face_down().is_empty() + && self.state.tableau3.face_down().is_empty() + && self.state.tableau4.face_down().is_empty() + && self.state.tableau5.face_down().is_empty() + && self.state.tableau6.face_down().is_empty() + && self.state.tableau7.face_down().is_empty() } } diff --git a/src/main.rs b/src/main.rs index 625b4ce..7073972 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ mod klondike; pub type Rng = rand::rngs::ThreadRng; -use card_game::{Card, Game, Session, Suit}; +use card_game::{Card, Game, Pile, Session, Suit}; use klondike::{ InstructionSrc, Klondike, KlondikeInstruction, KlondikePileId, KlondikePileStack, SkipCards, }; @@ -41,38 +41,30 @@ impl Display for OptionalCard<'_> { 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(); + let stock_count = self.state().stock().face_down().len(); writeln!(f, "Stock: {stock_count}")?; // Hand - let hand = self.pile(KlondikePileId::Stock).face_up().last(); + let hand = self.state().stock().face_up().last(); writeln!(f, "Hand: {}", OptionalCard(hand))?; // Foundations write!( f, "Foundations: {} {} {} {}", - OptionalCard(self.pile(KlondikePileId::Foundation1).face_up().last()), - OptionalCard(self.pile(KlondikePileId::Foundation2).face_up().last()), - OptionalCard(self.pile(KlondikePileId::Foundation3).face_up().last()), - OptionalCard(self.pile(KlondikePileId::Foundation4).face_up().last()), + OptionalCard(self.state().foundation1().last()), + OptionalCard(self.state().foundation2().last()), + OptionalCard(self.state().foundation3().last()), + OptionalCard(self.state().foundation4().last()), )?; writeln!(f)?; - for (i, tableau) in [ - KlondikePileId::Tableau1, - KlondikePileId::Tableau2, - KlondikePileId::Tableau3, - KlondikePileId::Tableau4, - KlondikePileId::Tableau5, - KlondikePileId::Tableau6, - KlondikePileId::Tableau7, - ] - .into_iter() - .enumerate() - { - write!(f, "T{} ", i + 1)?; - let pile = self.pile(tableau); + fn write_pile( + f: &mut std::fmt::Formatter<'_>, + pile: &Pile, + pile_id: usize, + ) -> std::fmt::Result { + write!(f, "T{} ", pile_id)?; for _ in pile.face_down() { write!(f, "]")?; } @@ -80,7 +72,15 @@ impl Display for Klondike { write!(f, "{card}")?; } writeln!(f)?; + Ok(()) } + write_pile(f, self.state().tableau1(), 1)?; + write_pile(f, self.state().tableau2(), 2)?; + write_pile(f, self.state().tableau3(), 3)?; + write_pile(f, self.state().tableau4(), 4)?; + write_pile(f, self.state().tableau5(), 5)?; + write_pile(f, self.state().tableau6(), 6)?; + write_pile(f, self.state().tableau7(), 7)?; Ok(()) }