Compare commits
3 Commits
d1df1c2eb3
...
9a6745b9de
| Author | SHA1 | Date | |
|---|---|---|---|
| 9a6745b9de | |||
| c8de8bad84 | |||
| 5ed4ec139f |
Generated
+9
-1
@@ -25,7 +25,6 @@ name = "card_game"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -125,11 +124,20 @@ version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "klondike"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"card_game",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "klondike-cli"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"card_game",
|
||||
"klondike",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"card_game",
|
||||
"klondike",
|
||||
"klondike-cli",
|
||||
]
|
||||
resolver = "3"
|
||||
|
||||
@@ -10,4 +10,3 @@ keywords = ["card", "cards", "solitaire", "klondike"]
|
||||
|
||||
[dependencies]
|
||||
arrayvec = "0.7.6"
|
||||
rand = { version = "0.10.1", default-features = false, features = ["thread_rng"] }
|
||||
|
||||
@@ -1,314 +0,0 @@
|
||||
use core::ops::RangeBounds;
|
||||
|
||||
// TODO: pub struct ValidInstruction<I>(I);
|
||||
pub trait Game {
|
||||
type Instruction;
|
||||
fn possible_instructions(&self) -> impl Iterator<Item = Self::Instruction> + use<Self>;
|
||||
fn is_instruction_valid(&self, instruction: Self::Instruction) -> bool;
|
||||
fn process_instruction(&mut self, instruction: Self::Instruction);
|
||||
fn is_win(&self) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub enum Suit {
|
||||
Spades = 0b00,
|
||||
Hearts = 0b01,
|
||||
Clubs = 0b10,
|
||||
Diamonds = 0b11,
|
||||
}
|
||||
impl Suit {
|
||||
pub const SUITS: [Self; 4] = [Self::Spades, Self::Hearts, Self::Clubs, Self::Diamonds];
|
||||
/// Is the suit red.
|
||||
pub fn is_red(self) -> bool {
|
||||
self as u8 & 0b01 != 0
|
||||
}
|
||||
/// Is the suit shape spikey. (Bouba/kiki)
|
||||
pub fn is_kiki(self) -> bool {
|
||||
self as u8 & 0b10 != 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub struct CardValue(u8);
|
||||
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> {
|
||||
let new_value = self.0.checked_add(offset)?;
|
||||
if 13 < new_value {
|
||||
None
|
||||
} else {
|
||||
Some(CardValue(new_value))
|
||||
}
|
||||
}
|
||||
pub fn checked_sub(self, offset: u8) -> Option<CardValue> {
|
||||
let new_value = self.0.checked_sub(offset)?;
|
||||
if new_value < 1 {
|
||||
None
|
||||
} else {
|
||||
Some(CardValue(new_value))
|
||||
}
|
||||
}
|
||||
}
|
||||
/// An identifier which specifies the deck id, suit, and card value.
|
||||
/// 2 bits for deck ID
|
||||
/// 2 bits for suit ID
|
||||
/// 4 bits for card Value
|
||||
/// TODO: better encoding for slightly more decks
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct Card(u8);
|
||||
impl Card {
|
||||
pub fn new(deck: u8, suit: Suit, CardValue(value): CardValue) -> Self {
|
||||
Self(deck << 6 | (suit as u8) << 4 | value)
|
||||
}
|
||||
pub fn value(&self) -> CardValue {
|
||||
let masked = self.0 & 0b1111;
|
||||
CardValue(masked)
|
||||
}
|
||||
pub fn suit(&self) -> Suit {
|
||||
let red = self.is_red();
|
||||
let kiki = self.is_kiki();
|
||||
match (kiki, red) {
|
||||
(false, false) => Suit::Spades,
|
||||
(false, true) => Suit::Hearts,
|
||||
(true, false) => Suit::Clubs,
|
||||
(true, true) => Suit::Diamonds,
|
||||
}
|
||||
}
|
||||
/// Is the suit red.
|
||||
pub fn is_red(&self) -> bool {
|
||||
self.0 & 0b010000 != 0
|
||||
}
|
||||
/// Is the suit shape spikey. (Bouba/kiki)
|
||||
pub fn is_kiki(&self) -> bool {
|
||||
self.0 & 0b100000 != 0
|
||||
}
|
||||
pub fn deck(&self) -> u8 {
|
||||
self.0 >> 6
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct Stack<const CAP: usize>(arrayvec::ArrayVec<Card, CAP>);
|
||||
impl<const CAP: usize> Stack<CAP> {
|
||||
pub const fn new() -> Self {
|
||||
Self(arrayvec::ArrayVec::new_const())
|
||||
}
|
||||
pub fn take_range<R: RangeBounds<usize>>(&mut self, range: R) -> Self {
|
||||
Stack::from_iter(self.drain(range))
|
||||
}
|
||||
}
|
||||
impl Stack<52> {
|
||||
/// Generate a full deck of cards with the specified deck id.
|
||||
pub fn full_deck(deck: u8) -> Self {
|
||||
let mut stack = arrayvec::ArrayVec::new();
|
||||
for suit in Suit::SUITS {
|
||||
for value in 1..=13 {
|
||||
stack.push(Card::new(deck, suit, CardValue(value)));
|
||||
}
|
||||
}
|
||||
Stack(stack)
|
||||
}
|
||||
}
|
||||
impl<const CAP: usize> From<arrayvec::ArrayVec<Card, CAP>> for Stack<CAP> {
|
||||
fn from(value: arrayvec::ArrayVec<Card, CAP>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
impl<const CAP: usize> FromIterator<Card> for Stack<CAP> {
|
||||
fn from_iter<T: IntoIterator<Item = Card>>(iter: T) -> Self {
|
||||
Self(arrayvec::ArrayVec::from_iter(iter))
|
||||
}
|
||||
}
|
||||
impl<const CAP: usize> core::ops::Deref for Stack<CAP> {
|
||||
type Target = arrayvec::ArrayVec<Card, CAP>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
impl<const CAP: usize> core::ops::DerefMut for Stack<CAP> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
impl<const CAP: usize> IntoIterator for Stack<CAP> {
|
||||
type Item = Card;
|
||||
type IntoIter = arrayvec::IntoIter<Card, CAP>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct Pile<const DN: usize, const UP: usize> {
|
||||
face_down: Stack<DN>,
|
||||
face_up: Stack<UP>,
|
||||
}
|
||||
impl<const DN: usize, const UP: usize> Pile<DN, UP> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
face_down: Stack::new(),
|
||||
face_up: Stack::new(),
|
||||
}
|
||||
}
|
||||
pub fn new_face_down(stack: Stack<DN>) -> Self {
|
||||
Self {
|
||||
face_down: stack,
|
||||
face_up: Stack::new(),
|
||||
}
|
||||
}
|
||||
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 {
|
||||
self.face_down.is_empty() && self.face_up.is_empty()
|
||||
}
|
||||
pub fn pop(&mut self) -> Option<Card> {
|
||||
self.face_up.pop()
|
||||
}
|
||||
pub fn pop_flip_up(&mut self) -> Option<Card> {
|
||||
let card = self.face_up.pop()?;
|
||||
if self.face_up.is_empty() {
|
||||
self.flip_up();
|
||||
}
|
||||
Some(card)
|
||||
}
|
||||
pub fn take_range<R: RangeBounds<usize>>(&mut self, range: R) -> Stack<UP> {
|
||||
// if self.face_up.get(range).is_none() {
|
||||
// return None;
|
||||
// }
|
||||
self.face_up.take_range(range)
|
||||
}
|
||||
pub fn take_range_flip_up<R: RangeBounds<usize>>(&mut self, range: R) -> Stack<UP> {
|
||||
let cards = self.take_range(range);
|
||||
if self.face_up.is_empty() {
|
||||
self.flip_up();
|
||||
}
|
||||
cards
|
||||
}
|
||||
pub fn push(&mut self, card: Card) {
|
||||
self.face_up.push(card);
|
||||
}
|
||||
pub fn extend<I: IntoIterator<Item = Card>>(&mut self, cards: I) {
|
||||
self.face_up.extend(cards);
|
||||
}
|
||||
pub fn face_up(&self) -> &[Card] {
|
||||
&self.face_up
|
||||
}
|
||||
pub fn face_down(&self) -> &[Card] {
|
||||
&self.face_down
|
||||
}
|
||||
}
|
||||
impl<const CAP: usize> Pile<CAP, CAP> {
|
||||
pub fn flip_it_and_reverse_it(&mut self) {
|
||||
self.swap_up_down();
|
||||
self.face_down.reverse();
|
||||
}
|
||||
pub fn swap_up_down(&mut self) {
|
||||
core::mem::swap(&mut self.face_up, &mut self.face_down);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct Session<G: Game> {
|
||||
seed: G,
|
||||
state: G,
|
||||
history: Vec<G::Instruction>,
|
||||
}
|
||||
impl<G: Game + Clone + Eq + core::hash::Hash> Session<G>
|
||||
where
|
||||
G::Instruction: Clone + Eq + core::hash::Hash,
|
||||
{
|
||||
pub fn new(state: G) -> Self {
|
||||
Self {
|
||||
seed: state.clone(),
|
||||
state,
|
||||
history: Vec::new(),
|
||||
}
|
||||
}
|
||||
pub fn state(&self) -> &G {
|
||||
&self.state
|
||||
}
|
||||
pub fn history(&self) -> &[G::Instruction] {
|
||||
&self.history
|
||||
}
|
||||
pub fn is_winnable(&self) -> Option<Vec<G::Instruction>> {
|
||||
let mut observed = std::collections::HashSet::new();
|
||||
struct StateMachine<G, P, I> {
|
||||
state: G,
|
||||
possible_instructions_iter: P,
|
||||
instruction: I,
|
||||
}
|
||||
let mut state = self.state.clone();
|
||||
let mut it = state.possible_instructions();
|
||||
let mut path = Vec::new();
|
||||
'outer: while !state.is_win() {
|
||||
observed.insert(state.clone());
|
||||
for instruction in &mut it {
|
||||
let mut next_state = state.clone();
|
||||
next_state.process_instruction(instruction.clone());
|
||||
if !observed.contains(&next_state) {
|
||||
let possible_instructions_iter =
|
||||
core::mem::replace(&mut it, next_state.possible_instructions());
|
||||
let state = core::mem::replace(&mut state, next_state);
|
||||
path.push(StateMachine {
|
||||
state,
|
||||
possible_instructions_iter,
|
||||
instruction,
|
||||
});
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
let Some(last_state) = path.pop() else {
|
||||
return None;
|
||||
};
|
||||
state = last_state.state;
|
||||
it = last_state.possible_instructions_iter;
|
||||
}
|
||||
Some(path.into_iter().map(|state| state.instruction).collect())
|
||||
}
|
||||
pub fn undo(&mut self) {
|
||||
// replay the entire history of the game except one move
|
||||
self.history.pop();
|
||||
let mut state = self.seed.clone();
|
||||
for instruction in self.history() {
|
||||
state.process_instruction(instruction.clone());
|
||||
}
|
||||
self.state = state;
|
||||
}
|
||||
}
|
||||
impl<G: Game> Game for Session<G>
|
||||
where
|
||||
G::Instruction: Clone,
|
||||
{
|
||||
type Instruction = G::Instruction;
|
||||
fn possible_instructions(&self) -> impl Iterator<Item = Self::Instruction> + use<G> {
|
||||
self.state.possible_instructions()
|
||||
}
|
||||
fn is_instruction_valid(&self, instruction: Self::Instruction) -> bool {
|
||||
self.state.is_instruction_valid(instruction)
|
||||
}
|
||||
fn process_instruction(&mut self, instruction: Self::Instruction) {
|
||||
self.history.push(instruction.clone());
|
||||
self.state.process_instruction(instruction);
|
||||
}
|
||||
fn is_win(&self) -> bool {
|
||||
self.state.is_win()
|
||||
}
|
||||
}
|
||||
+311
-9
@@ -1,12 +1,314 @@
|
||||
pub mod card_game;
|
||||
pub mod klondike;
|
||||
use core::ops::RangeBounds;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
// TODO: pub struct ValidInstruction<I>(I);
|
||||
pub trait Game {
|
||||
type Instruction;
|
||||
fn possible_instructions(&self) -> impl Iterator<Item = Self::Instruction> + use<Self>;
|
||||
fn is_instruction_valid(&self, instruction: Self::Instruction) -> bool;
|
||||
fn process_instruction(&mut self, instruction: Self::Instruction);
|
||||
fn is_win(&self) -> bool;
|
||||
}
|
||||
|
||||
pub type Rng = rand::rngs::ThreadRng;
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub enum Suit {
|
||||
Spades = 0b00,
|
||||
Hearts = 0b01,
|
||||
Clubs = 0b10,
|
||||
Diamonds = 0b11,
|
||||
}
|
||||
impl Suit {
|
||||
pub const SUITS: [Self; 4] = [Self::Spades, Self::Hearts, Self::Clubs, Self::Diamonds];
|
||||
/// Is the suit red.
|
||||
pub fn is_red(self) -> bool {
|
||||
self as u8 & 0b01 != 0
|
||||
}
|
||||
/// Is the suit shape spikey. (Bouba/kiki)
|
||||
pub fn is_kiki(self) -> bool {
|
||||
self as u8 & 0b10 != 0
|
||||
}
|
||||
}
|
||||
|
||||
// // test readme
|
||||
// #[doc = include_str!("../README.md")]
|
||||
// #[cfg(doctest)]
|
||||
// struct ReadmeDoctests;
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub struct CardValue(u8);
|
||||
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> {
|
||||
let new_value = self.0.checked_add(offset)?;
|
||||
if 13 < new_value {
|
||||
None
|
||||
} else {
|
||||
Some(CardValue(new_value))
|
||||
}
|
||||
}
|
||||
pub fn checked_sub(self, offset: u8) -> Option<CardValue> {
|
||||
let new_value = self.0.checked_sub(offset)?;
|
||||
if new_value < 1 {
|
||||
None
|
||||
} else {
|
||||
Some(CardValue(new_value))
|
||||
}
|
||||
}
|
||||
}
|
||||
/// An identifier which specifies the deck id, suit, and card value.
|
||||
/// 2 bits for deck ID
|
||||
/// 2 bits for suit ID
|
||||
/// 4 bits for card Value
|
||||
/// TODO: better encoding for slightly more decks
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct Card(u8);
|
||||
impl Card {
|
||||
pub fn new(deck: u8, suit: Suit, CardValue(value): CardValue) -> Self {
|
||||
Self(deck << 6 | (suit as u8) << 4 | value)
|
||||
}
|
||||
pub fn value(&self) -> CardValue {
|
||||
let masked = self.0 & 0b1111;
|
||||
CardValue(masked)
|
||||
}
|
||||
pub fn suit(&self) -> Suit {
|
||||
let red = self.is_red();
|
||||
let kiki = self.is_kiki();
|
||||
match (kiki, red) {
|
||||
(false, false) => Suit::Spades,
|
||||
(false, true) => Suit::Hearts,
|
||||
(true, false) => Suit::Clubs,
|
||||
(true, true) => Suit::Diamonds,
|
||||
}
|
||||
}
|
||||
/// Is the suit red.
|
||||
pub fn is_red(&self) -> bool {
|
||||
self.0 & 0b010000 != 0
|
||||
}
|
||||
/// Is the suit shape spikey. (Bouba/kiki)
|
||||
pub fn is_kiki(&self) -> bool {
|
||||
self.0 & 0b100000 != 0
|
||||
}
|
||||
pub fn deck(&self) -> u8 {
|
||||
self.0 >> 6
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct Stack<const CAP: usize>(arrayvec::ArrayVec<Card, CAP>);
|
||||
impl<const CAP: usize> Stack<CAP> {
|
||||
pub const fn new() -> Self {
|
||||
Self(arrayvec::ArrayVec::new_const())
|
||||
}
|
||||
pub fn take_range<R: RangeBounds<usize>>(&mut self, range: R) -> Self {
|
||||
Stack::from_iter(self.drain(range))
|
||||
}
|
||||
}
|
||||
impl Stack<52> {
|
||||
/// Generate a full deck of cards with the specified deck id.
|
||||
pub fn full_deck(deck: u8) -> Self {
|
||||
let mut stack = arrayvec::ArrayVec::new();
|
||||
for suit in Suit::SUITS {
|
||||
for value in 1..=13 {
|
||||
stack.push(Card::new(deck, suit, CardValue(value)));
|
||||
}
|
||||
}
|
||||
Stack(stack)
|
||||
}
|
||||
}
|
||||
impl<const CAP: usize> From<arrayvec::ArrayVec<Card, CAP>> for Stack<CAP> {
|
||||
fn from(value: arrayvec::ArrayVec<Card, CAP>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
impl<const CAP: usize> FromIterator<Card> for Stack<CAP> {
|
||||
fn from_iter<T: IntoIterator<Item = Card>>(iter: T) -> Self {
|
||||
Self(arrayvec::ArrayVec::from_iter(iter))
|
||||
}
|
||||
}
|
||||
impl<const CAP: usize> core::ops::Deref for Stack<CAP> {
|
||||
type Target = arrayvec::ArrayVec<Card, CAP>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
impl<const CAP: usize> core::ops::DerefMut for Stack<CAP> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
impl<const CAP: usize> IntoIterator for Stack<CAP> {
|
||||
type Item = Card;
|
||||
type IntoIter = arrayvec::IntoIter<Card, CAP>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct Pile<const DN: usize, const UP: usize> {
|
||||
face_down: Stack<DN>,
|
||||
face_up: Stack<UP>,
|
||||
}
|
||||
impl<const DN: usize, const UP: usize> Pile<DN, UP> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
face_down: Stack::new(),
|
||||
face_up: Stack::new(),
|
||||
}
|
||||
}
|
||||
pub fn new_face_down(stack: Stack<DN>) -> Self {
|
||||
Self {
|
||||
face_down: stack,
|
||||
face_up: Stack::new(),
|
||||
}
|
||||
}
|
||||
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 {
|
||||
self.face_down.is_empty() && self.face_up.is_empty()
|
||||
}
|
||||
pub fn pop(&mut self) -> Option<Card> {
|
||||
self.face_up.pop()
|
||||
}
|
||||
pub fn pop_flip_up(&mut self) -> Option<Card> {
|
||||
let card = self.face_up.pop()?;
|
||||
if self.face_up.is_empty() {
|
||||
self.flip_up();
|
||||
}
|
||||
Some(card)
|
||||
}
|
||||
pub fn take_range<R: RangeBounds<usize>>(&mut self, range: R) -> Stack<UP> {
|
||||
// if self.face_up.get(range).is_none() {
|
||||
// return None;
|
||||
// }
|
||||
self.face_up.take_range(range)
|
||||
}
|
||||
pub fn take_range_flip_up<R: RangeBounds<usize>>(&mut self, range: R) -> Stack<UP> {
|
||||
let cards = self.take_range(range);
|
||||
if self.face_up.is_empty() {
|
||||
self.flip_up();
|
||||
}
|
||||
cards
|
||||
}
|
||||
pub fn push(&mut self, card: Card) {
|
||||
self.face_up.push(card);
|
||||
}
|
||||
pub fn extend<I: IntoIterator<Item = Card>>(&mut self, cards: I) {
|
||||
self.face_up.extend(cards);
|
||||
}
|
||||
pub fn face_up(&self) -> &[Card] {
|
||||
&self.face_up
|
||||
}
|
||||
pub fn face_down(&self) -> &[Card] {
|
||||
&self.face_down
|
||||
}
|
||||
}
|
||||
impl<const CAP: usize> Pile<CAP, CAP> {
|
||||
pub fn flip_it_and_reverse_it(&mut self) {
|
||||
self.swap_up_down();
|
||||
self.face_down.reverse();
|
||||
}
|
||||
pub fn swap_up_down(&mut self) {
|
||||
core::mem::swap(&mut self.face_up, &mut self.face_down);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct Session<G: Game> {
|
||||
seed: G,
|
||||
state: G,
|
||||
history: Vec<G::Instruction>,
|
||||
}
|
||||
impl<G: Game + Clone + Eq + core::hash::Hash> Session<G>
|
||||
where
|
||||
G::Instruction: Clone + Eq + core::hash::Hash,
|
||||
{
|
||||
pub fn new(state: G) -> Self {
|
||||
Self {
|
||||
seed: state.clone(),
|
||||
state,
|
||||
history: Vec::new(),
|
||||
}
|
||||
}
|
||||
pub fn state(&self) -> &G {
|
||||
&self.state
|
||||
}
|
||||
pub fn history(&self) -> &[G::Instruction] {
|
||||
&self.history
|
||||
}
|
||||
pub fn is_winnable(&self) -> Option<Vec<G::Instruction>> {
|
||||
let mut observed = std::collections::HashSet::new();
|
||||
struct StateMachine<G, P, I> {
|
||||
state: G,
|
||||
possible_instructions_iter: P,
|
||||
instruction: I,
|
||||
}
|
||||
let mut state = self.state.clone();
|
||||
let mut it = state.possible_instructions();
|
||||
let mut path = Vec::new();
|
||||
'outer: while !state.is_win() {
|
||||
observed.insert(state.clone());
|
||||
for instruction in &mut it {
|
||||
let mut next_state = state.clone();
|
||||
next_state.process_instruction(instruction.clone());
|
||||
if !observed.contains(&next_state) {
|
||||
let possible_instructions_iter =
|
||||
core::mem::replace(&mut it, next_state.possible_instructions());
|
||||
let state = core::mem::replace(&mut state, next_state);
|
||||
path.push(StateMachine {
|
||||
state,
|
||||
possible_instructions_iter,
|
||||
instruction,
|
||||
});
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
let Some(last_state) = path.pop() else {
|
||||
return None;
|
||||
};
|
||||
state = last_state.state;
|
||||
it = last_state.possible_instructions_iter;
|
||||
}
|
||||
Some(path.into_iter().map(|state| state.instruction).collect())
|
||||
}
|
||||
pub fn undo(&mut self) {
|
||||
// replay the entire history of the game except one move
|
||||
self.history.pop();
|
||||
let mut state = self.seed.clone();
|
||||
for instruction in self.history() {
|
||||
state.process_instruction(instruction.clone());
|
||||
}
|
||||
self.state = state;
|
||||
}
|
||||
}
|
||||
impl<G: Game> Game for Session<G>
|
||||
where
|
||||
G::Instruction: Clone,
|
||||
{
|
||||
type Instruction = G::Instruction;
|
||||
fn possible_instructions(&self) -> impl Iterator<Item = Self::Instruction> + use<G> {
|
||||
self.state.possible_instructions()
|
||||
}
|
||||
fn is_instruction_valid(&self, instruction: Self::Instruction) -> bool {
|
||||
self.state.is_instruction_valid(instruction)
|
||||
}
|
||||
fn process_instruction(&mut self, instruction: Self::Instruction) {
|
||||
self.history.push(instruction.clone());
|
||||
self.state.process_instruction(instruction);
|
||||
}
|
||||
fn is_win(&self) -> bool {
|
||||
self.state.is_win()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,3 +5,4 @@ edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
card_game = { version = "0.1.0", path = "../card_game" }
|
||||
klondike = { version = "0.1.0", path = "../klondike" }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use card_game::card_game::{Card, Game, Pile, Session, Suit};
|
||||
use card_game::klondike::{
|
||||
use card_game::{Card, Game, Pile, Session, Suit};
|
||||
use klondike::{
|
||||
DstFoundation, DstTableau, Foundation, Klondike, KlondikeInstruction, KlondikePile,
|
||||
KlondikePileStack, SkipCards, Tableau, TableauStack,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "klondike"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
card_game = { version = "0.1.0", path = "../card_game" }
|
||||
rand = { version = "0.10.1", default-features = false, features = ["thread_rng"] }
|
||||
@@ -1,5 +1,14 @@
|
||||
use crate::Rng;
|
||||
use crate::card_game::{Card, CardValue, Game, Pile, Stack};
|
||||
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 {}
|
||||
@@ -480,8 +489,8 @@ impl Klondike {
|
||||
let mut deck = deck.into_iter();
|
||||
|
||||
// generate tableaus
|
||||
fn pile<const DN: usize>(deck: &mut arrayvec::IntoIter<Card, 52>) -> Pile<DN, 13> {
|
||||
let stack = arrayvec::ArrayVec::from_iter(deck.take(DN)).into();
|
||||
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
|
||||
@@ -495,7 +504,7 @@ impl Klondike {
|
||||
let tableau7 = pile(&mut deck);
|
||||
|
||||
// stock is remaining cards
|
||||
let stock = Pile::new_face_down(arrayvec::ArrayVec::from_iter(deck).into());
|
||||
let stock = Pile::new_face_down(Stack::from_iter(deck));
|
||||
|
||||
let state = KlondikeState {
|
||||
stock,
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::card_game::{Game, Session};
|
||||
use crate::klondike::Klondike;
|
||||
use crate::Klondike;
|
||||
use card_game::{Game, Session};
|
||||
#[test]
|
||||
fn test_is_winnable() {
|
||||
// is winnable
|
||||
Reference in New Issue
Block a user