fortunes_foundation/simulator/src/smart_dealer.rs

147 lines
5.3 KiB
Rust
Raw Normal View History

use rand::{seq::SliceRandom, Rng};
use crate::ruleset::{Card, Setup};
pub struct Deal {
pub slots: Vec<Vec<Card>>,
}
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<Deal> {
// 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<Card>> = 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<Card>> = 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<usize>, setup: &Setup, slots: &mut [Vec<Card>], 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<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, 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<Card>, 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;
}