restructured, flips working, benchmarking flips, got some basic terminal rendering

This commit is contained in:
jackjohn7 2025-11-04 01:04:50 -06:00
parent 10de749e1d
commit d7d8732904
17 changed files with 331 additions and 822 deletions

531
Cargo.lock generated
View file

@ -11,12 +11,6 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]] [[package]]
name = "anes" name = "anes"
version = "0.1.6" version = "0.1.6"
@ -29,45 +23,30 @@ version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
[[package]]
name = "anyhow"
version = "1.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.5.0" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "bitflags"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.19.0" version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "cassowary"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
[[package]] [[package]]
name = "cast" name = "cast"
version = "0.3.0" version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "castaway"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
dependencies = [
"rustversion",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.4" version = "1.0.4"
@ -126,20 +105,6 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" 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]] [[package]]
name = "criterion" name = "criterion"
version = "0.7.0" version = "0.7.0"
@ -198,106 +163,18 @@ version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 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]] [[package]]
name = "crunchy" name = "crunchy"
version = "0.2.4" version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" 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]] [[package]]
name = "either" name = "either"
version = "1.15.0" version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 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]] [[package]]
name = "half" name = "half"
version = "2.7.1" version = "2.7.1"
@ -309,58 +186,6 @@ dependencies = [
"zerocopy", "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]] [[package]]
name = "itertools" name = "itertools"
version = "0.13.0" version = "0.13.0"
@ -386,66 +211,12 @@ dependencies = [
"wasm-bindgen", "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]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.6" version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 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]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.19" version = "0.2.19"
@ -471,39 +242,10 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
name = "othello" name = "othello"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"criterion", "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]] [[package]]
name = "plotters" name = "plotters"
version = "0.3.7" version = "0.3.7"
@ -534,9 +276,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.102" version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e0f6df8eaa422d97d72edcd152e1451618fed47fabbdbd5a8864167b1d4aff7" checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@ -550,27 +292,6 @@ dependencies = [
"proc-macro2", "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]] [[package]]
name = "rayon" name = "rayon"
version = "1.11.0" version = "1.11.0"
@ -591,15 +312,6 @@ dependencies = [
"crossbeam-utils", "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]] [[package]]
name = "regex" name = "regex"
version = "1.12.2" version = "1.12.2"
@ -629,19 +341,6 @@ version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" 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]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.22" version = "1.0.22"
@ -663,12 +362,6 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.228" version = "1.0.228"
@ -712,76 +405,6 @@ dependencies = [
"serde_core", "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]] [[package]]
name = "syn" name = "syn"
version = "2.0.108" version = "2.0.108"
@ -805,38 +428,9 @@ dependencies = [
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.20" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[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]] [[package]]
name = "walkdir" name = "walkdir"
@ -848,12 +442,6 @@ dependencies = [
"winapi-util", "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]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.105" version = "0.2.105"
@ -909,52 +497,21 @@ dependencies = [
"wasm-bindgen", "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]] [[package]]
name = "winapi-util" name = "winapi-util"
version = "0.1.11" version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [ 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]] [[package]]
name = "windows-link" name = "windows-link"
version = "0.2.1" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 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]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.61.2" version = "0.61.2"
@ -964,70 +521,6 @@ dependencies = [
"windows-link", "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]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.8.27" version = "0.8.27"

View file

@ -1,7 +1,26 @@
[workspace] [package]
members = ["iagorithms", "othello"] name = "othello"
version = "0.1.0"
edition = "2024"
[workspace.dependencies] [lib]
neorusticus = "0.1.3" name = "othello"
ratatui = "0.29.0" path = "src/lib.rs"
[[bin]]
name = "othello-gui"
path = "src/main.rs"
[dependencies]
anyhow = "1.0.100"
[dev-dependencies]
criterion = "0.7.0" criterion = "0.7.0"
[[bench]]
name = "generation"
harness = false
[[bench]]
name = "reversals"
harness = false

View file

@ -3,7 +3,7 @@ use std::hint::black_box;
use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
use othello::{board::BitBoard, game::Team}; use othello::{board::BitBoard, game::Team};
const JONS: [&'static str; 20] = [ const JONS: [&str; 20] = [
"///3bw/3wb", "///3bw/3wb",
"///////6bw", "///////6bw",
"///////wb", "///////wb",

24
benches/reversals.rs Normal file
View file

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

View file

@ -1 +0,0 @@
/target

View file

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

View file

@ -1,225 +0,0 @@
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 ===");
Ok(())
}
}

1
othello/.gitignore vendored
View file

@ -1 +0,0 @@
/target

View file

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

View file

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

View file

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

View file

@ -3,7 +3,12 @@
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};
use crate::game::Team; use crate::{
board::view::{Overlay, View},
game::Team,
};
pub mod view;
pub type Board = u64; 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: // shifting to the left to bring it to match our current focus. Thus:
// Positive => SHL // Positive => SHL
// Negative => SHR // Negative => SHR
const DIRECTIONS: [i8; 8] = [9, 8, 7, 1, -1, -7, -8, -9];
// simply negating the A file // simply negating the A file
const NOT_A_FILE: Board = 0x7F7F7F7F7F7F7F7F; const NOT_A_FILE: Board = 0x7F7F7F7F7F7F7F7F;
// simply negating the H file // simply negating the H file
const NOT_H_FILE: Board = 0xFEFEFEFEFEFEFEFE; const NOT_H_FILE: Board = 0xFEFEFEFEFEFEFEFE;
const DIRECTION_MASKS: [Board; 8] = [ const SHIFT_MASK_COMBOS: [(i8, Board); 8] = [
NOT_A_FILE, // 9 (up right) (9, NOT_A_FILE), // 9 (up right)
Board::MAX, // 8 (up) (8, Board::MAX), // 8 (up)
NOT_H_FILE, // 7 (up left) (7, NOT_H_FILE), // 7 (up left)
NOT_A_FILE, // 1 (right) (1, NOT_A_FILE), // 1 (right)
NOT_H_FILE, // -1 (left) (-1, NOT_H_FILE), // -1 (left)
NOT_A_FILE, // -7 (down right) (-7, NOT_A_FILE), // -7 (down right)
Board::MAX, // -8 (down) (-8, Board::MAX), // -8 (down)
NOT_H_FILE, // -9 (down left) (-9, NOT_H_FILE), // -9 (down left)
]; ];
/// Represents the board state using two integers. This allows us to reduce the /// 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 /// 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 /// of Othello, but I stick to rigid Chess-esque conventions for the sake of my
/// sanity. /// sanity.
#[derive(Copy, Clone, PartialEq)]
pub struct BitBoard { pub struct BitBoard {
/// Contains all boards for game. In this case, there are only two. /// Contains all boards for game. In this case, there are only two.
/// The first is black. The second is white. /// The first is black. The second is white.
boards: [Board; 2], pub boards: [Board; 2],
} }
impl Default for BitBoard { impl Default for BitBoard {
@ -131,7 +136,7 @@ impl Display for BitBoard {
.unwrap(); .unwrap();
} }
if r != 0 { if r != 0 {
write!(f, "\n").unwrap(); writeln!(f).unwrap();
} }
} }
write!(f, "\n abcdefgh").unwrap(); write!(f, "\n abcdefgh").unwrap();
@ -141,15 +146,21 @@ impl Display for BitBoard {
fn shift_in_direction(magnitude: i8, value: Board) -> Board { fn shift_in_direction(magnitude: i8, value: Board) -> Board {
if magnitude >= 0 { if magnitude >= 0 {
let (result, _) = value.overflowing_shl(magnitude.abs() as u32); let (result, _) = value.overflowing_shl(magnitude.unsigned_abs() as u32);
result result
} else { } else {
let (result, _) = value.overflowing_shr(magnitude.abs() as u32); let (result, _) = value.overflowing_shr(magnitude.unsigned_abs() as u32);
result result
} }
} }
impl BitBoard { 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. /// Convert from a JON (Jack Othello Notation) string into a usable board.
/// Format: /// Format:
/// ///
@ -170,7 +181,7 @@ impl BitBoard {
let mut line: u8 = 7; let mut line: u8 = 7;
let mut rank_idx: u8 = 0; let mut rank_idx: u8 = 0;
let mut rank_jump: 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() { match c.to_ascii_lowercase() {
'/' => { '/' => {
line = line.checked_sub(1).ok_or("Malformed JON: Too many ranks")?; line = line.checked_sub(1).ok_or("Malformed JON: Too many ranks")?;
@ -187,7 +198,7 @@ impl BitBoard {
rank_idx += rank_jump + 1; rank_idx += rank_jump + 1;
rank_jump = 0; rank_jump = 0;
} }
'0'..'9' => { '0'..='8' => {
rank_jump = c rank_jump = c
.to_digit(10u32) .to_digit(10u32)
.ok_or("JON Parsing error: Failed to parse integer from character")? .ok_or("JON Parsing error: Failed to parse integer from character")?
@ -203,6 +214,16 @@ impl BitBoard {
} }
Ok(Self { boards }) Ok(Self { boards })
} }
pub fn player_at(&self, rank: u8, file: u8) -> Option<Team> {
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. /// Compute board with valid moves marked using only bitwise operations.
/// This has constant time complexity and is unbelievably fast. /// This has constant time complexity and is unbelievably fast.
pub fn available(&self, current_team: Team) -> Board { 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 // 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. // right side. We do the same for when looking left with the _h_ file.
for i in 0..8 { for (shift, mask) in SHIFT_MASK_COMBOS {
let shift = DIRECTIONS[i];
let mask = DIRECTION_MASKS[i];
// begin performing masks based on where opponent positions are // begin performing masks based on where opponent positions are
// since we can flank multiple pieces in a single direction, we // since we can flank multiple pieces in a single direction, we
// apply this logic seven times as this would account for the // apply this logic seven times as this would account for the
@ -281,9 +300,96 @@ impl BitBoard {
moves moves
} }
pub fn render(&self, view: impl Into<View>, overlays: Vec<Overlay>) -> 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<dyn Iterator<Item = i32>>,
View::RankDesc => Box::new(raw_r_range.rev()) as Box<dyn Iterator<Item = i32>>,
};
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 /// Just allows us to easily define squares in terms of their shift
macro_rules! bitboard_consts { macro_rules! bitboard_consts {
($($name:ident = $digit:expr), * $(,)?) => { ($($name:ident = $digit:expr), * $(,)?) => {
@ -577,4 +683,28 @@ mod tests {
let bb = BitBoard::from_jon("6wb").expect("Valid board"); let bb = BitBoard::from_jon("6wb").expect("Valid board");
assert_eq!(bb.available(Team::Black), F8); 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));
}
} }

17
src/board/view.rs Normal file
View file

@ -0,0 +1,17 @@
use crate::{board::Board, game::Team};
pub enum View {
RankAsc,
RankDesc,
}
impl From<Team> 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);

30
src/cli/mod.rs Normal file
View file

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

67
src/game.rs Normal file
View file

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

5
src/main.rs Normal file
View file

@ -0,0 +1,5 @@
mod cli;
fn main() -> anyhow::Result<()> {
cli::run()
}