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}