From d7d873290453ac99c6f8f86de3d510872dde08ae Mon Sep 17 00:00:00 2001 From: jackjohn7 Date: Tue, 4 Nov 2025 01:04:50 -0600 Subject: [PATCH] restructured, flips working, benchmarking flips, got some basic terminal rendering --- Cargo.lock | 531 +-------------------- Cargo.toml | 29 +- {othello/benches => benches}/generation.rs | 2 +- benches/reversals.rs | 24 + iagorithms/.gitignore | 1 - iagorithms/Cargo.toml | 7 - iagorithms/src/lib.rs | 225 --------- othello/.gitignore | 1 - othello/Cargo.toml | 22 - othello/src/game.rs | 17 - othello/src/main.rs | 3 - othello/src/board.rs => src/board/mod.rs | 172 ++++++- src/board/view.rs | 17 + src/cli/mod.rs | 30 ++ src/game.rs | 67 +++ {othello/src => src}/lib.rs | 0 src/main.rs | 5 + 17 files changed, 331 insertions(+), 822 deletions(-) rename {othello/benches => benches}/generation.rs (99%) create mode 100644 benches/reversals.rs delete mode 100644 iagorithms/.gitignore delete mode 100644 iagorithms/Cargo.toml delete mode 100644 iagorithms/src/lib.rs delete mode 100644 othello/.gitignore delete mode 100644 othello/Cargo.toml delete mode 100644 othello/src/game.rs delete mode 100644 othello/src/main.rs rename othello/src/board.rs => src/board/mod.rs (76%) create mode 100644 src/board/view.rs create mode 100644 src/cli/mod.rs create mode 100644 src/game.rs rename {othello/src => src}/lib.rs (100%) create mode 100644 src/main.rs diff --git a/Cargo.lock b/Cargo.lock index c98e6fd..f82e195 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,12 +11,6 @@ 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" @@ -29,45 +23,30 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + [[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" -dependencies = [ - "rustversion", -] - [[package]] name = "cfg-if" version = "1.0.4" @@ -126,20 +105,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" -[[package]] -name = "compact_str" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" -dependencies = [ - "castaway", - "cfg-if", - "itoa", - "rustversion", - "ryu", - "static_assertions", -] - [[package]] name = "criterion" version = "0.7.0" @@ -198,106 +163,18 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" -[[package]] -name = "crossterm" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" -dependencies = [ - "bitflags", - "crossterm_winapi", - "mio", - "parking_lot", - "rustix", - "signal-hook", - "signal-hook-mio", - "winapi", -] - -[[package]] -name = "crossterm_winapi" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" -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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn", -] - -[[package]] -name = "darling_macro" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" -dependencies = [ - "darling_core", - "quote", - "syn", -] - [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "half" version = "2.7.1" @@ -309,58 +186,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "iagorithms" -version = "0.1.0" -dependencies = [ - "neorusticus", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "indoc" -version = "2.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" -dependencies = [ - "rustversion", -] - -[[package]] -name = "instability" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435d80800b936787d62688c927b6490e887c7ef5ff9ce922c6c6050fca75eb9a" -dependencies = [ - "darling", - "indoc", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "itertools" version = "0.13.0" @@ -386,66 +211,12 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "libc" -version = "0.2.177" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" - -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" - -[[package]] -name = "lru" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" -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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" -dependencies = [ - "libc", - "log", - "wasi", - "windows-sys 0.59.0", -] - -[[package]] -name = "neorusticus" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f887dfb09c7315d803a0ceac77f27da5c84213c9b170dc8ae88d2b6b1739a" - [[package]] name = "num-traits" version = "0.2.19" @@ -471,39 +242,10 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" name = "othello" version = "0.1.0" dependencies = [ + "anyhow", "criterion", - "ratatui", ] -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "plotters" version = "0.3.7" @@ -534,9 +276,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.102" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e0f6df8eaa422d97d72edcd152e1451618fed47fabbdbd5a8864167b1d4aff7" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] @@ -550,27 +292,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "ratatui" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" -dependencies = [ - "bitflags", - "cassowary", - "compact_str", - "crossterm", - "indoc", - "instability", - "itertools", - "lru", - "paste", - "strum", - "unicode-segmentation", - "unicode-truncate", - "unicode-width 0.2.0", -] - [[package]] name = "rayon" version = "1.11.0" @@ -591,15 +312,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags", -] - [[package]] name = "regex" version = "1.12.2" @@ -629,19 +341,6 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.59.0", -] - [[package]] name = "rustversion" version = "1.0.22" @@ -663,12 +362,6 @@ 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" @@ -712,76 +405,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "signal-hook" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-mio" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" -dependencies = [ - "libc", - "mio", - "signal-hook", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" -dependencies = [ - "libc", -] - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn", -] - [[package]] name = "syn" version = "2.0.108" @@ -805,38 +428,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" - -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "unicode-truncate" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" -dependencies = [ - "itertools", - "unicode-segmentation", - "unicode-width 0.1.14", -] - -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - -[[package]] -name = "unicode-width" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "walkdir" @@ -848,12 +442,6 @@ dependencies = [ "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" @@ -909,52 +497,21 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -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", + "windows-sys", ] -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -964,70 +521,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -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" diff --git a/Cargo.toml b/Cargo.toml index d43b039..71ab6a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,26 @@ -[workspace] -members = ["iagorithms", "othello"] +[package] +name = "othello" +version = "0.1.0" +edition = "2024" -[workspace.dependencies] -neorusticus = "0.1.3" -ratatui = "0.29.0" +[lib] +name = "othello" +path = "src/lib.rs" + +[[bin]] +name = "othello-gui" +path = "src/main.rs" + +[dependencies] +anyhow = "1.0.100" + +[dev-dependencies] criterion = "0.7.0" + +[[bench]] +name = "generation" +harness = false + +[[bench]] +name = "reversals" +harness = false diff --git a/othello/benches/generation.rs b/benches/generation.rs similarity index 99% rename from othello/benches/generation.rs rename to benches/generation.rs index f3a39b3..19280de 100644 --- a/othello/benches/generation.rs +++ b/benches/generation.rs @@ -3,7 +3,7 @@ use std::hint::black_box; use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; use othello::{board::BitBoard, game::Team}; -const JONS: [&'static str; 20] = [ +const JONS: [&str; 20] = [ "///3bw/3wb", "///////6bw", "///////wb", diff --git a/benches/reversals.rs b/benches/reversals.rs new file mode 100644 index 0000000..4739682 --- /dev/null +++ b/benches/reversals.rs @@ -0,0 +1,24 @@ +use std::hint::black_box; + +use criterion::{Criterion, criterion_group, criterion_main}; +use othello::{ + board::{Board, squares::*}, + game::Game, +}; + +const PLAYS: [Board; 15] = [F5, F4, E3, F3, G4, G5, H4, H5, E6, D3, C4, C5, D6, C6, B5]; + +fn bench_available_all_positions_sequential(c: &mut Criterion) { + c.bench_function("play_four_move_opening", |b| { + b.iter(|| { + let mut game = Game::default(); + for play in PLAYS { + black_box(game.play(play)); + } + }); + }); +} + +criterion_group!(benches, bench_available_all_positions_sequential,); + +criterion_main!(benches); diff --git a/iagorithms/.gitignore b/iagorithms/.gitignore deleted file mode 100644 index ea8c4bf..0000000 --- a/iagorithms/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target diff --git a/iagorithms/Cargo.toml b/iagorithms/Cargo.toml deleted file mode 100644 index 9300154..0000000 --- a/iagorithms/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "iagorithms" -version = "0.1.0" -edition = "2024" - -[dependencies] -neorusticus.workspace = true diff --git a/iagorithms/src/lib.rs b/iagorithms/src/lib.rs deleted file mode 100644 index 5aede9a..0000000 --- a/iagorithms/src/lib.rs +++ /dev/null @@ -1,225 +0,0 @@ -use neorusticus::{PrologEngine, quick_query}; -use std::error::Error; - -#[cfg(test)] -mod tests { - use super::*; - #[test] - fn init() -> Result<(), Box> { - println!("=== Neorusticus Prolog Engine Demo ===\n"); - - // Create engine with conservative limits for safety - let mut engine = PrologEngine::with_limits(50); - - // Test basic parsing with error handling - println!("=== Testing Basic Operations ==="); - - // Test successful operations - match engine.parse_and_add("parent(tom, bob).") { - Ok(()) => println!("✓ Successfully added: parent(tom, bob)."), - Err(e) => { - println!("✗ Failed to add clause:"); - engine.print_error(&e); - } - } - - match engine.parse_and_add("parent(bob, ann).") { - Ok(()) => println!("✓ Successfully added: parent(bob, ann)."), - Err(e) => { - println!("✗ Failed to add clause:"); - engine.print_error(&e); - } - } - - // Test error cases - println!("\n=== Testing Error Handling ==="); - - // Test syntax errors - println!("Testing syntax errors:"); - - if let Err(e) = engine.parse_and_add("parent(tom bob).") { - // Missing comma - println!("✓ Caught syntax error: {}", e); - } - - if let Err(e) = engine.parse_and_add("parent(tom, bob") { - // Missing closing paren - println!("✓ Caught delimiter error: {}", e); - } - - if let Err(e) = engine.parse_and_add("parent(tom, bob)") { - // Missing dot - println!("✓ Caught missing dot error: {}", e); - } - - if let Err(e) = engine.parse_and_add("parent(tom, 999999999999999999999).") { - // Number too large - println!("✓ Caught number overflow error: {}", e); - } - - // Test arithmetic operations with error handling - println!("\n=== Testing Arithmetic Error Handling ==="); - - engine.parse_and_add("test_arith :- X is 5 + 3.")?; - - // Test division by zero - if let Err(e) = engine.parse_query("X is 5 // 0.") { - println!("✓ Caught division by zero: {}", e); - } - - // Test uninstantiated variable in arithmetic - if let Err(e) = engine.parse_query("X is Y + 1.") { - println!("✓ Caught uninstantiated variable: {}", e); - } - - // Test type mismatch - if let Err(e) = engine.parse_query("X is atom + 1.") { - println!("✓ Caught type mismatch: {}", e); - } - - // Test successful arithmetic - match engine.parse_query("X is 2 + 3 * 4.") { - Ok(solutions) => { - println!("✓ Arithmetic success:"); - engine.print_solutions(&solutions, &["X".to_string()]); - } - Err(e) => { - println!("✗ Unexpected arithmetic error:"); - engine.print_boxed_error(&e); - } - } - - // Test list operations with error handling - println!("\n=== Testing List Error Handling ==="); - - // Test invalid list structure - if let Err(e) = engine.parse_query("member(X, not_a_list).") { - println!("✓ Caught invalid list structure: {}", e); - } - - // Test uninstantiated list - if let Err(e) = engine.parse_query("length(L, 3).") { - println!("✓ Caught uninstantiated list: {}", e); - } - - // Test successful list operations - match engine.parse_query("append([1, 2], [3, 4], X).") { - Ok(solutions) => { - println!("✓ List append success:"); - engine.print_solutions(&solutions, &["X".to_string()]); - } - Err(e) => { - println!("✗ Unexpected list error:"); - engine.print_boxed_error(&e); - } - } - - // Test predicate suggestions - println!("\n=== Testing Predicate Suggestions ==="); - - if let Err(e) = engine.parse_query("lentgh([1, 2, 3], X).") { - // Typo in "length" - println!("✓ Predicate suggestion: {}", e); - } - - if let Err(e) = engine.parse_query("appendd([1], [2], X).") { - // Typo in "append" - println!("✓ Predicate suggestion: {}", e); - } - - // Test stack overflow protection with a safer approach - println!("\n=== Testing Stack Overflow Protection ==="); - - // Use a very conservative engine for this test - let mut test_engine = PrologEngine::with_limits(5); - - // Add the infinite recursion rule to the test engine only - match test_engine.parse_and_add("infinite(X) :- infinite(X).") { - Ok(_) => { - // Now test the query with the dangerous rule - match test_engine.parse_query("infinite(test).") { - Ok(_) => println!("✗ Failed to catch infinite recursion"), - Err(e) => println!("✓ Caught infinite recursion: {}", e), - } - } - Err(e) => { - println!("✗ Failed to add infinite rule: {}", e); - } - } - - // Test cut operations - println!("\n=== Testing Cut Operations ==="); - - engine.parse_and_add("max(X, Y, X) :- X >= Y, !.")?; - engine.parse_and_add("max(X, Y, Y).")?; - - match engine.parse_query("max(5, 3, Z).") { - Ok(solutions) => { - println!("✓ Cut operation success:"); - engine.print_solutions(&solutions, &["Z".to_string()]); - } - Err(e) => { - println!("✗ Unexpected cut error:"); - engine.print_boxed_error(&e); - } - } - - // Display engine statistics - println!("\n=== Engine Statistics ==="); - println!("{}", engine.get_stats()); - - // Test complex query with multiple error possibilities - println!("\n=== Testing Complex Query ==="); - - engine.parse_and_add("complex(X, Y) :- X > 0, Y is X * 2, Y < 20.")?; - - match engine.parse_query("complex(5, Z).") { - Ok(solutions) => { - println!("✓ Complex query success:"); - engine.print_solutions(&solutions, &["Z".to_string()]); - } - Err(e) => { - println!("✗ Complex query error:"); - engine.print_boxed_error(&e); - } - } - - // Test recovery from errors - println!("\n=== Testing Error Recovery ==="); - - // Even after errors, the engine should continue to work - match engine.parse_query("parent(tom, X).") { - Ok(solutions) => { - println!("✓ Engine recovered, normal operation:"); - engine.print_solutions(&solutions, &["X".to_string()]); - } - Err(e) => { - println!("✗ Engine failed to recover:"); - engine.print_boxed_error(&e); - } - } - - // Demonstrate the quick_query convenience function - println!("\n=== Testing Quick Query Function ==="); - - let clauses = &[ - "ancestor(X, Y) :- parent(X, Y).", - "ancestor(X, Z) :- parent(X, Y), ancestor(Y, Z).", - "parent(alice, bob).", - "parent(bob, charlie).", - "parent(charlie, diana).", - ]; - - match quick_query(clauses, "ancestor(alice, diana).") { - Ok(solutions) => { - println!("✓ Quick query found {} solutions", solutions.len()); - } - Err(e) => { - println!("✗ Quick query failed: {}", e); - } - } - - println!("\n=== All tests completed ==="); - Ok(()) - } -} diff --git a/othello/.gitignore b/othello/.gitignore deleted file mode 100644 index ea8c4bf..0000000 --- a/othello/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target diff --git a/othello/Cargo.toml b/othello/Cargo.toml deleted file mode 100644 index 067111c..0000000 --- a/othello/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "othello" -version = "0.1.0" -edition = "2024" - -[lib] -name = "othello" -path = "src/lib.rs" - -[[bin]] -name = "othello-tui" -path = "src/main.rs" - -[dependencies] -ratatui.workspace = true - -[dev-dependencies] -criterion.workspace = true - -[[bench]] -name = "generation" -harness = false diff --git a/othello/src/game.rs b/othello/src/game.rs deleted file mode 100644 index 7d82aa7..0000000 --- a/othello/src/game.rs +++ /dev/null @@ -1,17 +0,0 @@ -#[repr(u8)] -#[derive(Copy, Clone)] -pub enum Team { - Black, - White, -} - -impl Team { - /// Just return the other team or the next team. - /// This is useful for modeling state transfer - pub fn next(&self) -> Self { - match self { - Team::Black => Team::White, - Team::White => Team::Black, - } - } -} diff --git a/othello/src/main.rs b/othello/src/main.rs deleted file mode 100644 index e7a11a9..0000000 --- a/othello/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -} diff --git a/othello/src/board.rs b/src/board/mod.rs similarity index 76% rename from othello/src/board.rs rename to src/board/mod.rs index b2860f0..630d9d5 100644 --- a/othello/src/board.rs +++ b/src/board/mod.rs @@ -3,7 +3,12 @@ use std::fmt::{Debug, Display}; -use crate::game::Team; +use crate::{ + board::view::{Overlay, View}, + game::Team, +}; + +pub mod view; pub type Board = u64; @@ -25,20 +30,19 @@ pub type Board = u64; // shifting to the left to bring it to match our current focus. Thus: // Positive => SHL // Negative => SHR -const DIRECTIONS: [i8; 8] = [9, 8, 7, 1, -1, -7, -8, -9]; // simply negating the A file const NOT_A_FILE: Board = 0x7F7F7F7F7F7F7F7F; // simply negating the H file const NOT_H_FILE: Board = 0xFEFEFEFEFEFEFEFE; -const DIRECTION_MASKS: [Board; 8] = [ - NOT_A_FILE, // 9 (up right) - Board::MAX, // 8 (up) - NOT_H_FILE, // 7 (up left) - NOT_A_FILE, // 1 (right) - NOT_H_FILE, // -1 (left) - NOT_A_FILE, // -7 (down right) - Board::MAX, // -8 (down) - NOT_H_FILE, // -9 (down left) +const SHIFT_MASK_COMBOS: [(i8, Board); 8] = [ + (9, NOT_A_FILE), // 9 (up right) + (8, Board::MAX), // 8 (up) + (7, NOT_H_FILE), // 7 (up left) + (1, NOT_A_FILE), // 1 (right) + (-1, NOT_H_FILE), // -1 (left) + (-7, NOT_A_FILE), // -7 (down right) + (-8, Board::MAX), // -8 (down) + (-9, NOT_H_FILE), // -9 (down left) ]; /// Represents the board state using two integers. This allows us to reduce the @@ -75,10 +79,11 @@ const DIRECTION_MASKS: [Board; 8] = [ /// and files are to be rendered or oriented due to the symmetrical nature /// of Othello, but I stick to rigid Chess-esque conventions for the sake of my /// sanity. +#[derive(Copy, Clone, PartialEq)] pub struct BitBoard { /// Contains all boards for game. In this case, there are only two. /// The first is black. The second is white. - boards: [Board; 2], + pub boards: [Board; 2], } impl Default for BitBoard { @@ -131,7 +136,7 @@ impl Display for BitBoard { .unwrap(); } if r != 0 { - write!(f, "\n").unwrap(); + writeln!(f).unwrap(); } } write!(f, "\n abcdefgh").unwrap(); @@ -141,15 +146,21 @@ impl Display for BitBoard { fn shift_in_direction(magnitude: i8, value: Board) -> Board { if magnitude >= 0 { - let (result, _) = value.overflowing_shl(magnitude.abs() as u32); + let (result, _) = value.overflowing_shl(magnitude.unsigned_abs() as u32); result } else { - let (result, _) = value.overflowing_shr(magnitude.abs() as u32); + let (result, _) = value.overflowing_shr(magnitude.unsigned_abs() as u32); result } } impl BitBoard { + /// Create a new BitBoard from provided team boards. + pub fn new(black: Board, white: Board) -> Self { + Self { + boards: [black, white], + } + } /// Convert from a JON (Jack Othello Notation) string into a usable board. /// Format: /// @@ -170,7 +181,7 @@ impl BitBoard { let mut line: u8 = 7; let mut rank_idx: u8 = 0; let mut rank_jump: u8 = 0; - for c in fen.chars().into_iter() { + for c in fen.chars() { match c.to_ascii_lowercase() { '/' => { line = line.checked_sub(1).ok_or("Malformed JON: Too many ranks")?; @@ -187,7 +198,7 @@ impl BitBoard { rank_idx += rank_jump + 1; rank_jump = 0; } - '0'..'9' => { + '0'..='8' => { rank_jump = c .to_digit(10u32) .ok_or("JON Parsing error: Failed to parse integer from character")? @@ -203,6 +214,16 @@ impl BitBoard { } Ok(Self { boards }) } + + pub fn player_at(&self, rank: u8, file: u8) -> Option { + if self.boards[0] & (1 << (rank * 8 + file)) != 0 { + Some(Team::Black) + } else if self.boards[1] & (1 << (rank * 8 + file)) != 0 { + Some(Team::White) + } else { + None + } + } /// 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 { @@ -262,9 +283,7 @@ impl BitBoard { // in an orientation such that a disc in the _a_ file is flanked on its // right side. We do the same for when looking left with the _h_ file. - for i in 0..8 { - let shift = DIRECTIONS[i]; - let mask = DIRECTION_MASKS[i]; + for (shift, mask) in SHIFT_MASK_COMBOS { // begin performing masks based on where opponent positions are // since we can flank multiple pieces in a single direction, we // apply this logic seven times as this would account for the @@ -281,9 +300,96 @@ impl BitBoard { moves } + + pub fn render(&self, view: impl Into, overlays: Vec) -> String { + let view: View = view.into(); + let mut result = String::new(); + + let raw_r_range = 0..8; + let r_range = match view { + View::RankAsc => Box::new(raw_r_range) as Box>, + View::RankDesc => Box::new(raw_r_range.rev()) as Box>, + }; + + for (r_idx, r) in r_range.enumerate() { + let start_i = r * 8; + result.push_str(&format!("{}: ", r + 1)); + for i in start_i..(start_i + 8) { + // rshift to cut off bits on rhs, then AND on 1 to get just the last bit. + result.push_str(&format!( + "\x1b[42m{} \x1b[0m", + if (self.boards[0] >> i) & 1 == 1 { + String::from("\x1b[30m●") + } else if (self.boards[1] >> i) & 1 == 1 { + String::from("\x1b[97m●") + } else { + // if our overlay provides a character for the square, use it. + // otherwise use the default char '-' + overlays + .iter() + .find(|Overlay(board, _)| (board >> i) & 1 == 1) + .map(|Overlay(_, c)| *c) + .unwrap_or("░") + .to_owned() + } + )); + } + if r_idx != 7 { + result.push('\n'); + } + } + result.push_str("\n a b c d e f g h"); + + result + } + /// Apply play to a board and compute effected reversals + pub fn play(&mut self, current_team: Team, play: Board) { + // bitwise OR gives spots with either white OR black discs + // bitwise NEG gives the spots with neither white nor black discs + let mut flips = 0; + let current_team_idx = current_team as usize; + let (team, opponent) = ( + self.boards[current_team_idx], + self.boards[current_team.next() as usize], + ); + + // For each (shift, mask) pair, check whether there's an opponent disc in + // the direction of the shift. We mask with `mask` before shifting to avoid + // wrapping horizontally around the edges of the board. We do this six times + // since the maximum amount of flipped discs is six. After we're finished + // shifting, we shift once more with the same rules and then check if we + // intersect with a disc of our own by comparing the bitwise conjunction to + // zero. If it's greater than zero, then we've definitely detected a() flip(s) + // in that direction. We then add these to our running summation of flips. + + for (shift, mask) in SHIFT_MASK_COMBOS { + let mut flip = shift_in_direction(shift, play & mask) & opponent; + flip |= shift_in_direction(shift, flip & mask) & opponent; + flip |= shift_in_direction(shift, flip & mask) & opponent; + flip |= shift_in_direction(shift, flip & mask) & opponent; + flip |= shift_in_direction(shift, flip & mask) & opponent; + flip |= shift_in_direction(shift, flip & mask) & opponent; + if shift_in_direction(shift, flip & mask) & team > 0 { + flips |= flip; + } + } + + self.boards[current_team_idx] += flips; + self.boards[current_team.next() as usize] ^= flips; + + self.boards[current_team_idx] += play; + } + + /// Compute the score (B, W) by counting the excited bits in each board. + pub fn score(&self) -> (u32, u32) { + // `u64::count_ones()` is implemented `unsafe` and makes use of primitives in the + // low-level implementation of the language itself for maximum performance. Better + // to use this than implement myself. + (self.boards[0].count_ones(), self.boards[1].count_ones()) + } } -mod squares { +pub mod squares { /// Just allows us to easily define squares in terms of their shift macro_rules! bitboard_consts { ($($name:ident = $digit:expr), * $(,)?) => { @@ -577,4 +683,28 @@ mod tests { let bb = BitBoard::from_jon("6wb").expect("Valid board"); assert_eq!(bb.available(Team::Black), F8); } + + #[allow(dead_code)] + fn render_dbg(board: BitBoard) -> BitBoard { + println!("dbg:\n{}", board.render(View::RankAsc, vec![])); + board + } + + #[test] + fn play_works() { + let mut bb = BitBoard::default(); + assert_eq!(bb.score(), (2, 2)); + bb.play(Team::Black, E6); + assert_eq!(bb, BitBoard::new(D5 | E6 | E5 | E4, D4)); + assert_eq!(bb.score(), (4, 1)); + bb.play(Team::White, F4); + assert_eq!(bb, BitBoard::new(D5 | E6 | E5, D4 | E4 | F4)); + assert_eq!(bb.score(), (3, 3)); + bb.play(Team::Black, G3); + assert_eq!(bb, BitBoard::new(D5 | E6 | E5 | G3 | F4, D4 | E4)); + assert_eq!(bb.score(), (5, 2)); + bb.play(Team::White, E7); + assert_eq!(bb, BitBoard::new(D5 | G3 | F4, D4 | E4 | E5 | E6 | E7)); + assert_eq!(bb.score(), (3, 5)); + } } diff --git a/src/board/view.rs b/src/board/view.rs new file mode 100644 index 0000000..9f78a73 --- /dev/null +++ b/src/board/view.rs @@ -0,0 +1,17 @@ +use crate::{board::Board, game::Team}; + +pub enum View { + RankAsc, + RankDesc, +} + +impl From for View { + fn from(value: Team) -> Self { + match value { + Team::Black => View::RankDesc, + Team::White => View::RankAsc, + } + } +} + +pub struct Overlay(pub Board, pub &'static str); diff --git a/src/cli/mod.rs b/src/cli/mod.rs new file mode 100644 index 0000000..e075ad4 --- /dev/null +++ b/src/cli/mod.rs @@ -0,0 +1,30 @@ +use othello::{board::view::Overlay, game::Game}; + +pub fn run() -> anyhow::Result<()> { + let mut game = Game::default(); + + println!( + "{}", + game.board().render( + game.current_team, + vec![Overlay( + game.board().available(game.current_team), + "\x1b[95m*\x1b[37m" + )] + ) + ); + game.play(othello::board::squares::E6); + println!(); + println!( + "{}", + game.board().render( + game.current_team, + vec![Overlay( + game.board().available(game.current_team), + "\x1b[34m*\x1b[37m" + )] + ) + ); + + Ok(()) +} diff --git a/src/game.rs b/src/game.rs new file mode 100644 index 0000000..7f537a9 --- /dev/null +++ b/src/game.rs @@ -0,0 +1,67 @@ +use crate::board::{BitBoard, Board}; + +#[repr(u8)] +#[derive(Copy, Clone, Debug, PartialEq, Default)] +pub enum Team { + #[default] + Black, + White, +} + +impl Team { + /// Just return the other team or the next team. + /// This is useful for modeling state transfer + pub fn next(&self) -> Self { + match self { + Team::Black => Team::White, + Team::White => Team::Black, + } + } +} + +#[derive(Default)] +pub struct Game { + pub current_team: Team, + board: BitBoard, +} + +impl Game { + /// Play a move. Automatically transitions state to next player. + pub fn play(&mut self, player_move: Board) { + self.board.play(self.current_team, player_move); + self.current_team = self.current_team.next(); + } + + /// Play a move but first verify that the move is legal. + /// Automatically transitions state to next player. + pub fn safe_play(&mut self, player_move: Board) { + // TODO: validate that move is legal + self.play(player_move); + } + + pub fn board(&self) -> BitBoard { + self.board + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::board::squares::*; + #[test] + fn play_switches_team() { + let mut game = Game::default(); + assert_eq!(game.current_team, Team::Black); + game.play(E6); + assert_eq!(game.current_team, Team::White); + } + + #[test] + fn play_mutates_boards() { + let mut game = Game::default(); + let [og_b, og_w] = game.board().boards; + game.play(E6); + assert_ne!(og_b, game.board().boards[0]); + assert_ne!(og_w, game.board().boards[1]); + } +} diff --git a/othello/src/lib.rs b/src/lib.rs similarity index 100% rename from othello/src/lib.rs rename to src/lib.rs diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..de25299 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,5 @@ +mod cli; + +fn main() -> anyhow::Result<()> { + cli::run() +}