Files
card_game/card_game/src/lib.rs
T

606 lines
15 KiB
Rust

// test readme
#[doc = include_str!("../README.md")]
#[cfg(doctest)]
struct ReadmeDoctests;
use core::ops::RangeBounds;
// TODO: pub struct ValidInstruction<I>(I);
pub trait Game: Clone {
type Score: Clone + core::fmt::Debug;
type Stats: Clone + core::fmt::Debug;
type Config: Clone + core::fmt::Debug;
type Instruction: Clone + core::fmt::Debug;
fn score(&self, stats: &Self::Stats, config: &Self::Config) -> Self::Score;
fn possible_instructions(
&self,
config: &Self::Config,
) -> impl Iterator<Item = Self::Instruction> + use<Self>;
fn is_instruction_valid(&self, config: &Self::Config, instruction: Self::Instruction) -> bool;
fn process_instruction(
&mut self,
stats: &mut Self::Stats,
config: &Self::Config,
instruction: Self::Instruction,
);
fn is_win(&self) -> bool;
}
/// card_game supports up to 4 identifiably separate decks.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Deck {
Deck1 = 0b00,
Deck2 = 0b01,
Deck3 = 0b10,
Deck4 = 0b11,
}
impl Deck {
pub const fn new(deck: u8) -> Option<Self> {
use Deck::*;
Some(match deck {
0b00 => Deck1,
0b01 => Deck2,
0b10 => Deck3,
0b11 => Deck4,
_ => return None,
})
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
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];
pub const fn new(suit: u8) -> Option<Self> {
use Suit::*;
Some(match suit {
0b00 => Spades,
0b01 => Hearts,
0b10 => Clubs,
0b11 => Diamonds,
_ => return None,
})
}
/// Is the suit red.
pub const fn is_red(self) -> bool {
self as u8 & 0b01 != 0
}
/// Is the suit black.
pub const fn is_black(self) -> bool {
!self.is_red()
}
/// Suit value is 2 bits, is_red is the low bit.
pub const fn suit_high_bit(self) -> bool {
self as u8 & 0b10 != 0
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Rank {
Ace = 1,
Two = 2,
Three = 3,
Four = 4,
Five = 5,
Six = 6,
Seven = 7,
Eight = 8,
Nine = 9,
Ten = 10,
Jack = 11,
Queen = 12,
King = 13,
}
impl Rank {
pub const RANKS: [Self; 13] = [
Self::Ace,
Self::Two,
Self::Three,
Self::Four,
Self::Five,
Self::Six,
Self::Seven,
Self::Eight,
Self::Nine,
Self::Ten,
Self::Jack,
Self::Queen,
Self::King,
];
pub const fn new(rank: u8) -> Option<Self> {
use Rank::*;
Some(match rank {
1 => Ace,
2 => Two,
3 => Three,
4 => Four,
5 => Five,
6 => Six,
7 => Seven,
8 => Eight,
9 => Nine,
10 => Ten,
11 => Jack,
12 => Queen,
13 => King,
_ => return None,
})
}
/// Numeric value: Ace = 1, King = 13.
pub const fn value(self) -> u8 {
self as u8
}
pub const fn checked_add(self, offset: u8) -> Option<Rank> {
match (self as u8).checked_add(offset) {
Some(rank) => Self::new(rank),
None => None,
}
}
pub const fn checked_sub(self, offset: u8) -> Option<Rank> {
match (self as u8).checked_sub(offset) {
Some(rank) => Self::new(rank),
None => None,
}
}
}
/// A card which specifies the deck id, suit, and card value.
/// 2 bits for deck ID
/// 2 bits for suit ID
/// 4 bits for card Value
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct Card(core::num::NonZeroU8);
impl Card {
pub const fn new(deck: Deck, suit: Suit, rank: Rank) -> Self {
let packed = (deck as u8) << 6 | (suit as u8) << 4 | (rank as u8);
Self(core::num::NonZeroU8::new(packed).unwrap())
}
pub const fn rank(&self) -> Rank {
let masked = self.0.get() & 0b1111;
Rank::new(masked).unwrap()
}
pub const fn suit(&self) -> Suit {
let low_bit = self.is_red();
let high_bit = self.suit_high_bit();
match (high_bit, low_bit) {
(false, false) => Suit::Spades,
(false, true) => Suit::Hearts,
(true, false) => Suit::Clubs,
(true, true) => Suit::Diamonds,
}
}
/// Is the suit red.
pub const fn is_red(&self) -> bool {
self.0.get() & 0b010000 != 0
}
/// Suit value is 2 bits, is_red is the low bit.
pub const fn suit_high_bit(&self) -> bool {
self.0.get() & 0b100000 != 0
}
pub const fn deck(&self) -> Deck {
Deck::new(self.0.get() >> 6).unwrap()
}
}
#[derive(Clone, Debug, Default, 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: Deck) -> Self {
let mut stack = arrayvec::ArrayVec::new();
for suit in Suit::SUITS {
for rank in Rank::RANKS {
stack.push(Card::new(deck, suit, rank));
}
}
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()
}
}
/// A pile is a stack of face down cards and a stack of face up cards.
#[derive(Clone, Debug, Default, 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 const fn new() -> Self {
Self {
face_down: Stack::new(),
face_up: Stack::new(),
}
}
pub const fn new_face_down(stack: Stack<DN>) -> Self {
Self {
face_down: stack,
face_up: Stack::new(),
}
}
/// Returns whether a card was flipped up.
pub fn flip_up(&mut self) -> bool {
if let Some(card) = self.face_down.pop() {
self.face_up.push(card);
return true;
}
false
}
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()
}
/// Returns the popped card and whether a card was flipped up.
pub fn pop_flip_up(&mut self) -> (Option<Card>, bool) {
let card = match self.face_up.pop() {
Some(card) => card,
None => return (None, false),
};
let did_flip_up = if self.face_up.is_empty() {
self.flip_up()
} else {
false
};
(Some(card), did_flip_up)
}
pub fn take_range<R: RangeBounds<usize>>(&mut self, range: R) -> Stack<UP> {
self.face_up.take_range(range)
}
/// Returns the card range and whether a card was flipped up.
pub fn take_range_flip_up<R: RangeBounds<usize>>(&mut self, range: R) -> (Stack<UP>, bool) {
let cards = self.take_range(range);
let did_flip_up = if self.face_up.is_empty() {
self.flip_up()
} else {
false
};
(cards, did_flip_up)
}
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 const fn swap_up_down(&mut self) {
core::mem::swap(&mut self.face_up, &mut self.face_down);
}
}
#[derive(Clone, Debug)]
pub enum SolveError {
MovesBudgetExceeded,
StatesBudgetExceeded,
}
impl core::fmt::Display for SolveError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{self:?}")
}
}
impl core::error::Error for SolveError {}
/// The solution tends to be very large with long chains of moves that go back to the same state.
/// It is recommended to call .clean_solution() if the solution is actually going to be shown to a user.
pub struct Solution<G: Game> {
solution: Vec<StateSnapshot<G>>,
}
impl<G: Game + Eq + core::hash::Hash> Solution<G> {
pub const fn raw_solution(&self) -> &[StateSnapshot<G>] {
self.solution.as_slice()
}
/// Repeatedly remove the largest range of moves that goes back into the same state.
/// This is a very expensive operation when the solution is very long!
pub fn clean_solution(self) -> Vec<StateSnapshot<G>> {
let mut history = self.solution;
// history includes cycles
let mut state_index: std::collections::HashMap<_, _> = history
.iter()
.enumerate()
.map(|(i, snapshot)| (snapshot.state().clone(), i))
.collect();
// find the longest range where the start and end are the same state
while let Some(longest_range) = history
.iter()
.enumerate()
.filter_map(|(index, snapshot)| {
let &last_index = state_index.get(snapshot.state())?;
let longness = last_index - index;
(longness != 0).then_some(index..last_index)
})
.max_by_key(|range| range.len())
{
history.drain(longest_range);
for (i, snapshot) in history.iter().enumerate() {
state_index.insert(snapshot.state().clone(), i);
}
}
history
}
}
#[derive(Clone, Debug)]
pub enum SessionInstruction<I> {
Undo,
InnerInstruction(I),
}
#[derive(Clone, Debug, Default)]
pub struct SessionStats<S> {
inner: S,
undos: u32,
}
impl<S> SessionStats<S> {
pub const fn stats(&self) -> &S {
&self.inner
}
const fn increment_undos(&mut self) {
self.undos += 1;
}
pub const fn undos(&self) -> u32 {
self.undos
}
}
#[derive(Clone, Debug)]
pub struct SessionConfig<C> {
pub inner: C,
pub undo_penalty: i32,
pub solve_moves_budget: u64,
pub solve_states_budget: u64,
}
impl<C> SessionConfig<C> {
fn new_default(inner: C) -> Self {
Self {
inner,
undo_penalty: -15,
solve_moves_budget: 100_000,
solve_states_budget: 100_000,
}
}
}
impl<C: Default> Default for SessionConfig<C> {
fn default() -> Self {
Self::new_default(C::default())
}
}
#[derive(Clone, Debug)]
pub struct Session<G: Game> {
stats: SessionStats<G::Stats>,
config: SessionConfig<G::Config>,
state: SessionState<G>,
}
#[derive(Clone, Debug)]
pub struct StateSnapshot<G: Game> {
state: G,
instruction: G::Instruction,
}
impl<G: Game> StateSnapshot<G> {
pub const fn state(&self) -> &G {
&self.state
}
pub const fn instruction(&self) -> &G::Instruction {
&self.instruction
}
}
#[derive(Clone, Debug)]
pub struct SessionState<G: Game> {
state: G,
history: Vec<StateSnapshot<G>>,
}
impl<G: Game + Clone> SessionState<G> {
fn new(state: G) -> Self {
Self {
state,
history: Vec::new(),
}
}
}
impl<G: Game> SessionState<G> {
pub const fn state(&self) -> &G {
&self.state
}
}
impl<G: Game<Score = i32>> Session<G>
where
G: Eq + core::hash::Hash,
G::Stats: Default,
G::Instruction: Eq + core::hash::Hash,
{
pub fn new(state: G, config: SessionConfig<G::Config>) -> Self {
Self {
stats: SessionStats::default(),
config,
state: SessionState::new(state),
}
}
pub fn new_default(state: G) -> Self
where
G::Config: Default,
{
Self::new(state, Default::default())
}
pub const fn stats(&self) -> &SessionStats<G::Stats> {
&self.stats
}
pub const fn state(&self) -> &SessionState<G> {
&self.state
}
pub const fn config(&self) -> &SessionConfig<G::Config> {
&self.config
}
pub fn history(&self) -> &[StateSnapshot<G>] {
&self.state.history
}
pub fn undo(&mut self) {
self.state
.process_instruction(&mut self.stats, &self.config, SessionInstruction::Undo)
}
pub fn possible_instructions(&self) -> impl Iterator<Item = G::Instruction> + use<G> {
self.state.state.possible_instructions(&self.config.inner)
}
pub fn process_instruction(&mut self, instruction: G::Instruction) {
self.state.process_instruction(
&mut self.stats,
&self.config,
SessionInstruction::InnerInstruction(instruction),
)
}
pub fn is_win(&self) -> bool {
self.state.is_win()
}
/// Attempt to produce a solution.
pub fn solve(&self) -> Result<Option<Solution<G>>, SolveError> {
let mut state_moves = std::collections::HashMap::new();
let mut session = self.clone();
let mut moves = 0;
while !session.is_win() {
moves += 1;
if self.config.solve_moves_budget < moves {
return Err(SolveError::MovesBudgetExceeded);
}
if self.config.solve_states_budget < state_moves.len() as u64 {
return Err(SolveError::StatesBudgetExceeded);
}
// Continue existing iterator if it exists
let it = state_moves
.entry(session.state().state().clone())
.or_insert_with(|| {
session
.state()
.state()
.possible_instructions(&self.config().inner)
});
// Run one possible move
if let Some(instruction) = it.next() {
session.process_instruction(instruction);
continue;
}
// No more moves. If we can't undo we're done
if session.history().is_empty() {
return Ok(None);
} else {
session.undo();
}
}
Ok(Some(Solution {
solution: session.state.history,
}))
}
}
impl<G: Game<Score = i32>> Game for SessionState<G>
where
G::Stats: Default,
{
type Score = G::Score;
type Stats = SessionStats<G::Stats>;
type Config = SessionConfig<G::Config>;
type Instruction = SessionInstruction<G::Instruction>;
fn score(&self, stats: &Self::Stats, config: &Self::Config) -> Self::Score {
self.state.score(&stats.inner, &config.inner) + stats.undos as i32 * config.undo_penalty
}
fn possible_instructions(
&self,
config: &Self::Config,
) -> impl Iterator<Item = Self::Instruction> + use<G> {
self.state
.possible_instructions(&config.inner)
.map(SessionInstruction::InnerInstruction)
}
fn is_instruction_valid(&self, config: &Self::Config, instruction: Self::Instruction) -> bool {
match instruction {
SessionInstruction::Undo => !self.history.is_empty(),
SessionInstruction::InnerInstruction(instruction) => {
self.state.is_instruction_valid(&config.inner, instruction)
}
}
}
fn process_instruction(
&mut self,
stats: &mut Self::Stats,
config: &Self::Config,
instruction: Self::Instruction,
) {
match instruction {
SessionInstruction::Undo => {
if let Some(snapshot) = self.history.pop() {
self.state = snapshot.state;
stats.increment_undos();
}
}
SessionInstruction::InnerInstruction(instruction) => {
self.history.push(StateSnapshot {
state: self.state.clone(),
instruction: instruction.clone(),
});
self.state
.process_instruction(&mut stats.inner, &config.inner, instruction);
}
}
}
fn is_win(&self) -> bool {
self.state.is_win()
}
}