use rand::{seq::SliceRandom, Rng}; use crate::ruleset::{Card, Setup}; pub struct Deal { pub slots: Vec>, } impl Deal { pub fn deal(setup: &Setup, rng: &mut impl Rng) -> Deal { loop { if let Some(d) = Self::deal1(setup, rng) { return d; } } } fn deal1(setup: &Setup, rng: &mut impl Rng) -> Option { let n_usable_cards = setup.ruleset.usable_n_cards(); // there are n - 1 final slots, because we don't use the middle one let n_final_slots = (setup.ruleset.n_slots - 1) as usize; // we have an extra slot! it's the top slot, which blocks the wells let n_usable_slots = n_final_slots + 1; let mut slots: Vec> = vec![vec![]; n_usable_slots]; let mut max_height: Vec = vec![0; n_usable_slots]; let tower_height = n_usable_cards as usize / n_final_slots; for i in 1..=n_final_slots { max_height[i] = tower_height; } max_height[0] = 1; let mut wells = Self::generate_wells(setup, rng); let mut pops = Self::plan_pops(&wells, rng); while let Some(w) = pops.pop() { let is_arcana = w >= setup.ruleset.n_suits as usize; let mut exclude = None; if !is_arcana { // we can't put a card in the well if it's blocked, so make sure it's unblocked if let Some(c) = slots[0].pop() { Self::find_home(setup, rng, 1.0, c, None, Some(0), &mut slots, &max_height); } exclude = Some(0); // don't place the new card in the auxiliary slot } let card = wells[w].pop().expect("card must be present"); Self::find_home(setup, rng, 1.0, card, None, exclude, &mut slots, &max_height); // do some moves from acceptors to random slots const MAX_I: i32 = 48; for i in 0..MAX_I+1 { let src = rng.gen_range(0..slots.len()); if let Some(accepted_card) = Self::pop_accepted_card(setup, src, &mut slots) { Self::find_home(setup, rng, ((MAX_I - i) as f64)/(MAX_I as f64), accepted_card, Some(src), None, &mut slots, &max_height); } } } // are any stacks too tall? fix them max_height[0] = 0; // auxiliary slot must be empty for i in 0..slots.len() { while slots[i].len() > max_height[i] { let card = slots[i].pop().expect("must be a card"); Self::find_home(setup, rng, 0.0, card, None, None, &mut slots, &max_height); } } slots.remove(0); // get rid of the auxiliary slot if !Self::fix_instantly_accepted(setup, &mut slots) { return None; } for s in slots.iter() { assert_eq!(tower_height, s.len()); } return Some(Deal { slots } ) } fn generate_wells(setup: &Setup, rng: &mut impl Rng) -> Vec> { let split_point = if setup.ruleset.n_arcana == 0 { 0 } else { rng.gen_range(0..=setup.ruleset.n_arcana) }; let n_wells = setup.ruleset.n_suits + 2; let mut wells: Vec> = vec![vec![]; n_wells as usize + 2]; // fill the wells in suit order, relying on the order of the deck object for r in 1..setup.ruleset.n_cards_per_suit { for s in 0..setup.ruleset.n_suits { wells[s as usize].push(Card(setup.ruleset.n_cards_per_suit * s + r)) } } // now fill the wells for arcana let first_arcana = setup.ruleset.n_suits * setup.ruleset.n_cards_per_suit; let arcana0 = setup.ruleset.n_suits as usize; let arcana1 = arcana0 + 1; for r in 0..split_point { wells[arcana0].push(Card(first_arcana + r)); } for r in (split_point..setup.ruleset.n_arcana).rev() { wells[arcana1].push(Card(first_arcana + r)); } wells } fn plan_pops(wells: &[Vec], rng: &mut impl Rng) -> Vec { let mut pops = vec![]; for (well, contents) in wells.iter().enumerate() { for _ in 0..contents.len() { pops.push(well); } } pops.shuffle(rng); pops } fn peek2(stack: &Vec) -> (Option, Option) { let mut iter = stack.iter().rev(); let top = iter.next().cloned(); let second_to_top = iter.next().cloned(); return (second_to_top, top); } fn find_home(setup: &Setup, rng: &mut impl Rng, acceptor_odds: f64, card: Card, source: Option, exclude: Option, slots: &mut [Vec], max_height: &[usize]) { // if a card is sitting on an acceptor, it could have been moved there // from somewhere else let mut acceptors = vec![]; // if a card is sitting on a start point, it could not have been moved // there from somewhere else let mut start_points = vec![]; for s in 0..slots.len() { if Some(s) == exclude { // don't place it here, ever } else { if accepts(setup, slots[s].last().cloned(), card) { acceptors.push(s); } if slots[s].len() < max_height[s] { start_points.push(s); } } } let mut acceptor = acceptors.choose(rng).cloned(); let start_point = start_points.choose(rng).cloned(); if !rng.gen_bool(acceptor_odds) { // don't use an acceptor even though we could acceptor = None; } if let Some(a) = acceptor.or(start_point).or(source) { slots[a].push(card); } else { panic!("should not ever happen"); } } fn fix_instantly_accepted(setup: &Setup, slots: &mut [Vec]) -> bool { let first_arcana = setup.ruleset.n_suits * setup.ruleset.n_cards_per_suit; let mut instantly_accepted = vec![]; for &a in &setup.deck.aces { instantly_accepted.push(Card(a.0 + 1)) // twos } instantly_accepted.push(Card(first_arcana)); instantly_accepted.push(Card(first_arcana + setup.ruleset.n_arcana - 1)); for s in 0..slots.len() { let (second_to_last, last) = Self::peek2(&slots[s]); if let Some(last) = last { if instantly_accepted.contains(&last) { if let Some(c) = second_to_last { if instantly_accepted.contains(&c) { return false } let n = slots[s].len(); (slots[s][n-2],slots[s][n-1]) = (last, c); } else { return false; // confusing nonsense situation } } } } return true; } fn pop_accepted_card(setup: &Setup, src: usize, slots: &mut [Vec]) -> Option { let (card0, card1) = Self::peek2(&slots[src]); if let Some(c1) = card1 { if accepts(&setup, card0, c1) { slots[src].pop().expect("we just peeked at this"); return Some(c1); } } None } } fn accepts(setup: &Setup, prev: Option, next: Card) -> bool { let prev = prev.map(|p| setup.deck.cards[p.0 as usize]); let next = setup.deck.cards[next.0 as usize]; if let Some(p) = prev { return p.suit == next.suit && (p.rank + 1 == next.rank || p.rank == next.rank + 1); } return true; }