From 0de67fd423ebb60636f05a8222dd061b9a0cc4d4 Mon Sep 17 00:00:00 2001 From: jackjohn7 Date: Wed, 29 Oct 2025 21:51:24 -0500 Subject: [PATCH] init --- .gitignore | 1 + Cargo.lock | 612 ++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 6 + README.md | 20 ++ iagorithms/.gitignore | 1 + iagorithms/Cargo.toml | 7 + iagorithms/src/lib.rs | 226 ++++++++++++++++ othello/.gitignore | 1 + othello/Cargo.toml | 15 ++ othello/src/board.rs | 363 +++++++++++++++++++++++++ othello/src/game.rs | 6 + othello/src/lib.rs | 2 + othello/src/main.rs | 3 + 13 files changed, 1263 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 iagorithms/.gitignore create mode 100644 iagorithms/Cargo.toml create mode 100644 iagorithms/src/lib.rs create mode 100644 othello/.gitignore create mode 100644 othello/Cargo.toml create mode 100644 othello/src/board.rs create mode 100644 othello/src/game.rs create mode 100644 othello/src/lib.rs create mode 100644 othello/src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..eaac110 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,612 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[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 = "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 = "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 = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[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 = "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 = "othello" +version = "0.1.0" +dependencies = [ + "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 = "proc-macro2" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e0f6df8eaa422d97d72edcd152e1451618fed47fabbdbd5a8864167b1d4aff7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +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 = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.20" +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" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[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-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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +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" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..5db351c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] +members = ["iagorithms", "othello"] + +[workspace.dependencies] +neorusticus = "0.1.3" +ratatui = "0.29.0" diff --git a/README.md b/README.md new file mode 100644 index 0000000..c4fd78f --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# Othello + +## Project Structure + +``` +├── iagorithms <-- contains expert system AI library +│   └── ... +├── othello <-- contains main binary and library for Othello +│   └── ... +└── README.md <-- you are here +``` + +## Game Representation + +The game state is represented by what's known as a +[BitBoard](https://www.chessprogramming.org/Bitboards). I knew about these +already from researching Chess programming in the past. + +This does mean that when calling upon iagorithms' it will need to _explode_ +the bitboard into a format that can be pattern matched on. diff --git a/iagorithms/.gitignore b/iagorithms/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/iagorithms/.gitignore @@ -0,0 +1 @@ +/target diff --git a/iagorithms/Cargo.toml b/iagorithms/Cargo.toml new file mode 100644 index 0000000..9300154 --- /dev/null +++ b/iagorithms/Cargo.toml @@ -0,0 +1,7 @@ +[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 new file mode 100644 index 0000000..6ec1903 --- /dev/null +++ b/iagorithms/src/lib.rs @@ -0,0 +1,226 @@ +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 ==="); + panic!(); + Ok(()) + } +} diff --git a/othello/.gitignore b/othello/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/othello/.gitignore @@ -0,0 +1 @@ +/target diff --git a/othello/Cargo.toml b/othello/Cargo.toml new file mode 100644 index 0000000..3618813 --- /dev/null +++ b/othello/Cargo.toml @@ -0,0 +1,15 @@ +[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 diff --git a/othello/src/board.rs b/othello/src/board.rs new file mode 100644 index 0000000..eaca2b0 --- /dev/null +++ b/othello/src/board.rs @@ -0,0 +1,363 @@ +//! All things to do with the Othello game board. This module contains types, +//! traits, and functions for working with the board. + +use std::fmt::{Debug, Display}; + +use crate::game::Team; + +pub type Board = u64; + +// Directions: +// 7 8 9 +// -1 0 1 +// -9 -8 -7 +// +// 0 is where "we" are. The other numbers represent the index shifts to access +// both orthogonal and diagonal adjecent squares. +// +// Note that negative numbers indicate a shift to the right while positive ones +// indicate a left shift. +// +// When we want to shift from a higher place to a lower place (when looking +// straight to the right or up in any direction for example), we aren't really +// moving toward _it_. It's more so that we're moving that toward us. So a +// digit further along in the structure (positive numbers) will require +// 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) +]; + +/// Represents the board state using two integers. This allows us to reduce the +/// memory footprint by 75% when compared against using a byte[8][8] (64 +/// bytes). While digits in these boards do not have _place value_ per se, it +/// still may be helpful to think of the structure as being Little Endian and +/// by that I mean to say that a0 is the leftmost bit and h8 is the rightmost. +/// +/// The values are to be mapped as follows where each number indicates the bit +/// in the integer that corresponds to the position in the board: +/// +/// ```text +/// a: b: c: d: e: f: g: h: +/// 8: 56 57 58 59 60 61 62 63 :8 +/// 7: 48 49 50 51 52 53 54 55 :7 +/// 6: 40 41 42 43 44 45 46 47 :6 +/// 5: 32 33 34 35 36 37 38 39 :5 +/// 4: 24 25 26 27 28 29 30 31 :4 +/// 3: 16 17 18 19 20 21 22 23 :3 +/// 2: 08 09 10 11 12 13 14 15 :2 +/// 1: 00 01 02 03 04 05 06 07 :1 +/// a: b: c: d: e: f: g: h: +/// +/// = +/// +/// bits: 00 01 ... 07 08 09 ... 15 ... 56 57 ... 63 +/// board: a1 b1 ... h1 a2 b2 ... h2 ... a8 b8 ... h8 +/// ``` +/// +/// Note that I use the traditional nomenclature of ranks and files which +/// correspond to rows and columns respectively. +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], +} + +impl Default for BitBoard { + fn default() -> Self { + // Create board in standard starting structure + Self { + boards: [(1 << 36) + (1 << 27), (1 << 35) + (1 << 28)], + } + } +} + +impl Debug for BitBoard { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BitBoard") + .field("black", &format!("0x{:X}", &self.boards[0])) + .field("white", &format!("0x{:X}", &self.boards[1])) + .finish() + } +} + +impl Display for BitBoard { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for r in (0..8).rev() { + let start_i = r * 8; + 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. + write!( + f, + "{}", + if (self.boards[0] >> i) & 1 == 1 { + 'B' + } else if (self.boards[1] >> i) & 1 == 1 { + 'W' + } else { + '-' + } + ) + .unwrap(); + } + if r != 0 { + write!(f, "\n").unwrap(); + } + } + Ok(()) + } +} + +fn shift_in_direction(magnitude: i8, value: Board) -> Board { + if magnitude >= 0 { + let (result, _) = value.overflowing_shl(magnitude.abs() as u32); + result + } else { + let (result, _) = value.overflowing_shr(magnitude.abs() as u32); + result + } +} + +impl BitBoard { + /// Compute board with valid moves marked + 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 + let empties = !(self.boards[0] | self.boards[1]); + let mut moves = 0; + let (team, opponent) = ( + self.boards[current_team as usize], + self.boards[(current_team as usize + 1) % 2], + ); + + // Instead of looping through a structure and checking adjacent + // squares, we are able to rather easily aggregate these operations + // into a handful of bitwise operations. So for example, let's + // consider the starting board (illustrations omitting some + // unimportant ranks): + // + // Game Black White + // -------- -------- -------- + // ---WB--- = ----B--- + ---W---- + // ---BW--- ---B---- ----W--- + // -------- -------- -------- + // + // It's black's turn. Let's first consider how we might find any + // white pieces that are adjacent to black pieces. Let's just check + // for white pieces that are to the _right_ of black ones. We can do + // this by shifting all black digits to the left once (see info about + // shifts and directions earlier in module). + // + // Black << 1 = + // -------- + // -----B-- + // ----B--- + // -------- + // + // We can then find where this shifted board intersects with white's + // board using bitwise AND. + // + // Black<<1 & White = Right-adjacents (A) + // + // -------- -------- -------- + // -----B-- & ---W---- = -------- + // ----B--- ----W--- ----A--- + // -------- -------- -------- + // + // We now have all the white matrices to the right of black matrices. + // We can continue doing this in different directions using different + // values to shift with (they are the same every time), using masks + // here and there to avoid problems with the outer files, and then + // bitwise OR-ing these together to get a matrix containing all + // adjacencies. + // + // Certain shifts are associated with particular masks by matching + // indices. So for example, when we're looking to the right, we want to + // ignore the _h_ file. This is because there's no way of placing a piece + // in an orientation such that a piece in the _h_ file is flanked on its + // right side since the board ends. We do the same for when looking + // left with the _a_ file. + + for i in 0..8 { + let shift = DIRECTIONS[i]; + let mask = DIRECTION_MASKS[i]; + // begin performing masks based on where opponent positions are + let mut sub_move = shift_in_direction(shift, team & mask) & opponent; + sub_move |= shift_in_direction(shift, sub_move & mask) & opponent; + sub_move |= shift_in_direction(shift, sub_move & mask) & opponent; + sub_move |= shift_in_direction(shift, sub_move & mask) & opponent; + sub_move |= shift_in_direction(shift, sub_move & mask) & opponent; + sub_move |= shift_in_direction(shift, sub_move & mask) & opponent; + sub_move = shift_in_direction(shift, sub_move & mask) & empties; + moves |= sub_move; + } + + moves + } +} + +mod squares { + /// Just allows us to easily define squares in terms of their shift + macro_rules! bitboard_consts { + ($($name:ident = $digit:expr), * $(,)?) => { + $(#[allow(dead_code)] pub const $name: super::Board = 1 << $digit;)* + }; + } + + bitboard_consts!( + A1 = 0, + B1 = 1, + C1 = 2, + D1 = 3, + E1 = 4, + F1 = 5, + G1 = 6, + H1 = 7, + A2 = 8, + B2 = 9, + C2 = 10, + D2 = 11, + E2 = 12, + F2 = 13, + G2 = 14, + H2 = 15, + A3 = 16, + B3 = 17, + C3 = 18, + D3 = 19, + E3 = 20, + F3 = 21, + G3 = 22, + H3 = 23, + A4 = 24, + B4 = 25, + C4 = 26, + D4 = 27, + E4 = 28, + F4 = 29, + G4 = 30, + H4 = 31, + A5 = 32, + B5 = 33, + C5 = 34, + D5 = 35, + E5 = 36, + F5 = 37, + G5 = 38, + H5 = 39, + A6 = 40, + B6 = 41, + C6 = 42, + D6 = 43, + E6 = 44, + F6 = 45, + G6 = 46, + H6 = 47, + A7 = 48, + B7 = 49, + C7 = 50, + D7 = 51, + E7 = 52, + F7 = 53, + G7 = 54, + H7 = 55, + A8 = 56, + B8 = 57, + C8 = 58, + D8 = 59, + E8 = 60, + F8 = 61, + G8 = 62, + H8 = 63, + ); +} + +#[cfg(test)] +mod tests { + use super::squares::*; + use super::*; + + #[test] + fn test_masking_logic() { + // validating the tests used in the comment explaining the way we're + // generating moves. + let bb = BitBoard::default(); + let blk_shl = bb.boards[Team::Black as usize] << 1; + // validate that they are shifted to the right + assert_eq!(blk_shl, (1 << 37) + (1 << 28)); + // validate that they are shifted to the right visually + let expected_output = r"-------- +-------- +-------- +-----B-- +----B--- +-------- +-------- +--------"; + assert_eq!( + format!( + "{}", + BitBoard { + boards: [blk_shl, 0] + } + ), + expected_output + ); + + // test that we can mask with white squares. + let matched = blk_shl & bb.boards[Team::White as usize]; + // validate that they are matching only + assert_eq!(matched, !Board::MAX + (1 << 28)); + // validate that they match visually + let expected_output = r"-------- +-------- +-------- +-------- +----B--- +-------- +-------- +--------"; + assert_eq!( + format!( + "{}", + BitBoard { + boards: [matched, 0] + } + ), + expected_output + ); + } + + #[test] + fn avaliable_works() { + let bb = BitBoard::default(); + assert_eq!(bb.available(Team::Black), D6 + C5 + F4 + E3); + } + + #[test] + fn display_works() { + let bb = BitBoard::default(); + let expected_output = r"-------- +-------- +-------- +---WB--- +---BW--- +-------- +-------- +--------"; + assert_eq!(format!("{}", bb), expected_output); + } +} diff --git a/othello/src/game.rs b/othello/src/game.rs new file mode 100644 index 0000000..a960ff5 --- /dev/null +++ b/othello/src/game.rs @@ -0,0 +1,6 @@ +#[repr(u8)] +#[derive(Copy, Clone)] +pub enum Team { + Black, + White, +} diff --git a/othello/src/lib.rs b/othello/src/lib.rs new file mode 100644 index 0000000..7d756d4 --- /dev/null +++ b/othello/src/lib.rs @@ -0,0 +1,2 @@ +pub mod board; +pub mod game; diff --git a/othello/src/main.rs b/othello/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/othello/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +}