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 { // don't use the middle slot let n_slots = (setup.ruleset.n_slots - 1) as usize; let n_usable_cards = setup.ruleset.usable_n_cards(); let tower_height = n_usable_cards as usize / n_slots; let mut slots: Vec> = vec![vec![]; n_slots]; 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 virtual_wells: Vec> = vec![vec![]; n_wells as usize + 2]; // rely on the order of the deck for r in 1..setup.ruleset.n_cards_per_suit { // skip aces for s in 0..setup.ruleset.n_suits { virtual_wells[s as usize].push(Card(setup.ruleset.n_cards_per_suit * s + r)); } } let first_arcana = setup.ruleset.n_suits * setup.ruleset.n_cards_per_suit; for r in 0..split_point { virtual_wells[setup.ruleset.n_suits as usize].push(Card(r+first_arcana)) } for r in (split_point..setup.ruleset.n_arcana).rev() { virtual_wells[setup.ruleset.n_suits as usize + 1].push(Card(r+first_arcana)) } let mut pops = vec![]; for (well, contents) in virtual_wells.iter().enumerate() { for _ in 0..contents.len() { pops.push(well); } } pops.shuffle(rng); while let Some(w) = pops.pop() { fn find_home(card: Card, exclude: Option, setup: &Setup, slots: &mut [Vec], tower_height: usize, rng: &mut impl Rng) { let mut acceptors = vec![]; let mut not_full = vec![]; for s in 0..slots.len() { if Some(s) != exclude && slots[s].len() < tower_height { if accepts(setup, slots[s].last().cloned(), card) { acceptors.push(s) } not_full.push(s); } } acceptors.shuffle(rng); not_full.shuffle(rng); if rng.gen_bool(0.5) && acceptors.len() > 0 { let a = acceptors.first().unwrap(); slots[*a].push(card); } else if let Some(a) = not_full.first() { slots[*a].push(card); } else if let Some(e) = exclude { slots[e].push(card) } else { panic!("should not ever happen") } } let card = virtual_wells[w].pop().expect("card must be present"); find_home(card, None, setup, &mut slots, tower_height, rng); // move any card that is on an acceptor to a random slot for _ in 0..4 { let mut sources: Vec = (0..slots.len()).collect(); sources.shuffle(rng); for src in sources { let mut iter = slots[src].iter().rev(); let top = iter.next().cloned(); let second_to_top = iter.next().cloned(); if let Some(t) = top { if accepts(&setup, second_to_top, t) { slots[src].pop(); find_home(t, Some(src), setup, &mut slots, tower_height, rng); } } } } } 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)); // NOTE: We never used the free cell. The free cell can dig one deeper // So in theory most of these deals should be solvable using the freecell for s in 0..slots.len() { let mut iter = slots[s].iter().rev(); let last = iter.next().cloned(); let second_to_last = iter.next().cloned(); if let Some(last) = last { if instantly_accepted.contains(&last) { if let Some(c) = second_to_last { if instantly_accepted.contains(&c) { return None } let n = slots[s].len(); (slots[s][n-2],slots[s][n-1]) = (last, c); } else { return None; } } } } return Some(Deal { slots }); } } 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; }