use crate::basic::types::permutation::Permutation;
use crate::common::errors::CardError;
use crate::seal::slot::SlotId;
use alloc::collections::BTreeSet;
use alloc::vec::Vec;
use rand::Rng;
use rand::SeedableRng;
use rand::rngs::StdRng;
use rand::seq::SliceRandom;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(try_from = "Vec<SlotId>"))]
pub struct SlotPile(Vec<SlotId>);
impl TryFrom<Vec<SlotId>> for SlotPile {
type Error = CardError;
fn try_from(slots: Vec<SlotId>) -> Result<Self, CardError> {
Self::from_slots(slots)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SlotAudit {
pub expected: usize,
pub actual: usize,
pub duplicate_slots: Vec<SlotId>,
}
impl SlotAudit {
#[must_use]
pub fn is_ok(&self) -> bool {
self.expected == self.actual && self.duplicate_slots.is_empty()
}
}
impl SlotPile {
#[must_use]
pub fn new(n: u16) -> Self {
Self((0..n).map(SlotId::new).collect())
}
pub fn from_slots(slots: Vec<SlotId>) -> Result<Self, CardError> {
let mut seen = BTreeSet::new();
for slot in &slots {
if !seen.insert(*slot) {
return Err(CardError::DuplicateSlot(slot.get()));
}
}
Ok(Self(slots))
}
#[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[must_use]
pub fn slots(&self) -> &[SlotId] {
&self.0
}
#[must_use]
pub fn contains(&self, slot: SlotId) -> bool {
self.0.contains(&slot)
}
#[must_use]
pub fn position(&self, slot: SlotId) -> Option<usize> {
self.0.iter().position(|s| *s == slot)
}
pub fn take(&mut self, slot: SlotId) -> Option<SlotId> {
let i = self.position(slot)?;
Some(self.0.remove(i))
}
pub fn draw_first(&mut self) -> Option<SlotId> {
if self.0.is_empty() {
None
} else {
Some(self.0.remove(0))
}
}
pub fn draw(&mut self, n: usize) -> Option<Self> {
if n > self.0.len() {
return None;
}
let rest = self.0.split_off(n);
Some(Self(core::mem::replace(&mut self.0, rest)))
}
pub fn shuffle_with_rng<R: Rng + ?Sized>(&mut self, rng: &mut R) {
self.0.shuffle(rng);
}
pub fn shuffle_with_seed(&mut self, seed: u64) {
self.shuffle_with_rng(&mut StdRng::seed_from_u64(seed));
}
pub fn permute(&self, p: &Permutation) -> Result<Self, CardError> {
Ok(Self(p.apply(&self.0)?))
}
pub fn cut(&mut self, at: usize) -> Result<(), CardError> {
let p = Permutation::rotation(self.len(), at)?;
self.0 = p.apply(&self.0)?;
Ok(())
}
#[must_use]
pub fn audit(&self, expected: usize) -> SlotAudit {
let mut seen = BTreeSet::new();
let mut duplicate_slots = Vec::new();
for slot in &self.0 {
if !seen.insert(*slot) && !duplicate_slots.contains(slot) {
duplicate_slots.push(*slot);
}
}
SlotAudit {
expected,
actual: self.0.len(),
duplicate_slots,
}
}
}
#[cfg(test)]
#[allow(non_snake_case)]
mod seal__slot_pile_tests {
use super::*;
use crate::prelude::*;
use alloc::collections::BTreeSet;
use alloc::vec::Vec;
fn ids(v: &[u16]) -> Vec<SlotId> {
v.iter().map(|&i| SlotId::new(i)).collect()
}
#[test]
fn slot_pile__new_is_identity_order() {
let p = SlotPile::new(5);
assert_eq!(p.len(), 5);
assert!(!p.is_empty());
assert_eq!(p.slots(), &ids(&[0, 1, 2, 3, 4])[..]);
assert!(SlotPile::new(0).is_empty());
assert_eq!(SlotPile::default(), SlotPile::new(0));
}
#[test]
fn slot_pile__from_slots_rejects_duplicates() {
assert_eq!(
SlotPile::from_slots(ids(&[0, 1, 1])),
Err(CardError::DuplicateSlot(1))
);
let p = SlotPile::from_slots(ids(&[2, 0])).unwrap();
assert_eq!(p.slots(), &ids(&[2, 0])[..], "order is kept");
}
#[test]
fn slot_pile__contains_and_position() {
let p = SlotPile::from_slots(ids(&[7, 3, 9])).unwrap();
assert!(p.contains(SlotId::new(3)));
assert!(!p.contains(SlotId::new(4)));
assert_eq!(p.position(SlotId::new(9)), Some(2));
assert_eq!(p.position(SlotId::new(4)), None);
}
#[test]
fn slot_pile__take_by_name() {
let mut p = SlotPile::new(5);
assert_eq!(p.take(SlotId::new(2)), Some(SlotId::new(2)));
assert_eq!(p.slots(), &ids(&[0, 1, 3, 4])[..]);
assert!(!p.contains(SlotId::new(2)));
assert_eq!(p.take(SlotId::new(2)), None);
}
#[test]
fn slot_pile__draw_first() {
let mut p = SlotPile::from_slots(ids(&[4, 1])).unwrap();
assert_eq!(p.draw_first(), Some(SlotId::new(4)));
assert_eq!(p.draw_first(), Some(SlotId::new(1)));
assert_eq!(p.draw_first(), None);
}
#[test]
fn slot_pile__draw_all_or_nothing() {
let mut p = SlotPile::new(5);
let hand = p.draw(3).unwrap();
assert_eq!(hand.slots(), &ids(&[0, 1, 2])[..]);
assert_eq!(p.slots(), &ids(&[3, 4])[..]);
assert_eq!(p.draw(3), None);
assert_eq!(
p.slots(),
&ids(&[3, 4])[..],
"a rejected draw changes nothing"
);
}
#[test]
fn slot_pile__rejected_ops_change_nothing() {
let before = SlotPile::new(6);
let mut after = before.clone();
assert_eq!(after.draw(7), None);
assert_eq!(after.cut(7), Err(CardError::InvalidCut(7)));
assert!(after.permute(&Permutation::identity(5).unwrap()).is_err());
assert_eq!(before, after);
}
#[test]
fn slot_pile__shuffle_permutes_slot_set() {
let mut p = SlotPile::new(52);
let before: BTreeSet<SlotId> = p.slots().iter().copied().collect();
p.shuffle_with_seed(11);
let after: BTreeSet<SlotId> = p.slots().iter().copied().collect();
assert_eq!(before, after);
assert_ne!(p, SlotPile::new(52));
let mut q = SlotPile::new(52);
q.shuffle_with_seed(11);
assert_eq!(p, q, "deterministic for a seed");
}
#[test]
fn slot_pile__shuffle_agrees_with_pile_shuffle_for_same_rng() {
let deck = Standard52::deck();
for seed in [0_u64, 5, 99] {
let mut slots = SlotPile::new(52);
slots.shuffle_with_seed(seed);
let shuffled = deck.shuffled_with_seed(seed);
for (i, slot) in slots.slots().iter().enumerate() {
assert_eq!(
shuffled.cards()[i],
deck.cards()[slot.index()],
"seed {seed}"
);
}
}
}
#[test]
fn slot_pile__permute_and_cut() {
let p = SlotPile::new(5);
let rot = Permutation::rotation(5, 2).unwrap();
assert_eq!(p.permute(&rot).unwrap().slots(), &ids(&[2, 3, 4, 0, 1])[..]);
let mut c = p.clone();
c.cut(2).unwrap();
assert_eq!(c, p.permute(&rot).unwrap());
assert_eq!(
p.permute(&Permutation::identity(4).unwrap()),
Err(CardError::PermutationLength {
expected: 4,
actual: 5
})
);
}
#[test]
fn slot_pile__audit_counts_and_finds_duplicates() {
let ok = SlotPile::new(5).audit(5);
assert!(ok.is_ok());
assert_eq!(ok.expected, 5);
assert_eq!(ok.actual, 5);
assert!(ok.duplicate_slots.is_empty());
let short = SlotPile::new(4).audit(5);
assert!(!short.is_ok());
let dup = SlotPile(ids(&[1, 2, 1, 3, 3]));
let a = dup.audit(5);
assert!(!a.is_ok());
assert_eq!(a.duplicate_slots, ids(&[1, 3]));
}
#[cfg(feature = "serde")]
#[test]
fn slot_pile__serde_roundtrip_and_rejects_duplicates() {
let p = SlotPile::from_slots(ids(&[3, 1, 2])).unwrap();
let json = serde_json::to_string(&p).unwrap();
assert_eq!(serde_json::from_str::<SlotPile>(&json).unwrap(), p);
assert!(serde_json::from_str::<SlotPile>("[1,1]").is_err());
}
}