diff --git a/simulator/src/smart_dealer.rs b/simulator/src/smart_dealer.rs index 3233866..2388b2f 100644 --- a/simulator/src/smart_dealer.rs +++ b/simulator/src/smart_dealer.rs @@ -10,108 +10,167 @@ impl Deal { pub fn deal(setup: &Setup, rng: &mut impl Rng) -> Deal { loop { if let Some(d) = Self::deal1(setup, rng) { - return d + 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 aux_slot = n_slots; 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+1]; + // 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; - let split_point = - if setup.ruleset.n_arcana == 0 { 0 } - else { rng.gen_range(0..setup.ruleset.n_arcana) }; + // 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 virtual_wells: Vec> = vec![vec![]; n_wells as usize + 2]; + let mut 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 + // 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 { - virtual_wells[s as usize].push(Card(setup.ruleset.n_cards_per_suit * s + r)); + 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 { - virtual_wells[setup.ruleset.n_suits as usize].push(Card(r+first_arcana)) + wells[arcana0].push(Card(first_arcana + r)); } for r in (split_point..setup.ruleset.n_arcana).rev() { - virtual_wells[setup.ruleset.n_suits as usize + 1].push(Card(r+first_arcana)) + 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 virtual_wells.iter().enumerate() { + for (well, contents) in wells.iter().enumerate() { for _ in 0..contents.len() { pops.push(well); } } pops.shuffle(rng); + pops + } - fn find_home(card: Card, exclude: Option, setup: &Setup, slots: &mut [Vec], aux_slot: usize, tower_height: usize, rng: &mut impl Rng) { - let mut acceptors = vec![]; - let mut not_full = vec![]; - for s in 0..slots.len() { - let not_too_tall = slots[s].len() < if s == aux_slot { 1 } else { tower_height }; - if Some(s) != exclude { - if not_too_tall && accepts(setup, slots[s].last().cloned(), card) { - acceptors.push(s) - } - if not_too_tall { - not_full.push(s); - } - } - } + 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); + } - 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) + 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 { - panic!("should not ever happen") - } - } + if accepts(setup, slots[s].last().cloned(), card) { + acceptors.push(s); + } - while let Some(w) = pops.pop() { - let card = virtual_wells[w].pop().expect("card must be present"); - find_home(card, None, setup, &mut slots, aux_slot, tower_height, rng); - - // move any card that is on an acceptor to a random slot - for _ in 0..15 { - 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, aux_slot, tower_height, rng); - } - } + if slots[s].len() < max_height[s] { + start_points.push(s); } } } - if let Some(top) = slots[n_slots].pop() { - assert!(slots[n_slots].len() == 0); - find_home(top, Some(n_slots), setup, &mut slots, aux_slot, tower_height, rng); - assert!(slots[n_slots].len() == 0); + 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 @@ -119,38 +178,39 @@ impl Deal { instantly_accepted.push(Card(first_arcana)); instantly_accepted.push(Card(first_arcana + setup.ruleset.n_arcana - 1)); - for s in 0..n_slots { - let mut iter = slots[s].iter().rev(); - let last = iter.next().cloned(); - let second_to_last = iter.next().cloned(); + 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 None + return false } let n = slots[s].len(); (slots[s][n-2],slots[s][n-1]) = (last, c); } else { - return None; + return false; // confusing nonsense situation } } } } - - - for s in 0..n_slots { - assert!(slots[s].len() == tower_height) - } - - slots.pop(); // get rid of aux slot - - return Some(Deal { slots }); + 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 {