Rust simulator
This commit is contained in:
parent
bdc8c91078
commit
4a0673d085
1
simulator/.gitignore
vendored
Normal file
1
simulator/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
target/
|
89
simulator/Cargo.lock
generated
Normal file
89
simulator/Cargo.lock
generated
Normal file
@ -0,0 +1,89 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.153"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simulator"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"rand",
|
||||
"xxhash-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "xxhash-rust"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53be06678ed9e83edb1745eb72efc0bbcd7b5c3c35711a860906aed827a13d61"
|
11
simulator/Cargo.toml
Normal file
11
simulator/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "simulator"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.79"
|
||||
rand = "0.8.5"
|
||||
xxhash-rust = { version = "0.8.8", features = ["xxh3"] }
|
335
simulator/src/board.rs
Normal file
335
simulator/src/board.rs
Normal file
@ -0,0 +1,335 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::{ruleset::{Card, CardMetadata, Setup}, zobrist::{Feature, Zobrist}};
|
||||
use rand::Rng;
|
||||
use rand::seq::SliceRandom;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash)]
|
||||
pub enum Move {
|
||||
ToSlot { from_slot: u8, to_slot: u8 },
|
||||
ToWell { from_slot: u8, to_well: u8 }
|
||||
}
|
||||
|
||||
|
||||
pub struct Board<'a> {
|
||||
zobrist: Zobrist,
|
||||
|
||||
checkpoints: Vec<usize>,
|
||||
log: Vec<Move>,
|
||||
|
||||
setup: &'a Setup,
|
||||
slots: Vec<Slot<'a>>,
|
||||
wells: Vec<Well<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Board<'a> {
|
||||
pub fn new(setup: &'a Setup) -> Self {
|
||||
let mut slots = vec![];
|
||||
let mut wells = vec![];
|
||||
|
||||
// ..n_slots: normal
|
||||
for i in 0..setup.ruleset.n_slots {
|
||||
slots.push(Slot::new(setup, slots.len() as u8, 32));
|
||||
}
|
||||
// n_slots: top cell
|
||||
slots.push(Slot::new(setup, slots.len() as u8, 1));
|
||||
|
||||
// ..n_suits: conventional wells
|
||||
for _ in &setup.deck.suits {
|
||||
wells.push(Well::new(setup, wells.len() as u8, Box::new(
|
||||
|prev: Option<CardMetadata>, new: CardMetadata| {
|
||||
if let Some(p) = prev {
|
||||
if new.suit != p.suit { return false }
|
||||
return new.suit == p.suit && new.rank == p.rank + 1
|
||||
}
|
||||
panic!("well should have had an ace or something to start")
|
||||
}
|
||||
)))
|
||||
}
|
||||
|
||||
if setup.ruleset.n_arcana > 0 {
|
||||
// n_suits: arcana ascending
|
||||
wells.push(Well::new(setup, wells.len() as u8, Box::new(
|
||||
|prev, new| {
|
||||
if new.suit != b'a' { return false }
|
||||
if let Some(p) = prev {
|
||||
return new.rank == p.rank + 1;
|
||||
}
|
||||
return new.rank == 0;
|
||||
}
|
||||
)));
|
||||
|
||||
// n_suits+1: arcana descending
|
||||
let top_rank = setup.ruleset.n_arcana - 1;
|
||||
wells.push(Well::new(setup, wells.len() as u8, Box::new(
|
||||
move |prev, new| {
|
||||
if new.suit != b'a' { return false }
|
||||
if let Some(p) = prev {
|
||||
return new.rank + 1 == p.rank;
|
||||
}
|
||||
return new.rank == top_rank;
|
||||
}
|
||||
)))
|
||||
}
|
||||
Board {
|
||||
zobrist: Zobrist::new(),
|
||||
|
||||
checkpoints: vec![],
|
||||
log: vec![],
|
||||
|
||||
setup,
|
||||
slots,
|
||||
wells,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display(&self) {
|
||||
println!("Wells:");
|
||||
for w in &self.wells {
|
||||
println!("- {:?}", w.contents);
|
||||
}
|
||||
println!("\nSlots:");
|
||||
for s in &self.slots {
|
||||
println!("- {:?}", s.contents);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn zobrist_key(&self) -> u64 {
|
||||
self.zobrist.value
|
||||
}
|
||||
|
||||
pub fn is_won(&self) -> bool {
|
||||
for s in self.slots.iter() {
|
||||
if s.peek().is_some() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
pub fn deal(&mut self, rng: &mut impl Rng) {
|
||||
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));
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
fn settle(&mut self) {
|
||||
while self.settle1() { }
|
||||
}
|
||||
|
||||
fn settle1(&mut self) -> bool {
|
||||
let special_slot_full = self.slots[self.setup.ruleset.n_slots as usize].peek().is_some();
|
||||
for s in 0..self.slots.len() {
|
||||
if let Some(top) = self.slots[s].peek() {
|
||||
for w in 0..self.wells.len() {
|
||||
if special_slot_full && (w as u8) < self.setup.ruleset.n_suits {
|
||||
// top wells are blocked
|
||||
} else if self.wells[w].would_accept(top) {
|
||||
self.internal_perform(Move::ToWell { from_slot: s as u8, to_well: w as u8 });
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Do this without allocs?
|
||||
pub fn legal_moves(&self) -> Vec<Move> {
|
||||
let mut moves = vec![];
|
||||
for i in 0..self.slots.len() {
|
||||
if let Some(top) = self.slots[i].peek() {
|
||||
for j in 0..self.slots.len() {
|
||||
if i == j { continue; }
|
||||
if !self.slots[j].would_accept(top) { continue; }
|
||||
moves.push(Move::ToSlot { from_slot: i as u8, to_slot: j as u8 })
|
||||
}
|
||||
}
|
||||
}
|
||||
moves
|
||||
}
|
||||
|
||||
fn create_checkpoint(&mut self) {
|
||||
self.checkpoints.push(self.log.len());
|
||||
}
|
||||
|
||||
pub fn perform(&mut self, m: Move) {
|
||||
self.create_checkpoint();
|
||||
self.internal_perform(m);
|
||||
self.settle();
|
||||
}
|
||||
|
||||
pub fn undo(&mut self) {
|
||||
let cp = self.checkpoints.pop().expect("undo() without perform()");
|
||||
while self.log.len() > cp {
|
||||
let m = self.log.pop().unwrap();
|
||||
self.internal_unperform(m)
|
||||
}
|
||||
}
|
||||
|
||||
fn internal_perform(&mut self, m: Move) {
|
||||
// println!("internal perform: {:?}", m);
|
||||
match m {
|
||||
Move::ToWell { from_slot, to_well } => {
|
||||
let top = self.slots[from_slot as usize].pop(&mut self.zobrist).expect("move from empty slot");
|
||||
self.wells[to_well as usize].push(&mut self.zobrist, top);
|
||||
}
|
||||
Move::ToSlot { from_slot, to_slot } => {
|
||||
let top = self.slots[from_slot as usize].pop(&mut self.zobrist).expect("move from empty slot");
|
||||
self.slots[to_slot as usize].push(&mut self.zobrist, top);
|
||||
}
|
||||
}
|
||||
self.log.push(m);
|
||||
}
|
||||
|
||||
fn internal_unperform(&mut self, m: Move) {
|
||||
// println!("internal unperform: {:?}", m);
|
||||
match m {
|
||||
Move::ToWell { from_slot, to_well } => {
|
||||
let top = self.wells[to_well as usize].pop(&mut self.zobrist).expect("move from empty well");
|
||||
self.slots[from_slot as usize].push(&mut self.zobrist, top);
|
||||
}
|
||||
Move::ToSlot { from_slot, to_slot } => {
|
||||
let top = self.slots[to_slot as usize].pop(&mut self.zobrist).expect("move from empty slot");
|
||||
self.slots[from_slot as usize].push(&mut self.zobrist, top);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Slot<'a> {
|
||||
ix: u8,
|
||||
setup: &'a Setup,
|
||||
max_n: u8,
|
||||
contents: Vec<Card>,
|
||||
}
|
||||
|
||||
impl<'a> Slot<'a> {
|
||||
fn new(setup: &'a Setup, ix: u8, max_n: u8) -> Self {
|
||||
Slot {
|
||||
setup,
|
||||
ix,
|
||||
contents: Vec::with_capacity(max_n as usize),
|
||||
max_n,
|
||||
}
|
||||
}
|
||||
|
||||
fn push(&mut self, zobrist: &mut Zobrist, new: Card) {
|
||||
zobrist.toggle(Feature::CardAt { card: new, slot: self.ix, depth: self.contents.len() as u8});
|
||||
self.contents.push(new);
|
||||
}
|
||||
|
||||
fn peek(&self) -> Option<Card> {
|
||||
self.contents.last().cloned()
|
||||
}
|
||||
|
||||
fn pop(&mut self, zobrist: &mut Zobrist) -> Option<Card> {
|
||||
let card = self.contents.pop();
|
||||
let depth = self.contents.len() as u8;
|
||||
if let Some(card) = card {
|
||||
zobrist.toggle(Feature::CardAt { card, slot: self.ix, depth });
|
||||
}
|
||||
return card
|
||||
}
|
||||
|
||||
fn would_accept(&self, top: Card) -> bool {
|
||||
if self.contents.len() as u8 >= self.max_n {
|
||||
return false;
|
||||
}
|
||||
|
||||
let last = self.contents.last();
|
||||
if let Some(l) = last {
|
||||
let l = self.setup.deck.cards[l.0 as usize];
|
||||
let n = self.setup.deck.cards[top.0 as usize];
|
||||
return l.suit == n.suit && (l.rank + 1 == n.rank || l.rank == n.rank + 1);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
type AcceptCb = Box<dyn Fn(Option<CardMetadata>, CardMetadata) -> bool>;
|
||||
|
||||
struct Well<'a> {
|
||||
setup: &'a Setup,
|
||||
ix: u8,
|
||||
accept_cb: AcceptCb,
|
||||
contents: Vec<Card>,
|
||||
}
|
||||
|
||||
impl<'a> Well<'a> {
|
||||
fn new(setup: &'a Setup, ix: u8, accept_cb: AcceptCb) -> Self {
|
||||
Well {
|
||||
setup,
|
||||
ix,
|
||||
accept_cb,
|
||||
contents: vec![]
|
||||
}
|
||||
}
|
||||
|
||||
fn push(&mut self, zobrist: &mut Zobrist, new: Card) {
|
||||
zobrist.toggle(Feature::WellHas { card: new, well: self.ix });
|
||||
self.contents.push(new);
|
||||
}
|
||||
|
||||
fn pop(&mut self, zobrist: &mut Zobrist) -> Option<Card> {
|
||||
let card = self.contents.pop();
|
||||
if let Some(c) = card {
|
||||
zobrist.toggle(Feature::WellHas { card: c, well: self.ix });
|
||||
}
|
||||
card
|
||||
}
|
||||
|
||||
fn would_accept(&self, new: Card) -> bool {
|
||||
let prev = self.contents.last().map(|p|
|
||||
self.setup.deck.cards[p.0 as usize]
|
||||
);
|
||||
let new = self.setup.deck.cards[new.0 as usize];
|
||||
return (self.accept_cb)(prev, new);
|
||||
}
|
||||
}
|
89
simulator/src/main.rs
Normal file
89
simulator/src/main.rs
Normal file
@ -0,0 +1,89 @@
|
||||
use board::Board;
|
||||
use ruleset::Ruleset;
|
||||
use seen::Seen;
|
||||
|
||||
use crate::{ruleset::Card, zobrist::Zobrist};
|
||||
|
||||
mod board;
|
||||
mod ruleset;
|
||||
mod seen;
|
||||
mod zobrist;
|
||||
|
||||
|
||||
fn main() {
|
||||
/*
|
||||
let ruleset = Ruleset {
|
||||
n_slots: 11,
|
||||
n_suits: 4,
|
||||
n_cards_per_suit: 13,
|
||||
n_arcana: 22
|
||||
};
|
||||
*/
|
||||
/*
|
||||
let ruleset = Ruleset {
|
||||
n_slots: 5,
|
||||
n_suits: 1,
|
||||
n_cards_per_suit: 9,
|
||||
n_arcana: 0
|
||||
};
|
||||
*/
|
||||
/*
|
||||
let ruleset = Ruleset {
|
||||
n_slots: 7,
|
||||
n_suits: 2,
|
||||
n_cards_per_suit: 9,
|
||||
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.display();
|
||||
|
||||
println!("is_winnable: {}", is_winnable(board));
|
||||
|
||||
// println!("Legal moves: {:#?}", board.legal_moves());
|
||||
|
||||
}
|
||||
|
||||
fn is_winnable(mut board: Board<'_>) -> bool {
|
||||
let mut seen = Seen::new();
|
||||
|
||||
explore(0, &mut board, &mut seen)
|
||||
}
|
||||
|
||||
fn explore(depth: usize, board: &mut Board<'_>, seen: &mut Seen) -> bool {
|
||||
if depth > 200 {
|
||||
return false;
|
||||
}
|
||||
|
||||
if seen.contains(board.zobrist_key()) {
|
||||
return false
|
||||
}
|
||||
seen.add(board.zobrist_key());
|
||||
|
||||
if board.is_won() {
|
||||
board.display();
|
||||
return true
|
||||
}
|
||||
|
||||
for m in board.legal_moves() {
|
||||
let hash_1 = board.zobrist_key();
|
||||
board.perform(m);
|
||||
// println!("try: {:X?} {:?}", board.zobrist_key(), m);
|
||||
if explore(depth + 1, board, seen) {
|
||||
return true;
|
||||
}
|
||||
// println!("undo: {:X?} {:?}", board.zobrist_key(), m);
|
||||
board.undo();
|
||||
let hash_2 = board.zobrist_key();
|
||||
assert_eq!(hash_1, hash_2)
|
||||
}
|
||||
return false;
|
||||
}
|
94
simulator/src/ruleset.rs
Normal file
94
simulator/src/ruleset.rs
Normal file
@ -0,0 +1,94 @@
|
||||
use anyhow::bail;
|
||||
|
||||
pub struct Ruleset {
|
||||
pub n_slots: u8,
|
||||
pub n_suits: u8,
|
||||
pub n_cards_per_suit: u8,
|
||||
pub n_arcana: u8,
|
||||
}
|
||||
|
||||
pub struct Deck {
|
||||
pub aces: Vec<Card>,
|
||||
pub suits: Vec<u8>,
|
||||
pub cards: Vec<CardMetadata>,
|
||||
}
|
||||
|
||||
pub struct Setup {
|
||||
pub ruleset: Ruleset,
|
||||
pub deck: Deck
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Card(pub u8);
|
||||
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct CardMetadata { pub suit: u8, pub rank: u8 }
|
||||
|
||||
impl Ruleset {
|
||||
fn usable_slots(&self) -> u8 { self.n_slots - 1 }
|
||||
fn total_n_cards(&self) -> u8 {
|
||||
self.n_arcana + self.n_suits * self.n_cards_per_suit
|
||||
}
|
||||
fn usable_n_cards(&self) -> u8 {
|
||||
self.total_n_cards() - self.n_suits
|
||||
}
|
||||
|
||||
pub fn compile(self) -> anyhow::Result<Setup> {
|
||||
self.validate()?;
|
||||
let deck = self.create_deck();
|
||||
Ok(Setup {
|
||||
ruleset: self,
|
||||
deck
|
||||
})
|
||||
}
|
||||
fn validate(&self) -> anyhow::Result<()> {
|
||||
if self.n_slots > 11 { bail!("max 11 slots") }
|
||||
if self.n_suits > 5 { bail!("max 5 suits") }
|
||||
if self.n_cards_per_suit > 13 { bail!("max 13 cards per suit") }
|
||||
if self.n_arcana > 99 { bail!("max 99 arcana") }
|
||||
if self.usable_slots() % 2 != 0 {
|
||||
bail!("must have even number of usable slots")
|
||||
}
|
||||
if self.usable_n_cards() % self.usable_slots() != 0 {
|
||||
bail!(
|
||||
"needs {} more cards for an even deal",
|
||||
self.usable_slots() - (self.usable_n_cards() % self.usable_slots())
|
||||
)
|
||||
}
|
||||
let instantly_placed_cards = self.n_suits +
|
||||
if self.n_arcana == 0 { 0 }
|
||||
else if self.n_arcana == 1 { 1 }
|
||||
else { 2 };
|
||||
if self.total_n_cards() - instantly_placed_cards < self.usable_slots() {
|
||||
bail!("can't place all cards without automoves");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_deck(&self) -> Deck {
|
||||
let possible_suits = b"pscwb";
|
||||
let mut aces = vec![];
|
||||
let mut suits: Vec<u8> = vec![];
|
||||
suits.extend(possible_suits[..self.n_suits as usize].into_iter());
|
||||
let mut cards = vec![];
|
||||
|
||||
// suited cards
|
||||
for &suit in &suits {
|
||||
for rank in 1..=self.n_cards_per_suit {
|
||||
if rank == 1 {
|
||||
aces.push(Card(cards.len() as u8))
|
||||
}
|
||||
cards.push(CardMetadata { suit, rank: rank as u8 })
|
||||
}
|
||||
}
|
||||
|
||||
// arcana
|
||||
for rank in 0..self.n_arcana {
|
||||
cards.push(CardMetadata {suit: b'a', rank});
|
||||
}
|
||||
|
||||
Deck { aces, suits, cards }
|
||||
}
|
||||
}
|
33
simulator/src/seen.rs
Normal file
33
simulator/src/seen.rs
Normal file
@ -0,0 +1,33 @@
|
||||
|
||||
|
||||
const SIZE: usize = 0x800000;
|
||||
const SIZE_MASK: usize = SIZE - 1;
|
||||
|
||||
pub struct Seen {
|
||||
pub bitarr: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Seen {
|
||||
pub fn new() -> Seen {
|
||||
Seen {
|
||||
bitarr: vec![0; SIZE]
|
||||
}
|
||||
}
|
||||
|
||||
fn fix(&self, ix: u64) -> (usize, u8) {
|
||||
let hi = (ix >> 3) & SIZE_MASK as u64;
|
||||
let lo = 1 << (ix & 0x7);
|
||||
(hi as usize, lo)
|
||||
}
|
||||
|
||||
// TODO: Elide bounds checks
|
||||
pub fn add(&mut self, ix: u64) {
|
||||
let (hi, lo) = self.fix(ix);
|
||||
self.bitarr[hi] |= lo;
|
||||
}
|
||||
|
||||
pub fn contains(&self, ix: u64) -> bool {
|
||||
let (hi, lo) = self.fix(ix);
|
||||
self.bitarr[hi] & lo != 0
|
||||
}
|
||||
}
|
36
simulator/src/zobrist.rs
Normal file
36
simulator/src/zobrist.rs
Normal file
@ -0,0 +1,36 @@
|
||||
use crate::ruleset::Card;
|
||||
use xxhash_rust::xxh3::xxh3_64;
|
||||
|
||||
const ZOBRIST_CONSTANT: u32 = 0xba7f00d5;
|
||||
|
||||
pub struct Zobrist {
|
||||
pub value: u64
|
||||
}
|
||||
|
||||
impl Zobrist {
|
||||
pub fn new() -> Zobrist {
|
||||
Zobrist { value: 0 }
|
||||
}
|
||||
pub fn toggle(&mut self, feature: Feature) {
|
||||
self.value ^= feature.zobrist()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Feature {
|
||||
CardAt { card: Card, slot: u8, depth: u8 },
|
||||
WellHas { card: Card, well: u8 }
|
||||
}
|
||||
|
||||
impl Feature {
|
||||
pub fn zobrist(&self) -> u64 {
|
||||
let mut msg: [u8; 8] = [0; 8];
|
||||
msg[0..4].copy_from_slice(&ZOBRIST_CONSTANT.to_le_bytes());
|
||||
match *self {
|
||||
Self::CardAt { card, slot, depth } => msg[4..8].copy_from_slice(&[0, card.0, slot, depth]),
|
||||
Self::WellHas { card, well } => msg[4..7].copy_from_slice(&[1, card.0, well]),
|
||||
}
|
||||
xxh3_64(&msg)
|
||||
}
|
||||
}
|
29
simulator/src/zobrist_test.old
Normal file
29
simulator/src/zobrist_test.old
Normal file
@ -0,0 +1,29 @@
|
||||
use crate::{ruleset::Card, zobrist::Zobrist};
|
||||
|
||||
mod board;
|
||||
mod ruleset;
|
||||
mod seen;
|
||||
mod zobrist;
|
||||
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
let mut zob = Zobrist::new();
|
||||
for feat in [
|
||||
zobrist::Feature::CardAt { card: Card(0), slot: 0, depth: 0 },
|
||||
zobrist::Feature::CardAt { card: Card(0), slot: 0, depth: 1 },
|
||||
zobrist::Feature::CardAt { card: Card(0), slot: 0, depth: 2 },
|
||||
zobrist::Feature::CardAt { card: Card(0), slot: 0, depth: 3 },
|
||||
|
||||
zobrist::Feature::CardAt { card: Card(0), slot: 0, depth: 0 },
|
||||
zobrist::Feature::CardAt { card: Card(0), slot: 0, depth: 1 },
|
||||
zobrist::Feature::CardAt { card: Card(0), slot: 0, depth: 2 },
|
||||
zobrist::Feature::CardAt { card: Card(0), slot: 0, depth: 3 },
|
||||
] {
|
||||
let old_zob = zob.value;
|
||||
println!("{:?} => {:016X?}!", feat, feat.zobrist());
|
||||
zob.toggle(feat);
|
||||
let new_zob = zob.value;
|
||||
println!("Zobrist: {:016X?} => {:016X?}", old_zob, new_zob);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user