Refactor KlondikeInstruction (#4)

Fixes #2

Reviewed-on: #4
Co-authored-by: Rhys Lloyd <krakow20@gmail.com>
Co-committed-by: Rhys Lloyd <krakow20@gmail.com>
This commit was merged in pull request #4.
This commit is contained in:
2026-05-16 19:09:11 +00:00
committed by Quaternions
parent 0d98f8e25e
commit a8051b221b
2 changed files with 341 additions and 420 deletions
+284 -314
View File
@@ -9,9 +9,8 @@ impl Default for KlondikeConfig {
} }
} }
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum KlondikePileId { pub enum Tableau {
Tableau1, Tableau1,
Tableau2, Tableau2,
Tableau3, Tableau3,
@@ -19,15 +18,11 @@ pub enum KlondikePileId {
Tableau5, Tableau5,
Tableau6, Tableau6,
Tableau7, Tableau7,
Foundation1,
Foundation2,
Foundation3,
Foundation4,
Stock,
} }
impl KlondikePileId { impl Tableau {
const ITER_BEGIN: Self = Self::Tableau1;
const fn next(self) -> Option<Self> { const fn next(self) -> Option<Self> {
use KlondikePileId::*; use Tableau::*;
Some(match self { Some(match self {
Tableau1 => Tableau2, Tableau1 => Tableau2,
Tableau2 => Tableau3, Tableau2 => Tableau3,
@@ -35,229 +30,228 @@ impl KlondikePileId {
Tableau4 => Tableau5, Tableau4 => Tableau5,
Tableau5 => Tableau6, Tableau5 => Tableau6,
Tableau6 => Tableau7, Tableau6 => Tableau7,
Tableau7 => Foundation1, Tableau7 => return None,
Foundation1 => Foundation2,
Foundation2 => Foundation3,
Foundation3 => Foundation4,
Foundation4 => Stock,
Stock => return None,
}) })
} }
} }
/// high four bits for stack depth, low four bits for Pile Id
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct InstructionSrc(u8); pub enum Foundation {
impl InstructionSrc { Foundation1,
const STOCK: Self = InstructionSrc::new(KlondikePileStack::Stock); Foundation2,
pub const fn new(src: KlondikePileStack) -> Self { Foundation3,
match src { Foundation4,
KlondikePileStack::Tableau1(skip_cards) => { }
Self(KlondikePileId::Tableau1 as u8 + ((skip_cards as u8) << 4)) impl Foundation {
} const ITER_BEGIN: Self = Self::Foundation1;
KlondikePileStack::Tableau2(skip_cards) => {
Self(KlondikePileId::Tableau2 as u8 + ((skip_cards as u8) << 4))
}
KlondikePileStack::Tableau3(skip_cards) => {
Self(KlondikePileId::Tableau3 as u8 + ((skip_cards as u8) << 4))
}
KlondikePileStack::Tableau4(skip_cards) => {
Self(KlondikePileId::Tableau4 as u8 + ((skip_cards as u8) << 4))
}
KlondikePileStack::Tableau5(skip_cards) => {
Self(KlondikePileId::Tableau5 as u8 + ((skip_cards as u8) << 4))
}
KlondikePileStack::Tableau6(skip_cards) => {
Self(KlondikePileId::Tableau6 as u8 + ((skip_cards as u8) << 4))
}
KlondikePileStack::Tableau7(skip_cards) => {
Self(KlondikePileId::Tableau7 as u8 + ((skip_cards as u8) << 4))
}
KlondikePileStack::Foundation1 => Self(KlondikePileId::Foundation1 as u8),
KlondikePileStack::Foundation2 => Self(KlondikePileId::Foundation2 as u8),
KlondikePileStack::Foundation3 => Self(KlondikePileId::Foundation3 as u8),
KlondikePileStack::Foundation4 => Self(KlondikePileId::Foundation4 as u8),
KlondikePileStack::Stock => Self(KlondikePileId::Stock as u8),
}
}
const fn into_spec(self) -> KlondikePileStack {
// SAFETY: there is no way to construct an invalid InstructionSrc
let pile = unsafe { core::mem::transmute(self.0 & 0b1111) };
match pile {
KlondikePileId::Tableau1 => {
KlondikePileStack::Tableau1(unsafe { core::mem::transmute(self.0 >> 4) })
}
KlondikePileId::Tableau2 => {
KlondikePileStack::Tableau2(unsafe { core::mem::transmute(self.0 >> 4) })
}
KlondikePileId::Tableau3 => {
KlondikePileStack::Tableau3(unsafe { core::mem::transmute(self.0 >> 4) })
}
KlondikePileId::Tableau4 => {
KlondikePileStack::Tableau4(unsafe { core::mem::transmute(self.0 >> 4) })
}
KlondikePileId::Tableau5 => {
KlondikePileStack::Tableau5(unsafe { core::mem::transmute(self.0 >> 4) })
}
KlondikePileId::Tableau6 => {
KlondikePileStack::Tableau6(unsafe { core::mem::transmute(self.0 >> 4) })
}
KlondikePileId::Tableau7 => {
KlondikePileStack::Tableau7(unsafe { core::mem::transmute(self.0 >> 4) })
}
KlondikePileId::Foundation1 => KlondikePileStack::Foundation1,
KlondikePileId::Foundation2 => KlondikePileStack::Foundation2,
KlondikePileId::Foundation3 => KlondikePileStack::Foundation3,
KlondikePileId::Foundation4 => KlondikePileStack::Foundation4,
KlondikePileId::Stock => KlondikePileStack::Stock,
}
}
const fn next(self) -> Option<Self> { const fn next(self) -> Option<Self> {
match self.into_spec().next() { use Foundation::*;
Some(s) => Some(Self::new(s)), Some(match self {
None => None, Foundation1 => Foundation2,
} Foundation2 => Foundation3,
Foundation3 => Foundation4,
Foundation4 => return None,
})
} }
} }
impl From<InstructionSrc> for KlondikePileId {
fn from(value: InstructionSrc) -> Self { #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
match value.into_spec() { pub enum KlondikePile {
KlondikePileStack::Tableau1(_) => KlondikePileId::Tableau1, Tableau(Tableau),
KlondikePileStack::Tableau2(_) => KlondikePileId::Tableau2, Stock,
KlondikePileStack::Tableau3(_) => KlondikePileId::Tableau3, Foundation(Foundation),
KlondikePileStack::Tableau4(_) => KlondikePileId::Tableau4, }
KlondikePileStack::Tableau5(_) => KlondikePileId::Tableau5, impl KlondikePile {
KlondikePileStack::Tableau6(_) => KlondikePileId::Tableau6, const ITER_BEGIN: Self = Self::Tableau(Tableau::ITER_BEGIN);
KlondikePileStack::Tableau7(_) => KlondikePileId::Tableau7, const fn next(self) -> Option<Self> {
KlondikePileStack::Foundation1 => KlondikePileId::Foundation1, Some(match self {
KlondikePileStack::Foundation2 => KlondikePileId::Foundation2, Self::Tableau(tableau_stack) => match tableau_stack.next() {
KlondikePileStack::Foundation3 => KlondikePileId::Foundation3, Some(tableau_stack) => Self::Tableau(tableau_stack),
KlondikePileStack::Foundation4 => KlondikePileId::Foundation4, None => Self::Stock,
KlondikePileStack::Stock => KlondikePileId::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)] #[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum SkipCards { pub enum SkipCards {
Zero, Skip0,
One, Skip1,
Two, Skip2,
Three, Skip3,
Four, Skip4,
Five, Skip5,
Six, Skip6,
Seven, Skip7,
Eight, Skip8,
Nine, Skip9,
Ten, Skip10,
Eleven, Skip11,
Twelve, Skip12,
} }
impl SkipCards { impl SkipCards {
const ITER_BEGIN: Self = Self::Skip0;
const fn next(self) -> Option<Self> { const fn next(self) -> Option<Self> {
use SkipCards::*; use SkipCards::*;
Some(match self { Some(match self {
Zero => One, Skip0 => Skip1,
One => Two, Skip1 => Skip2,
Two => Three, Skip2 => Skip3,
Three => Four, Skip3 => Skip4,
Four => Five, Skip4 => Skip5,
Five => Six, Skip5 => Skip6,
Six => Seven, Skip6 => Skip7,
Seven => Eight, Skip7 => Skip8,
Eight => Nine, Skip8 => Skip9,
Nine => Ten, Skip9 => Skip10,
Ten => Eleven, Skip10 => Skip11,
Eleven => Twelve, Skip11 => Skip12,
Twelve => return None, Skip12 => return None,
})
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum KlondikePileStack {
Tableau1(SkipCards),
Tableau2(SkipCards),
Tableau3(SkipCards),
Tableau4(SkipCards),
Tableau5(SkipCards),
Tableau6(SkipCards),
Tableau7(SkipCards),
Foundation1,
Foundation2,
Foundation3,
Foundation4,
Stock,
}
impl KlondikePileStack {
const fn next(self) -> Option<Self> {
use KlondikePileStack::*;
Some(match self {
Tableau1(skip) => match skip.next() {
Some(next) => Tableau1(next),
None => Tableau2(SkipCards::Zero),
},
Tableau2(skip) => match skip.next() {
Some(next) => Tableau2(next),
None => Tableau3(SkipCards::Zero),
},
Tableau3(skip) => match skip.next() {
Some(next) => Tableau3(next),
None => Tableau4(SkipCards::Zero),
},
Tableau4(skip) => match skip.next() {
Some(next) => Tableau4(next),
None => Tableau5(SkipCards::Zero),
},
Tableau5(skip) => match skip.next() {
Some(next) => Tableau5(next),
None => Tableau6(SkipCards::Zero),
},
Tableau6(skip) => match skip.next() {
Some(next) => Tableau6(next),
None => Tableau7(SkipCards::Zero),
},
Tableau7(skip) => match skip.next() {
Some(next) => Tableau7(next),
None => Foundation1,
},
Foundation1 => Foundation2,
Foundation2 => Foundation3,
Foundation3 => Foundation4,
Foundation4 => Stock,
Stock => return None,
}) })
} }
} }
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct KlondikeInstruction { pub struct TableauStack {
pub src: InstructionSrc, pub tableau: Tableau,
pub dst: KlondikePileId, pub skip_cards: SkipCards,
} }
impl KlondikeInstruction {
pub const fn stock() -> Self { impl TableauStack {
Self { const ITER_BEGIN: Self = Self {
src: InstructionSrc::STOCK, tableau: Tableau::ITER_BEGIN,
dst: KlondikePileId::Stock, skip_cards: SkipCards::ITER_BEGIN,
} };
}
const fn next(self) -> Option<Self> { const fn next(self) -> Option<Self> {
let KlondikeInstruction { src, dst } = self; let TableauStack {
if let Some(next_dst) = dst.next() { tableau,
return Some(Self { src, dst: next_dst }); skip_cards,
} } = self;
if let Some(next_src) = src.next() { if let Some(skip_cards) = skip_cards.next() {
return Some(Self { return Some(Self {
src: next_src, tableau,
dst: KlondikePileId::Tableau1, skip_cards,
});
}
if let Some(tableau) = tableau.next() {
let skip_cards = SkipCards::Skip0;
return Some(Self {
tableau,
skip_cards,
}); });
} }
None 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 TABLEAUS: usize = 7;
const fn sum(n: usize) -> usize { const fn sum(n: usize) -> usize {
n * (n + 1) / 2 n * (n + 1) / 2
@@ -313,128 +307,106 @@ impl KlondikeState {
pub const fn tableau7(&self) -> &Pile<6, 13> { pub const fn tableau7(&self) -> &Pile<6, 13> {
&self.tableau7 &self.tableau7
} }
fn src_card(&self, src: InstructionSrc) -> Option<&Card> { fn card(&self, src: KlondikePileStack) -> Option<&Card> {
match src.into_spec() { match src {
KlondikePileStack::Tableau1(skip_cards) => { KlondikePileStack::Tableau(TableauStack {
self.tableau1.face_up().get(skip_cards as usize) 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::Tableau2(skip_cards) => {
self.tableau2.face_up().get(skip_cards as usize)
}
KlondikePileStack::Tableau3(skip_cards) => {
self.tableau3.face_up().get(skip_cards as usize)
}
KlondikePileStack::Tableau4(skip_cards) => {
self.tableau4.face_up().get(skip_cards as usize)
}
KlondikePileStack::Tableau5(skip_cards) => {
self.tableau5.face_up().get(skip_cards as usize)
}
KlondikePileStack::Tableau6(skip_cards) => {
self.tableau6.face_up().get(skip_cards as usize)
}
KlondikePileStack::Tableau7(skip_cards) => {
self.tableau7.face_up().get(skip_cards as usize)
}
KlondikePileStack::Foundation1 => self.foundations[1 - 1].last(),
KlondikePileStack::Foundation2 => self.foundations[2 - 1].last(),
KlondikePileStack::Foundation3 => self.foundations[3 - 1].last(),
KlondikePileStack::Foundation4 => self.foundations[4 - 1].last(),
KlondikePileStack::Stock => self.stock.face_up().last(), KlondikePileStack::Stock => self.stock.face_up().last(),
} }
} }
fn take_src_cards(&mut self, src: InstructionSrc) -> Stack<13> { fn top_card(&self, src: KlondikePile) -> Option<&Card> {
match src.into_spec() { match src {
KlondikePileStack::Tableau1(skip_cards) => { KlondikePile::Tableau(tableau) => match tableau {
self.tableau1.take_range_flip_up(skip_cards as usize..) 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_cards(&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::Tableau2(skip_cards) => {
self.tableau2.take_range_flip_up(skip_cards as usize..)
}
KlondikePileStack::Tableau3(skip_cards) => {
self.tableau3.take_range_flip_up(skip_cards as usize..)
}
KlondikePileStack::Tableau4(skip_cards) => {
self.tableau4.take_range_flip_up(skip_cards as usize..)
}
KlondikePileStack::Tableau5(skip_cards) => {
self.tableau5.take_range_flip_up(skip_cards as usize..)
}
KlondikePileStack::Tableau6(skip_cards) => {
self.tableau6.take_range_flip_up(skip_cards as usize..)
}
KlondikePileStack::Tableau7(skip_cards) => {
self.tableau7.take_range_flip_up(skip_cards as usize..)
}
KlondikePileStack::Foundation1 => Stack::from_iter(self.foundations[1 - 1].pop()),
KlondikePileStack::Foundation2 => Stack::from_iter(self.foundations[2 - 1].pop()),
KlondikePileStack::Foundation3 => Stack::from_iter(self.foundations[3 - 1].pop()),
KlondikePileStack::Foundation4 => Stack::from_iter(self.foundations[4 - 1].pop()),
KlondikePileStack::Stock => Stack::from_iter(self.stock.pop()), KlondikePileStack::Stock => Stack::from_iter(self.stock.pop()),
} }
} }
fn dst_card(&self, dst: KlondikePileId) -> Option<&Card> { fn take_top_card(&mut self, src: KlondikePile) -> Option<Card> {
match dst { match src {
KlondikePileId::Tableau1 => self.tableau1.face_up().last(), KlondikePile::Tableau(tableau) => match tableau {
KlondikePileId::Tableau2 => self.tableau2.face_up().last(), Tableau::Tableau1 => self.tableau1.pop(),
KlondikePileId::Tableau3 => self.tableau3.face_up().last(), Tableau::Tableau2 => self.tableau2.pop(),
KlondikePileId::Tableau4 => self.tableau4.face_up().last(), Tableau::Tableau3 => self.tableau3.pop(),
KlondikePileId::Tableau5 => self.tableau5.face_up().last(), Tableau::Tableau4 => self.tableau4.pop(),
KlondikePileId::Tableau6 => self.tableau6.face_up().last(), Tableau::Tableau5 => self.tableau5.pop(),
KlondikePileId::Tableau7 => self.tableau7.face_up().last(), Tableau::Tableau6 => self.tableau6.pop(),
KlondikePileId::Foundation1 => self.foundations[1 - 1].last(), Tableau::Tableau7 => self.tableau7.pop(),
KlondikePileId::Foundation2 => self.foundations[2 - 1].last(), },
KlondikePileId::Foundation3 => self.foundations[3 - 1].last(), KlondikePile::Foundation(foundation) => self.foundations[foundation as usize].pop(),
KlondikePileId::Foundation4 => self.foundations[4 - 1].last(), KlondikePile::Stock => self.stock.pop(),
KlondikePileId::Stock => None,
} }
} }
fn extend_dst_pile(&mut self, dst: KlondikePileId, cards: Stack<13>) { fn extend<I: IntoIterator<Item = Card>>(&mut self, dst: KlondikePile, cards: I) {
match dst { match dst {
KlondikePileId::Tableau1 => self.tableau1.extend(cards), KlondikePile::Tableau(tableau) => match tableau {
KlondikePileId::Tableau2 => self.tableau2.extend(cards), Tableau::Tableau1 => self.tableau1.extend(cards),
KlondikePileId::Tableau3 => self.tableau3.extend(cards), Tableau::Tableau2 => self.tableau2.extend(cards),
KlondikePileId::Tableau4 => self.tableau4.extend(cards), Tableau::Tableau3 => self.tableau3.extend(cards),
KlondikePileId::Tableau5 => self.tableau5.extend(cards), Tableau::Tableau4 => self.tableau4.extend(cards),
KlondikePileId::Tableau6 => self.tableau6.extend(cards), Tableau::Tableau5 => self.tableau5.extend(cards),
KlondikePileId::Tableau7 => self.tableau7.extend(cards), Tableau::Tableau6 => self.tableau6.extend(cards),
KlondikePileId::Foundation1 => self.foundations[1 - 1].extend(cards), Tableau::Tableau7 => self.tableau7.extend(cards),
KlondikePileId::Foundation2 => self.foundations[2 - 1].extend(cards), },
KlondikePileId::Foundation3 => self.foundations[3 - 1].extend(cards), KlondikePile::Foundation(foundation) => {
KlondikePileId::Foundation4 => self.foundations[4 - 1].extend(cards), self.foundations[foundation as usize].extend(cards)
KlondikePileId::Stock => (), }
KlondikePile::Stock => self.stock.extend(cards),
} }
} }
fn is_instruction_valid(&self, instruction: KlondikeInstruction) -> bool { fn is_instruction_valid(&self, instruction: KlondikeInstruction) -> bool {
match instruction { match instruction {
// Stock -> Stock draws a card or resets the stock // Stock -> Stock draws a card or resets the stock
KlondikeInstruction { KlondikeInstruction::RotateStock => {
src: InstructionSrc::STOCK,
dst: KlondikePileId::Stock,
} => {
// cannot move stock when stock is empty // cannot move stock when stock is empty
!self.stock.is_empty() !self.stock.is_empty()
} }
// cannot move cards to stock
KlondikeInstruction {
src: _,
dst: KlondikePileId::Stock,
} => false,
// moving to foundation has special rules // moving to foundation has special rules
KlondikeInstruction { src, dst } KlondikeInstruction::DstFoundation(dst_foundation) => {
if matches!(
dst,
KlondikePileId::Foundation1
| KlondikePileId::Foundation2
| KlondikePileId::Foundation3
| KlondikePileId::Foundation4
) =>
{
// get the top cards // get the top cards
if let Some(src_card) = self.src_card(src) { if let Some(src_card) = self.top_card(dst_foundation.src) {
match self.dst_card(dst) { match self.top_card(dst_foundation.foundation.into()) {
// destination card exists // destination card exists
Some(dst_card) => { Some(dst_card) => {
// suit matches? // suit matches?
@@ -450,10 +422,10 @@ impl KlondikeState {
} }
} }
// other = move to tableau // other = move to tableau
KlondikeInstruction { src, dst } => { KlondikeInstruction::DstTableau(dst_tableau) => {
// get the top cards // get the cards
if let Some(src_card) = self.src_card(src) { if let Some(src_card) = self.card(dst_tableau.src) {
match self.dst_card(dst) { match self.top_card(dst_tableau.tableau.into()) {
// destination card exists // destination card exists
Some(dst_card) => { Some(dst_card) => {
// red-ness is opposite? // red-ness is opposite?
@@ -478,10 +450,7 @@ pub struct KlondikeIter {
impl KlondikeIter { impl KlondikeIter {
const fn new() -> Self { const fn new() -> Self {
Self { Self {
instruction: Some(KlondikeInstruction { instruction: Some(KlondikeInstruction::ITER_BEGIN),
src: InstructionSrc::new(KlondikePileStack::Tableau1(SkipCards::Zero)),
dst: KlondikePileId::Tableau2,
}),
} }
} }
} }
@@ -558,19 +527,20 @@ impl Game for Klondike {
fn process_instruction(&mut self, instruction: Self::Instruction) { fn process_instruction(&mut self, instruction: Self::Instruction) {
match instruction { match instruction {
// Reset the stock if it's empty // Reset the stock if it's empty
KlondikeInstruction { KlondikeInstruction::RotateStock => {
src: InstructionSrc::STOCK,
dst: KlondikePileId::Stock,
} => {
if self.state.stock.face_down().is_empty() { if self.state.stock.face_down().is_empty() {
self.state.stock.flip_it_and_reverse_it(); self.state.stock.flip_it_and_reverse_it();
} else { } else {
self.state.stock.flip_up(); self.state.stock.flip_up();
} }
} }
KlondikeInstruction { src, dst } => { KlondikeInstruction::DstFoundation(DstFoundation { src, foundation }) => {
let cards = self.state.take_src_cards(src); let cards = self.state.take_top_card(src);
self.state.extend_dst_pile(dst, cards); self.state.extend(foundation.into(), cards);
}
KlondikeInstruction::DstTableau(DstTableau { src, tableau }) => {
let cards = self.state.take_cards(src);
self.state.extend(tableau.into(), cards);
} }
} }
} }
+57 -106
View File
@@ -5,8 +5,8 @@ pub type Rng = rand::rngs::ThreadRng;
use card_game::{Card, Game, Pile, Session, Suit}; use card_game::{Card, Game, Pile, Session, Suit};
use klondike::{ use klondike::{
InstructionSrc, Klondike, KlondikeInstruction, KlondikePileId, KlondikePileStack, DstFoundation, DstTableau, Foundation, Klondike, KlondikeInstruction, KlondikePile,
KlondikeState, SkipCards, KlondikePileStack, SkipCards, Tableau, TableauStack,
}; };
use std::fmt::Display; use std::fmt::Display;
@@ -91,8 +91,8 @@ impl Display for Klondike {
struct Invalid; struct Invalid;
struct Parsed<T>(T); struct Parsed<T>(T);
struct NaiveInstruction { struct NaiveInstruction {
src: KlondikePileId, src: KlondikePile,
dst: KlondikePileId, dst: KlondikePile,
} }
impl core::str::FromStr for NaiveInstruction { impl core::str::FromStr for NaiveInstruction {
type Err = Invalid; type Err = Invalid;
@@ -102,22 +102,22 @@ impl core::str::FromStr for NaiveInstruction {
Ok(NaiveInstruction { src, dst }) Ok(NaiveInstruction { src, dst })
} }
} }
impl core::str::FromStr for Parsed<KlondikePileId> { impl core::str::FromStr for Parsed<KlondikePile> {
type Err = Invalid; type Err = Invalid;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Parsed(match s { Ok(Parsed(match s {
"st" => KlondikePileId::Stock, "st" => KlondikePile::Stock,
"t1" => KlondikePileId::Tableau1, "t1" => KlondikePile::Tableau(Tableau::Tableau1),
"t2" => KlondikePileId::Tableau2, "t2" => KlondikePile::Tableau(Tableau::Tableau2),
"t3" => KlondikePileId::Tableau3, "t3" => KlondikePile::Tableau(Tableau::Tableau3),
"t4" => KlondikePileId::Tableau4, "t4" => KlondikePile::Tableau(Tableau::Tableau4),
"t5" => KlondikePileId::Tableau5, "t5" => KlondikePile::Tableau(Tableau::Tableau5),
"t6" => KlondikePileId::Tableau6, "t6" => KlondikePile::Tableau(Tableau::Tableau6),
"t7" => KlondikePileId::Tableau7, "t7" => KlondikePile::Tableau(Tableau::Tableau7),
"f1" => KlondikePileId::Foundation1, "f1" => KlondikePile::Foundation(Foundation::Foundation1),
"f2" => KlondikePileId::Foundation2, "f2" => KlondikePile::Foundation(Foundation::Foundation2),
"f3" => KlondikePileId::Foundation3, "f3" => KlondikePile::Foundation(Foundation::Foundation3),
"f4" => KlondikePileId::Foundation4, "f4" => KlondikePile::Foundation(Foundation::Foundation4),
_ => return Err(Invalid), _ => return Err(Invalid),
})) }))
} }
@@ -152,99 +152,48 @@ fn find_valid_instruction(
naive_instruction: NaiveInstruction, naive_instruction: NaiveInstruction,
) -> Option<KlondikeInstruction> { ) -> Option<KlondikeInstruction> {
const SKIP_LIST: [SkipCards; 13] = [ const SKIP_LIST: [SkipCards; 13] = [
SkipCards::Zero, SkipCards::Skip0,
SkipCards::One, SkipCards::Skip1,
SkipCards::Two, SkipCards::Skip2,
SkipCards::Three, SkipCards::Skip3,
SkipCards::Four, SkipCards::Skip4,
SkipCards::Five, SkipCards::Skip5,
SkipCards::Six, SkipCards::Skip6,
SkipCards::Seven, SkipCards::Skip7,
SkipCards::Eight, SkipCards::Skip8,
SkipCards::Nine, SkipCards::Skip9,
SkipCards::Ten, SkipCards::Skip10,
SkipCards::Eleven, SkipCards::Skip11,
SkipCards::Twelve, SkipCards::Skip12,
]; ];
let dst = naive_instruction.dst; let instruction = match (naive_instruction.dst, naive_instruction.src) {
let src = match naive_instruction.src { (KlondikePile::Tableau(tableau), src) => {
KlondikePileId::Tableau1 => { let src = match src {
for skip in SKIP_LIST { KlondikePile::Tableau(src_tableau) => {
let src = InstructionSrc::new(KlondikePileStack::Tableau1(skip)); for skip_cards in SKIP_LIST {
let instruction = KlondikeInstruction { src, dst }; let src = KlondikePileStack::Tableau(TableauStack {
if state.is_instruction_valid(instruction) { tableau: src_tableau,
return Some(instruction); skip_cards,
});
let instruction =
KlondikeInstruction::DstTableau(DstTableau { tableau, src });
if state.is_instruction_valid(instruction) {
return Some(instruction);
}
}
return None;
} }
} KlondikePile::Stock => KlondikePileStack::Stock,
return None; KlondikePile::Foundation(foundation) => KlondikePileStack::Foundation(foundation),
};
KlondikeInstruction::DstTableau(DstTableau { tableau, src })
} }
KlondikePileId::Tableau2 => { (KlondikePile::Stock, KlondikePile::Stock) => KlondikeInstruction::RotateStock,
for skip in SKIP_LIST { (KlondikePile::Foundation(foundation), src) => {
let src = InstructionSrc::new(KlondikePileStack::Tableau2(skip)); KlondikeInstruction::DstFoundation(DstFoundation { foundation, src })
let instruction = KlondikeInstruction { src, dst };
if state.is_instruction_valid(instruction) {
return Some(instruction);
}
}
return None;
} }
KlondikePileId::Tableau3 => { _ => return None,
for skip in SKIP_LIST {
let src = InstructionSrc::new(KlondikePileStack::Tableau3(skip));
let instruction = KlondikeInstruction { src, dst };
if state.is_instruction_valid(instruction) {
return Some(instruction);
}
}
return None;
}
KlondikePileId::Tableau4 => {
for skip in SKIP_LIST {
let src = InstructionSrc::new(KlondikePileStack::Tableau4(skip));
let instruction = KlondikeInstruction { src, dst };
if state.is_instruction_valid(instruction) {
return Some(instruction);
}
}
return None;
}
KlondikePileId::Tableau5 => {
for skip in SKIP_LIST {
let src = InstructionSrc::new(KlondikePileStack::Tableau5(skip));
let instruction = KlondikeInstruction { src, dst };
if state.is_instruction_valid(instruction) {
return Some(instruction);
}
}
return None;
}
KlondikePileId::Tableau6 => {
for skip in SKIP_LIST {
let src = InstructionSrc::new(KlondikePileStack::Tableau6(skip));
let instruction = KlondikeInstruction { src, dst };
if state.is_instruction_valid(instruction) {
return Some(instruction);
}
}
return None;
}
KlondikePileId::Tableau7 => {
for skip in SKIP_LIST {
let src = InstructionSrc::new(KlondikePileStack::Tableau7(skip));
let instruction = KlondikeInstruction { src, dst };
if state.is_instruction_valid(instruction) {
return Some(instruction);
}
}
return None;
}
KlondikePileId::Foundation1 => InstructionSrc::new(KlondikePileStack::Foundation1),
KlondikePileId::Foundation2 => InstructionSrc::new(KlondikePileStack::Foundation2),
KlondikePileId::Foundation3 => InstructionSrc::new(KlondikePileStack::Foundation3),
KlondikePileId::Foundation4 => InstructionSrc::new(KlondikePileStack::Foundation4),
KlondikePileId::Stock => InstructionSrc::new(KlondikePileStack::Stock),
}; };
let instruction = KlondikeInstruction { src, dst };
state state
.is_instruction_valid(instruction) .is_instruction_valid(instruction)
.then_some(instruction) .then_some(instruction)
@@ -281,7 +230,9 @@ fn main() -> Result<(), std::io::Error> {
println!("No valid moves!"); println!("No valid moves!");
} }
} }
SessionInstruction::Stock => session.process_instruction(KlondikeInstruction::stock()), SessionInstruction::Stock => {
session.process_instruction(KlondikeInstruction::RotateStock)
}
SessionInstruction::Klondike(naive_instruction) => { SessionInstruction::Klondike(naive_instruction) => {
if let Some(instruction) = if let Some(instruction) =
find_valid_instruction(session.state(), naive_instruction) find_valid_instruction(session.state(), naive_instruction)