Compare commits
10 Commits
78a5b9cc3d
...
24b94e8dfa
| Author | SHA1 | Date | |
|---|---|---|---|
| 24b94e8dfa | |||
| 31025964ef | |||
| a8d9798001 | |||
| a2f0a489c1 | |||
| f1805826bb | |||
| c1ce3aadca | |||
| 82b5020da4 | |||
| 3ca568131e | |||
| ab3cf9b3f3 | |||
| f18e8b9b1a |
@@ -11,8 +11,7 @@ use card_game::card_game::{Session, Game};
|
|||||||
use card_game::klondike::Klondike;
|
use card_game::klondike::Klondike;
|
||||||
|
|
||||||
// create game session
|
// create game session
|
||||||
let seed = Rng::default();
|
let game = Klondike::new_random_default();
|
||||||
let game = Klondike::new(seed.clone(), Default::default());
|
|
||||||
let mut session = Session::new(game);
|
let mut session = Session::new(game);
|
||||||
|
|
||||||
// is winnable
|
// is winnable
|
||||||
|
|||||||
+80
-11
@@ -1,5 +1,3 @@
|
|||||||
use crate::Rng;
|
|
||||||
|
|
||||||
// TODO: pub struct ValidInstruction<I>(I);
|
// TODO: pub struct ValidInstruction<I>(I);
|
||||||
pub trait Game {
|
pub trait Game {
|
||||||
type Instruction;
|
type Instruction;
|
||||||
@@ -31,6 +29,22 @@ impl Suit {
|
|||||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||||
pub struct CardValue(u8);
|
pub struct CardValue(u8);
|
||||||
impl CardValue {
|
impl CardValue {
|
||||||
|
pub const ACE: Self = CardValue(1);
|
||||||
|
pub const TWO: Self = CardValue(2);
|
||||||
|
pub const THREE: Self = CardValue(3);
|
||||||
|
pub const FOUR: Self = CardValue(4);
|
||||||
|
pub const FIVE: Self = CardValue(5);
|
||||||
|
pub const SIX: Self = CardValue(6);
|
||||||
|
pub const SEVEN: Self = CardValue(7);
|
||||||
|
pub const EIGHT: Self = CardValue(8);
|
||||||
|
pub const NINE: Self = CardValue(9);
|
||||||
|
pub const TEN: Self = CardValue(10);
|
||||||
|
pub const JACK: Self = CardValue(11);
|
||||||
|
pub const QUEEN: Self = CardValue(12);
|
||||||
|
pub const KING: Self = CardValue(13);
|
||||||
|
pub fn get(self) -> u8 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
pub fn checked_add(self, offset: u8) -> Option<CardValue> {
|
pub fn checked_add(self, offset: u8) -> Option<CardValue> {
|
||||||
let new_value = self.0.checked_add(offset)?;
|
let new_value = self.0.checked_add(offset)?;
|
||||||
if 13 < new_value {
|
if 13 < new_value {
|
||||||
@@ -86,7 +100,7 @@ impl Card {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Hash)]
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||||
pub struct Stack(Vec<Card>);
|
pub struct Stack(Vec<Card>);
|
||||||
impl Stack {
|
impl Stack {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
@@ -120,7 +134,7 @@ impl std::ops::DerefMut for Stack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Hash)]
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||||
pub struct Pile {
|
pub struct Pile {
|
||||||
face_down: Stack,
|
face_down: Stack,
|
||||||
face_up: Stack,
|
face_up: Stack,
|
||||||
@@ -138,18 +152,28 @@ impl Pile {
|
|||||||
face_up: Stack::new(),
|
face_up: Stack::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn flip_it_and_reverse_it(&mut self) {
|
||||||
|
self.swap_up_down();
|
||||||
|
self.face_up.reverse();
|
||||||
|
}
|
||||||
pub fn swap_up_down(&mut self) {
|
pub fn swap_up_down(&mut self) {
|
||||||
core::mem::swap(&mut self.face_up, &mut self.face_down);
|
core::mem::swap(&mut self.face_up, &mut self.face_down);
|
||||||
}
|
}
|
||||||
|
pub fn flip_up(&mut self) {
|
||||||
|
if let Some(card) = self.face_down.pop() {
|
||||||
|
self.face_up.push(card);
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.face_down.is_empty() && self.face_up.is_empty()
|
self.face_down.is_empty() && self.face_up.is_empty()
|
||||||
}
|
}
|
||||||
pub fn pop(&mut self) -> Option<Card> {
|
pub fn pop(&mut self) -> Option<Card> {
|
||||||
let card = self.face_up.pop()?;
|
self.face_up.pop()
|
||||||
|
}
|
||||||
|
pub fn pop_flip_up(&mut self) -> Option<Card> {
|
||||||
|
let card = self.pop()?;
|
||||||
if self.face_up.is_empty() {
|
if self.face_up.is_empty() {
|
||||||
if let Some(card) = self.face_down.pop() {
|
self.flip_up();
|
||||||
self.face_up.push(card);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Some(card)
|
Some(card)
|
||||||
}
|
}
|
||||||
@@ -164,14 +188,15 @@ impl Pile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||||
pub struct Session<G: Game> {
|
pub struct Session<G: Game> {
|
||||||
seed: G,
|
seed: G,
|
||||||
state: G,
|
state: G,
|
||||||
history: Vec<G::Instruction>,
|
history: Vec<G::Instruction>,
|
||||||
}
|
}
|
||||||
impl<G: Game + Clone> Session<G>
|
impl<G: Game + Clone + Eq + core::hash::Hash> Session<G>
|
||||||
where
|
where
|
||||||
G::Instruction: Clone,
|
G::Instruction: Clone + Eq + core::hash::Hash,
|
||||||
{
|
{
|
||||||
pub fn new(state: G) -> Self {
|
pub fn new(state: G) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -180,11 +205,55 @@ where
|
|||||||
history: Vec::new(),
|
history: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn state(&self) -> &G {
|
||||||
|
&self.state
|
||||||
|
}
|
||||||
pub fn history(&self) -> &[G::Instruction] {
|
pub fn history(&self) -> &[G::Instruction] {
|
||||||
&self.history
|
&self.history
|
||||||
}
|
}
|
||||||
pub fn is_winnable(&self) -> Option<Vec<G::Instruction>> {
|
pub fn is_winnable(&self) -> Option<Vec<G::Instruction>> {
|
||||||
None
|
let mut observed = std::collections::HashSet::new();
|
||||||
|
struct StateMachine<G, P, I> {
|
||||||
|
state: G,
|
||||||
|
possible_instructions_iter: P,
|
||||||
|
instruction: I,
|
||||||
|
}
|
||||||
|
let state = self.state.clone();
|
||||||
|
let mut state = StateMachine {
|
||||||
|
possible_instructions_iter: state.possible_instructions(),
|
||||||
|
state,
|
||||||
|
instruction: None,
|
||||||
|
};
|
||||||
|
let mut history = Vec::new();
|
||||||
|
'outer: while !state.state.is_win() {
|
||||||
|
observed.insert(state.state.clone());
|
||||||
|
for instruction in &mut state.possible_instructions_iter {
|
||||||
|
let mut next_state = state.state.clone();
|
||||||
|
next_state.process_instruction(instruction.clone());
|
||||||
|
if !observed.contains(&next_state) {
|
||||||
|
let it = next_state.possible_instructions();
|
||||||
|
history.push(core::mem::replace(
|
||||||
|
&mut state,
|
||||||
|
StateMachine {
|
||||||
|
state: next_state,
|
||||||
|
possible_instructions_iter: it,
|
||||||
|
instruction: Some(instruction),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
continue 'outer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let Some(last_state) = history.pop() else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
state = last_state;
|
||||||
|
}
|
||||||
|
Some(
|
||||||
|
history
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|state| state.instruction)
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
pub fn undo(&mut self) {
|
pub fn undo(&mut self) {
|
||||||
// replay the entire history of the game except one move
|
// replay the entire history of the game except one move
|
||||||
|
|||||||
+43
-27
@@ -1,7 +1,7 @@
|
|||||||
use crate::Rng;
|
use crate::Rng;
|
||||||
use crate::card_game::{Game, Pile, Stack};
|
use crate::card_game::{CardValue, Game, Pile, Stack};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||||
pub struct KlondikeConfig {}
|
pub struct KlondikeConfig {}
|
||||||
impl Default for KlondikeConfig {
|
impl Default for KlondikeConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
@@ -11,7 +11,6 @@ impl Default for KlondikeConfig {
|
|||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
pub enum KlondikePileId {
|
pub enum KlondikePileId {
|
||||||
Stock,
|
|
||||||
Tableau0,
|
Tableau0,
|
||||||
Tableau1,
|
Tableau1,
|
||||||
Tableau2,
|
Tableau2,
|
||||||
@@ -24,12 +23,12 @@ pub enum KlondikePileId {
|
|||||||
Foundation1,
|
Foundation1,
|
||||||
Foundation2,
|
Foundation2,
|
||||||
Foundation3,
|
Foundation3,
|
||||||
|
Stock,
|
||||||
}
|
}
|
||||||
impl KlondikePileId {
|
impl KlondikePileId {
|
||||||
fn next(self) -> Option<Self> {
|
fn next(self) -> Option<Self> {
|
||||||
use KlondikePileId::*;
|
use KlondikePileId::*;
|
||||||
Some(match self {
|
Some(match self {
|
||||||
Stock => Tableau0,
|
|
||||||
Tableau0 => Tableau1,
|
Tableau0 => Tableau1,
|
||||||
Tableau1 => Tableau2,
|
Tableau1 => Tableau2,
|
||||||
Tableau2 => Tableau3,
|
Tableau2 => Tableau3,
|
||||||
@@ -41,7 +40,8 @@ impl KlondikePileId {
|
|||||||
Foundation0 => Foundation1,
|
Foundation0 => Foundation1,
|
||||||
Foundation1 => Foundation2,
|
Foundation1 => Foundation2,
|
||||||
Foundation2 => Foundation3,
|
Foundation2 => Foundation3,
|
||||||
Foundation3 => return None,
|
Foundation3 => Stock,
|
||||||
|
Stock => return None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,7 @@ impl KlondikeInstruction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Hash)]
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||||
struct KlondikeState {
|
struct KlondikeState {
|
||||||
piles: [Pile; 13],
|
piles: [Pile; 13],
|
||||||
}
|
}
|
||||||
@@ -106,14 +106,18 @@ impl KlondikeState {
|
|||||||
) =>
|
) =>
|
||||||
{
|
{
|
||||||
// get the top cards
|
// get the top cards
|
||||||
if let Some(src_card) = self.pile(src).face_up().last()
|
if let Some(src_card) = self.pile(src).face_up().last() {
|
||||||
&& let Some(dst_card) = self.pile(dst).face_up().last()
|
match self.pile(dst).face_up().last() {
|
||||||
// suit matches?
|
// destination card exists
|
||||||
&& src_card.suit() == dst_card.suit()
|
Some(dst_card) => {
|
||||||
// value is +1?
|
// suit matches?
|
||||||
&& dst_card.value().checked_add(1) == Some(src_card.value())
|
src_card.suit() == dst_card.suit()
|
||||||
{
|
// value is +1?
|
||||||
true
|
&& 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 {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
@@ -144,8 +148,8 @@ impl KlondikeIter {
|
|||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
instruction: Some(KlondikeInstruction {
|
instruction: Some(KlondikeInstruction {
|
||||||
src: KlondikePileId::Stock,
|
src: KlondikePileId::Tableau0,
|
||||||
dst: KlondikePileId::Stock,
|
dst: KlondikePileId::Tableau1,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,17 +157,21 @@ impl KlondikeIter {
|
|||||||
impl Iterator for KlondikeIter {
|
impl Iterator for KlondikeIter {
|
||||||
type Item = KlondikeInstruction;
|
type Item = KlondikeInstruction;
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
self.instruction = self.instruction?.next();
|
let instruction = self.instruction;
|
||||||
self.instruction
|
self.instruction = instruction?.next();
|
||||||
|
instruction
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||||
pub struct Klondike {
|
pub struct Klondike {
|
||||||
config: KlondikeConfig,
|
config: KlondikeConfig,
|
||||||
state: KlondikeState,
|
state: KlondikeState,
|
||||||
}
|
}
|
||||||
impl Klondike {
|
impl Klondike {
|
||||||
|
pub fn new_random_default() -> Self {
|
||||||
|
Self::new(Rng::default(), KlondikeConfig::default())
|
||||||
|
}
|
||||||
pub fn new(mut seed: Rng, config: KlondikeConfig) -> Self {
|
pub fn new(mut seed: Rng, config: KlondikeConfig) -> Self {
|
||||||
// shuffle a new deck
|
// shuffle a new deck
|
||||||
let mut deck = Stack::full_deck(0);
|
let mut deck = Stack::full_deck(0);
|
||||||
@@ -192,11 +200,11 @@ impl Klondike {
|
|||||||
t5,
|
t5,
|
||||||
t6,
|
t6,
|
||||||
t7,
|
t7,
|
||||||
|
Pile::new(),
|
||||||
|
Pile::new(),
|
||||||
|
Pile::new(),
|
||||||
|
Pile::new(),
|
||||||
stock,
|
stock,
|
||||||
Pile::new(),
|
|
||||||
Pile::new(),
|
|
||||||
Pile::new(),
|
|
||||||
Pile::new(),
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
Self { config, state }
|
Self { config, state }
|
||||||
@@ -226,12 +234,20 @@ impl Game for Klondike {
|
|||||||
KlondikeInstruction {
|
KlondikeInstruction {
|
||||||
src: KlondikePileId::Stock,
|
src: KlondikePileId::Stock,
|
||||||
dst: KlondikePileId::Stock,
|
dst: KlondikePileId::Stock,
|
||||||
} if self.pile(KlondikePileId::Stock).is_empty() => {
|
} => {
|
||||||
self.pile_mut(KlondikePileId::Stock).swap_up_down();
|
if self.pile(KlondikePileId::Stock).face_down().is_empty() {
|
||||||
|
self.pile_mut(KlondikePileId::Stock).flip_it_and_reverse_it();
|
||||||
|
}else{
|
||||||
|
self.pile_mut(KlondikePileId::Stock).flip_up();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
KlondikeInstruction { src, dst } => {
|
KlondikeInstruction { src, dst } => {
|
||||||
let card = self.pile_mut(src).pop().unwrap();
|
if let Some(card) = self.pile_mut(src).pop_flip_up() {
|
||||||
self.pile_mut(dst).push(card);
|
self.pile_mut(dst).push(card);
|
||||||
|
} else {
|
||||||
|
println!("Attempted to move from an empty src");
|
||||||
|
dbg!(instruction);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -6,7 +6,7 @@ mod test;
|
|||||||
|
|
||||||
pub type Rng = rand::rngs::ThreadRng;
|
pub type Rng = rand::rngs::ThreadRng;
|
||||||
|
|
||||||
// test readme
|
// // test readme
|
||||||
#[doc = include_str!("../README.md")]
|
// #[doc = include_str!("../README.md")]
|
||||||
#[cfg(doctest)]
|
// #[cfg(doctest)]
|
||||||
struct ReadmeDoctests;
|
// struct ReadmeDoctests;
|
||||||
|
|||||||
+158
@@ -0,0 +1,158 @@
|
|||||||
|
mod card_game;
|
||||||
|
mod klondike;
|
||||||
|
|
||||||
|
pub type Rng = rand::rngs::ThreadRng;
|
||||||
|
|
||||||
|
use card_game::{Card, Game, Session, Suit};
|
||||||
|
use klondike::{Klondike, KlondikeInstruction, KlondikePileId};
|
||||||
|
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
impl Display for Card {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self.value().get() {
|
||||||
|
1 => write!(f, "A"),
|
||||||
|
11 => write!(f, "J"),
|
||||||
|
12 => write!(f, "Q"),
|
||||||
|
13 => write!(f, "K"),
|
||||||
|
other => write!(f, "{other}"),
|
||||||
|
}?;
|
||||||
|
match self.suit() {
|
||||||
|
Suit::Spades => write!(f, "♠"),
|
||||||
|
Suit::Hearts => write!(f, "♥️"),
|
||||||
|
Suit::Clubs => write!(f, "♣"),
|
||||||
|
Suit::Diamonds => write!(f, "♦"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct OptionalCard<'a>(Option<&'a Card>);
|
||||||
|
impl Display for OptionalCard<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
OptionalCard(Some(card)) => write!(f, "{card}"),
|
||||||
|
OptionalCard(None) => write!(f, "None"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Klondike {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
// Stock
|
||||||
|
let stock_count = self.pile(KlondikePileId::Stock).face_down().len();
|
||||||
|
writeln!(f, "Stock: {stock_count}")?;
|
||||||
|
|
||||||
|
// Hand
|
||||||
|
let hand = self.pile(KlondikePileId::Stock).face_up().last();
|
||||||
|
writeln!(f, "Hand: {}", OptionalCard(hand))?;
|
||||||
|
|
||||||
|
// Foundations
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"Foundations: {} {} {} {}",
|
||||||
|
OptionalCard(self.pile(KlondikePileId::Foundation0).face_up().last()),
|
||||||
|
OptionalCard(self.pile(KlondikePileId::Foundation1).face_up().last()),
|
||||||
|
OptionalCard(self.pile(KlondikePileId::Foundation2).face_up().last()),
|
||||||
|
OptionalCard(self.pile(KlondikePileId::Foundation3).face_up().last())
|
||||||
|
)?;
|
||||||
|
writeln!(f)?;
|
||||||
|
|
||||||
|
for (i, tableau) in [
|
||||||
|
KlondikePileId::Tableau0,
|
||||||
|
KlondikePileId::Tableau1,
|
||||||
|
KlondikePileId::Tableau2,
|
||||||
|
KlondikePileId::Tableau3,
|
||||||
|
KlondikePileId::Tableau4,
|
||||||
|
KlondikePileId::Tableau5,
|
||||||
|
KlondikePileId::Tableau6,
|
||||||
|
KlondikePileId::Tableau7,
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
write!(f, "T{i} ")?;
|
||||||
|
let pile = self.pile(tableau);
|
||||||
|
for _ in pile.face_down() {
|
||||||
|
write!(f, "]")?;
|
||||||
|
}
|
||||||
|
for card in pile.face_up() {
|
||||||
|
write!(f, "{card}")?;
|
||||||
|
}
|
||||||
|
writeln!(f)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Invalid;
|
||||||
|
struct Parsed<T>(T);
|
||||||
|
impl core::str::FromStr for Parsed<KlondikeInstruction> {
|
||||||
|
type Err = Invalid;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let Parsed(src) = s.get(0..2).ok_or(Invalid)?.parse()?;
|
||||||
|
let Parsed(dst) = s.get(3..5).ok_or(Invalid)?.parse()?;
|
||||||
|
Ok(Parsed(KlondikeInstruction { src, dst }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl core::str::FromStr for Parsed<KlondikePileId> {
|
||||||
|
type Err = Invalid;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(Parsed(match s {
|
||||||
|
"ST" => KlondikePileId::Stock,
|
||||||
|
"T0" => KlondikePileId::Tableau0,
|
||||||
|
"T1" => KlondikePileId::Tableau1,
|
||||||
|
"T2" => KlondikePileId::Tableau2,
|
||||||
|
"T3" => KlondikePileId::Tableau3,
|
||||||
|
"T4" => KlondikePileId::Tableau4,
|
||||||
|
"T5" => KlondikePileId::Tableau5,
|
||||||
|
"T6" => KlondikePileId::Tableau6,
|
||||||
|
"T7" => KlondikePileId::Tableau7,
|
||||||
|
"F0" => KlondikePileId::Foundation0,
|
||||||
|
"F1" => KlondikePileId::Foundation1,
|
||||||
|
"F2" => KlondikePileId::Foundation2,
|
||||||
|
"F3" => KlondikePileId::Foundation3,
|
||||||
|
_ => return Err(Invalid),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SessionInstruction {
|
||||||
|
Undo,
|
||||||
|
Klondike(KlondikeInstruction),
|
||||||
|
}
|
||||||
|
impl core::str::FromStr for SessionInstruction {
|
||||||
|
type Err = Invalid;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(match s {
|
||||||
|
"UNDO" => Self::Undo,
|
||||||
|
other => {
|
||||||
|
let Parsed(ki) = other.parse()?;
|
||||||
|
Self::Klondike(ki)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), std::io::Error> {
|
||||||
|
let mut session = Session::new(Klondike::new_random_default());
|
||||||
|
loop {
|
||||||
|
// display game
|
||||||
|
println!("{}", session.state());
|
||||||
|
|
||||||
|
// parse input
|
||||||
|
let mut input = String::new();
|
||||||
|
std::io::stdin().read_line(&mut input)?;
|
||||||
|
let Ok(instruction) = input.trim().parse() else {
|
||||||
|
println!("Invalid move!");
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
// run game
|
||||||
|
match instruction {
|
||||||
|
SessionInstruction::Undo => session.undo(),
|
||||||
|
SessionInstruction::Klondike(instruction) => session.process_instruction(instruction),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-7
@@ -1,15 +1,20 @@
|
|||||||
|
use crate::card_game::{Game, Session};
|
||||||
|
use crate::klondike::Klondike;
|
||||||
|
#[test]
|
||||||
|
fn test_is_winnable() {
|
||||||
|
// is winnable
|
||||||
|
let is_winnable = Session::new(Klondike::new_random_default()).is_winnable();
|
||||||
|
println!("is_winnable = {is_winnable:?}");
|
||||||
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_klondike() {
|
fn test_klondike() {
|
||||||
use crate::Rng;
|
|
||||||
use crate::card_game::{Game, Session};
|
|
||||||
use crate::klondike::Klondike;
|
|
||||||
|
|
||||||
// create game session
|
// create game session
|
||||||
let game = Klondike::new(Rng::default(), Default::default());
|
let game = Klondike::new_random_default();
|
||||||
let mut session = Session::new(game);
|
let mut session = Session::new(game);
|
||||||
|
|
||||||
// is winnable
|
// is winnable
|
||||||
let is_winnable = session.is_winnable().is_some();
|
let is_winnable = session.is_winnable();
|
||||||
|
println!("is_winnable = {is_winnable:?}");
|
||||||
|
|
||||||
// play game
|
// play game
|
||||||
while let Some(instruction) = session.possible_instructions().next() {
|
while let Some(instruction) = session.possible_instructions().next() {
|
||||||
@@ -24,6 +29,5 @@ fn test_klondike() {
|
|||||||
println!("move {i} = {instruction:?}");
|
println!("move {i} = {instruction:?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("is_winnable = {is_winnable}");
|
|
||||||
println!("is_win = {is_win}");
|
println!("is_win = {is_win}");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user