|
|
|
@@ -0,0 +1,567 @@
|
|
|
|
|
pub type Rng = rand::rngs::ThreadRng;
|
|
|
|
|
|
|
|
|
|
use card_game::{Card, CardValue, Game, Pile, Stack};
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod test;
|
|
|
|
|
|
|
|
|
|
// test readme
|
|
|
|
|
#[doc = include_str!("../README.md")]
|
|
|
|
|
#[cfg(doctest)]
|
|
|
|
|
struct ReadmeDoctests;
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
|
|
|
|
pub struct KlondikeConfig {}
|
|
|
|
|
impl Default for KlondikeConfig {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
KlondikeConfig {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
|
|
|
pub enum Tableau {
|
|
|
|
|
Tableau1,
|
|
|
|
|
Tableau2,
|
|
|
|
|
Tableau3,
|
|
|
|
|
Tableau4,
|
|
|
|
|
Tableau5,
|
|
|
|
|
Tableau6,
|
|
|
|
|
Tableau7,
|
|
|
|
|
}
|
|
|
|
|
impl Tableau {
|
|
|
|
|
const ITER_BEGIN: Self = Self::Tableau1;
|
|
|
|
|
const fn next(self) -> Option<Self> {
|
|
|
|
|
use Tableau::*;
|
|
|
|
|
Some(match self {
|
|
|
|
|
Tableau1 => Tableau2,
|
|
|
|
|
Tableau2 => Tableau3,
|
|
|
|
|
Tableau3 => Tableau4,
|
|
|
|
|
Tableau4 => Tableau5,
|
|
|
|
|
Tableau5 => Tableau6,
|
|
|
|
|
Tableau6 => Tableau7,
|
|
|
|
|
Tableau7 => return None,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
|
|
|
pub enum Foundation {
|
|
|
|
|
Foundation1,
|
|
|
|
|
Foundation2,
|
|
|
|
|
Foundation3,
|
|
|
|
|
Foundation4,
|
|
|
|
|
}
|
|
|
|
|
impl Foundation {
|
|
|
|
|
const ITER_BEGIN: Self = Self::Foundation1;
|
|
|
|
|
const fn next(self) -> Option<Self> {
|
|
|
|
|
use Foundation::*;
|
|
|
|
|
Some(match self {
|
|
|
|
|
Foundation1 => Foundation2,
|
|
|
|
|
Foundation2 => Foundation3,
|
|
|
|
|
Foundation3 => Foundation4,
|
|
|
|
|
Foundation4 => return None,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
|
|
|
pub enum KlondikePile {
|
|
|
|
|
Tableau(Tableau),
|
|
|
|
|
Stock,
|
|
|
|
|
Foundation(Foundation),
|
|
|
|
|
}
|
|
|
|
|
impl KlondikePile {
|
|
|
|
|
const ITER_BEGIN: Self = Self::Tableau(Tableau::ITER_BEGIN);
|
|
|
|
|
const fn next(self) -> Option<Self> {
|
|
|
|
|
Some(match self {
|
|
|
|
|
Self::Tableau(tableau_stack) => match tableau_stack.next() {
|
|
|
|
|
Some(tableau_stack) => Self::Tableau(tableau_stack),
|
|
|
|
|
None => Self::Stock,
|
|
|
|
|
},
|
|
|
|
|
Self::Stock => Self::Foundation(Foundation::ITER_BEGIN),
|
|
|
|
|
Self::Foundation(foundation) => match foundation.next() {
|
|
|
|
|
Some(foundation) => Self::Foundation(foundation),
|
|
|
|
|
None => return None,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
impl From<Tableau> for KlondikePile {
|
|
|
|
|
fn from(value: Tableau) -> Self {
|
|
|
|
|
KlondikePile::Tableau(value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
impl From<Foundation> for KlondikePile {
|
|
|
|
|
fn from(value: Foundation) -> Self {
|
|
|
|
|
KlondikePile::Foundation(value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[repr(u8)]
|
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
|
|
|
pub enum SkipCards {
|
|
|
|
|
Skip0,
|
|
|
|
|
Skip1,
|
|
|
|
|
Skip2,
|
|
|
|
|
Skip3,
|
|
|
|
|
Skip4,
|
|
|
|
|
Skip5,
|
|
|
|
|
Skip6,
|
|
|
|
|
Skip7,
|
|
|
|
|
Skip8,
|
|
|
|
|
Skip9,
|
|
|
|
|
Skip10,
|
|
|
|
|
Skip11,
|
|
|
|
|
Skip12,
|
|
|
|
|
}
|
|
|
|
|
impl SkipCards {
|
|
|
|
|
const ITER_BEGIN: Self = Self::Skip0;
|
|
|
|
|
const fn next(self) -> Option<Self> {
|
|
|
|
|
use SkipCards::*;
|
|
|
|
|
Some(match self {
|
|
|
|
|
Skip0 => Skip1,
|
|
|
|
|
Skip1 => Skip2,
|
|
|
|
|
Skip2 => Skip3,
|
|
|
|
|
Skip3 => Skip4,
|
|
|
|
|
Skip4 => Skip5,
|
|
|
|
|
Skip5 => Skip6,
|
|
|
|
|
Skip6 => Skip7,
|
|
|
|
|
Skip7 => Skip8,
|
|
|
|
|
Skip8 => Skip9,
|
|
|
|
|
Skip9 => Skip10,
|
|
|
|
|
Skip10 => Skip11,
|
|
|
|
|
Skip11 => Skip12,
|
|
|
|
|
Skip12 => return None,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
|
|
|
pub struct TableauStack {
|
|
|
|
|
pub tableau: Tableau,
|
|
|
|
|
pub skip_cards: SkipCards,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl TableauStack {
|
|
|
|
|
const ITER_BEGIN: Self = Self {
|
|
|
|
|
tableau: Tableau::ITER_BEGIN,
|
|
|
|
|
skip_cards: SkipCards::ITER_BEGIN,
|
|
|
|
|
};
|
|
|
|
|
const fn next(self) -> Option<Self> {
|
|
|
|
|
let TableauStack {
|
|
|
|
|
tableau,
|
|
|
|
|
skip_cards,
|
|
|
|
|
} = self;
|
|
|
|
|
if let Some(skip_cards) = skip_cards.next() {
|
|
|
|
|
return Some(Self {
|
|
|
|
|
tableau,
|
|
|
|
|
skip_cards,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if let Some(tableau) = tableau.next() {
|
|
|
|
|
let skip_cards = SkipCards::Skip0;
|
|
|
|
|
return Some(Self {
|
|
|
|
|
tableau,
|
|
|
|
|
skip_cards,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
|
|
|
pub enum KlondikePileStack {
|
|
|
|
|
Tableau(TableauStack),
|
|
|
|
|
Stock,
|
|
|
|
|
Foundation(Foundation),
|
|
|
|
|
}
|
|
|
|
|
impl KlondikePileStack {
|
|
|
|
|
const ITER_BEGIN: Self = Self::Tableau(TableauStack::ITER_BEGIN);
|
|
|
|
|
const fn next(self) -> Option<Self> {
|
|
|
|
|
Some(match self {
|
|
|
|
|
Self::Tableau(tableau_stack) => match tableau_stack.next() {
|
|
|
|
|
Some(tableau_stack) => Self::Tableau(tableau_stack),
|
|
|
|
|
None => Self::Stock,
|
|
|
|
|
},
|
|
|
|
|
Self::Stock => Self::Foundation(Foundation::ITER_BEGIN),
|
|
|
|
|
Self::Foundation(foundation) => match foundation.next() {
|
|
|
|
|
Some(foundation) => Self::Foundation(foundation),
|
|
|
|
|
None => return None,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
|
|
|
pub struct DstFoundation {
|
|
|
|
|
pub src: KlondikePile,
|
|
|
|
|
pub foundation: Foundation,
|
|
|
|
|
}
|
|
|
|
|
impl DstFoundation {
|
|
|
|
|
const ITER_BEGIN: Self = Self {
|
|
|
|
|
src: KlondikePile::ITER_BEGIN,
|
|
|
|
|
foundation: Foundation::ITER_BEGIN,
|
|
|
|
|
};
|
|
|
|
|
const fn next(self) -> Option<Self> {
|
|
|
|
|
let DstFoundation { src, foundation } = self;
|
|
|
|
|
if let Some(src) = src.next() {
|
|
|
|
|
return Some(Self { src, foundation });
|
|
|
|
|
}
|
|
|
|
|
if let Some(foundation) = foundation.next() {
|
|
|
|
|
let src = KlondikePile::ITER_BEGIN;
|
|
|
|
|
return Some(Self { src, foundation });
|
|
|
|
|
}
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
|
|
|
pub struct DstTableau {
|
|
|
|
|
pub src: KlondikePileStack,
|
|
|
|
|
pub tableau: Tableau,
|
|
|
|
|
}
|
|
|
|
|
impl DstTableau {
|
|
|
|
|
const ITER_BEGIN: Self = Self {
|
|
|
|
|
src: KlondikePileStack::ITER_BEGIN,
|
|
|
|
|
tableau: Tableau::ITER_BEGIN,
|
|
|
|
|
};
|
|
|
|
|
const fn next(self) -> Option<Self> {
|
|
|
|
|
let DstTableau { src, tableau } = self;
|
|
|
|
|
if let Some(src) = src.next() {
|
|
|
|
|
return Some(Self { src, tableau });
|
|
|
|
|
}
|
|
|
|
|
if let Some(tableau) = tableau.next() {
|
|
|
|
|
let src = KlondikePileStack::ITER_BEGIN;
|
|
|
|
|
return Some(Self { src, tableau });
|
|
|
|
|
}
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
|
|
|
pub enum KlondikeInstruction {
|
|
|
|
|
DstFoundation(DstFoundation),
|
|
|
|
|
DstTableau(DstTableau),
|
|
|
|
|
RotateStock,
|
|
|
|
|
}
|
|
|
|
|
impl KlondikeInstruction {
|
|
|
|
|
const ITER_BEGIN: Self = Self::DstFoundation(DstFoundation::ITER_BEGIN);
|
|
|
|
|
const fn next(self) -> Option<Self> {
|
|
|
|
|
Some(match self {
|
|
|
|
|
Self::DstFoundation(dst_foundation) => match dst_foundation.next() {
|
|
|
|
|
Some(dst_foundation) => Self::DstFoundation(dst_foundation),
|
|
|
|
|
None => Self::DstTableau(DstTableau::ITER_BEGIN),
|
|
|
|
|
},
|
|
|
|
|
Self::DstTableau(tableau) => match tableau.next() {
|
|
|
|
|
Some(tableau) => Self::DstTableau(tableau),
|
|
|
|
|
None => Self::RotateStock,
|
|
|
|
|
},
|
|
|
|
|
Self::RotateStock => return None,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const TABLEAUS: usize = 7;
|
|
|
|
|
const fn sum(n: usize) -> usize {
|
|
|
|
|
n * (n + 1) / 2
|
|
|
|
|
}
|
|
|
|
|
const STOCK: usize = 52 - sum(TABLEAUS);
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
|
|
|
|
pub struct KlondikeState {
|
|
|
|
|
stock: Pile<STOCK, STOCK>,
|
|
|
|
|
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 {
|
|
|
|
|
pub const fn stock(&self) -> &Pile<STOCK, STOCK> {
|
|
|
|
|
&self.stock
|
|
|
|
|
}
|
|
|
|
|
pub const fn foundation1(&self) -> &Stack<13> {
|
|
|
|
|
&self.foundations[Foundation::Foundation1 as usize]
|
|
|
|
|
}
|
|
|
|
|
pub const fn foundation2(&self) -> &Stack<13> {
|
|
|
|
|
&self.foundations[Foundation::Foundation2 as usize]
|
|
|
|
|
}
|
|
|
|
|
pub const fn foundation3(&self) -> &Stack<13> {
|
|
|
|
|
&self.foundations[Foundation::Foundation3 as usize]
|
|
|
|
|
}
|
|
|
|
|
pub const fn foundation4(&self) -> &Stack<13> {
|
|
|
|
|
&self.foundations[Foundation::Foundation4 as usize]
|
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
pub fn card(&self, src: KlondikePileStack) -> Option<&Card> {
|
|
|
|
|
match src {
|
|
|
|
|
KlondikePileStack::Tableau(TableauStack {
|
|
|
|
|
tableau,
|
|
|
|
|
skip_cards,
|
|
|
|
|
}) => match tableau {
|
|
|
|
|
Tableau::Tableau1 => self.tableau1.face_up().get(skip_cards as usize),
|
|
|
|
|
Tableau::Tableau2 => self.tableau2.face_up().get(skip_cards as usize),
|
|
|
|
|
Tableau::Tableau3 => self.tableau3.face_up().get(skip_cards as usize),
|
|
|
|
|
Tableau::Tableau4 => self.tableau4.face_up().get(skip_cards as usize),
|
|
|
|
|
Tableau::Tableau5 => self.tableau5.face_up().get(skip_cards as usize),
|
|
|
|
|
Tableau::Tableau6 => self.tableau6.face_up().get(skip_cards as usize),
|
|
|
|
|
Tableau::Tableau7 => self.tableau7.face_up().get(skip_cards as usize),
|
|
|
|
|
},
|
|
|
|
|
KlondikePileStack::Foundation(foundation) => {
|
|
|
|
|
self.foundations[foundation as usize].last()
|
|
|
|
|
}
|
|
|
|
|
KlondikePileStack::Stock => self.stock.face_up().last(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
pub fn top_card(&self, src: KlondikePile) -> Option<&Card> {
|
|
|
|
|
match src {
|
|
|
|
|
KlondikePile::Tableau(tableau) => match tableau {
|
|
|
|
|
Tableau::Tableau1 => self.tableau1.face_up().last(),
|
|
|
|
|
Tableau::Tableau2 => self.tableau2.face_up().last(),
|
|
|
|
|
Tableau::Tableau3 => self.tableau3.face_up().last(),
|
|
|
|
|
Tableau::Tableau4 => self.tableau4.face_up().last(),
|
|
|
|
|
Tableau::Tableau5 => self.tableau5.face_up().last(),
|
|
|
|
|
Tableau::Tableau6 => self.tableau6.face_up().last(),
|
|
|
|
|
Tableau::Tableau7 => self.tableau7.face_up().last(),
|
|
|
|
|
},
|
|
|
|
|
KlondikePile::Foundation(foundation) => self.foundations[foundation as usize].last(),
|
|
|
|
|
KlondikePile::Stock => self.stock.face_up().last(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fn take_stack(&mut self, src: KlondikePileStack) -> Stack<13> {
|
|
|
|
|
match src {
|
|
|
|
|
KlondikePileStack::Tableau(TableauStack {
|
|
|
|
|
tableau,
|
|
|
|
|
skip_cards,
|
|
|
|
|
}) => match tableau {
|
|
|
|
|
Tableau::Tableau1 => self.tableau1.take_range_flip_up(skip_cards as usize..),
|
|
|
|
|
Tableau::Tableau2 => self.tableau2.take_range_flip_up(skip_cards as usize..),
|
|
|
|
|
Tableau::Tableau3 => self.tableau3.take_range_flip_up(skip_cards as usize..),
|
|
|
|
|
Tableau::Tableau4 => self.tableau4.take_range_flip_up(skip_cards as usize..),
|
|
|
|
|
Tableau::Tableau5 => self.tableau5.take_range_flip_up(skip_cards as usize..),
|
|
|
|
|
Tableau::Tableau6 => self.tableau6.take_range_flip_up(skip_cards as usize..),
|
|
|
|
|
Tableau::Tableau7 => self.tableau7.take_range_flip_up(skip_cards as usize..),
|
|
|
|
|
},
|
|
|
|
|
KlondikePileStack::Foundation(foundation) => {
|
|
|
|
|
Stack::from_iter(self.foundations[foundation as usize].pop())
|
|
|
|
|
}
|
|
|
|
|
KlondikePileStack::Stock => Stack::from_iter(self.stock.pop()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fn take_top_card(&mut self, src: KlondikePile) -> Option<Card> {
|
|
|
|
|
match src {
|
|
|
|
|
KlondikePile::Tableau(tableau) => match tableau {
|
|
|
|
|
Tableau::Tableau1 => self.tableau1.pop_flip_up(),
|
|
|
|
|
Tableau::Tableau2 => self.tableau2.pop_flip_up(),
|
|
|
|
|
Tableau::Tableau3 => self.tableau3.pop_flip_up(),
|
|
|
|
|
Tableau::Tableau4 => self.tableau4.pop_flip_up(),
|
|
|
|
|
Tableau::Tableau5 => self.tableau5.pop_flip_up(),
|
|
|
|
|
Tableau::Tableau6 => self.tableau6.pop_flip_up(),
|
|
|
|
|
Tableau::Tableau7 => self.tableau7.pop_flip_up(),
|
|
|
|
|
},
|
|
|
|
|
KlondikePile::Foundation(foundation) => self.foundations[foundation as usize].pop(),
|
|
|
|
|
KlondikePile::Stock => self.stock.pop_flip_up(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fn extend<I: IntoIterator<Item = Card>>(&mut self, dst: KlondikePile, cards: I) {
|
|
|
|
|
match dst {
|
|
|
|
|
KlondikePile::Tableau(tableau) => match tableau {
|
|
|
|
|
Tableau::Tableau1 => self.tableau1.extend(cards),
|
|
|
|
|
Tableau::Tableau2 => self.tableau2.extend(cards),
|
|
|
|
|
Tableau::Tableau3 => self.tableau3.extend(cards),
|
|
|
|
|
Tableau::Tableau4 => self.tableau4.extend(cards),
|
|
|
|
|
Tableau::Tableau5 => self.tableau5.extend(cards),
|
|
|
|
|
Tableau::Tableau6 => self.tableau6.extend(cards),
|
|
|
|
|
Tableau::Tableau7 => self.tableau7.extend(cards),
|
|
|
|
|
},
|
|
|
|
|
KlondikePile::Foundation(foundation) => {
|
|
|
|
|
self.foundations[foundation as usize].extend(cards)
|
|
|
|
|
}
|
|
|
|
|
KlondikePile::Stock => self.stock.extend(cards),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
pub fn is_instruction_valid(&self, instruction: KlondikeInstruction) -> bool {
|
|
|
|
|
match instruction {
|
|
|
|
|
// Stock -> Stock draws a card or resets the stock
|
|
|
|
|
KlondikeInstruction::RotateStock => {
|
|
|
|
|
// cannot move stock when stock is empty
|
|
|
|
|
!self.stock.is_empty()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// moving to foundation has special rules
|
|
|
|
|
KlondikeInstruction::DstFoundation(dst_foundation) => {
|
|
|
|
|
// get the top cards
|
|
|
|
|
if let Some(src_card) = self.top_card(dst_foundation.src) {
|
|
|
|
|
match self.top_card(dst_foundation.foundation.into()) {
|
|
|
|
|
// destination card exists
|
|
|
|
|
Some(dst_card) => {
|
|
|
|
|
// suit matches?
|
|
|
|
|
src_card.suit() == dst_card.suit()
|
|
|
|
|
// value is +1?
|
|
|
|
|
&& dst_card.value().checked_add(1) == Some(src_card.value())
|
|
|
|
|
}
|
|
|
|
|
// only ace is allowed to go onto empty foundation
|
|
|
|
|
None => src_card.value() == CardValue::ACE,
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// other = move to tableau
|
|
|
|
|
KlondikeInstruction::DstTableau(dst_tableau) => {
|
|
|
|
|
// get the cards
|
|
|
|
|
if let Some(src_card) = self.card(dst_tableau.src) {
|
|
|
|
|
match self.top_card(dst_tableau.tableau.into()) {
|
|
|
|
|
// destination card exists
|
|
|
|
|
Some(dst_card) => {
|
|
|
|
|
// red-ness is opposite?
|
|
|
|
|
src_card.is_red() != dst_card.is_red()
|
|
|
|
|
// value is -1?
|
|
|
|
|
&& dst_card.value().checked_sub(1) == Some(src_card.value())
|
|
|
|
|
}
|
|
|
|
|
// only king is allowed to go onto empty tableau
|
|
|
|
|
None => src_card.value() == CardValue::KING,
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub struct KlondikeIter {
|
|
|
|
|
instruction: Option<KlondikeInstruction>,
|
|
|
|
|
}
|
|
|
|
|
impl KlondikeIter {
|
|
|
|
|
const fn new() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
instruction: Some(KlondikeInstruction::ITER_BEGIN),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
impl Iterator for KlondikeIter {
|
|
|
|
|
type Item = KlondikeInstruction;
|
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
|
|
|
let instruction = self.instruction;
|
|
|
|
|
self.instruction = instruction?.next();
|
|
|
|
|
instruction
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
|
|
|
|
pub struct Klondike {
|
|
|
|
|
config: KlondikeConfig,
|
|
|
|
|
state: KlondikeState,
|
|
|
|
|
}
|
|
|
|
|
impl Klondike {
|
|
|
|
|
pub fn new_random_default() -> Self {
|
|
|
|
|
Self::new(Rng::default(), KlondikeConfig::default())
|
|
|
|
|
}
|
|
|
|
|
pub fn new(mut seed: Rng, config: KlondikeConfig) -> Self {
|
|
|
|
|
// shuffle a new deck
|
|
|
|
|
let mut deck = Stack::full_deck(0);
|
|
|
|
|
use rand::seq::SliceRandom;
|
|
|
|
|
deck.shuffle(&mut seed);
|
|
|
|
|
let mut deck = deck.into_iter();
|
|
|
|
|
|
|
|
|
|
// generate tableaus
|
|
|
|
|
fn pile<const DN: usize>(deck: &mut <Stack<52> as IntoIterator>::IntoIter) -> Pile<DN, 13> {
|
|
|
|
|
let stack = Stack::from_iter(deck.take(DN));
|
|
|
|
|
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(Stack::from_iter(deck));
|
|
|
|
|
|
|
|
|
|
let state = KlondikeState {
|
|
|
|
|
stock,
|
|
|
|
|
foundations: core::array::from_fn(|_| Stack::new()),
|
|
|
|
|
tableau1,
|
|
|
|
|
tableau2,
|
|
|
|
|
tableau3,
|
|
|
|
|
tableau4,
|
|
|
|
|
tableau5,
|
|
|
|
|
tableau6,
|
|
|
|
|
tableau7,
|
|
|
|
|
};
|
|
|
|
|
Self { config, state }
|
|
|
|
|
}
|
|
|
|
|
pub const fn state(&self) -> &KlondikeState {
|
|
|
|
|
&self.state
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Game for Klondike {
|
|
|
|
|
type Instruction = KlondikeInstruction;
|
|
|
|
|
fn possible_instructions(&self) -> impl Iterator<Item = Self::Instruction> + use<> {
|
|
|
|
|
let state = self.state.clone();
|
|
|
|
|
KlondikeIter::new().filter(move |&instruction| state.is_instruction_valid(instruction))
|
|
|
|
|
}
|
|
|
|
|
fn is_instruction_valid(&self, instruction: Self::Instruction) -> bool {
|
|
|
|
|
self.state.is_instruction_valid(instruction)
|
|
|
|
|
}
|
|
|
|
|
fn process_instruction(&mut self, instruction: Self::Instruction) {
|
|
|
|
|
match instruction {
|
|
|
|
|
// Reset the stock if it's empty
|
|
|
|
|
KlondikeInstruction::RotateStock => {
|
|
|
|
|
if self.state.stock.face_down().is_empty() {
|
|
|
|
|
self.state.stock.flip_it_and_reverse_it();
|
|
|
|
|
} else {
|
|
|
|
|
self.state.stock.flip_up();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
KlondikeInstruction::DstFoundation(DstFoundation { src, foundation }) => {
|
|
|
|
|
let cards = self.state.take_top_card(src);
|
|
|
|
|
self.state.extend(foundation.into(), cards);
|
|
|
|
|
}
|
|
|
|
|
KlondikeInstruction::DstTableau(DstTableau { src, tableau }) => {
|
|
|
|
|
let cards = self.state.take_stack(src);
|
|
|
|
|
self.state.extend(tableau.into(), cards);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fn is_win(&self) -> bool {
|
|
|
|
|
// 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()
|
|
|
|
|
}
|
|
|
|
|
}
|