This commit is contained in:
jackjohn7 2025-10-29 21:51:24 -05:00
commit 0de67fd423
13 changed files with 1263 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
target

612
Cargo.lock generated Normal file
View file

@ -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"

6
Cargo.toml Normal file
View file

@ -0,0 +1,6 @@
[workspace]
members = ["iagorithms", "othello"]
[workspace.dependencies]
neorusticus = "0.1.3"
ratatui = "0.29.0"

20
README.md Normal file
View file

@ -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.

1
iagorithms/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

7
iagorithms/Cargo.toml Normal file
View file

@ -0,0 +1,7 @@
[package]
name = "iagorithms"
version = "0.1.0"
edition = "2024"
[dependencies]
neorusticus.workspace = true

226
iagorithms/src/lib.rs Normal file
View file

@ -0,0 +1,226 @@
use neorusticus::{PrologEngine, quick_query};
use std::error::Error;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn init() -> Result<(), Box<dyn Error>> {
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(())
}
}

1
othello/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

15
othello/Cargo.toml Normal file
View file

@ -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

363
othello/src/board.rs Normal file
View file

@ -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);
}
}

6
othello/src/game.rs Normal file
View file

@ -0,0 +1,6 @@
#[repr(u8)]
#[derive(Copy, Clone)]
pub enum Team {
Black,
White,
}

2
othello/src/lib.rs Normal file
View file

@ -0,0 +1,2 @@
pub mod board;
pub mod game;

3
othello/src/main.rs Normal file
View file

@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}