2024-02-06 06:46:23 +00:00
|
|
|
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 {
|
|
|
|
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);
|
|
|
|
|
2024-02-08 06:12:36 +00:00
|
|
|
while let Some((w, n)) = pops.pop() {
|
|
|
|
for _ in 0..n {
|
|
|
|
let card = wells[w].pop().expect("card must be present");
|
|
|
|
Self::find_home(setup, rng, card, None, None, &mut slots, &max_height, true);
|
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 06:12:36 +00:00
|
|
|
Self::find_home(setup, rng, card, None, None, &mut slots, &max_height, false);
|
2024-02-08 03:36:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
slots.remove(0); // get rid of the auxiliary slot
|
2024-02-08 06:12:36 +00:00
|
|
|
let mut shuffles = 0;
|
2024-02-08 04:05:00 +00:00
|
|
|
for s in 0..slots.len() {
|
2024-02-08 06:12:36 +00:00
|
|
|
let extra_shuf = shuffles + 1;
|
|
|
|
while setup.deck.instantly_accepted.contains(slots[s].last().expect("each slot must contain cards")) {
|
|
|
|
slots[s].shuffle(rng);
|
|
|
|
shuffles = extra_shuf
|
2024-02-08 04:05:00 +00:00
|
|
|
}
|
2024-02-08 03:36:42 +00:00
|
|
|
}
|
2024-02-08 06:12:36 +00:00
|
|
|
for _ in shuffles..2 {
|
|
|
|
slots.choose_mut(rng).unwrap().shuffle(rng);
|
|
|
|
}
|
2024-02-08 03:36:42 +00:00
|
|
|
|
|
|
|
for s in slots.iter() {
|
|
|
|
assert_eq!(tower_height, s.len());
|
|
|
|
}
|
|
|
|
|
2024-02-08 06:12:36 +00:00
|
|
|
return Deal { slots }
|
2024-02-08 03:36:42 +00:00
|
|
|
}
|
2024-02-06 06:46:23 +00:00
|
|
|
|
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) };
|
|
|
|
|
2024-02-06 06:46:23 +00:00
|
|
|
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-06 06:46:23 +00:00
|
|
|
|
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 {
|
2024-02-06 06:46:23 +00:00
|
|
|
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-06 06:46:23 +00:00
|
|
|
}
|
2024-02-08 03:36:42 +00:00
|
|
|
}
|
2024-02-06 06:46:23 +00:00
|
|
|
|
2024-02-08 03:36:42 +00:00
|
|
|
// now fill the wells for arcana
|
2024-02-06 06:46:23 +00:00
|
|
|
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;
|
2024-02-06 06:46:23 +00:00
|
|
|
for r in 0..split_point {
|
2024-02-08 03:36:42 +00:00
|
|
|
wells[arcana0].push(Card(first_arcana + r));
|
2024-02-06 06:46:23 +00:00
|
|
|
}
|
|
|
|
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-06 06:46:23 +00:00
|
|
|
}
|
|
|
|
|
2024-02-08 03:36:42 +00:00
|
|
|
wells
|
|
|
|
}
|
|
|
|
|
2024-02-08 06:12:36 +00:00
|
|
|
fn plan_pops(wells: &[Vec<Card>], rng: &mut impl Rng) -> Vec<(usize, usize)> {
|
2024-02-06 06:46:23 +00:00
|
|
|
let mut pops = vec![];
|
2024-02-08 03:36:42 +00:00
|
|
|
for (well, contents) in wells.iter().enumerate() {
|
2024-02-06 06:46:23 +00:00
|
|
|
for _ in 0..contents.len() {
|
|
|
|
pops.push(well);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pops.shuffle(rng);
|
|
|
|
|
2024-02-08 06:12:36 +00:00
|
|
|
let mut rle = vec![];
|
|
|
|
let mut current: Option<usize> = None;
|
|
|
|
let mut count = 0;
|
|
|
|
while let Some(nxt) = pops.pop() {
|
|
|
|
if current == Some(nxt) && count < 4 {
|
|
|
|
count += 1;
|
|
|
|
} else {
|
|
|
|
if let Some(prev) = current {
|
|
|
|
rle.push((prev, count))
|
|
|
|
}
|
|
|
|
current = Some(nxt);
|
|
|
|
count = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(prev) = current {
|
|
|
|
if count > 0 {
|
|
|
|
rle.push((prev, count))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
rle
|
2024-02-08 03:36:42 +00:00
|
|
|
}
|
|
|
|
|
2024-02-08 06:12:36 +00:00
|
|
|
fn find_home(setup: &Setup, rng: &mut impl Rng, card: Card, source: Option<usize>, exclude: Option<usize>, slots: &mut [Vec<Card>], max_height: &[usize], allow_too_tall: 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-06 06:46:23 +00:00
|
|
|
|
2024-02-08 03:36:42 +00:00
|
|
|
for s in 0..slots.len() {
|
|
|
|
if Some(s) == exclude {
|
|
|
|
// don't place it here, ever
|
2024-02-08 06:12:36 +00:00
|
|
|
} else if false { // slots[s].len() == max_height[s] - 1 && setup.deck.instantly_accepted.contains(&card) {
|
2024-02-08 04:05:00 +00:00
|
|
|
// can't place an instantly accepted card at the bottom of a slot
|
2024-02-08 02:36:08 +00:00
|
|
|
} else {
|
2024-02-08 06:12:36 +00:00
|
|
|
if allow_too_tall && accepts(setup, slots[s].last().cloned(), card) {
|
|
|
|
acceptors.push(s);
|
|
|
|
} else if slots[s].len() < max_height[s] {
|
2024-02-08 03:36:42 +00:00
|
|
|
acceptors.push(s);
|
2024-02-06 06:46:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-08 06:12:36 +00:00
|
|
|
let acceptor = acceptors.choose(rng).cloned();
|
2024-02-08 02:36:08 +00:00
|
|
|
|
2024-02-08 06:12:36 +00:00
|
|
|
if let Some(a) = acceptor.or(source) {
|
2024-02-08 03:36:42 +00:00
|
|
|
slots[a].push(card);
|
|
|
|
} else {
|
2024-02-08 06:12:36 +00:00
|
|
|
panic!("should never happen")
|
2024-02-08 02:36:08 +00:00
|
|
|
}
|
2024-02-06 06:46:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|