Rust: dealer that cheats on the player's behalf
This commit is contained in:
parent
4a0673d085
commit
d9bb730ecd
@ -1,8 +1,4 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::{ruleset::{Card, CardMetadata, Setup}, zobrist::{Feature, Zobrist}};
|
||||
use rand::Rng;
|
||||
use rand::seq::SliceRandom;
|
||||
use crate::{ruleset::{Card, CardMetadata, Setup}, smart_dealer::Deal, zobrist::{Feature, Zobrist}};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash)]
|
||||
pub enum Move {
|
||||
@ -28,7 +24,7 @@ impl<'a> Board<'a> {
|
||||
let mut wells = vec![];
|
||||
|
||||
// ..n_slots: normal
|
||||
for i in 0..setup.ruleset.n_slots {
|
||||
for _ in 0..setup.ruleset.n_slots {
|
||||
slots.push(Slot::new(setup, slots.len() as u8, 32));
|
||||
}
|
||||
// n_slots: top cell
|
||||
@ -107,55 +103,18 @@ impl<'a> Board<'a> {
|
||||
return true
|
||||
}
|
||||
|
||||
pub fn deal(&mut self, rng: &mut impl Rng) {
|
||||
pub fn deal(&mut self, deal: Deal) {
|
||||
let n_usable_slots = self.setup.ruleset.n_slots - 1;
|
||||
|
||||
let mut available = HashSet::new();
|
||||
for c in 0..self.setup.deck.cards.len() {
|
||||
available.insert(Card(c as u8));
|
||||
for (w, c) in self.setup.deck.aces.iter().enumerate() {
|
||||
self.wells[w].push(&mut self.zobrist, *c);
|
||||
}
|
||||
|
||||
// place aces in wells
|
||||
for i in 0..self.setup.deck.aces.len() {
|
||||
let ace = self.setup.deck.aces[i];
|
||||
self.wells[i].push(&mut self.zobrist, self.setup.deck.aces[i]);
|
||||
available.remove(&ace);
|
||||
}
|
||||
|
||||
let mut eligible_bottom_row: Vec<Card> =
|
||||
(0..self.setup.deck.cards.len())
|
||||
.map(|i| Card(i as u8))
|
||||
.filter(|card| {
|
||||
if !available.contains(card) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for w in self.wells.iter() {
|
||||
if w.would_accept(*card) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
.collect();
|
||||
|
||||
eligible_bottom_row.shuffle(rng);
|
||||
let bottom_row: Vec<Card> = (&eligible_bottom_row[..n_usable_slots as usize]).iter().cloned().collect();
|
||||
for i in bottom_row.iter() {
|
||||
available.remove(i);
|
||||
}
|
||||
|
||||
let mut eligible: Vec<Card> = (0..self.setup.deck.cards.len())
|
||||
.map(|i| Card(i as u8))
|
||||
.filter(|c| available.contains(&c))
|
||||
.collect();
|
||||
eligible.shuffle(rng);
|
||||
eligible.extend(bottom_row);
|
||||
|
||||
for (i, card) in eligible.iter().cloned().enumerate() {
|
||||
let i = i as u8 % n_usable_slots;
|
||||
let real_slot = if i < n_usable_slots / 2 { i } else { i + 1 };
|
||||
self.slots[real_slot as usize].push(&mut self.zobrist, card);
|
||||
for (s, content) in deal.slots.iter().enumerate() {
|
||||
let real_slot = if s < n_usable_slots as usize / 2 { s } else { s + 1 };
|
||||
for &card in content {
|
||||
self.slots[real_slot].push(&mut self.zobrist, card)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,15 +2,22 @@ use board::Board;
|
||||
use ruleset::Ruleset;
|
||||
use seen::Seen;
|
||||
|
||||
use crate::{ruleset::Card, zobrist::Zobrist};
|
||||
use crate::smart_dealer::Deal;
|
||||
|
||||
mod board;
|
||||
mod ruleset;
|
||||
mod seen;
|
||||
mod smart_dealer;
|
||||
mod zobrist;
|
||||
|
||||
|
||||
fn main() {
|
||||
let ruleset = Ruleset {
|
||||
n_slots: 11,
|
||||
n_suits: 5,
|
||||
n_cards_per_suit: 10,
|
||||
n_arcana: 25
|
||||
};
|
||||
/*
|
||||
let ruleset = Ruleset {
|
||||
n_slots: 11,
|
||||
@ -35,15 +42,17 @@ fn main() {
|
||||
n_arcana: 8
|
||||
};
|
||||
*/
|
||||
/*
|
||||
let ruleset = Ruleset {
|
||||
n_slots: 9,
|
||||
n_suits: 3,
|
||||
n_cards_per_suit: 11,
|
||||
n_arcana: 18
|
||||
};
|
||||
*/
|
||||
let setup = ruleset.compile().expect("compilation should succeed");
|
||||
let mut board = Board::new(&setup);
|
||||
board.deal(&mut rand::thread_rng());
|
||||
board.deal(Deal::deal(&setup, &mut rand::thread_rng()));
|
||||
board.display();
|
||||
|
||||
println!("is_winnable: {}", is_winnable(board));
|
||||
|
@ -31,7 +31,7 @@ impl Ruleset {
|
||||
fn total_n_cards(&self) -> u8 {
|
||||
self.n_arcana + self.n_suits * self.n_cards_per_suit
|
||||
}
|
||||
fn usable_n_cards(&self) -> u8 {
|
||||
pub fn usable_n_cards(&self) -> u8 {
|
||||
self.total_n_cards() - self.n_suits
|
||||
}
|
||||
|
||||
|
147
simulator/src/smart_dealer.rs
Normal file
147
simulator/src/smart_dealer.rs
Normal file
@ -0,0 +1,147 @@
|
||||
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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user