Skip to main content

games/solitaire/
game.rs

1use crate::solitaire::{
2    SolitaireCard,
3    errors::{SError, SolitaireError},
4};
5
6/// Area of cards
7#[repr(C)]
8#[derive(
9    Copy, Clone, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq, Ord, PartialOrd, Hash,
10)]
11pub enum Area {
12    /// Stockpile of cards
13    Stockpile,
14    /// Waste pile
15    Waste,
16    /// Tablue (amount: 0 is all)
17    Tablue {
18        /// Tablue row (0-7)
19        row: usize,
20        /// Amount of cards to move
21        /// If amount is 0, selects the whole row
22        /// If amount is > 1, selects cards from the top of stack up to the number
23        amount: usize,
24    },
25    /// Foundation stack
26    Foundation {
27        /// Foundation row
28        row: usize,
29    },
30}
31
32/// Solitaire
33#[derive(
34    Clone, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq, Ord, PartialOrd, Hash,
35)]
36pub struct Solitaire {
37    // Number of moves made
38    i_moves: u32,
39    /// stockpile of cards
40    pub stockpile: crate::deck::Deck<crate::cards::StandardCard>,
41    /// Waste pile
42    pub waste: Vec<crate::cards::StandardCard>,
43    /// Table
44    pub tablue: [Vec<SolitaireCard>; 7],
45    /// Foundation pile
46    pub foundation: [Vec<SolitaireCard>; 4],
47}
48
49impl Solitaire {
50    /// New solitaire game
51    pub fn new() -> Self {
52        let mut deck = crate::deck::Deck::default();
53        deck.shuffle();
54        let mut cards = deck.draw_many(28);
55        Solitaire {
56            i_moves: 0,
57            stockpile: deck,
58            waste: Vec::with_capacity(47),
59            tablue: [
60                // 0 Down, 1 Up
61                vec![SolitaireCard::new_up(cards.remove(0))],
62                // 1 Down, 1 Up
63                vec![
64                    SolitaireCard::new_down(cards.remove(0)),
65                    SolitaireCard::new_up(cards.remove(0)),
66                ],
67                // 2 Down, 1 Up
68                vec![
69                    SolitaireCard::new_down(cards.remove(0)),
70                    SolitaireCard::new_down(cards.remove(0)),
71                    SolitaireCard::new_up(cards.remove(0)),
72                ],
73                // 3 Down, 1 Up
74                vec![
75                    SolitaireCard::new_down(cards.remove(0)),
76                    SolitaireCard::new_down(cards.remove(0)),
77                    SolitaireCard::new_down(cards.remove(0)),
78                    SolitaireCard::new_up(cards.remove(0)),
79                ],
80                // 4 Down, 1 Up
81                vec![
82                    SolitaireCard::new_down(cards.remove(0)),
83                    SolitaireCard::new_down(cards.remove(0)),
84                    SolitaireCard::new_down(cards.remove(0)),
85                    SolitaireCard::new_down(cards.remove(0)),
86                    SolitaireCard::new_up(cards.remove(0)),
87                ],
88                // 5 Down, 1 Up
89                vec![
90                    SolitaireCard::new_down(cards.remove(0)),
91                    SolitaireCard::new_down(cards.remove(0)),
92                    SolitaireCard::new_down(cards.remove(0)),
93                    SolitaireCard::new_down(cards.remove(0)),
94                    SolitaireCard::new_down(cards.remove(0)),
95                    SolitaireCard::new_up(cards.remove(0)),
96                ],
97                // 6 Down, 1 Up
98                vec![
99                    SolitaireCard::new_down(cards.remove(0)),
100                    SolitaireCard::new_down(cards.remove(0)),
101                    SolitaireCard::new_down(cards.remove(0)),
102                    SolitaireCard::new_down(cards.remove(0)),
103                    SolitaireCard::new_down(cards.remove(0)),
104                    SolitaireCard::new_down(cards.remove(0)),
105                    SolitaireCard::new_up(cards.remove(0)),
106                ],
107            ],
108            foundation: [
109                Vec::with_capacity(13),
110                Vec::with_capacity(13),
111                Vec::with_capacity(13),
112                Vec::with_capacity(13),
113            ],
114        }
115    }
116    /// Move a card from stockpile to waste pile,
117    /// if stockpile is empty, move waste to stockpile and draw card
118    ///
119    /// # Errors
120    //
121    /// Errors if there is no more cards to draw
122    /// Can also error if moves exceed u32
123    pub fn draw_card(&mut self) -> SError<crate::cards::StandardCard> {
124        self.i_moves = self
125            .i_moves
126            .checked_add(1)
127            .ok_or(SolitaireError::OutOfMoves)?;
128        Ok(match self.stockpile.draw() {
129            Ok(card) => {
130                self.waste.push(card);
131                card
132            }
133            Err(_) => {
134                self.stockpile = self.waste.drain(..).collect();
135                self.stockpile
136                    .draw()
137                    .map_err(|_| SolitaireError::OutOfCardsError)?
138            }
139        })
140    }
141    /// Number of moves performed
142    pub fn moves(&self) -> u32 {
143        self.i_moves
144    }
145
146    /// Check if the request is within a valid range
147    ///
148    /// # Errors
149    ///
150    /// Errors if the card doesnt fit the range
151    ///
152    /// Target rules:
153    /// - Foundation
154    ///     - The row must be less than 4
155    /// - Stockpile
156    ///     - Waste must not be empty
157    ///     - Stockpile must not be empty
158    /// - Waste
159    ///     - Waste must not be empty
160    /// - Tablue
161    ///     - Row must be less than 7
162    ///     - The requested amount must be less than or equal to the number of faceup cards
163    ///     - The tablue must be all faceup if requested amount is 0 (All)
164    ///
165    /// Destination:
166    /// - Foundation
167    ///     - Row less than 4
168    /// - Tablue
169    ///     - Row less than 7
170    /// - Stockpile OR Waste
171    ///     - Always ok
172    fn valid_range(&self, target: Area, destination: Area) -> SError<()> {
173        let valid = match target {
174            Area::Foundation { row } => row < 4,
175            Area::Stockpile => !(self.waste.is_empty() & self.stockpile.is_empty()),
176            Area::Waste => !(self.waste.is_empty()),
177            Area::Tablue { row, amount } => {
178                row < 7
179                    && (amount <= self.tablue[row].iter().filter(|c| c.is_faceup()).count()
180                        || (amount == 0 && self.tablue[row].iter().all(|c| c.is_faceup())))
181            }
182        };
183        if !valid {
184            return Err(SolitaireError::OutOfRangeError);
185        };
186        let valid = match destination {
187            Area::Foundation { row } => row < 4,
188            Area::Tablue { row, .. } => row < 7,
189            Area::Stockpile | Area::Waste => true,
190        };
191        if valid {
192            Ok(())
193        } else {
194            Err(SolitaireError::OutOfRangeError)
195        }
196    }
197    /// Peek at the next card
198    ///
199    /// # Errors
200    ///
201    /// Errors if there is no cards to peek at from either
202    /// the stockpile or waste
203    pub fn peek(&self) -> SError<crate::cards::StandardCard> {
204        if self.stockpile.is_empty() {
205            if let Some(card) = self.waste.last() {
206                Ok(*card)
207            } else {
208                Err(SolitaireError::OutOfRangeError)
209            }
210        } else if let Some(card) = self.stockpile.peek() {
211            Ok(*card)
212        } else {
213            Err(SolitaireError::OutOfRangeError)
214        }
215    }
216
217    /// Move card(s) from target -> Destination
218    ///
219    /// # Errors
220    ///
221    /// First Check phase:
222    ///     /// Target rules:
223    /// - Foundation
224    ///     - The row must be less than 4
225    /// - Stockpile
226    ///     - Waste must not be empty
227    ///     - Stockpile must not be empty
228    /// - Waste
229    ///     - Waste must not be empty
230    /// - Tablue
231    ///     - Row must be less than 7
232    ///     - The requested amount must be less than or equal to the number of faceup cards
233    ///     - The tablue must be all faceup if requested amount is 0 (All)
234    ///
235    /// Destination:
236    /// - Foundation
237    ///     - Row less than 4
238    /// - Tablue
239    ///     - Row less than 7
240    /// - Stockpile OR Waste
241    ///     - Always ok
242    ///
243    /// Second Phase:
244    ///
245    /// Target Stockpile:
246    /// - Stockpile (Not Allowed)
247    /// - Waste
248    ///     - Failed to draw from stockpile
249    /// - Foundation
250    ///     - Card(s) not allowed to be stacked (Solitaire Rules)
251    /// - Tablue
252    ///     - Card(s) not allowed to be stacked (Solitaire Rules)
253    ///
254    ///
255    /// Target Waste:
256    /// - Stockpile (Never allowed)
257    /// - Waste (Never allowed)
258    /// - Foundation
259    ///     - Card(s) not allowed to be stacked (Solitaire Rules)
260    /// - Tablue
261    ///     - Card(s) not allowed to be stacked (Solitaire Rules)
262    ///
263    ///
264    /// Target Tablue:
265    /// - Stockpile (Never allowed)
266    /// - Waste (Never allowed)
267    /// - Tablue
268    ///     - Card(s) not allowed to be stacked (Solitaire Rules)
269    /// - Foundation
270    ///     - Card(s) not allowed to be stacked (Solitaire Rules)
271    ///
272    ///
273    /// Target Foundation:
274    /// - Waste (Never allowed)
275    /// - Stockpile (Never allowed)
276    /// - Foundation (Never allowed)
277    /// - Tablue
278    ///     - Card(s) not allowed to be stacked (Solitaire Rules)
279    ///
280    ///  Can also error if moves exceed the max of u32
281    pub fn move_to(&mut self, target: Area, destination: Area) -> SError<()> {
282        self.valid_range(target, destination)?; // Validate movement range
283        let mut drawed_card = false;
284        match target {
285            Area::Stockpile => match destination {
286                Area::Waste => {
287                    self.waste.push(
288                        self.stockpile
289                            .draw()
290                            .map_err(|_| SolitaireError::IllegalMovementError)?,
291                    );
292                    Ok(())
293                }
294                Area::Stockpile => Err(SolitaireError::IllegalMovementError),
295                Area::Foundation { row } => {
296                    let stackable = self
297                        .peek()
298                        .ok()
299                        .map(|top| {
300                            let bot = self.foundation[row].last().copied();
301                            crate::solitaire::utils::can_stack(
302                                bot,
303                                SolitaireCard {
304                                    card: top,
305                                    face_down: false,
306                                },
307                                true,
308                            )
309                        })
310                        .unwrap_or(false);
311                    if stackable {
312                        drawed_card = true;
313                        let card = SolitaireCard {
314                            card: self.draw_card()?,
315                            face_down: false,
316                        };
317                        self.foundation[row].push(card);
318                        Ok(())
319                    } else {
320                        Err(SolitaireError::IllegalMovementError)
321                    }
322                }
323                Area::Tablue { row, .. } => {
324                    let stackable = self
325                        .peek()
326                        .ok()
327                        .map(|top| {
328                            let bot = self.tablue[row].last().copied();
329
330                            crate::solitaire::utils::can_stack(
331                                bot,
332                                SolitaireCard {
333                                    card: top,
334                                    face_down: false,
335                                },
336                                false,
337                            )
338                        })
339                        .unwrap_or(false);
340                    if stackable {
341                        drawed_card = true;
342                        let card = SolitaireCard {
343                            card: self.draw_card()?,
344                            face_down: false,
345                        };
346                        self.tablue[row].push(card);
347                        Ok(())
348                    } else {
349                        Err(SolitaireError::IllegalMovementError)
350                    }
351                }
352            },
353            Area::Waste => match destination {
354                Area::Stockpile | Area::Waste => Err(SolitaireError::IllegalMovementError),
355                Area::Foundation { row } => {
356                    let stackable = self
357                        .waste
358                        .last()
359                        .map(|top| {
360                            let top = SolitaireCard {
361                                card: *top,
362                                face_down: false,
363                            };
364                            let bot = self.foundation[row].last().copied();
365                            crate::solitaire::utils::can_stack(bot, top, true)
366                        })
367                        .unwrap_or(false);
368                    if stackable {
369                        self.foundation[row].push(SolitaireCard {
370                            card: self.waste.remove(self.waste.len() - 1),
371                            face_down: false,
372                        });
373                        Ok(())
374                    } else {
375                        Err(SolitaireError::IllegalMovementError)
376                    }
377                }
378                Area::Tablue { row, .. } => {
379                    let stackable = self
380                        .waste
381                        .last()
382                        .map(|top| {
383                            let top = SolitaireCard {
384                                card: *top,
385                                face_down: false,
386                            };
387                            let bot = self.tablue[row].last().copied();
388                            crate::solitaire::utils::can_stack(bot, top, false)
389                        })
390                        .unwrap_or(false);
391                    if stackable {
392                        self.tablue[row].push(SolitaireCard {
393                            card: self.waste.remove(self.waste.len() - 1),
394                            face_down: false,
395                        });
396                        Ok(())
397                    } else {
398                        Err(SolitaireError::IllegalMovementError)
399                    }
400                }
401            },
402            Area::Tablue { row, amount } => match destination {
403                Area::Stockpile | Area::Waste => Err(SolitaireError::IllegalMovementError),
404                Area::Tablue {
405                    row: destination_row,
406                    ..
407                } => {
408                    let stack: Vec<SolitaireCard> = if amount == 0 {
409                        // If any card is face down, cant move the whole tablue
410                        if self.tablue[row].iter().any(|c| c.is_facedown()) {
411                            return Err(SolitaireError::IllegalMovementError);
412                        }
413                        self.tablue[row].drain(0..)
414                    } else {
415                        let l = self.tablue[row].len();
416                        self.tablue[row].drain(l - amount..l)
417                    }
418                    .collect();
419                    let stackable = stack
420                        .first()
421                        .map(|c| {
422                            crate::solitaire::utils::can_stack(
423                                self.tablue[destination_row].last().copied(),
424                                *c,
425                                false,
426                            )
427                        })
428                        .unwrap_or(false);
429                    if stackable {
430                        self.tablue[destination_row].extend(stack);
431                        Ok(())
432                    } else {
433                        Err(SolitaireError::IllegalMovementError)
434                    }
435                }
436                Area::Foundation {
437                    row: destination_row,
438                } => {
439                    if row >= 7 || destination_row >= 7 {
440                        return Err(SolitaireError::OutOfRangeError);
441                    };
442                    let stackable = self.tablue[0]
443                        .last()
444                        .map(|top| {
445                            self.foundation[destination_row]
446                                .last()
447                                .map(|bot| {
448                                    crate::solitaire::utils::can_stack(Some(*bot), *top, true)
449                                })
450                                .unwrap_or_else(|| {
451                                    crate::solitaire::utils::can_stack(None, *top, true)
452                                })
453                        })
454                        .unwrap_or(false);
455                    if stackable {
456                        self.foundation[destination_row]
457                            .push(self.tablue[row].remove(self.tablue[row].len() - 1));
458                        Ok(())
459                    } else {
460                        Err(SolitaireError::IllegalMovementError)
461                    }
462                }
463            },
464            Area::Foundation { row } => match destination {
465                Area::Waste | Area::Stockpile | Area::Foundation { .. } => {
466                    Err(SolitaireError::IllegalMovementError)
467                }
468                Area::Tablue {
469                    row: destination_row,
470                    ..
471                } => {
472                    let stackable = self.foundation[row]
473                        .last()
474                        .map(|top| {
475                            let bot = self.tablue[destination_row].last().copied();
476                            crate::solitaire::utils::can_stack(bot, *top, false)
477                        })
478                        .unwrap_or(false);
479                    if stackable {
480                        self.tablue[destination_row]
481                            .push(self.foundation[row].remove(self.foundation[row].len() - 1));
482                        Ok(())
483                    } else {
484                        Err(SolitaireError::IllegalMovementError)
485                    }
486                }
487            },
488        }?;
489        if !drawed_card {
490            // Draw card increments move as well
491            self.i_moves = self
492                .i_moves
493                .checked_add(1)
494                .ok_or(SolitaireError::OutOfMoves)?;
495        }
496        Ok(())
497    }
498
499    /// Check if the solitaire game is a win
500    pub fn game_won(&self) -> bool {
501        // TODO: More complex game state checker
502        if !self.stockpile.is_empty() {
503            return false;
504        }
505        if !self.waste.is_empty() {
506            return false;
507        }
508        if self.tablue.iter().all(|tablue_row| tablue_row.is_empty()) {
509            return false;
510        }
511        if self
512            .foundation
513            .iter()
514            .all(|foundation_row| foundation_row.len() == 13)
515        {
516            return true;
517        }
518        false
519    }
520    // TODO: Implement display method
521}
522
523impl Default for Solitaire {
524    fn default() -> Self {
525        Self::new()
526    }
527}