changes + benchmarks
This commit is contained in:
parent
ad28713775
commit
0d0b5786a2
371
Cargo.lock
generated
371
Cargo.lock
generated
@ -2,6 +2,27 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anes"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.6"
|
||||
@ -44,12 +65,70 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cast"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "ciborium"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
|
||||
dependencies = [
|
||||
"ciborium-io",
|
||||
"ciborium-ll",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ciborium-io"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
|
||||
|
||||
[[package]]
|
||||
name = "ciborium-ll"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
|
||||
dependencies = [
|
||||
"ciborium-io",
|
||||
"half",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.15.10"
|
||||
@ -63,6 +142,73 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
|
||||
dependencies = [
|
||||
"anes",
|
||||
"cast",
|
||||
"ciborium",
|
||||
"clap",
|
||||
"criterion-plot",
|
||||
"is-terminal",
|
||||
"itertools",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"oorandom",
|
||||
"plotters",
|
||||
"rayon",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"tinytemplate",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion-plot"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
|
||||
dependencies = [
|
||||
"cast",
|
||||
"itertools",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.13.0"
|
||||
@ -93,6 +239,22 @@ dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
|
||||
|
||||
[[package]]
|
||||
name = "indicatif"
|
||||
version = "0.17.11"
|
||||
@ -106,6 +268,32 @@ dependencies = [
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.77"
|
||||
@ -134,6 +322,12 @@ version = "0.4.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "num"
|
||||
version = "0.4.3"
|
||||
@ -219,12 +413,19 @@ version = "1.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
version = "11.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9"
|
||||
|
||||
[[package]]
|
||||
name = "othello"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bitvec",
|
||||
"criterion",
|
||||
"either",
|
||||
"indicatif",
|
||||
"lazy_static",
|
||||
@ -233,6 +434,34 @@ dependencies = [
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plotters"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"plotters-backend",
|
||||
"plotters-svg",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plotters-backend"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
|
||||
|
||||
[[package]]
|
||||
name = "plotters-svg"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
|
||||
dependencies = [
|
||||
"plotters-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.10.0"
|
||||
@ -303,6 +532,108 @@ dependencies = [
|
||||
"zerocopy 0.8.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.217"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.217"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.138"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
@ -326,6 +657,16 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "tinytemplate"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.16"
|
||||
@ -338,6 +679,16 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.13.3+wasi-0.2.2"
|
||||
@ -355,6 +706,7 @@ checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
@ -404,6 +756,16 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-time"
|
||||
version = "1.1.0"
|
||||
@ -414,6 +776,15 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
|
||||
16
Cargo.toml
16
Cargo.toml
@ -3,6 +3,15 @@ name = "othello"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "othello"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "othello_game"
|
||||
path = "src/main.rs"
|
||||
|
||||
|
||||
[profile.release]
|
||||
# for profiling
|
||||
debug = true
|
||||
@ -18,3 +27,10 @@ lazy_static = "1.5"
|
||||
num = "0.4"
|
||||
rand = "0.9"
|
||||
static_assertions = "1.1"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.5", features = ["html_reports"] }
|
||||
|
||||
[[bench]]
|
||||
name = "future_children"
|
||||
harness = false
|
||||
|
||||
24
benches/future_children.rs
Normal file
24
benches/future_children.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use std::hint::black_box;
|
||||
|
||||
// use crate::future_move::FutureMove;
|
||||
use othello::{board::Board, future_moves::FutureMoves, piece::Piece};
|
||||
|
||||
fn future_move_bench(depth: usize, expire: usize) {
|
||||
let mut fut = FutureMoves::new(Piece::Black, depth, expire);
|
||||
fut.update(&Board::new().starting_pos());
|
||||
let _best_move = fut.best_move().inspect(|&(i, j)| {
|
||||
if !fut.update_root_coord(i, j) {
|
||||
panic!("update_root_coord failed");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
c.bench_function("depth 6 expire 4", |b| {
|
||||
b.iter(|| future_move_bench(black_box(6), black_box(4)))
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
@ -1,383 +1,4 @@
|
||||
use crate::{
|
||||
agent::Agent,
|
||||
board::{Board, Winner},
|
||||
piece::Piece,
|
||||
};
|
||||
use indicatif::{ProgressIterator, ProgressStyle};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Move {
|
||||
/// `i` position of move
|
||||
i: usize,
|
||||
|
||||
/// `j` position of move
|
||||
j: usize,
|
||||
|
||||
/// [`Board`] state after move is made
|
||||
board: Board,
|
||||
|
||||
/// Current winner of the match
|
||||
winner: Winner,
|
||||
|
||||
/// Index of this move's parent
|
||||
parent: Option<usize>,
|
||||
|
||||
/// Indices of this Move's Children
|
||||
children: Vec<usize>,
|
||||
|
||||
/// Value of this move
|
||||
value: i64,
|
||||
|
||||
color: Piece,
|
||||
|
||||
lazy_children: bool,
|
||||
}
|
||||
|
||||
impl Move {
|
||||
pub const fn coords(&self) -> (usize, usize) {
|
||||
(self.i, self.j)
|
||||
}
|
||||
|
||||
fn compute_self_value(&self, agent_color: Piece) -> i64 {
|
||||
let mut self_value = self.board.net_score(agent_color) as i64;
|
||||
|
||||
if self.winner == Winner::Player(!agent_color) {
|
||||
// if this board results in the opponent winning, MAJORLY negatively weigh this move
|
||||
// NOTE! this branch isn't completely deleted because if so, the bot wouldn't make a move.
|
||||
// We shouldn't prune branches because we still need to always react to the opponent's moves
|
||||
self_value = i64::MIN;
|
||||
} else if self.winner == Winner::Player(agent_color) {
|
||||
// results in a win for the agent
|
||||
self_value = i64::MAX;
|
||||
}
|
||||
// TODO! handle ties... what should they be valued as? maybe `i64::MAX / 2` or 0?
|
||||
|
||||
self_value
|
||||
}
|
||||
}
|
||||
|
||||
struct FutureMoves {
|
||||
/// Arena containing all [`Move`]
|
||||
arena: Vec<Move>,
|
||||
|
||||
/// Index of the [`Move`] tree's root node
|
||||
current_root: Option<usize>,
|
||||
|
||||
/// Current generated depth of the Arena
|
||||
current_depth: usize,
|
||||
|
||||
/// Target depth of children to generate
|
||||
max_depth: usize,
|
||||
|
||||
/// Color w.r.t
|
||||
agent_color: Piece,
|
||||
}
|
||||
|
||||
impl FutureMoves {
|
||||
pub const fn new(agent_color: Piece, max_depth: usize) -> Self {
|
||||
Self {
|
||||
arena: Vec::new(),
|
||||
current_root: None,
|
||||
current_depth: 0,
|
||||
max_depth,
|
||||
agent_color,
|
||||
}
|
||||
}
|
||||
|
||||
const LAZY_EXPIRE: usize = 6;
|
||||
|
||||
/// Generate children for all children of `nodes`
|
||||
fn extend_layers(&mut self) {
|
||||
let mut next_nodes: Vec<usize> = (0..self.arena.len())
|
||||
// we want to select all nodes that don't have children, or are lazy (need to maybe be regenerated)
|
||||
.filter(|&idx| self.arena[idx].children.is_empty() || self.arena[idx].lazy_children)
|
||||
.filter(|&idx| self.is_connected_to_root(idx)) // put here so this will not extend needlessly before prunes
|
||||
.collect();
|
||||
|
||||
for i in self.current_depth..=self.max_depth {
|
||||
next_nodes = next_nodes
|
||||
.into_iter()
|
||||
.progress_with_style(
|
||||
ProgressStyle::with_template(&format!(
|
||||
"Generating children (depth: {}/{}): ({{pos}}/{{len}}) {{per_sec}}",
|
||||
i, self.max_depth
|
||||
))
|
||||
.unwrap(),
|
||||
)
|
||||
.flat_map(|node_idx| {
|
||||
self.generate_children(
|
||||
node_idx,
|
||||
if self.arena[node_idx].lazy_children
|
||||
&& self.depth_of(node_idx) - 1 < Self::LAZY_EXPIRE
|
||||
{
|
||||
false
|
||||
} else {
|
||||
// this is a non-lazy_children child, should it be lazy?
|
||||
i > Self::LAZY_EXPIRE
|
||||
},
|
||||
)
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
}
|
||||
self.current_depth = self.max_depth;
|
||||
}
|
||||
|
||||
/// Determines if a [`Move`] at index `idx` is connected to `self.current_root`
|
||||
/// Returns `false` if `self.current_root` is None
|
||||
fn is_connected_to_root(&self, idx: usize) -> bool {
|
||||
if let Some(root) = self.current_root {
|
||||
let mut current = Some(idx);
|
||||
while let Some(parent_idx) = current {
|
||||
if parent_idx == root {
|
||||
return true;
|
||||
}
|
||||
current = self.arena[parent_idx].parent;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Creates children for a parent (`parent`), returns an iterator it's children's indexes
|
||||
fn generate_children(
|
||||
&mut self,
|
||||
parent_idx: usize,
|
||||
lazy_children: bool,
|
||||
) -> Option<impl Iterator<Item = usize>> {
|
||||
// early-exit if a winner for the parent already exists
|
||||
if self.arena[parent_idx].winner != Winner::None {
|
||||
return None;
|
||||
}
|
||||
|
||||
let new_color = !self.arena[parent_idx].color;
|
||||
let parent_lazy = self.arena[parent_idx].lazy_children;
|
||||
let mut new: Vec<Move> =
|
||||
// use [`Board::all_positions`] here instead of [`Board::possible_moves`]
|
||||
// because we use [`Board::what_if`] later and we want to reduce calls to [`Board::propegate_from_dry`]
|
||||
Board::all_positions()
|
||||
.flat_map(|(i, j)| self.arena[parent_idx].board.what_if(i, j, new_color).map(|x| (i, j, x)))
|
||||
.map(|(i, j, new_board)| Move {
|
||||
i,
|
||||
j,
|
||||
board: new_board,
|
||||
winner: new_board.game_winner(!self.arena[parent_idx].color),
|
||||
parent: Some(parent_idx),
|
||||
children: Vec::new(),
|
||||
value: 0,
|
||||
color: new_color,
|
||||
lazy_children,
|
||||
}).collect();
|
||||
|
||||
// negative, because we want the max value to be at the first index
|
||||
new.sort_by_key(|x| -x.compute_self_value(self.agent_color));
|
||||
|
||||
// keep the TOP_K children `self.agent_color`-color moves
|
||||
const TOP_K_CHILDREN: usize = 1;
|
||||
|
||||
// we want to keep only the best move of the agent
|
||||
if lazy_children && new_color == self.agent_color && new.len() > TOP_K_CHILDREN {
|
||||
// TODO! Move this to `extend_layers` so we can prune based on recursive [`Move`] value
|
||||
|
||||
new.drain(TOP_K_CHILDREN..);
|
||||
}
|
||||
|
||||
let start_idx = self.arena.len();
|
||||
if parent_lazy && !lazy_children {
|
||||
// this move's children are being regenerated after lazy child expiration, don't append first node
|
||||
if new.len() > 1 {
|
||||
self.arena.extend(new.drain(1..));
|
||||
} else {
|
||||
// nothing will be appended
|
||||
// even though it was sorted the first time around
|
||||
// there's still no more than one element (which is already in the arena)
|
||||
return None;
|
||||
}
|
||||
} else {
|
||||
self.arena.extend(new);
|
||||
}
|
||||
let new_indices = start_idx..self.arena.len();
|
||||
|
||||
self.arena[parent_idx].children.extend(new_indices.clone());
|
||||
|
||||
Some(new_indices)
|
||||
}
|
||||
|
||||
/// Given an index from `self.arena`, what depth is it at? 1-indexed (ROOT IS AT INDEX 1)
|
||||
fn depth_of(&self, node_idx: usize) -> usize {
|
||||
let mut depth = 0;
|
||||
let mut current = Some(node_idx);
|
||||
while let Some(parent_idx) = current {
|
||||
depth += 1;
|
||||
current = self.arena[parent_idx].parent;
|
||||
}
|
||||
|
||||
depth
|
||||
}
|
||||
|
||||
fn compute_values(&mut self, indexes: impl Iterator<Item = usize>) {
|
||||
// PERF! pre-organize all indexes based on what depth they're at
|
||||
// previously, I did a lookup map based on if a node was visited, still resulted in a full
|
||||
// O(n) iteration each depth
|
||||
let mut by_depth: Vec<Vec<usize>> = (0..=self.max_depth + 2).map(|_| Vec::new()).collect();
|
||||
for idx in indexes {
|
||||
let depth = self.depth_of(idx);
|
||||
// -1 because `depth_of` is one-indexed
|
||||
by_depth[depth - 1].push(idx);
|
||||
}
|
||||
|
||||
for (depth, nodes) in by_depth.into_iter().enumerate().rev() {
|
||||
for idx in nodes {
|
||||
let self_value =
|
||||
self.arena[idx].compute_self_value(self.agent_color) / (depth + 1) as i64;
|
||||
|
||||
let children_value = self.arena[idx]
|
||||
.children
|
||||
.iter()
|
||||
.map(|&child| self.arena[child].value)
|
||||
.sum::<i64>()
|
||||
.checked_div(self.arena[idx].children.len() as i64)
|
||||
.unwrap_or(0);
|
||||
|
||||
self.arena[idx].value = self_value + children_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn best_move(&self) -> Option<(usize, usize)> {
|
||||
self.current_root
|
||||
.and_then(|x| {
|
||||
self.arena[x]
|
||||
.children
|
||||
.iter()
|
||||
.max_by_key(|&&idx| self.arena[idx].value)
|
||||
})
|
||||
.inspect(|&&x| {
|
||||
assert_eq!(
|
||||
self.arena[x].color, self.agent_color,
|
||||
"selected move color should be the same as the color of the agent"
|
||||
);
|
||||
})
|
||||
.map(|&x| self.arena[x].coords())
|
||||
}
|
||||
|
||||
/// Updates `FutureMoves` based on the current state of the board
|
||||
/// The board is supposed to be after the opposing move
|
||||
pub fn update(&mut self, board: &Board) {
|
||||
let curr_board = self
|
||||
.arena
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, m)| {
|
||||
&m.board == board && (m.parent == self.current_root) && self.current_root.is_some()
|
||||
})
|
||||
.map(|(idx, _)| idx);
|
||||
|
||||
if let Some(curr_board_idx) = curr_board {
|
||||
self.update_root_idx(curr_board_idx);
|
||||
} else {
|
||||
println!("Generating root of FutureMoves");
|
||||
self.arena.clear();
|
||||
self.arena.push(Move {
|
||||
i: 0,
|
||||
j: 0,
|
||||
board: *board,
|
||||
winner: Winner::None,
|
||||
parent: None,
|
||||
children: Vec::new(),
|
||||
value: 0,
|
||||
color: !self.agent_color,
|
||||
lazy_children: false,
|
||||
});
|
||||
self.update_root_idx(0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the root based on the coordinate of the move
|
||||
/// Returns a boolean, `true` if the operation was successful, false if not
|
||||
#[must_use = "You must check if the root was properly set"]
|
||||
pub fn update_root_coord(&mut self, i: usize, j: usize) -> bool {
|
||||
self.arena
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, node)| {
|
||||
node.parent == self.current_root
|
||||
&& self.current_root.is_some()
|
||||
&& node.i == i
|
||||
&& node.j == j
|
||||
})
|
||||
.next()
|
||||
.map(|x| x.0)
|
||||
.inspect(|&root| self.update_root_idx(root))
|
||||
.is_some()
|
||||
}
|
||||
|
||||
fn update_root_idx(&mut self, idx: usize) {
|
||||
self.current_root = Some(idx);
|
||||
self.current_depth -= self.depth_of(idx) - 1;
|
||||
self.refocus_tree();
|
||||
self.extend_layers();
|
||||
self.compute_values(0..self.arena.len());
|
||||
}
|
||||
|
||||
/// Rebuilds the Arena based on `self.current_root`, prunes unrelated nodes
|
||||
fn refocus_tree(&mut self) {
|
||||
let Some(root) = self.current_root else {
|
||||
return;
|
||||
};
|
||||
|
||||
// make sure `root` doesn't reference another node
|
||||
self.arena[root].parent = None;
|
||||
|
||||
let mut retain = vec![false; self.arena.len()];
|
||||
|
||||
// stack is going to be AT MAXIMUM, the size of the array,
|
||||
// so lets just pre-allocate it
|
||||
let mut stack: Vec<usize> = Vec::with_capacity(self.arena.len());
|
||||
stack.push(root);
|
||||
|
||||
// traverse children of the current root
|
||||
while let Some(idx) = stack.pop() {
|
||||
retain[idx] = true;
|
||||
stack.extend(self.arena[idx].children.iter());
|
||||
}
|
||||
|
||||
let mut index_map = vec![None; self.arena.len()];
|
||||
|
||||
self.arena = retain
|
||||
.into_iter()
|
||||
.enumerate() // old_idx
|
||||
.zip(self.arena.drain(..))
|
||||
.flat_map(|((old_idx, keep), node)| keep.then_some((old_idx, node))) // filter out unrelated nodes
|
||||
.enumerate() // new_idx
|
||||
.map(|(new_idx, (old_idx, mut node))| {
|
||||
index_map[old_idx] = Some(new_idx);
|
||||
|
||||
if let Some(parent) = node.parent.as_mut() {
|
||||
if let Some(new_parent) = index_map[*parent] {
|
||||
*parent = new_parent;
|
||||
} else {
|
||||
// make sure we don't have dangling parents
|
||||
node.parent = None;
|
||||
}
|
||||
}
|
||||
|
||||
node.children.retain_mut(|c| {
|
||||
if let Some(new_c) = index_map[*c] {
|
||||
*c = new_c;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
node
|
||||
})
|
||||
.collect();
|
||||
|
||||
self.current_root = index_map[root];
|
||||
}
|
||||
}
|
||||
use crate::{agent::Agent, board::Board, future_moves::FutureMoves, piece::Piece};
|
||||
|
||||
pub struct ComplexAgent {
|
||||
color: Piece,
|
||||
@ -386,10 +7,10 @@ pub struct ComplexAgent {
|
||||
|
||||
impl ComplexAgent {
|
||||
pub const fn new(color: Piece) -> Self {
|
||||
const MAX_DEPTH: usize = 10;
|
||||
const MAX_DEPTH: usize = 15;
|
||||
Self {
|
||||
color,
|
||||
future_moves: FutureMoves::new(color, MAX_DEPTH),
|
||||
future_moves: FutureMoves::new(color, MAX_DEPTH, 4),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -398,7 +19,7 @@ impl Agent for ComplexAgent {
|
||||
fn next_move(&mut self, board: &Board) -> Option<(usize, usize)> {
|
||||
self.future_moves.update(board);
|
||||
|
||||
println!("# of moves stored: {}", self.future_moves.arena.len());
|
||||
println!("# of moves stored: {}", self.future_moves.len());
|
||||
|
||||
self.future_moves.best_move().inspect(|&(i, j)| {
|
||||
if !self.future_moves.update_root_coord(i, j) {
|
||||
|
||||
381
src/future_moves.rs
Normal file
381
src/future_moves.rs
Normal file
@ -0,0 +1,381 @@
|
||||
use indicatif::{ProgressIterator, ProgressStyle};
|
||||
|
||||
use crate::{
|
||||
board::{Board, Winner},
|
||||
piece::Piece,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Move {
|
||||
/// `i` position of move
|
||||
i: usize,
|
||||
|
||||
/// `j` position of move
|
||||
j: usize,
|
||||
|
||||
/// [`Board`] state after move is made
|
||||
board: Board,
|
||||
|
||||
/// Current winner of the match
|
||||
winner: Winner,
|
||||
|
||||
/// Index of this move's parent
|
||||
parent: Option<usize>,
|
||||
|
||||
/// Indices of this Move's Children
|
||||
children: Vec<usize>,
|
||||
|
||||
/// Value of this move
|
||||
value: i64,
|
||||
|
||||
color: Piece,
|
||||
|
||||
lazy_children: bool,
|
||||
}
|
||||
|
||||
impl Move {
|
||||
pub const fn coords(&self) -> (usize, usize) {
|
||||
(self.i, self.j)
|
||||
}
|
||||
|
||||
fn compute_self_value(&self, agent_color: Piece) -> i64 {
|
||||
let mut self_value = self.board.net_score(agent_color) as i64;
|
||||
|
||||
if self.winner == Winner::Player(!agent_color) {
|
||||
// if this board results in the opponent winning, MAJORLY negatively weigh this move
|
||||
// NOTE! this branch isn't completely deleted because if so, the bot wouldn't make a move.
|
||||
// We shouldn't prune branches because we still need to always react to the opponent's moves
|
||||
self_value = i64::MIN;
|
||||
} else if self.winner == Winner::Player(agent_color) {
|
||||
// results in a win for the agent
|
||||
self_value = i64::MAX;
|
||||
}
|
||||
// TODO! handle ties... what should they be valued as? maybe `i64::MAX / 2` or 0?
|
||||
|
||||
self_value
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FutureMoves {
|
||||
/// Arena containing all [`Move`]
|
||||
arena: Vec<Move>,
|
||||
|
||||
/// Index of the [`Move`] tree's root node
|
||||
current_root: Option<usize>,
|
||||
|
||||
/// Current generated depth of the Arena
|
||||
current_depth: usize,
|
||||
|
||||
/// Target depth of children to generate
|
||||
max_depth: usize,
|
||||
|
||||
/// How many deep should the lazy children status expire?
|
||||
lazy_expire: usize,
|
||||
|
||||
/// Color w.r.t
|
||||
agent_color: Piece,
|
||||
}
|
||||
|
||||
impl FutureMoves {
|
||||
pub const fn new(agent_color: Piece, max_depth: usize, lazy_expire: usize) -> Self {
|
||||
Self {
|
||||
arena: Vec::new(),
|
||||
current_root: None,
|
||||
current_depth: 0,
|
||||
max_depth,
|
||||
agent_color,
|
||||
lazy_expire,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.arena.len()
|
||||
}
|
||||
|
||||
/// Generate children for all children of `nodes`
|
||||
fn extend_layers(&mut self) {
|
||||
let mut next_nodes: Vec<usize> = (0..self.arena.len())
|
||||
// we want to select all nodes that don't have children, or are lazy (need to maybe be regenerated)
|
||||
.filter(|&idx| self.arena[idx].children.is_empty() || self.arena[idx].lazy_children)
|
||||
.filter(|&idx| self.is_connected_to_root(idx)) // put here so this will not extend needlessly before prunes
|
||||
.collect();
|
||||
|
||||
for i in self.current_depth..=self.max_depth {
|
||||
next_nodes = next_nodes
|
||||
.into_iter()
|
||||
.progress_with_style(
|
||||
ProgressStyle::with_template(&format!(
|
||||
"Generating children (depth: {}/{}): ({{pos}}/{{len}}) {{per_sec}}",
|
||||
i, self.max_depth
|
||||
))
|
||||
.unwrap(),
|
||||
)
|
||||
.flat_map(|node_idx| {
|
||||
self.generate_children(
|
||||
node_idx,
|
||||
if self.arena[node_idx].lazy_children {
|
||||
self.depth_of(node_idx) - 1
|
||||
} else {
|
||||
i
|
||||
} > self.lazy_expire,
|
||||
)
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
}
|
||||
self.current_depth = self.max_depth;
|
||||
}
|
||||
|
||||
/// Determines if a [`Move`] at index `idx` is connected to `self.current_root`
|
||||
/// Returns `false` if `self.current_root` is None
|
||||
fn is_connected_to_root(&self, idx: usize) -> bool {
|
||||
if let Some(root) = self.current_root {
|
||||
let mut current = Some(idx);
|
||||
while let Some(parent_idx) = current {
|
||||
if parent_idx == root {
|
||||
return true;
|
||||
}
|
||||
current = self.arena[parent_idx].parent;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Creates children for a parent (`parent`), returns an iterator it's children's indexes
|
||||
fn generate_children(
|
||||
&mut self,
|
||||
parent_idx: usize,
|
||||
lazy_children: bool,
|
||||
) -> Option<impl Iterator<Item = usize>> {
|
||||
// early-exit if a winner for the parent already exists
|
||||
if self.arena[parent_idx].winner != Winner::None {
|
||||
return None;
|
||||
}
|
||||
|
||||
let new_color = !self.arena[parent_idx].color;
|
||||
let parent_lazy = self.arena[parent_idx].lazy_children;
|
||||
let mut new: Vec<Move> =
|
||||
// use [`Board::all_positions`] here instead of [`Board::possible_moves`]
|
||||
// because we use [`Board::what_if`] later and we want to reduce calls to [`Board::propegate_from_dry`]
|
||||
Board::all_positions()
|
||||
.flat_map(|(i, j)| self.arena[parent_idx].board.what_if(i, j, new_color).map(|x| (i, j, x)))
|
||||
.map(|(i, j, new_board)| Move {
|
||||
i,
|
||||
j,
|
||||
board: new_board,
|
||||
winner: new_board.game_winner(!self.arena[parent_idx].color),
|
||||
parent: Some(parent_idx),
|
||||
children: Vec::new(),
|
||||
value: 0,
|
||||
color: new_color,
|
||||
lazy_children,
|
||||
}).collect();
|
||||
|
||||
// negative, because we want the max value to be at the first index
|
||||
new.sort_by_key(|x| -x.compute_self_value(self.agent_color));
|
||||
|
||||
// keep the TOP_K children `self.agent_color`-color moves
|
||||
const TOP_K_CHILDREN: usize = 1;
|
||||
|
||||
// we want to keep only the best move of the agent
|
||||
if lazy_children && new_color == self.agent_color && new.len() > TOP_K_CHILDREN {
|
||||
new.drain(TOP_K_CHILDREN..);
|
||||
}
|
||||
|
||||
let start_idx = self.arena.len();
|
||||
if parent_lazy && !lazy_children {
|
||||
self.arena[parent_idx].lazy_children = false;
|
||||
// this move's children are being regenerated after lazy child expiration, don't append first node
|
||||
if new.len() > 1 {
|
||||
self.arena.extend(new.drain(1..));
|
||||
} else {
|
||||
// nothing will be appended
|
||||
// even though it was sorted the first time around
|
||||
// there's still no more than one element (which is already in the arena)
|
||||
return None;
|
||||
}
|
||||
} else {
|
||||
self.arena.extend(new);
|
||||
}
|
||||
let new_indices = start_idx..self.arena.len();
|
||||
|
||||
self.arena[parent_idx].children.extend(new_indices.clone());
|
||||
|
||||
Some(new_indices)
|
||||
}
|
||||
|
||||
/// Given an index from `self.arena`, what depth is it at? 1-indexed (ROOT IS AT INDEX 1)
|
||||
fn depth_of(&self, node_idx: usize) -> usize {
|
||||
let mut depth = 0;
|
||||
let mut current = Some(node_idx);
|
||||
while let Some(parent_idx) = current {
|
||||
depth += 1;
|
||||
current = self.arena[parent_idx].parent;
|
||||
}
|
||||
|
||||
depth
|
||||
}
|
||||
|
||||
fn compute_values(&mut self, indexes: impl Iterator<Item = usize>) {
|
||||
// PERF! pre-organize all indexes based on what depth they're at
|
||||
// previously, I did a lookup map based on if a node was visited, still resulted in a full
|
||||
// O(n) iteration each depth
|
||||
let mut by_depth: Vec<Vec<usize>> = (0..=self.max_depth + 2).map(|_| Vec::new()).collect();
|
||||
for idx in indexes {
|
||||
let depth = self.depth_of(idx);
|
||||
// -1 because `depth_of` is one-indexed
|
||||
by_depth[depth - 1].push(idx);
|
||||
}
|
||||
|
||||
for (depth, nodes) in by_depth.into_iter().enumerate().rev() {
|
||||
for idx in nodes {
|
||||
let self_value =
|
||||
self.arena[idx].compute_self_value(self.agent_color) / (depth + 1) as i64;
|
||||
|
||||
let children_value = self.arena[idx]
|
||||
.children
|
||||
.iter()
|
||||
.map(|&child| self.arena[child].value)
|
||||
.sum::<i64>()
|
||||
.checked_div(self.arena[idx].children.len() as i64)
|
||||
.unwrap_or(0);
|
||||
|
||||
self.arena[idx].value = self_value + children_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn best_move(&self) -> Option<(usize, usize)> {
|
||||
self.current_root
|
||||
.and_then(|x| {
|
||||
self.arena[x]
|
||||
.children
|
||||
.iter()
|
||||
.max_by_key(|&&idx| self.arena[idx].value)
|
||||
})
|
||||
.inspect(|&&x| {
|
||||
assert_eq!(
|
||||
self.arena[x].color, self.agent_color,
|
||||
"selected move color should be the same as the color of the agent"
|
||||
);
|
||||
})
|
||||
.map(|&x| self.arena[x].coords())
|
||||
}
|
||||
|
||||
/// Updates `FutureMoves` based on the current state of the board
|
||||
/// The board is supposed to be after the opposing move
|
||||
pub fn update(&mut self, board: &Board) {
|
||||
let curr_board = self
|
||||
.arena
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, m)| {
|
||||
&m.board == board && (m.parent == self.current_root) && self.current_root.is_some()
|
||||
})
|
||||
.map(|(idx, _)| idx);
|
||||
|
||||
if let Some(curr_board_idx) = curr_board {
|
||||
self.update_root_idx(curr_board_idx);
|
||||
} else {
|
||||
// println!("Generating root of FutureMoves");
|
||||
self.arena.clear();
|
||||
self.arena.push(Move {
|
||||
i: 0,
|
||||
j: 0,
|
||||
board: *board,
|
||||
winner: Winner::None,
|
||||
parent: None,
|
||||
children: Vec::new(),
|
||||
value: 0,
|
||||
color: !self.agent_color,
|
||||
lazy_children: false,
|
||||
});
|
||||
self.update_root_idx(0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the root based on the coordinate of the move
|
||||
/// Returns a boolean, `true` if the operation was successful, false if not
|
||||
#[must_use = "You must check if the root was properly set"]
|
||||
pub fn update_root_coord(&mut self, i: usize, j: usize) -> bool {
|
||||
self.arena
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, node)| {
|
||||
node.parent == self.current_root
|
||||
&& self.current_root.is_some()
|
||||
&& node.i == i
|
||||
&& node.j == j
|
||||
})
|
||||
.map(|x| x.0)
|
||||
.inspect(|&root| self.update_root_idx(root))
|
||||
.is_some()
|
||||
}
|
||||
|
||||
fn update_root_idx(&mut self, idx: usize) {
|
||||
self.current_root = Some(idx);
|
||||
self.current_depth -= self.depth_of(idx) - 1;
|
||||
self.refocus_tree();
|
||||
self.extend_layers();
|
||||
self.compute_values(0..self.arena.len());
|
||||
}
|
||||
|
||||
/// Rebuilds the Arena based on `self.current_root`, prunes unrelated nodes
|
||||
fn refocus_tree(&mut self) {
|
||||
let Some(root) = self.current_root else {
|
||||
return;
|
||||
};
|
||||
|
||||
// make sure `root` doesn't reference another node
|
||||
self.arena[root].parent = None;
|
||||
|
||||
let mut retain = vec![false; self.arena.len()];
|
||||
|
||||
// stack is going to be AT MAXIMUM, the size of the array,
|
||||
// so lets just pre-allocate it
|
||||
let mut stack: Vec<usize> = Vec::with_capacity(self.arena.len());
|
||||
stack.push(root);
|
||||
|
||||
// traverse children of the current root
|
||||
while let Some(idx) = stack.pop() {
|
||||
retain[idx] = true;
|
||||
stack.extend(self.arena[idx].children.iter());
|
||||
}
|
||||
|
||||
let mut index_map = vec![None; self.arena.len()];
|
||||
|
||||
self.arena = retain
|
||||
.into_iter()
|
||||
.enumerate() // old_idx
|
||||
.zip(self.arena.drain(..))
|
||||
.flat_map(|((old_idx, keep), node)| keep.then_some((old_idx, node))) // filter out unrelated nodes
|
||||
.enumerate() // new_idx
|
||||
.map(|(new_idx, (old_idx, mut node))| {
|
||||
index_map[old_idx] = Some(new_idx);
|
||||
|
||||
if let Some(parent) = node.parent.as_mut() {
|
||||
if let Some(new_parent) = index_map[*parent] {
|
||||
*parent = new_parent;
|
||||
} else {
|
||||
// make sure we don't have dangling parents
|
||||
node.parent = None;
|
||||
}
|
||||
}
|
||||
|
||||
node.children.retain_mut(|c| {
|
||||
if let Some(new_c) = index_map[*c] {
|
||||
*c = new_c;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
node
|
||||
})
|
||||
.collect();
|
||||
|
||||
self.current_root = index_map[root];
|
||||
}
|
||||
}
|
||||
8
src/lib.rs
Normal file
8
src/lib.rs
Normal file
@ -0,0 +1,8 @@
|
||||
mod agent;
|
||||
mod bitboard;
|
||||
pub mod board;
|
||||
mod complexagent;
|
||||
pub mod future_moves;
|
||||
mod game;
|
||||
mod misc;
|
||||
pub mod piece;
|
||||
@ -5,6 +5,7 @@ mod agent;
|
||||
mod bitboard;
|
||||
mod board;
|
||||
mod complexagent;
|
||||
pub mod future_moves;
|
||||
mod game;
|
||||
mod misc;
|
||||
mod piece;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user