Skip to main content

solkit/
gconf.rs

1use crate::card::{Card, Face, Suit};
2use crate::err::SolError;
3
4#[derive(Clone, Copy, PartialEq)]
5pub enum FaceOrder {
6    Asc,
7    Desc,
8    Any,
9}
10
11pub fn str_to_face_order(s: &str) -> Result<FaceOrder, SolError> {
12    match s {
13        "asc" | "ascending" | "inc" | "increasing" => Ok(FaceOrder::Asc),
14        "desc" | "descending" | "dec" | "decreasing" => Ok(FaceOrder::Desc),
15        "any" | "alternate" => Ok(FaceOrder::Any),
16        _ => Err(SolError::InvalidFaceOrder(s.to_string())),
17    }
18}
19
20#[derive(Clone, Copy, PartialEq)]
21pub enum SuitOrder {
22    SameSuit,
23    SameColor,
24    AlternateColor,
25    ExceptSame,
26    Any,
27    Forbid,
28}
29
30pub fn str_to_suit_order(s: &str) -> Result<SuitOrder, SolError> {
31    match s {
32        "same" | "same suit" | "samesuit" => Ok(SuitOrder::SameSuit),
33        "exceptsame" | "except same" | "except" => Ok(SuitOrder::ExceptSame),
34        "same color" | "samecolor" => Ok(SuitOrder::SameColor),
35        "alternate" | "alternate color" | "alternatecolor" => Ok(SuitOrder::AlternateColor),
36        "none" | "forbid" | "disable" => Ok(SuitOrder::Forbid),
37        "any" => Ok(SuitOrder::Any),
38        _ => Err(SolError::InvalidSuitOrder(s.to_string())),
39    }
40}
41
42// deck pile configuration
43#[derive(Clone, Copy)]
44pub struct PileConf {
45    pub deal_by: u8,        // how many cards to move from deck to waste at a time
46    pub redeals: i8,        // redeals left
47    pub pile_to_cols: bool, // deal to columns instead of waste
48}
49
50impl PileConf {
51    pub fn validate(&self) -> Result<(), SolError> {
52        if self.deal_by == 0 || self.deal_by > 16 {
53            return Err(SolError::InvalidDealBy(self.deal_by));
54        }
55        Ok(())
56    }
57}
58
59// foundation pile configuration
60#[derive(Clone, Copy)]
61pub struct FndSlot {
62    pub first: Face,          // face of the card that starts the pile
63    pub suit: Suit,           // suit of the card that starts the pile
64    pub forder: FaceOrder,    // face order
65    pub sorder: SuitOrder,    // suit order
66    pub filler: Option<Card>, // the foundation will have this card at start (i.e, the pile is never empty)
67}
68
69// free-cell configuration
70#[derive(Clone, Copy)]
71pub struct TempConf {
72    pub count: u8, // the number of free cells
73}
74
75impl TempConf {
76    pub fn validate(&self) -> Result<(), SolError> {
77        if self.count > 4 {
78            return Err(SolError::InvalidTempNumber(self.count));
79        }
80        Ok(())
81    }
82}
83
84// column configuration
85#[derive(Clone, Copy)]
86pub struct ColConf {
87    pub count: u8,       // initial number of cards
88    pub up: u8,          // initial number of face-up cards
89    pub take_only: bool, // a user cannot put cards to the pile, only take from it
90}
91
92// which cards can be move from a column to another pile
93#[derive(Clone, Copy, PartialEq)]
94pub enum Playable {
95    Top,     // only the top one
96    Any,     // any number of face-up cards
97    Ordered, // any number of face-up cards from the top of a pile if the cards are in order
98}
99
100#[derive(Clone)]
101pub struct Conf {
102    pub chance: Option<u16>, // chance of winning 1 of N (if known)
103    pub name: String,        // solitaire unique name
104
105    pub deck_count: u8,     // number of decks (1 or 2)
106    pub playable: Playable, // what cards in a column are playable
107
108    pub pile: Option<PileConf>, // deck and waste configuration
109    pub fnd: Vec<FndSlot>,      // foundation configuration
110    pub temp: Option<TempConf>, // number of temp slots
111    pub cols: Vec<ColConf>,     // column configuration
112    pub col_forder: FaceOrder,  // face order of cards in columns
113    pub col_sorder: SuitOrder,  // suit order of cards in columns
114    // Face of card that must start a pile when it gets empty.
115    // Unavail: empty col cannot be filled
116    pub col_refill: Face,
117}
118
119impl Default for Conf {
120    fn default() -> Conf {
121        Conf {
122            name: String::new(),
123            chance: None,
124            deck_count: 1,
125            playable: Playable::Top,
126            pile: None,
127            fnd: Vec::new(),
128            temp: None,
129            cols: Vec::new(),
130            col_forder: FaceOrder::Desc,
131            col_sorder: SuitOrder::SameSuit,
132            col_refill: Face::Unavail,
133        }
134    }
135}
136
137impl Conf {
138    pub fn new() -> Self {
139        Default::default()
140    }
141    pub fn validate(&self) -> Result<(), SolError> {
142        if self.deck_count == 0 || self.deck_count > 2 {
143            return Err(SolError::InvalidDeckNumber(self.deck_count));
144        }
145        if let Some(ref cfg) = self.temp {
146            cfg.validate()?;
147        }
148        if let Some(ref dc) = self.pile {
149            dc.validate()?;
150        }
151        if self.fnd.is_empty() {
152            return Err(SolError::NoFoundation);
153        }
154        for w in &self.fnd {
155            if w.first == Face::Empty {
156                return Err(SolError::NoFoundationStart);
157            }
158        }
159        if self.cols.is_empty() {
160            return Err(SolError::NoCols);
161        }
162        if self.cols.len() > 10 {
163            return Err(SolError::InvalidColNumber(self.cols.len() as u8));
164        }
165        Ok(())
166    }
167
168    pub fn deal_by(&self) -> u8 {
169        if let Some(ref pconf) = self.pile {
170            pconf.deal_by
171        } else {
172            0
173        }
174    }
175
176    pub fn redeals(&self) -> i8 {
177        if let Some(ref pconf) = self.pile {
178            pconf.redeals
179        } else {
180            0
181        }
182    }
183}