From 10de749e1db714c4833c428d884fef990b78a820 Mon Sep 17 00:00:00 2001 From: jackjohn7 Date: Thu, 30 Oct 2025 04:26:49 -0500 Subject: [PATCH] cleanup, add bench --- .cargo/config.toml | 2 + Cargo.lock | 437 ++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + iagorithms/src/lib.rs | 1 - othello/Cargo.toml | 7 + othello/benches/generation.rs | 199 ++++++++++++++++ othello/src/board.rs | 39 ++- 7 files changed, 678 insertions(+), 8 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 othello/benches/generation.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..5979e01 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[alias] +test-all = "test --workspace --all-targets" diff --git a/Cargo.lock b/Cargo.lock index eaac110..c98e6fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,24 +2,63 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + [[package]] name = "allocator-api2" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[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.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + [[package]] name = "bitflags" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + [[package]] name = "cassowary" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "castaway" version = "0.2.4" @@ -35,6 +74,58 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[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.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" + [[package]] name = "compact_str" version = "0.8.1" @@ -49,6 +140,64 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "criterion" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338" +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 = "crossterm" version = "0.28.1" @@ -74,6 +223,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "darling" version = "0.20.11" @@ -143,6 +298,17 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -210,6 +376,16 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "js-sys" +version = "0.3.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "libc" version = "0.2.177" @@ -246,6 +422,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + [[package]] name = "mio" version = "1.0.4" @@ -264,10 +446,32 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c0f887dfb09c7315d803a0ceac77f27da5c84213c9b170dc8ae88d2b6b1739a" +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + [[package]] name = "othello" version = "0.1.0" dependencies = [ + "criterion", "ratatui", ] @@ -300,6 +504,34 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[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 = "proc-macro2" version = "1.0.102" @@ -339,6 +571,26 @@ dependencies = [ "unicode-width 0.2.0", ] +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -348,6 +600,35 @@ dependencies = [ "bitflags", ] +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + [[package]] name = "rustix" version = "0.38.44" @@ -373,12 +654,64 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[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 = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + [[package]] name = "signal-hook" version = "0.3.18" @@ -460,6 +793,16 @@ dependencies = [ "unicode-ident", ] +[[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.20" @@ -495,12 +838,77 @@ 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.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasm-bindgen" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" @@ -517,6 +925,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -610,3 +1027,23 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 5db351c..d43b039 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,4 @@ members = ["iagorithms", "othello"] [workspace.dependencies] neorusticus = "0.1.3" ratatui = "0.29.0" +criterion = "0.7.0" diff --git a/iagorithms/src/lib.rs b/iagorithms/src/lib.rs index 6ec1903..5aede9a 100644 --- a/iagorithms/src/lib.rs +++ b/iagorithms/src/lib.rs @@ -220,7 +220,6 @@ mod tests { } println!("\n=== All tests completed ==="); - panic!(); Ok(()) } } diff --git a/othello/Cargo.toml b/othello/Cargo.toml index 3618813..067111c 100644 --- a/othello/Cargo.toml +++ b/othello/Cargo.toml @@ -13,3 +13,10 @@ path = "src/main.rs" [dependencies] ratatui.workspace = true + +[dev-dependencies] +criterion.workspace = true + +[[bench]] +name = "generation" +harness = false diff --git a/othello/benches/generation.rs b/othello/benches/generation.rs new file mode 100644 index 0000000..f3a39b3 --- /dev/null +++ b/othello/benches/generation.rs @@ -0,0 +1,199 @@ +use std::hint::black_box; + +use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; +use othello::{board::BitBoard, game::Team}; + +const JONS: [&'static str; 20] = [ + "///3bw/3wb", + "///////6bw", + "///////wb", + "wwwwwwww/wwwwwwww/////", + "//1b2w/1bwwww/1bwbww/1bwwww/1b3/", + "///3bw/2bbw/3b//", + "///////bww", + "////bwwwww1b///", + "www/wbw/www////", + "///3b/2bwb/3b//", + "5bw//////", + "///////wb", + "7w/6b/5b", + "w/1b/2b", + "/////5w/6w/7b", + "/////2w/1w/b", + "b/w/w/w//b//", + "////bwwwww1b///", + "///3w///", + "///////", +]; + +const JON_NAMES: [&str; 20] = [ + "starting_position", + "near_endgame", + "corner_setup_a1", + "white_dominance", + "complex_midgame", + "after_first_move", + "edge_capture", + "long_horizontal", + "surrounded_black", + "white_surrounded", + "corner_setup_h8", + "corner_setup_h1", + "diagonal_line", + "short_diagonal", + "anti_diagonal", + "anti_diagonal_short", + "vertical_capture", + "long_horizontal_alt", + "single_piece", + "empty_board", +]; + +fn bench_available_black(c: &mut Criterion) { + let mut group = c.benchmark_group("available_black"); + + for (i, jon) in JONS.iter().enumerate() { + let bb = BitBoard::from_jon(jon).expect("Valid JON"); + group.bench_with_input( + BenchmarkId::new("position", JON_NAMES[i]), + &bb, + |b, board| { + b.iter(|| black_box(board.available(black_box(Team::Black)))); + }, + ); + } + + group.finish(); +} + +fn bench_available_white(c: &mut Criterion) { + let mut group = c.benchmark_group("available_white"); + + for (i, jon) in JONS.iter().enumerate() { + let bb = BitBoard::from_jon(jon).expect("Valid JON"); + group.bench_with_input( + BenchmarkId::new("position", JON_NAMES[i]), + &bb, + |b, board| { + b.iter(|| black_box(board.available(black_box(Team::White)))); + }, + ); + } + + group.finish(); +} + +fn bench_available_both_teams(c: &mut Criterion) { + let mut group = c.benchmark_group("available_both_teams"); + + for (i, jon) in JONS.iter().enumerate() { + let bb = BitBoard::from_jon(jon).expect("Valid JON"); + group.bench_with_input( + BenchmarkId::new("position", JON_NAMES[i]), + &bb, + |b, board| { + b.iter(|| { + let black_moves = black_box(board.available(Team::Black)); + let white_moves = black_box(board.available(Team::White)); + black_box((black_moves, white_moves)) + }); + }, + ); + } + + group.finish(); +} + +fn bench_available_starting_only(c: &mut Criterion) { + let bb = BitBoard::default(); + + c.bench_function("starting_position_black", |b| { + b.iter(|| black_box(bb.available(black_box(Team::Black)))); + }); + + c.bench_function("starting_position_white", |b| { + b.iter(|| black_box(bb.available(black_box(Team::White)))); + }); +} + +fn bench_available_worst_case(c: &mut Criterion) { + // Positions that might be slower due to more pieces or complexity + let worst_cases = [ + ("complex_midgame", "//1b2w/1bwwww/1bwbww/1bwwww/1b3/"), + ("white_dominance", "wwwwwwww/wwwwwwww/////"), + ("long_capture", "////bwwwww1b///"), + ]; + + let mut group = c.benchmark_group("available_worst_case"); + + for (name, jon) in worst_cases.iter() { + let bb = BitBoard::from_jon(jon).expect("Valid JON"); + group.bench_with_input(BenchmarkId::new("black", name), &bb, |b, board| { + b.iter(|| black_box(board.available(Team::Black))); + }); + group.bench_with_input(BenchmarkId::new("white", name), &bb, |b, board| { + b.iter(|| black_box(board.available(Team::White))); + }); + } + + group.finish(); +} + +fn bench_available_best_case(c: &mut Criterion) { + // Positions that should be fast (empty board, single piece, etc.) + let best_cases = [ + ("empty_board", "///////"), + ("single_piece", "///3w///"), + ("corner_only", "///////wb"), + ]; + + let mut group = c.benchmark_group("available_best_case"); + + for (name, jon) in best_cases.iter() { + let bb = BitBoard::from_jon(jon).expect("Valid JON"); + group.bench_with_input(BenchmarkId::new("black", name), &bb, |b, board| { + b.iter(|| black_box(board.available(Team::Black))); + }); + group.bench_with_input(BenchmarkId::new("white", name), &bb, |b, board| { + b.iter(|| black_box(board.available(Team::White))); + }); + } + + group.finish(); +} + +fn bench_available_all_positions_sequential(c: &mut Criterion) { + let boards: Vec = JONS + .iter() + .map(|jon| BitBoard::from_jon(jon).expect("Valid JON")) + .collect(); + + c.bench_function("all_positions_black_sequential", |b| { + b.iter(|| { + for board in &boards { + black_box(board.available(Team::Black)); + } + }); + }); + + c.bench_function("all_positions_white_sequential", |b| { + b.iter(|| { + for board in &boards { + black_box(board.available(Team::White)); + } + }); + }); +} + +criterion_group!( + benches, + bench_available_black, + bench_available_white, + bench_available_both_teams, + bench_available_starting_only, + bench_available_worst_case, + bench_available_best_case, + bench_available_all_positions_sequential, +); + +criterion_main!(benches); diff --git a/othello/src/board.rs b/othello/src/board.rs index fa3f28d..b2860f0 100644 --- a/othello/src/board.rs +++ b/othello/src/board.rs @@ -151,7 +151,21 @@ fn shift_in_direction(magnitude: i8, value: Board) -> Board { impl BitBoard { /// Convert from a JON (Jack Othello Notation) string into a usable board. - pub fn from_jon(fen: &str) -> Result { + /// Format: + /// + /// | Symbol or Pattern | Meaning | + /// | ------------------ | --------------------------------- | + /// | `/` | _next line_ | + /// | `[0-9]` | _jump squares_ | + /// | `[wWbB]` | _white or black disc at position_ | + /// + /// Example: `///3bw/3wb///` is the JON for the starting position. + /// It means three empty ranks, three empty spaces followed by a black disc, + /// a white disc, next rank, three empty spaces followed by a white disc, + /// a black disc, and then three empty ranks. It also technically could be + /// truncated after the second `b` as `///3bw/3wb` since trailing rank is + /// optional. + pub fn from_jon(fen: &str) -> Result { let mut boards: [Board; 2] = [0, 0]; let mut line: u8 = 7; let mut rank_idx: u8 = 0; @@ -159,7 +173,7 @@ impl BitBoard { for c in fen.chars().into_iter() { match c.to_ascii_lowercase() { '/' => { - line -= 1; + line = line.checked_sub(1).ok_or("Malformed JON: Too many ranks")?; rank_idx = 0; rank_jump = 0; } @@ -176,17 +190,21 @@ impl BitBoard { '0'..'9' => { rank_jump = c .to_digit(10u32) - .ok_or("Failed to parse integer from character")? + .ok_or("JON Parsing error: Failed to parse integer from character")? as u8; } - _ => { - return Err("Malformed FEN: Unexpected character encountered"); + c => { + return Err(format!( + "Malformed JON: Unexpected character encountered ({})", + c + )); } } } Ok(Self { boards }) } - /// Compute board with valid moves marked + /// Compute board with valid moves marked using only bitwise operations. + /// This has constant time complexity and is unbelievably fast. pub fn available(&self, current_team: Team) -> Board { // bitwise OR gives spots with either white OR black discs // bitwise NEG gives the spots with neither white nor black discs @@ -370,8 +388,15 @@ mod tests { #[test] fn jon_works() { let bb = BitBoard::from_jon("///3bw/3wb///").expect("Starting board should be valid"); - println!("{}", bb); assert_eq!(bb.boards, BitBoard::default().boards); + // trailing slashes are optional + let bb = BitBoard::from_jon("///3bw/3wb") + .expect("Starting board without trailing slashes should be valid"); + assert_eq!(bb.boards, BitBoard::default().boards); + // test with too many slashes + assert!(BitBoard::from_jon("////////").is_err()); + // test with unexpected char + assert!(BitBoard::from_jon("//c/////").is_err()); } #[test]