add zobrist hashing, WIP transposition table
This commit is contained in:
parent
aa562d4ab6
commit
88131d9ab0
9 changed files with 362 additions and 137 deletions
279
Cargo.lock
generated
279
Cargo.lock
generated
|
|
@ -35,6 +35,12 @@ version = "1.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.19.0"
|
||||
|
|
@ -53,6 +59,17 @@ version = "1.0.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ciborium"
|
||||
version = "0.2.2"
|
||||
|
|
@ -105,6 +122,15 @@ version = "0.7.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
version = "0.7.0"
|
||||
|
|
@ -176,15 +202,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"rand_core",
|
||||
"wasip2",
|
||||
"wasip3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -198,6 +238,45 @@ dependencies = [
|
|||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
dependencies = [
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "id-arena"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.17.0",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
|
|
@ -223,12 +302,24 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "leb128fmt"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.177"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.6"
|
||||
|
|
@ -295,12 +386,13 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
name = "prettyplease"
|
||||
version = "0.2.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -323,38 +415,26 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.2"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||
checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
|
||||
dependencies = [
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"chacha20",
|
||||
"getrandom",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.3"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
|
|
@ -426,6 +506,12 @@ dependencies = [
|
|||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
|
|
@ -496,6 +582,12 @@ version = "1.0.22"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
|
|
@ -512,7 +604,16 @@ version = "1.0.1+wasi-0.2.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
"wit-bindgen 0.46.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasip3"
|
||||
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
|
||||
dependencies = [
|
||||
"wit-bindgen 0.51.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -560,6 +661,40 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
|
||||
dependencies = [
|
||||
"leb128fmt",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-metadata"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indexmap",
|
||||
"wasm-encoder",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"hashbrown 0.15.5",
|
||||
"indexmap",
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.82"
|
||||
|
|
@ -600,6 +735,94 @@ version = "0.46.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||
dependencies = [
|
||||
"wit-bindgen-rust-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-core"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"indexmap",
|
||||
"prettyplease",
|
||||
"syn",
|
||||
"wasm-metadata",
|
||||
"wit-bindgen-core",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-macro"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-component"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags",
|
||||
"indexmap",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"wasm-encoder",
|
||||
"wasm-metadata",
|
||||
"wasmparser",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-parser"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"id-arena",
|
||||
"indexmap",
|
||||
"log",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"unicode-xid",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.27"
|
||||
|
|
|
|||
|
|
@ -13,11 +13,11 @@ path = "src/main.rs"
|
|||
|
||||
[dependencies]
|
||||
anyhow = "1.0.100"
|
||||
rand = "0.10.1"
|
||||
regex = "1.12.2"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.7.0"
|
||||
rand = "0.9.2"
|
||||
|
||||
[[bench]]
|
||||
name = "generation"
|
||||
|
|
|
|||
100
src/ai.rs
100
src/ai.rs
|
|
@ -142,106 +142,6 @@ pub fn alphabeta(mut game: Game, depth: u8, mut alpha: i8, mut beta: i8) -> (Boa
|
|||
}
|
||||
}
|
||||
|
||||
/// Using alpha-beta pruning with printing of intermediate data for debugging
|
||||
/// and monitoring purposes.
|
||||
pub fn alphabeta_with_printing(game: Game, depth: u8) -> (Board, i8) {
|
||||
let (b, moves, eval) = alphabeta_with_printing_inner(game, depth, i8::MIN + 1, i8::MAX - 1);
|
||||
println!("beep. boop. we assessed {} moves.", moves);
|
||||
(b, eval)
|
||||
}
|
||||
|
||||
/// Using alpha-beta pruning and the minimax algorithm, determine the best move
|
||||
/// for a game with a recursion depth of `depth`.
|
||||
///
|
||||
/// We use a very simple evaluation heuristic: (Black squares - White squares).
|
||||
fn alphabeta_with_printing_inner(
|
||||
mut game: Game,
|
||||
depth: u8,
|
||||
mut alpha: i8,
|
||||
mut beta: i8,
|
||||
) -> (Board, usize, i8) {
|
||||
// if we reach our maximum recursion depth, return evaluation
|
||||
if depth == 0 {
|
||||
return (0, 0, game.score().diff());
|
||||
}
|
||||
let moves = game.available();
|
||||
if moves == 0 {
|
||||
// if no move, skip and continue recursion
|
||||
// this seems to technically introduce a bias against move-chains
|
||||
// that include skips. I haven't found it to be a big deal in play.
|
||||
game.skip();
|
||||
return (
|
||||
0,
|
||||
0,
|
||||
alphabeta_with_printing_inner(game, depth - 1, alpha, beta).2,
|
||||
);
|
||||
}
|
||||
|
||||
// just initially assume that the best move is no move at all. This will
|
||||
// inevitably be corrected.
|
||||
let mut best_move: Board = 0;
|
||||
// we initially rank moves based on a couple basic heuristics:
|
||||
// - corner pieces are best
|
||||
// - edge pieces are great
|
||||
// - others considered last
|
||||
// This just allows us to prune the tree a bit more aggressively
|
||||
// since we're considering the "best" moves first.
|
||||
// We do this by mapping moves to ranked moves and then sorting.
|
||||
let mut moves = explode_board(moves).map(MoveRank::from).collect::<Vec<_>>();
|
||||
moves.sort();
|
||||
let moves = moves
|
||||
.into_iter()
|
||||
.map(MoveRank::into_inner)
|
||||
.collect::<Vec<_>>();
|
||||
let mut num_moves = moves.len();
|
||||
// I just establish a convention of maximizing for black and minimizing for white.
|
||||
// I'm not sure if that's conventional or not, but it's what I chose.
|
||||
match game.current_team {
|
||||
Team::Black => {
|
||||
for mv in moves {
|
||||
let mut g = game.clone();
|
||||
g.play(mv);
|
||||
// maximize for the evaluation of subsequent moves
|
||||
let (_, num_moves_prime, evaluation) =
|
||||
alphabeta_with_printing_inner(g, depth - 1, alpha, beta);
|
||||
num_moves += num_moves_prime;
|
||||
// if our evaluated move is superior to the alpha, update
|
||||
// it.
|
||||
if evaluation > alpha {
|
||||
alpha = evaluation;
|
||||
best_move = mv;
|
||||
};
|
||||
// if our beta is less than alpha, prune the node.
|
||||
if beta <= alpha {
|
||||
break;
|
||||
}
|
||||
}
|
||||
(best_move, num_moves, alpha)
|
||||
}
|
||||
Team::White => {
|
||||
for mv in moves {
|
||||
let mut g = game.clone();
|
||||
g.play(mv);
|
||||
// minimize for the evaluation of subsequent moves
|
||||
let (_, num_moves_prime, evaluation) =
|
||||
alphabeta_with_printing_inner(g, depth - 1, alpha, beta);
|
||||
num_moves += num_moves_prime;
|
||||
// if our evaluated move produces lower eval than the beta,
|
||||
// update beta.
|
||||
if evaluation < beta {
|
||||
beta = evaluation;
|
||||
best_move = mv;
|
||||
};
|
||||
// if our beta is less than alpha, prune the node.
|
||||
if beta <= alpha {
|
||||
break;
|
||||
}
|
||||
}
|
||||
(best_move, num_moves, beta)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use std::fmt::{Debug, Display};
|
|||
use crate::{
|
||||
board::view::{Overlay, View},
|
||||
game::Team,
|
||||
zobrist::{ZOBRIST_TABLE, ZOBRIST_TURN},
|
||||
};
|
||||
|
||||
pub mod view;
|
||||
|
|
@ -446,6 +447,23 @@ impl BitBoard {
|
|||
self.boards[1].count_ones() as u8,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn compute_hash(&self, playing: Team) -> u64 {
|
||||
let mut hash = 0;
|
||||
for (player, board) in self.boards.iter().enumerate() {
|
||||
for offset in 0..64 as u64 {
|
||||
if (1 << offset) & board > 0 {
|
||||
hash ^= ZOBRIST_TABLE[player][offset as usize];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(playing, Team::White) {
|
||||
hash ^= *ZOBRIST_TURN;
|
||||
}
|
||||
|
||||
hash
|
||||
}
|
||||
}
|
||||
|
||||
pub mod squares {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::io;
|
||||
|
||||
use othello::{
|
||||
ai::{alphabeta, alphabeta_with_printing},
|
||||
ai::alphabeta,
|
||||
board::{
|
||||
Board, Score,
|
||||
view::{Overlay, View},
|
||||
|
|
@ -106,10 +106,6 @@ pub fn run() -> anyhow::Result<()> {
|
|||
let (mv, eval) = alphabeta(game.clone(), 14, i8::MIN + 1, i8::MAX - 1);
|
||||
println!("beep. boop. eval = {eval}");
|
||||
game.play(mv);
|
||||
} else {
|
||||
let (mv, eval) = alphabeta_with_printing(game.clone(), 14);
|
||||
println!("beep. boop. eval = {eval}");
|
||||
game.play(mv);
|
||||
}
|
||||
board_changed = true;
|
||||
}
|
||||
|
|
|
|||
33
src/game.rs
33
src/game.rs
|
|
@ -1,4 +1,7 @@
|
|||
use crate::board::{BitBoard, Board, Score};
|
||||
use crate::{
|
||||
board::{BitBoard, Board, Score},
|
||||
zobrist::{ZOBRIST_TABLE, ZOBRIST_TURN},
|
||||
};
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Default)]
|
||||
|
|
@ -19,16 +22,31 @@ impl Team {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
// TODO: Look into potentially better memory alignment for this
|
||||
#[derive(Clone)]
|
||||
pub struct Game {
|
||||
pub current_team: Team,
|
||||
hash: u64,
|
||||
board: BitBoard,
|
||||
}
|
||||
|
||||
impl Default for Game {
|
||||
fn default() -> Self {
|
||||
let board = BitBoard::default();
|
||||
let team = Team::default();
|
||||
Self {
|
||||
current_team: team,
|
||||
board: Default::default(),
|
||||
hash: board.compute_hash(team),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Game {
|
||||
/// Play a move. Automatically transitions state to next player.
|
||||
pub fn play(&mut self, player_move: Board) {
|
||||
self.hash ^=
|
||||
ZOBRIST_TABLE[self.current_team as usize][player_move.trailing_zeros() as usize];
|
||||
self.hash ^= *ZOBRIST_TURN;
|
||||
self.board.play(self.current_team, player_move);
|
||||
self.current_team = self.current_team.next();
|
||||
}
|
||||
|
|
@ -45,6 +63,7 @@ impl Game {
|
|||
|
||||
/// Skip the current player
|
||||
pub fn skip(&mut self) {
|
||||
self.hash ^= *ZOBRIST_TURN;
|
||||
self.current_team = self.current_team.next();
|
||||
}
|
||||
|
||||
|
|
@ -76,6 +95,7 @@ impl Game {
|
|||
pub(crate) fn from_parts(current_team: Team, board: BitBoard) -> Self {
|
||||
Self {
|
||||
current_team,
|
||||
hash: board.compute_hash(current_team),
|
||||
board,
|
||||
}
|
||||
}
|
||||
|
|
@ -85,6 +105,13 @@ impl Game {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::board::squares::*;
|
||||
|
||||
#[test]
|
||||
fn game_inits_with_hash() {
|
||||
let game = Game::default();
|
||||
assert_ne!(game.hash, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn play_switches_team() {
|
||||
let mut game = Game::default();
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
pub mod ai;
|
||||
pub mod board;
|
||||
pub mod game;
|
||||
mod table;
|
||||
mod zobrist;
|
||||
|
|
|
|||
32
src/table.rs
Normal file
32
src/table.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::{board::BitBoard, game::Game, zobrist::ZOBRIST_TABLE};
|
||||
|
||||
pub enum Bound {
|
||||
Exact,
|
||||
Lower,
|
||||
Upper,
|
||||
}
|
||||
|
||||
pub struct TTEntry {
|
||||
bound: Bound,
|
||||
evaluation: i8,
|
||||
depth: u8,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TTable {
|
||||
// replace with `DashMap` if we utilize concurrency
|
||||
inner: HashMap<u64, TTEntry>,
|
||||
}
|
||||
|
||||
impl TTable {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn upsert(&mut self, game: &Game) {
|
||||
//self.inner.entry(key).or_insert(default)
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
27
src/zobrist.rs
Normal file
27
src/zobrist.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use std::sync::LazyLock;
|
||||
|
||||
use rand::{RngExt, SeedableRng, rngs::StdRng};
|
||||
|
||||
pub static ZOBRIST_TABLE: LazyLock<[[u64; 64]; 2]> = LazyLock::new(generate_zobrist);
|
||||
pub static ZOBRIST_TURN: LazyLock<u64> = LazyLock::new(generate_zobrist_turn);
|
||||
|
||||
fn generate_zobrist() -> [[u64; 64]; 2] {
|
||||
let seed: u64 = std::env::var("ZOBRIST_SEED")
|
||||
.unwrap_or("42".into())
|
||||
.parse()
|
||||
.expect("Zobrist seed must be a valid unsigned integer");
|
||||
|
||||
let mut rng = StdRng::seed_from_u64(seed);
|
||||
|
||||
std::array::from_fn(|_| std::array::from_fn(|_| rng.random()))
|
||||
}
|
||||
|
||||
fn generate_zobrist_turn() -> u64 {
|
||||
let seed: u64 = std::env::var("ZOBRIST_TURN_SEED")
|
||||
.unwrap_or("24".into())
|
||||
.parse()
|
||||
.expect("Zobrist turn seed must be a valid unsigned integer");
|
||||
|
||||
let mut rng = StdRng::seed_from_u64(seed);
|
||||
rng.random()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue