fortunes_foundation/simulator/src/smart_dealer.rs

213 lines
7.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 {
2024-02-08 04:05:00 +00:00
let mut tries = 0;
loop {
if let Some(d) = Self::deal1(setup, rng) {
2024-02-08 04:05:00 +00:00
println!("Generated ({} tries)", tries);
2024-02-08 03:36:42 +00:00
return d;
}
2024-02-08 04:05:00 +00:00
tries += 1;
}
}
2024-02-08 03:36:42 +00:00
fn deal1(setup: &Setup, rng: &mut impl Rng) -> Option<Deal> {
let n_usable_cards = setup.ruleset.usable_n_cards();
2024-02-08 03:36:42 +00:00
// 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<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() {
2024-02-08 04:05:00 +00:00
if !Self::find_home(setup, rng, 1.0, c, None, Some(0), &mut slots, &max_height) {
return None
}
2024-02-08 03:36:42 +00:00
}
exclude = Some(0); // don't place the new card in the auxiliary slot
}
let card = wells[w].pop().expect("card must be present");
2024-02-08 04:05:00 +00:00
if !Self::find_home(setup, rng, 1.0, card, None, exclude, &mut slots, &max_height) {
return None;
}
2024-02-08 03:36:42 +00:00
// 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) {
2024-02-08 04:05:00 +00:00
let odds = ((MAX_I - i) as f64)/(MAX_I as f64);
if !Self::find_home(
setup, rng,
odds,
accepted_card, Some(src), None, &mut slots, &max_height
) {
return None
}
2024-02-08 03:36:42 +00:00
}
}
}
// 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");
2024-02-08 04:05:00 +00:00
if !Self::find_home(setup, rng, 0.0, card, None, None, &mut slots, &max_height) {
return None
}
2024-02-08 03:36:42 +00:00
}
}
slots.remove(0); // get rid of the auxiliary slot
2024-02-08 04:05:00 +00:00
for s in 0..slots.len() {
if setup.deck.instantly_accepted.contains(slots[s].last().expect("each slot must contain cards")) {
return None;
}
2024-02-08 03:36:42 +00:00
}
for s in slots.iter() {
assert_eq!(tower_height, s.len());
}
return Some(Deal { slots } )
}
2024-02-08 03:36:42 +00:00
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;
2024-02-08 03:36:42 +00:00
let mut wells: Vec<Vec<Card>> = vec![vec![]; n_wells as usize + 2];
2024-02-08 03:36:42 +00:00
// 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 {
2024-02-08 03:36:42 +00:00
wells[s as usize].push(Card(setup.ruleset.n_cards_per_suit * s + r))
}
2024-02-08 03:36:42 +00:00
}
2024-02-08 03:36:42 +00:00
// now fill the wells for arcana
let first_arcana = setup.ruleset.n_suits * setup.ruleset.n_cards_per_suit;
2024-02-08 03:36:42 +00:00
let arcana0 = setup.ruleset.n_suits as usize;
let arcana1 = arcana0 + 1;
for r in 0..split_point {
2024-02-08 03:36:42 +00:00
wells[arcana0].push(Card(first_arcana + r));
}
for r in (split_point..setup.ruleset.n_arcana).rev() {
2024-02-08 03:36:42 +00:00
wells[arcana1].push(Card(first_arcana + r));
}
2024-02-08 03:36:42 +00:00
wells
}
fn plan_pops(wells: &[Vec<Card>], rng: &mut impl Rng) -> Vec<usize> {
let mut pops = vec![];
2024-02-08 03:36:42 +00:00
for (well, contents) in wells.iter().enumerate() {
for _ in 0..contents.len() {
pops.push(well);
}
}
pops.shuffle(rng);
2024-02-08 03:36:42 +00:00
pops
}
2024-02-08 03:36:42 +00:00
fn peek2(stack: &Vec<Card>) -> (Option<Card>, Option<Card>) {
let mut iter = stack.iter().rev();
let top = iter.next().cloned();
let second_to_top = iter.next().cloned();
return (second_to_top, top);
}
2024-02-08 04:05:00 +00:00
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]) -> bool {
2024-02-08 03:36:42 +00:00
// if a card is sitting on an acceptor, it could have been moved there
// from somewhere else
let mut acceptors = vec![];
2024-02-08 03:36:42 +00:00
// 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
2024-02-08 04:05:00 +00:00
} else if slots[s].len() == max_height[s] - 1 && setup.deck.instantly_accepted.contains(&card) {
// can't place an instantly accepted card at the bottom of a slot
} else {
2024-02-08 03:36:42 +00:00
if accepts(setup, slots[s].last().cloned(), card) {
acceptors.push(s);
}
2024-02-08 03:36:42 +00:00
if slots[s].len() < max_height[s] {
start_points.push(s);
}
}
}
2024-02-08 03:36:42 +00:00
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;
}
2024-02-08 03:36:42 +00:00
if let Some(a) = acceptor.or(start_point).or(source) {
slots[a].push(card);
2024-02-08 04:05:00 +00:00
return true;
2024-02-08 03:36:42 +00:00
} else {
2024-02-08 04:05:00 +00:00
return false;
}
2024-02-08 03:36:42 +00:00
}
2024-02-08 03:36:42 +00:00
fn pop_accepted_card(setup: &Setup, src: usize, slots: &mut [Vec<Card>]) -> Option<Card> {
let (card0, card1) = Self::peek2(&slots[src]);
2024-02-08 03:36:42 +00:00
if let Some(c1) = card1 {
if accepts(&setup, card0, c1) {
slots[src].pop().expect("we just peeked at this");
return Some(c1);
}
}
2024-02-08 03:36:42 +00:00
None
}
}
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;
}