712 lines
22 KiB
Rust
712 lines
22 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),
|
|
#[error("Foundations need to be the same suit")]
|
|
WrongSuit,
|
|
}
|
|
|
|
impl Card {
|
|
pub fn can_be_placed_on_pile(&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(())
|
|
}
|
|
|
|
pub fn can_be_placed_on_foundation(&self, top: &Option<Card>) -> Result<(), StackingError> {
|
|
match top {
|
|
None => {
|
|
if self.value == Value::Ace {
|
|
return Ok(());
|
|
} else {
|
|
return Err(StackingError::NotAdjacent(self.to_string(), "an empty foundation".to_string()));
|
|
}
|
|
},
|
|
Some(c) => {
|
|
if self.suit != c.suit {
|
|
return Err(StackingError::WrongSuit);
|
|
}
|
|
if self.value.indexed_values() + 1 == c.value.indexed_values() {
|
|
return Ok(());
|
|
} else {
|
|
return Err(StackingError::NotAdjacent(self.to_string(), c.to_string()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub 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)]
|
|
pub struct CardAndPosition {
|
|
card: Option<Card>,
|
|
pos: CardPosition,
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Copy, Clone)]
|
|
pub enum CardPosition {
|
|
TopWaste, // I don't think we'd ever interact with anything other than the top of the Waste
|
|
Pile(usize, usize), // (PileNumber, Index)
|
|
Foundation(usize)
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
pub enum NumPassesThroughDeck {
|
|
Unlimited,
|
|
Limited(u64),
|
|
}
|
|
|
|
pub const NUM_PILES_KLONDIKE: usize = 7;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Klondike {
|
|
pub piles: [Vec<Card>; NUM_PILES_KLONDIKE],
|
|
pub deck: Vec<Card>, // this term is a bit overloaded
|
|
pub waste: Vec<Card>,
|
|
pub foundation: [Vec<Card>; 4], // 4 = len of num suits
|
|
max_num_passes_through_deck: NumPassesThroughDeck,
|
|
pub current_num_passes_through_deck: u64,
|
|
pub num_cards_turned: u8,
|
|
}
|
|
|
|
impl Klondike {
|
|
pub fn deck_to_waste(self: &mut Self) {
|
|
for _ in 0..self.num_cards_turned {
|
|
let card = self.deck.pop();
|
|
match card {
|
|
None => (),
|
|
Some(c) => self.waste.push(c),
|
|
}
|
|
}
|
|
}
|
|
// TODO return some sort of error
|
|
pub fn waste_to_deck(self: &mut Self) {
|
|
match self.max_num_passes_through_deck {
|
|
NumPassesThroughDeck::Unlimited => (),
|
|
NumPassesThroughDeck::Limited(n) => if n >= self.current_num_passes_through_deck {
|
|
// no!
|
|
return
|
|
},
|
|
}
|
|
if self.deck.len() != 0 {
|
|
// no!
|
|
} else {
|
|
self.deck = self.waste.clone();
|
|
self.waste = vec![];
|
|
self.current_num_passes_through_deck += 1;
|
|
}
|
|
}
|
|
|
|
pub fn move_card(self, source_card: &CardAndPosition, dest_card: &CardAndPosition) -> bool {
|
|
// TODO raise errors properly
|
|
assert!(source_card.card.is_some());
|
|
assert_eq!(source_card.card.unwrap().visible, true);
|
|
dest_card.pos.is_valid_dest();
|
|
|
|
// Maybe TODO - check the .cards is the actual card in that position
|
|
|
|
match dest_card.pos {
|
|
CardPosition::Pile(_, _) => self.move_card_to_pile(&source_card, &dest_card),
|
|
CardPosition::Foundation(_f) => self.move_card_to_foundation(source_card, dest_card),
|
|
CardPosition::TopWaste => unreachable!()
|
|
}
|
|
}
|
|
|
|
pub fn move_card_to_foundation(mut self, source_card: &CardAndPosition, dest_card: &CardAndPosition) -> bool {
|
|
// TODO check the cards referenced in source and dest are the ones that are actually there
|
|
// TODO - ditto this for the "move to pile" function
|
|
// TODO actually learn Rust properly so I can figure out why I need to clone the whole struct to check a value
|
|
if source_card.pos != CardPosition::TopWaste && !self.clone().is_card_top_of_pile(&source_card.pos) {
|
|
// TODO as above - make all these proper errors
|
|
return false;
|
|
}
|
|
if source_card.card.unwrap().can_be_placed_on_foundation(&dest_card.card).is_err() {
|
|
return false;
|
|
}
|
|
|
|
if let CardPosition::Foundation(foundation_index) = dest_card.pos {
|
|
match source_card.pos {
|
|
CardPosition::TopWaste => {
|
|
let card = self.waste.pop().unwrap();
|
|
self.foundation[foundation_index].push(card);
|
|
return true;
|
|
},
|
|
CardPosition::Pile(pile_index, _) => {
|
|
let card = self.piles[pile_index].pop().unwrap();
|
|
self.foundation[foundation_index].push(card);
|
|
return true;
|
|
},
|
|
CardPosition::Foundation(_) => {
|
|
unreachable!()
|
|
},
|
|
}
|
|
}
|
|
unreachable!();
|
|
}
|
|
|
|
pub fn move_card_to_pile(mut self, source_card: &CardAndPosition, dest_card: &CardAndPosition) -> bool {
|
|
if source_card.card.unwrap().can_be_placed_on_pile(&dest_card.card.unwrap()).is_err() {
|
|
return false;
|
|
}
|
|
let (dpile_index, dcard_index) = match dest_card.pos {
|
|
CardPosition::Pile(p, i) => (p, i),
|
|
CardPosition::TopWaste | CardPosition::Foundation(_) => return false
|
|
};
|
|
if dcard_index != self.piles[dpile_index].len() - 1 {
|
|
// Can't move to anything other than top of pile
|
|
// this should already have been checked in the is_card_top...
|
|
// maybe I'll just delete this check...
|
|
return false;
|
|
}
|
|
match source_card.pos {
|
|
CardPosition::TopWaste => {
|
|
let card = self.waste.pop().unwrap();
|
|
self.piles[dpile_index].push(card);
|
|
},
|
|
CardPosition::Pile(spile_index, scard_index) => {
|
|
let num_cards_to_take = self.piles[spile_index].len() - scard_index; // -1 maybe?
|
|
let mut cards: Vec<Card> = Vec::new();
|
|
for _ in 0..num_cards_to_take {
|
|
cards.push(self.piles[spile_index].pop().unwrap());
|
|
}
|
|
for card in cards {
|
|
self.piles[dpile_index].push(card);
|
|
}
|
|
// TODO Properly learn rust and why I can't use drain & extend methods
|
|
// https://doc.rust-lang.org/std/vec/struct.Vec.html#method.drain
|
|
},
|
|
CardPosition::Foundation(s_index) => {
|
|
let card = self.foundation[s_index].pop().unwrap();
|
|
self.piles[dpile_index].push(card);
|
|
},
|
|
}
|
|
true
|
|
}
|
|
|
|
fn is_card_top_of_pile(self, pos: &CardPosition) -> bool {
|
|
// TODO consider, at which point the Pos::Pile() ranges etc are correct
|
|
match pos {
|
|
CardPosition::Pile(pile_index, card_index) => {
|
|
match self.piles.get(*pile_index) {
|
|
Some(pile_index) => {
|
|
if *card_index == (pile_index.len() - 1) {
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
None => false
|
|
}
|
|
},
|
|
CardPosition::TopWaste | CardPosition::Foundation(_) => false
|
|
}
|
|
}
|
|
|
|
pub fn lowest_visible_card_in_pile_from_index(self, pile: usize, index: usize) -> usize {
|
|
if self.piles[pile].len() == 0 {
|
|
return 0;
|
|
}
|
|
for (i, card) in self.piles[pile].iter().skip(index).enumerate() {
|
|
if card.visible {
|
|
return i;
|
|
}
|
|
}
|
|
panic!("Pile with only facedown cards - this is wrong")
|
|
}
|
|
}
|
|
|
|
impl CardPosition {
|
|
fn is_valid_dest(&self) {
|
|
// TODO as with many other places - should probably raise an error instead of panic
|
|
match self {
|
|
CardPosition::TopWaste => panic!("You can't move cards to waste"),
|
|
CardPosition::Pile(_, _) | CardPosition::Foundation(_) => (),
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
for card in &mut deck.cards {
|
|
card.visible = true;
|
|
}
|
|
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_pile(&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_pile(&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_pile(&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_pile(&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"),
|
|
StackingError::WrongSuit => unreachable!(),
|
|
}
|
|
} else {
|
|
assert!(false, "Cards are not adjacent - should be an error")
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn basic_foundation_stacking() {
|
|
let testing_card = Card {
|
|
suit: Suit::Spade,
|
|
value: Value::Ace,
|
|
..Default::default()
|
|
};
|
|
assert_eq!(testing_card.can_be_placed_on_foundation(&None), Ok(()));
|
|
let testing_card = Card {
|
|
suit: Suit::Spade,
|
|
value: Value::Two,
|
|
..Default::default()
|
|
};
|
|
assert!(testing_card.can_be_placed_on_foundation(&None).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn moving_to_foundation() {
|
|
// GOOD: Ace from Pile to Empty Foundation
|
|
let mut klon = Klondike::default();
|
|
let ace = Card {
|
|
suit: Suit::Spade,
|
|
value: Value::Ace,
|
|
.. Default::default()
|
|
};
|
|
klon.piles[0].push(ace.clone());
|
|
let source_card = CardAndPosition {
|
|
card: Some(ace.clone()),
|
|
pos: CardPosition::Pile(0, 1)
|
|
};
|
|
let dest_card = CardAndPosition {
|
|
card: None,
|
|
pos: CardPosition::Foundation(0),
|
|
};
|
|
assert!(klon.move_card_to_foundation(&source_card, &dest_card));
|
|
|
|
// BAD: Non-Ace from Pile to Empty Foundaction
|
|
let mut klon = Klondike::default();
|
|
let two = Card {
|
|
suit: Suit::Spade,
|
|
value: Value::Two,
|
|
.. Default::default()
|
|
};
|
|
klon.piles[0].push(two.clone());
|
|
let source_card = CardAndPosition {
|
|
card: Some(two.clone()),
|
|
pos: CardPosition::Pile(0, 1)
|
|
};
|
|
let dest_card = CardAndPosition {
|
|
card: None,
|
|
pos: CardPosition::Foundation(0),
|
|
};
|
|
assert!(!klon.move_card_to_foundation(&source_card, &dest_card));
|
|
|
|
// GOOD: Ace from TopWaste to Empty Foundaction
|
|
let mut klon = Klondike::default();
|
|
let ace = Card {
|
|
suit: Suit::Spade,
|
|
value: Value::Ace,
|
|
.. Default::default()
|
|
};
|
|
klon.waste.push(ace.clone());
|
|
let source_card = CardAndPosition {
|
|
card: Some(ace.clone()),
|
|
pos: CardPosition::TopWaste,
|
|
};
|
|
let dest_card = CardAndPosition {
|
|
card: None,
|
|
pos: CardPosition::Foundation(0),
|
|
};
|
|
assert!(klon.move_card_to_foundation(&source_card, &dest_card));
|
|
|
|
// BAD: Non-Ace from TopWaste to Empty Foundaction
|
|
let mut klon = Klondike::default();
|
|
let two = Card {
|
|
suit: Suit::Spade,
|
|
value: Value::Two,
|
|
.. Default::default()
|
|
};
|
|
klon.waste.push(two.clone());
|
|
let source_card = CardAndPosition {
|
|
card: Some(two.clone()),
|
|
pos: CardPosition::TopWaste,
|
|
};
|
|
let dest_card = CardAndPosition {
|
|
card: None,
|
|
pos: CardPosition::Foundation(0),
|
|
};
|
|
assert!(!klon.move_card_to_foundation(&source_card, &dest_card));
|
|
|
|
// GOOD: Two from TopWaste to Foundaction with Ace there
|
|
let mut klon = Klondike::default();
|
|
let ace = Card {
|
|
suit: Suit::Spade,
|
|
value: Value::Ace,
|
|
.. Default::default()
|
|
};
|
|
let two = Card {
|
|
suit: Suit::Spade,
|
|
value: Value::Two,
|
|
.. Default::default()
|
|
};
|
|
klon.waste.push(two.clone());
|
|
klon.piles[0].push(ace.clone());
|
|
let source_card = CardAndPosition {
|
|
card: Some(ace.clone()),
|
|
pos: CardPosition::TopWaste,
|
|
};
|
|
let dest_card = CardAndPosition {
|
|
card: None,
|
|
pos: CardPosition::Foundation(0),
|
|
};
|
|
assert!(klon.move_card_to_foundation(&source_card, &dest_card));
|
|
|
|
|
|
|
|
// TODO the following cases:
|
|
// - moving a card from pile to foundation when something is already there
|
|
// - moving Ace from waste to top of pile
|
|
// - moving a card frmo waste to foundation when something is already there
|
|
// => for cases where it'll both work and not work - when cards are / aren't present
|
|
}
|
|
|
|
#[test]
|
|
fn move_to_foundation_with_wrong_suit() {
|
|
let mut klon = Klondike::default();
|
|
let ace = Card {
|
|
suit: Suit::Heart,
|
|
value: Value::Ace,
|
|
.. Default::default()
|
|
};
|
|
let two = Card {
|
|
suit: Suit::Spade,
|
|
value: Value::Two,
|
|
.. Default::default()
|
|
};
|
|
klon.waste.push(two.clone());
|
|
klon.piles[0].push(ace.clone());
|
|
let source_card = CardAndPosition {
|
|
card: Some(ace.clone()),
|
|
pos: CardPosition::TopWaste,
|
|
};
|
|
let dest_card = CardAndPosition {
|
|
card: Some(two.clone()),
|
|
pos: CardPosition::Foundation(0),
|
|
};
|
|
assert!(!klon.move_card_to_foundation(&source_card, &dest_card));
|
|
// TODO this is passing - but I don't think it's passing for the right reason
|
|
}
|
|
|
|
#[test]
|
|
fn move_ace_to_foundation_with_card_present() {
|
|
let mut klon = Klondike::default();
|
|
let ace_heart = Card {
|
|
suit: Suit::Heart,
|
|
value: Value::Ace,
|
|
.. Default::default()
|
|
};
|
|
let ace_spade = Card {
|
|
suit: Suit::Spade,
|
|
value: Value::Ace,
|
|
.. Default::default()
|
|
};
|
|
klon.waste.push(ace_heart.clone());
|
|
klon.piles[0].push(ace_spade.clone());
|
|
let source_card = CardAndPosition {
|
|
card: Some(ace_heart.clone()),
|
|
pos: CardPosition::TopWaste,
|
|
};
|
|
let dest_card = CardAndPosition {
|
|
card: Some(ace_spade.clone()),
|
|
pos: CardPosition::Foundation(0),
|
|
};
|
|
assert!(!klon.move_card_to_foundation(&source_card, &dest_card));
|
|
}
|
|
|
|
#[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 move_pile_card_to_good_pile() {
|
|
let mut klon = Klondike::default();
|
|
let ace = Card {
|
|
suit: Suit::Heart,
|
|
value: Value::Ace,
|
|
.. Default::default()
|
|
};
|
|
let two = Card {
|
|
suit: Suit::Spade,
|
|
value: Value::Two,
|
|
.. Default::default()
|
|
};
|
|
klon.piles[0].push(two.clone());
|
|
klon.piles[1].push(ace.clone());
|
|
let source_card = CardAndPosition {
|
|
card: Some(ace.clone()),
|
|
pos: CardPosition::Pile(1, 2),
|
|
};
|
|
let dest_card = CardAndPosition {
|
|
card: Some(two.clone()),
|
|
pos: CardPosition::Pile(0, 1),
|
|
};
|
|
assert!(klon.move_card_to_pile(&source_card, &dest_card));
|
|
}
|
|
#[test]
|
|
fn move_pile_card_to_bad_pile() {
|
|
let mut klon = Klondike::default();
|
|
let ace = Card {
|
|
suit: Suit::Heart,
|
|
value: Value::Ace,
|
|
.. Default::default()
|
|
};
|
|
let two = Card {
|
|
suit: Suit::Diamond,
|
|
value: Value::Two,
|
|
.. Default::default()
|
|
};
|
|
klon.piles[0].push(two.clone());
|
|
klon.piles[1].push(ace.clone());
|
|
let source_card = CardAndPosition {
|
|
card: Some(ace.clone()),
|
|
pos: CardPosition::Pile(1, 2),
|
|
};
|
|
let dest_card = CardAndPosition {
|
|
card: Some(two.clone()),
|
|
pos: CardPosition::Pile(0, 1),
|
|
};
|
|
assert!(!klon.move_card_to_pile(&source_card, &dest_card));
|
|
}
|
|
}
|