Smarter smart dealer!
This commit is contained in:
parent
0b3d45378f
commit
bff92e51ab
@ -10,108 +10,167 @@ impl Deal {
|
|||||||
pub fn deal(setup: &Setup, rng: &mut impl Rng) -> Deal {
|
pub fn deal(setup: &Setup, rng: &mut impl Rng) -> Deal {
|
||||||
loop {
|
loop {
|
||||||
if let Some(d) = Self::deal1(setup, rng) {
|
if let Some(d) = Self::deal1(setup, rng) {
|
||||||
return d
|
return d;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deal1(setup: &Setup, rng: &mut impl Rng) -> Option<Deal> {
|
fn deal1(setup: &Setup, rng: &mut impl Rng) -> Option<Deal> {
|
||||||
// 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 n_usable_cards = setup.ruleset.usable_n_cards();
|
||||||
let tower_height = n_usable_cards as usize / n_slots;
|
|
||||||
|
|
||||||
let mut slots: Vec<Vec<Card>> = 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 =
|
// we have an extra slot! it's the top slot, which blocks the wells
|
||||||
if setup.ruleset.n_arcana == 0 { 0 }
|
let n_usable_slots = n_final_slots + 1;
|
||||||
else { rng.gen_range(0..setup.ruleset.n_arcana) };
|
|
||||||
|
|
||||||
|
let mut slots: Vec<Vec<Card>> = vec![vec![]; n_usable_slots];
|
||||||
|
let mut max_height: Vec<usize> = 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<Vec<Card>> {
|
||||||
|
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 n_wells = setup.ruleset.n_suits + 2;
|
||||||
|
|
||||||
let mut virtual_wells: Vec<Vec<Card>> = vec![vec![]; n_wells as usize + 2];
|
let mut wells: Vec<Vec<Card>> = vec![vec![]; n_wells as usize + 2];
|
||||||
|
|
||||||
// rely on the order of the deck
|
// fill the wells in suit order, relying on the order of the deck object
|
||||||
for r in 1..setup.ruleset.n_cards_per_suit { // skip aces
|
for r in 1..setup.ruleset.n_cards_per_suit {
|
||||||
for s in 0..setup.ruleset.n_suits {
|
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 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 {
|
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() {
|
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<Card>], rng: &mut impl Rng) -> Vec<usize> {
|
||||||
let mut pops = 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() {
|
for _ in 0..contents.len() {
|
||||||
pops.push(well);
|
pops.push(well);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pops.shuffle(rng);
|
pops.shuffle(rng);
|
||||||
|
pops
|
||||||
|
}
|
||||||
|
|
||||||
fn find_home(card: Card, exclude: Option<usize>, setup: &Setup, slots: &mut [Vec<Card>], aux_slot: usize, tower_height: usize, rng: &mut impl Rng) {
|
fn peek2(stack: &Vec<Card>) -> (Option<Card>, Option<Card>) {
|
||||||
let mut acceptors = vec![];
|
let mut iter = stack.iter().rev();
|
||||||
let mut not_full = vec![];
|
let top = iter.next().cloned();
|
||||||
for s in 0..slots.len() {
|
let second_to_top = iter.next().cloned();
|
||||||
let not_too_tall = slots[s].len() < if s == aux_slot { 1 } else { tower_height };
|
return (second_to_top, top);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
acceptors.shuffle(rng);
|
fn find_home(setup: &Setup, rng: &mut impl Rng, acceptor_odds: f64, card: Card, source: Option<usize>, exclude: Option<usize>, slots: &mut [Vec<Card>], max_height: &[usize]) {
|
||||||
not_full.shuffle(rng);
|
// if a card is sitting on an acceptor, it could have been moved there
|
||||||
if rng.gen_bool(0.5) && acceptors.len() > 0 {
|
// from somewhere else
|
||||||
let a = acceptors.first().unwrap();
|
let mut acceptors = vec![];
|
||||||
slots[*a].push(card);
|
|
||||||
} else if let Some(a) = not_full.first() {
|
// if a card is sitting on a start point, it could not have been moved
|
||||||
slots[*a].push(card);
|
// there from somewhere else
|
||||||
} else if let Some(e) = exclude {
|
let mut start_points = vec![];
|
||||||
slots[e].push(card)
|
|
||||||
|
for s in 0..slots.len() {
|
||||||
|
if Some(s) == exclude {
|
||||||
|
// don't place it here, ever
|
||||||
} else {
|
} else {
|
||||||
panic!("should not ever happen")
|
if accepts(setup, slots[s].last().cloned(), card) {
|
||||||
}
|
acceptors.push(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
while let Some(w) = pops.pop() {
|
if slots[s].len() < max_height[s] {
|
||||||
let card = virtual_wells[w].pop().expect("card must be present");
|
start_points.push(s);
|
||||||
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<usize> = (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 let Some(top) = slots[n_slots].pop() {
|
let mut acceptor = acceptors.choose(rng).cloned();
|
||||||
assert!(slots[n_slots].len() == 0);
|
let start_point = start_points.choose(rng).cloned();
|
||||||
find_home(top, Some(n_slots), setup, &mut slots, aux_slot, tower_height, rng);
|
if !rng.gen_bool(acceptor_odds) {
|
||||||
assert!(slots[n_slots].len() == 0);
|
// 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<Card>]) -> bool {
|
||||||
|
let first_arcana = setup.ruleset.n_suits * setup.ruleset.n_cards_per_suit;
|
||||||
let mut instantly_accepted = vec![];
|
let mut instantly_accepted = vec![];
|
||||||
for &a in &setup.deck.aces {
|
for &a in &setup.deck.aces {
|
||||||
instantly_accepted.push(Card(a.0 + 1)) // twos
|
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));
|
||||||
instantly_accepted.push(Card(first_arcana + setup.ruleset.n_arcana - 1));
|
instantly_accepted.push(Card(first_arcana + setup.ruleset.n_arcana - 1));
|
||||||
|
|
||||||
for s in 0..n_slots {
|
for s in 0..slots.len() {
|
||||||
let mut iter = slots[s].iter().rev();
|
let (second_to_last, last) = Self::peek2(&slots[s]);
|
||||||
let last = iter.next().cloned();
|
|
||||||
let second_to_last = iter.next().cloned();
|
|
||||||
|
|
||||||
if let Some(last) = last {
|
if let Some(last) = last {
|
||||||
if instantly_accepted.contains(&last) {
|
if instantly_accepted.contains(&last) {
|
||||||
if let Some(c) = second_to_last {
|
if let Some(c) = second_to_last {
|
||||||
if instantly_accepted.contains(&c) {
|
if instantly_accepted.contains(&c) {
|
||||||
return None
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
let n = slots[s].len();
|
let n = slots[s].len();
|
||||||
(slots[s][n-2],slots[s][n-1]) = (last, c);
|
(slots[s][n-2],slots[s][n-1]) = (last, c);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return None;
|
return false; // confusing nonsense situation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
for s in 0..n_slots {
|
|
||||||
assert!(slots[s].len() == tower_height)
|
|
||||||
}
|
|
||||||
|
|
||||||
slots.pop(); // get rid of aux slot
|
|
||||||
|
|
||||||
return Some(Deal { slots });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pop_accepted_card(setup: &Setup, src: usize, slots: &mut [Vec<Card>]) -> Option<Card> {
|
||||||
|
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<Card>, next: Card) -> bool {
|
fn accepts(setup: &Setup, prev: Option<Card>, next: Card) -> bool {
|
||||||
|
Loading…
Reference in New Issue
Block a user