use rand::{seq::SliceRandom, Rng}; use crate::ruleset::{self, Card, Setup}; pub struct Deal { pub slots: Vec>, } impl Deal { pub fn deal(setup: &Setup, rng: &mut impl Rng) -> Deal { 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, n)) = pops.pop() { for _ in 0..n { let card = wells[w].pop().expect("card must be present"); Self::find_home(setup, rng, card, &mut slots, &max_height, true); } } // 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, card, &mut slots, &max_height, false); } } slots.remove(0); // get rid of the auxiliary slot let mut shuffles = 0; for s in 0..slots.len() { let extra_shuf = shuffles + 1; while setup.deck.instantly_accepted.contains(slots[s].last().expect("each slot must contain cards")) { slots[s].shuffle(rng); shuffles = extra_shuf } } // TODO: Use this in the game too let max_shuffles = 2; // if setup.ruleset.n_suits < 5 { 2 } else { 1 }; for _ in shuffles..max_shuffles { slots.choose_mut(rng).unwrap().shuffle(rng); } for s in slots.iter() { assert_eq!(tower_height, s.len()); } return 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<(usize, usize)> { let mut pops = vec![]; for (well, contents) in wells.iter().enumerate() { for _ in 0..contents.len() { pops.push(well); } } pops.shuffle(rng); let mut rle = vec![]; let mut current: Option = None; let mut count = 0; while let Some(nxt) = pops.pop() { if current == Some(nxt) && count < 4 { count += 1; } else { if let Some(prev) = current { rle.push((prev, count)) } current = Some(nxt); count = 1; } } if let Some(prev) = current { if count > 0 { rle.push((prev, count)) } } rle } fn find_home(setup: &Setup, rng: &mut impl Rng, card: Card, slots: &mut [Vec], max_height: &[usize], allow_too_tall: bool) { // if a card is sitting on an acceptor, it could have been moved there // from somewhere else let mut acceptors = vec![]; for s in 0..slots.len() { if (allow_too_tall && accepts(setup, slots[s].last().cloned(), card)) || slots[s].len() < max_height[s] { acceptors.push(s); } } let acceptor = acceptors.choose(rng).cloned(); if let Some(a) = acceptor { slots[a].push(card); } else { panic!("should never happen") } } } 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; }