diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index d6dc18a..f9647b5 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -19,7 +19,7 @@ jobs: run: cargo build - name: Install just - uses: extractions/setup-just@v2 + uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 - name: Install Nextest uses: baptiste0928/cargo-install@v3 diff --git a/.github/workflows/taghunt.yml b/.github/workflows/taghunt.yml new file mode 100644 index 0000000..6b1d80e --- /dev/null +++ b/.github/workflows/taghunt.yml @@ -0,0 +1,31 @@ +name: Taghunt + +on: + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install just + uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 + + - name: Install ripgrep + run: sudo apt install -y ripgrep + + - name: Tag Hunt + id: taghunt + run: | + cat < $GITHUB_OUTPUT + stdout=$(just taghunt) + EOF + + + - name: Add PR Comment + uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc + with: + message: | + ${{ steps.taghunt.outputs.stdout }} diff --git a/Cargo.lock b/Cargo.lock index 60c4009..e57e6ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,30 +4,18 @@ version = 4 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "ahash" -version = "0.8.11" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aho-corasick" @@ -40,9 +28,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +checksum = "611cc2ae7d2e242c457e4be7f97036b8ad9ca152b499f53faf99b1ed8fc2553f" [[package]] name = "anes" @@ -84,32 +72,23 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.71" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", -] - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", + "windows-targets 0.52.6", ] [[package]] @@ -147,9 +126,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "cassowary" @@ -172,15 +151,6 @@ dependencies = [ "rustversion", ] -[[package]] -name = "cc" -version = "1.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" -dependencies = [ - "shlex", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -274,20 +244,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "compact_str" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" -dependencies = [ - "castaway", - "cfg-if", - "itoa", - "rustversion", - "ryu", - "static_assertions", -] - [[package]] name = "const-str" version = "0.5.7" @@ -412,22 +368,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "crossterm" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" -dependencies = [ - "bitflags 2.6.0", - "crossterm_winapi", - "mio 1.0.2", - "parking_lot", - "rustix", - "signal-hook", - "signal-hook-mio", - "winapi", -] - [[package]] name = "crossterm_winapi" version = "0.9.1" @@ -469,26 +409,28 @@ dependencies = [ ] [[package]] -name = "errno" -version = "0.3.9" +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "foldhash" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "getrandom" @@ -503,9 +445,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "half" @@ -519,38 +461,34 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" dependencies = [ - "ahash", "allocator-api2", + "equivalent", + "foldhash", ] [[package]] name = "hazel" version = "0.1.0" dependencies = [ - "bincode", "clap 2.34.0", "const-str", "criterion", "criterion-macro", "crossbeam", - "either", "lazy_static", "mutants", - "nom", - "paste", "pgn-reader", "quickcheck", "quickcheck_macros", "rand", - "ratatui 0.27.0", + "ratatui", "rayon", "serde", "serde_json", - "thiserror", "tokio", "tokio-stream", "tokio-util", @@ -558,7 +496,6 @@ dependencies = [ "tracing-appender", "tracing-subscriber", "tracing-test", - "tui-input", "yaml-rust 0.4.5", ] @@ -589,16 +526,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" -[[package]] -name = "instability" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b23a0c8dfe501baac4adf6ebbfa6eddf8f0c07f56b058cc1288017e32397846c" -dependencies = [ - "quote", - "syn 2.0.77", -] - [[package]] name = "is-terminal" version = "0.4.13" @@ -636,9 +563,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] @@ -651,9 +578,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.159" +version = "0.2.162" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" [[package]] name = "linked-hash-map" @@ -661,12 +588,6 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" -[[package]] -name = "linux-raw-sys" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" - [[package]] name = "lock_api" version = "0.4.12" @@ -685,9 +606,9 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lru" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ "hashbrown", ] @@ -707,19 +628,13 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "adler", + "adler2", ] [[package]] @@ -742,7 +657,6 @@ checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi 0.3.9", "libc", - "log", "wasi", "windows-sys 0.52.0", ] @@ -759,16 +673,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -796,18 +700,18 @@ dependencies = [ [[package]] name = "object" -version = "0.32.2" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "oorandom" @@ -864,9 +768,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "plotters" @@ -913,9 +817,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] @@ -989,8 +893,8 @@ checksum = "d16546c5b5962abf8ce6e2881e722b4e0ae3b6f1a08a26ae3573c55853ca68d3" dependencies = [ "bitflags 2.6.0", "cassowary", - "compact_str 0.7.1", - "crossterm 0.27.0", + "compact_str", + "crossterm", "itertools 0.13.0", "lru", "paste", @@ -1002,27 +906,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "ratatui" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdef7f9be5c0122f890d58bdf4d964349ba6a6161f705907526d891efabba57d" -dependencies = [ - "bitflags 2.6.0", - "cassowary", - "compact_str 0.8.0", - "crossterm 0.28.1", - "instability", - "itertools 0.13.0", - "lru", - "paste", - "strum", - "strum_macros", - "unicode-segmentation", - "unicode-truncate", - "unicode-width", -] - [[package]] name = "rayon" version = "1.10.0" @@ -1045,23 +928,23 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "355ae415ccd3a04315d3f8246e86d67689ea74d88d915576e1589a351062a13b" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] [[package]] name = "regex" -version = "1.10.6" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -1075,13 +958,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", ] [[package]] @@ -1092,9 +975,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustc-demangle" @@ -1102,24 +985,11 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" -[[package]] -name = "rustix" -version = "0.38.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" -dependencies = [ - "bitflags 2.6.0", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", -] - [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" [[package]] name = "ryu" @@ -1144,22 +1014,22 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -1195,12 +1065,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - [[package]] name = "signal-hook" version = "0.3.17" @@ -1219,7 +1083,6 @@ checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", "mio 0.8.11", - "mio 1.0.2", "signal-hook", ] @@ -1255,7 +1118,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" dependencies = [ "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -1289,7 +1152,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -1305,9 +1168,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", @@ -1325,22 +1188,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -1396,9 +1259,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.40.0" +version = "1.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" dependencies = [ "backtrace", "bytes", @@ -1420,7 +1283,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -1478,7 +1341,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -1538,17 +1401,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" dependencies = [ "quote", - "syn 2.0.77", -] - -[[package]] -name = "tui-input" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd137780d743c103a391e06fe952487f914b299a4fe2c3626677f6a6339a7c6b" -dependencies = [ - "ratatui 0.28.1", - "unicode-width", + "syn 2.0.87", ] [[package]] @@ -1592,12 +1445,6 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - [[package]] name = "walkdir" version = "2.5.0" @@ -1616,9 +1463,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", "once_cell", @@ -1627,24 +1474,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1652,28 +1499,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" dependencies = [ "js-sys", "wasm-bindgen", @@ -1891,5 +1738,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] diff --git a/Cargo.toml b/Cargo.toml index 1178460..1a0ebaf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,20 +14,19 @@ path="src/lib.rs" name="hazel" path="src/main.rs" + +# I might featureflag some stuff, e.g., serde and that, to reduce compile times? + [dependencies] clap = { version = "2.33", features = ["yaml"]} yaml-rust = "0.4.1" lazy_static = "1.4.0" -either = "1.12" rand = "0.8.5" tracing = "0.1" serde = { version = "1.0.203", features = ["derive"] } -bincode = "1.3.3" pgn-reader = "0.26.0" -thiserror = "1.0.61" ratatui = "0.27.0" -serde_json = "1.0.132" -nom = "7.1.3" +serde_json = "1.0.117" rayon = "1.10.0" crossbeam = "0.8.4" tracing-appender = "0.2" @@ -35,7 +34,6 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } tokio = { version = "1.16.0", features = ["full"] } tokio-stream = "0.1.16" tokio-util = { version = "0.7.12", features = ["io"] } -tui-input = "0.10.1" const-str = "0.5.7" mutants = "0.0.3" @@ -44,12 +42,12 @@ criterion = "0.5.1" criterion-macro = "0.4.0" quickcheck = "1.0.3" quickcheck_macros = "1.0.0" -paste = "1.0.15" tracing-test = "0.2.5" -[[bench]] -name = "benchmarks" -harness = true +# Removing benchmarking temporarily +#[[bench]] +#name = "benchmarks" +#harness = true [profile.bench] opt-level = 3 diff --git a/Justfile b/Justfile index 4758dca..50bb719 100644 --- a/Justfile +++ b/Justfile @@ -32,9 +32,6 @@ test *ARGS: return 0 } - - - if [[ -n "{{ARGS}}" ]]; then echo "Running tests with args: {{ARGS}}" just nextest {{ARGS}} @@ -65,5 +62,18 @@ miri-test: cloc *args: cloc --vcs=git --exclude-ext=.rc . {{args}} -mutants: - cargo mutants -- --profile=mutants --all-targets +mutants *ARGS: + # cargo mutants -t 90 -j 8 -E 'bitboard' -E "intrinsics" -E "Mask" -E "tokio" -E "Stockfish" -E "ui" -E "PEXTBoard" --test-tool nextest -- --cargo-profile=mutants --all-targets {{ARGS}} -j 4 + cargo mutants -j 4 -E 'bitboard' -E "intrinsics" -E "Mask" -E "tokio" -E "Stockfish" -E "ui" -E "PEXTBoard" --test-tool nextest -- --cargo-profile=mutants --all-targets {{ARGS}} -j 4 + + +taghunt: + @just _taghunt "BUG" "FIXME" "HACK" "NOTE" "TODO" "OQ" + +_taghunt *TAGS: + #!/usr/bin/env bash + for tag in {{TAGS}}; do + echo -n "$tag=$(rg --glob \!Justfile $tag . | wc -l)
" + done + echo + diff --git a/LOG.md b/LOG.md index 9050610..8f85b82 100644 --- a/LOG.md +++ b/LOG.md @@ -971,3 +971,126 @@ Somewhere in there I also want to make more progress on the UI. My plan is to have two worktrees, `UI` and whatever thing I''m working on at the time. As I implement stuff, I can keep chipping at the UI as I go. +# 31-OCT-2024 + +## 2007 - gamerep + +Spooky season is 'pon us, and I have deleted a bunch of code. I've been working on the `Game` representation, and I +think I have the design down (almost) + +The main problem, it seems, is metadata. In order to know, for instance, who is allowed to castle, I need to know if any +one of a number of events has occured over the course of the game up to this point. This can be calculated each time I +need it, and it is admittedly not the most expensive thing in the world, but it gets tricky when I need to know the +metadata state in a particular variation of a particular position and it can very rapidly get hairy. + +The `Alter` system works really nicely for this, I have an `Alteration` struct that has much simpler primitive +operations to the board, so I though, "Can I extend this language?" and the answer is "Actually I should just build +another language." + +So now I have `ChessRule`, which includes the higher level actions that 'compile' to this lower level 'alteration', +I've also added a couple alterations variants. `Clear` simply instructs the implementor to reset the state of the board +to nothing, and `Tag` allows a 4 byte 'tag' in the output that is otherwise ignored. These allow me to easily generate a +single stream of Alterations that can: + +1. Describe the change to the boardstate over time +2. Represent arbitrary trees of games +3. Be easily 'compressed' to a 'compact' form (distinct from, but equivalent too, FEN) for any single boardstate + +Since `Alteration`s are reversible (mod Clear, more in a sec), I can easily scroll forward and backward within the space +of a single 'clear' call, and if I need to rewind to before that, I just rewind back to the previous clear and rerun. + +This leaves the existing `Alteration` stuff more or less unchanged, and allows me to use this new higher level language +to describe a chess game as a stream of `ChessRule`s, which can track higher level details. The Game Representation is +just a vector of `ChessRule` that gets `Move`s `#make`d on it, and when the move is made, the representation can also +add any number of metadata variants too, these then get 'compiled' in the current context and added to the vector of +`Alteration`s that then represent the game. The metadata gets added as `Tag` variants in the GameRep. + +All this still requires a bunch of work and testing, in particular getting the `Tag` parts right is proving a little +tricky. I initially thought of sticking a [u8] slice in the tag, and I still, tbh, want to do that, but it ends up +touching a bunch of the system with lifetime annotations and that seems really lousy. + +Current plan is just to power through till I get something that can represent a PGN and get that wired up to the UI, +maybe with some kind of insight from PGN -> ChessRule -> Alteration + +A practical effect of this is also that many things currently implementing `Engine` really shouldn't, since they can't +track metadata, which makes a _ton_ of sense in retrospect. I kept trying to figure out how to reconcile why it felt so +weird that any board rep was a kind of 'engine', but in this model, they're not, they're just `Query + Alter`, +make and unmake are really part of another trait, `Play`, which is different than something which can be merely +'altered'; it implies an understanding and ability to track metadata for the given game. In my case, there is only one, +but I have a `Play` trait which specifies a particular included type, `Rule`, which takes the equivalent of the +`Alteration` struct (in this case, ChessRule) and has an `apply` and `unwind` method (and _mut variants) to apply and +... unwind the given 'Rule'[1] and track the metadata internal to the structure, the trait doesn't care about the +content or how that metadata is stored, just that it is. + +Movegen can then take a state as compiled to `Alterations`, then start creating whatever moves it likes as `ChessRule` +variants piled on top, the `ChessRule` includes the idea of a `Variation` which is a delimited sequence of moves +branching off from the previous. These can be nested to create arbitrary structures. Seeking to a particular variation +involves simply unwinding from the variation backward until you find a `Clear`, and then reading back the result. + +Now we get back to engines, Engines take UCI commands, `Play`able objects take `ChessRules` (or whatever other variant), +our system says every UCI command should translate to some set of `ChessRules` that can then be compiled to +`Alteration`s and applied to some `Alter + Query` object to represent the game. This allows a single representation +which can easily be tracked through, the resulting state can be 'compressed' to a `Clear`, a series of`Place` +operations, and then a set of `Tag`s to define the metadata, and this representation is easy to turn into any other +board representation you like. So if you want to use SIMDified bitboards, you just need to tell it how to process the +`Alteration` stream, and once it is in your domain, how you use that representation is your business. Once you want to +send results back, compile them to `Alteration` or `ChessRule`s (or even a mixed stream of them) and send them back. + +I think this'll work well for my purpose, but I do expect I'm adding some amount of overhead compared to a direct +implementation. Fortunately, I should be able to test that later if I like by directly interpreting the `ChessRule` +without the `Alteration` layer, and that will give me some sense of how much overhead I'm adding by the extra jump. + +[1] Here I'm doing a classic math thing of taking a word with a well known and well-understood meaning and using it in a +way which _almost_ fits that meaning but stretches it just a little past comfort. In normal terms, a 'rule' is an +assertion toward obedience. You have a rule that says "Thou shalt X", and you better be X-ing or else you're breaking +the rule. + +However, rules can also be 'applied', as in, "Use L'Hopital's Rule to solve this limit". In this case, the rule is an +algorithm or technique or manipulation. + +Rules can also be 'observed', as in "The rule of law", where the rule is a principle or standard that is generally +understood to be 'the way things work' and is used to guide behavior, though not strictly enforce obedience to some +particular interpretation. + +In `Hazel`, a rule is simple 'An operation that can be applied to a gamestate'. It's a little bit of all three, but +really it's closest analog is an 'Arrow' between objects. In the "Category"[2] of all Chess games, where arrows are +game-legality-preserving moves between boardstates, a rule is associated with every one of those arrows. + +[2] I don't actually know if this is a proper category, so maybe it's just a Graph with some extra steps, but I like to +think of it this way. + +# 10-NOV-2024 + +## 1219 - gamerep + +I've got `Variation` (formerly Game, Line, HalfPly and Ply... it's been through a few iterations) basically working. +It can at least represent a simple variation, and while the API is going to need some finishwork, I think the design +works and I should be able to flatten a PGN into it. + +I'm debating now between merging and tackling that in a separate PR, or just pushing through and getting it done. I +think I might try to push through and see how painful it is. If it comes together quickly then I'll go for it, but +otherwise I'll plan to merge and then tackle it in a separate PR. + +I have an existing 'no-variations' PGN example I can use, and I think I should probably have the test exist in the +`tests/` subdir as it's more of an integration test than a unit test. I plan to use the `Shakmaty` PGN parser for now, +but would like to build my own eventually. + +## 1628 - gamerep + +I think I'm going to merge. + +I also think I'm going to write my own parser (probably with `nom`, maybe fully by hand). + +Here's why. + +1. The next step really is a big change in design, I'm going to be doing parser stuff, it's not going to be particularly + chess-y, and it *should* be pretty _simple_ believe it or not. +2. The model I have and the model the pgn-reader/shakmaty use are very different. I *should* be able to directly read a + variation in a single pass, no visitors or whatever, just a single read to translate each move to the correct format. + The OTS dependencies expect a visitor-pattern approach because they (rightly) believe most people won't be doing weird + bytecode shit. +3. Doing it myself means I can drop a couple dependencies, which is very cool. + +I'll probably use `nom`, but I may even try a simple RD parser myself, since the format is pretty simple. I will +probably build a `PGN` object that holds all the metadata and the actual variation, which can then be produced by/handed +off to the actual Engine. diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs deleted file mode 100644 index 133a691..0000000 --- a/benches/benchmarks.rs +++ /dev/null @@ -1,120 +0,0 @@ -#![feature(custom_test_frameworks)] -#![test_runner(criterion::runner)] -// NOTE: This is necessary because `bench!` is not super bright and just makes everything mutable for convenience sake. -#![allow(unused_mut)] - - -/* -* -* NOTE: FIXME: -* -* These benchmarks are old and crusty, someday, I will reimplement them, but a functioning engine -* is my priority right now. Originally I mostly wanted to mess around with Criterion, but along the -* way it turns our I preferred working on Chess as a problem then on benchmarking as a tool. -* -* So until I need to start optimizing for performance, these are bitrot. -*/ - -pub use criterion::{black_box, Criterion, Throughput}; -pub use criterion_macro::criterion; - -use rand::distributions::{Distribution, Uniform}; - -/* -#[macro_use] -extern crate lazy_static; -*/ -#[macro_use] -extern crate paste; - -/* -use hazel::bitboard::*; -use hazel::constants::*; -use hazel::ply::*; -//use hazel::constants::magic::*; -mod bitboard; -mod movegen; -mod ply; -*/ - -/// A helper macro for quickly defining benchmarks. Invoke as follows: -/// -/// bench!( -/// group: GroupNameUsuallyAStructName, -/// pretty: "a nicer looking name for the report", -/// name: a_valid_name_for_the_defined_function, -/// test: { some test using the variables defined below }, -/// where: -/// var => value; -/// can => specify; -/// many => vars(functions, allowed, too) -/// ); -/// -/// This will define a benchmark function, set it to measure throughput in items/s, and do wall-time. -/// -/// TODO: Make the field ordering arbitrary -/// TODO: make `name` optional and autogenerate function names -/// TODO: better variable setup (would love it if it were just raw rust there) -#[macro_export] -macro_rules! bench { - (group: $group:ident, pretty: $pretty:tt, name: $name:ident, test: $test:block, where: $($var:ident => $val:expr;)* ) => { - paste! { - #[allow(non_snake_case)] - #[criterion(config())] - pub fn [<$group _ $name>](c: &mut Criterion) { - $( - // FIXME: This declares everything mutable-by-default, but the macro should ideally let you specify this. - let mut $var = $val; - )* - let mut g = c.benchmark_group(stringify!($group)); - g.throughput(Throughput::Elements(1 as u64)); - g.bench_function(format!("{}::{}", stringify!($group), $pretty), |b| b.iter(|| - black_box($test) - )); - g.finish(); - } - } - } -} - -fn config() -> Criterion { - Criterion::default().sample_size(25000) -} - -// NOTE: These measurements serve as references for 'the fastest possible thing' this machine can do. -// It's performance should basically be 1 operation + any overhead, giving some context to the -// other benchmarks. -bench!( - group: Reference, - pretty: "Measurement (Integer Operations)", - name: reference, - test: { 2i64 + 2i64 }, - where: -); - -pub fn random_double() -> f64 { - let range = Uniform::new(f64::MIN, f64::MAX); - let mut rng = rand::thread_rng(); - - range.sample(&mut rng) -} - -pub fn random_usize() -> usize { - let range = Uniform::new(usize::MIN, usize::MAX); - let mut rng = rand::thread_rng(); - - range.sample(&mut rng) -} - -pub fn random_u64() -> u64 { - let range = Uniform::new(u64::MIN, u64::MAX); - let mut rng = rand::thread_rng(); - - range.sample(&mut rng) -} - -/* -pub fn random_bitboard() -> Bitboard { - Bitboard::from(random_u64()) -} -*/ diff --git a/benches/bitboard/bitops.rs b/benches/bitboard/bitops.rs deleted file mode 100644 index 8bf2c94..0000000 --- a/benches/bitboard/bitops.rs +++ /dev/null @@ -1,70 +0,0 @@ -use super::*; - -bench!( - group: Bitboard, - pretty: "&/2", - name: and_op, - test: { b1 & b2 }, - where: - b1 => random_bitboard(); - b2 => random_bitboard(); -); - -bench!( - group: Bitboard, - pretty: "|/2", - name: or_op, - test: { b1 | b2 }, - where: - b1 => random_bitboard(); - b2 => random_bitboard(); -); - -bench!( - group: Bitboard, - pretty: "^/2", - name: xor_op, - test: { b1 ^ b2 }, - where: - b1 => random_bitboard(); - b2 => random_bitboard(); -); - -bench!( - group: Bitboard, - pretty: "!/1", - name: and_op, - test: { !b1 }, - where: - b1 => random_bitboard(); -); - -bench!( - group: Bitboard, - pretty: "&=/2", - name: and_assign_op, - test: { b1 &= b2 }, - where: - b1 => random_bitboard(); - b2 => random_bitboard(); -); - -bench!( - group: Bitboard, - pretty: "|=/2", - name: or_assign_op, - test: { b1 |= b2 }, - where: - b1 => random_bitboard(); - b2 => random_bitboard(); -); - -bench!( - group: Bitboard, - pretty: "^=/2", - name: xor_assign_op, - test: { b1 ^= b2 }, - where: - b1 => random_bitboard(); - b2 => random_bitboard(); -); diff --git a/benches/bitboard/creation.rs b/benches/bitboard/creation.rs deleted file mode 100644 index d2b32ea..0000000 --- a/benches/bitboard/creation.rs +++ /dev/null @@ -1,17 +0,0 @@ -use super::*; - -bench!( - group: Bitboard, - pretty: "full/0", - name: full, - test: { Bitboard::full() }, - where: -); - -bench!( - group: Bitboard, - pretty: "from_notation/0", - name: from_notation, - test: { Bitboard::from_notation("d4") }, - where: -); diff --git a/benches/bitboard/mod.rs b/benches/bitboard/mod.rs deleted file mode 100644 index ced378f..0000000 --- a/benches/bitboard/mod.rs +++ /dev/null @@ -1,108 +0,0 @@ -use super::*; - -mod bitops; -mod creation; -mod shifts; - -bench!( - group: Bitboard, - pretty: "is_empty/0", - name: is_empty, - test: { bb.is_empty() }, - where: - bb => Bitboard::empty(); -); - -bench!( - group: Bitboard, - pretty: "is_full/0", - name: is_full, - test: { bb.is_full() }, - where: - bb => Bitboard::full(); -); - -bench!( - group: Bitboard, - pretty: "set/2", - name: set, - test: { bb.set(rank,file) }, - where: - bb => Bitboard::empty(); - rank => random_usize() % 8; - file => random_usize() % 8; -); - -bench!( - group: Bitboard, - pretty: "set_by_index/2", - name: set_by_index, - test: { bb.set_by_index(index) }, - where: - bb => Bitboard::empty(); - index => random_usize() % 64; -); - -bench!( - group: Bitboard, - pretty: "set_by_notation/2", - name: set_by_notation, - test: { bb.set_by_notation(notation) }, - where: - bb => Bitboard::empty(); - notation => "d4"; -); - -bench!( - group: Bitboard, - pretty: "unset/2", - name: unset, - test: { bb.unset(3,3) }, - where: - bb => Bitboard::from_notation("d4"); -); - -bench!( - group: Bitboard, - pretty: "flip/2", - name: unset, - test: { bb.flip(3,3) }, - where: - bb => Bitboard::from_notation("d4"); -); - -bench!( - group: Bitboard, - pretty: "is_set/2", - name: is_set, - test: { bb.is_set(3,3) }, - where: - bb => Bitboard::from_notation("d4"); -); - -bench!( - group: Bitboard, - pretty: "is_index_set/1", - name: is_index_set, - test: { bb.is_index_set(0o33) }, - where: - bb => Bitboard::from_notation("d4"); -); - -bench!( - group: Bitboard, - pretty: "is_notation_set/1", - name: is_notation_set, - test: { bb.is_notation_set("d4") }, - where: - bb => Bitboard::from_notation("d4"); -); - -bench!( - group: Bitboard, - pretty: "count/0", - name: count, - test: { bb.count() }, - where: - bb => Bitboard::from(random_u64()); -); diff --git a/benches/bitboard/shifts.rs b/benches/bitboard/shifts.rs deleted file mode 100644 index 7254aea..0000000 --- a/benches/bitboard/shifts.rs +++ /dev/null @@ -1,36 +0,0 @@ -use super::*; - -pub fn random_direction() -> Direction { - let d = random_usize() % 8; - match d { - 0 => Direction::N, - 1 => Direction::NE, - 2 => Direction::E, - 3 => Direction::SE, - 4 => Direction::S, - 5 => Direction::SW, - 6 => Direction::W, - 7 => Direction::NW, - _ => panic!("Not possible"), - } -} - -bench!( - group: Bitboard, - pretty: "shift/1", - name: shift, - test: { b1.shift(dir) }, - where: - b1 => random_bitboard(); - dir => random_direction(); -); - -bench!( - group: Bitboard, - pretty: "shift_mut/1", - name: shift_mut, - test: { b1.shift_mut(dir) }, - where: - b1 => random_bitboard(); - dir => random_direction(); -); diff --git a/benches/movegen/mod.rs b/benches/movegen/mod.rs deleted file mode 100644 index b46d475..0000000 --- a/benches/movegen/mod.rs +++ /dev/null @@ -1,39 +0,0 @@ -use super::*; - -use hazel::pextboard::{slow_bishop_attacks, slow_rook_attacks}; - -bench!( - group: MoveGen, - pretty: "rook_attacks/2 - Slow Method", - name: rook_attacks_slow, - test: { slow_rook_attacks(rook_idx, random_bitboard()) }, - where: - rook_idx => Bitboard::from(1 << (random_u64() % 64)); -); - -bench!( - group: MoveGen, - pretty: "rook_attacks/2 - PEXTBoard Method", - name: rook_attacks_slow, - test: { hazel::pextboard::attacks_for(Piece::Rook, rook_idx, random_bitboard()) }, - where: - rook_idx => random_usize() % 64; -); - -bench!( - group: MoveGen, - pretty: "bishop_attacks/2 - Slow Method", - name: bishop_attacks_slow, - test: { slow_bishop_attacks(bishop_idx, random_bitboard()) }, - where: - bishop_idx => Bitboard::from(1 << (random_u64() % 64)); -); - -bench!( - group: MoveGen, - pretty: "bishop_attacks/2 - PEXTBoard Method", - name: bishop_attacks_slow, - test: { hazel::pextboard::attacks_for(Piece::Bishop, bishop_idx, random_bitboard()) }, - where: - bishop_idx => random_usize() % 64; -); diff --git a/benches/ply/mod.rs b/benches/ply/mod.rs deleted file mode 100644 index 953c45a..0000000 --- a/benches/ply/mod.rs +++ /dev/null @@ -1,109 +0,0 @@ -use super::*; - -lazy_static! { - pub static ref START_POSITION_FEN: String = - String::from("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); - pub static ref SIMPLE_POSITION_FEN: String = - String::from("8/5k1p/2n5/3N4/6P1/3K4/8/8 w - - 0 1"); - pub static ref LONDON_POSITION_FEN: String = - String::from("r1bqk2r/pp2bppp/2n1pn2/2pp4/3P1B2/2P1PN1P/PP1N1PP1/R2QKB1R b KQkq - 0 7"); - pub static ref START_POSITION: Ply = Ply::from_fen(&START_POSITION_FEN); - pub static ref LONDON_POSITION: Ply = Ply::from_fen(&LONDON_POSITION_FEN); - pub static ref SIMPLE_POSITION: Ply = Ply::from_fen(&SIMPLE_POSITION_FEN); -} - -bench!( - group: Ply, - pretty: "occupancy_for/1 - White, Starting Position", - name: occupancy_for_1, - test: { START_POSITION.occupancy_for(Color::WHITE) }, - where: -); - -bench!( - group: Ply, - pretty: "occupancy_for/1 - Black, Starting Position", - name: occupancy_for_2, - test: { START_POSITION.occupancy_for(Color::BLACK) }, - where: -); - -bench!( - group: Ply, - pretty: "occupancy_for/1 - White, Simple Position", - name: occupancy_for_3, - test: { SIMPLE_POSITION.occupancy_for(Color::WHITE) }, - where: -); - -bench!( - group: Ply, - pretty: "occupancy_for/1 - Black, Simple Position", - name: occupancy_for_4, - test: { SIMPLE_POSITION.occupancy_for(Color::BLACK) }, - where: -); - -bench!( - group: Ply, - pretty: "occupancy_for/1 - White, London Position", - name: occupancy_for_5, - test: { LONDON_POSITION.occupancy_for(Color::WHITE) }, - where: -); - -bench!( - group: Ply, - pretty: "occupancy_for/1 - Black, London Position", - name: occupancy_for_6, - test: { LONDON_POSITION.occupancy_for(Color::BLACK) }, - where: -); - -bench!( - group: Ply, - pretty: "occupancy/0 - Starting Position", - name: occupancy_1, - test: { START_POSITION.occupancy() }, - where: -); - -bench!( - group: Ply, - pretty: "occupancy/0 - Simple Position", - name: occupancy_2, - test: { SIMPLE_POSITION.occupancy() }, - where: -); - -bench!( - group: Ply, - pretty: "occupancy/0 - London Position", - name: occupancy_3, - test: { LONDON_POSITION.occupancy() }, - where: -); - -bench!( - group: Ply, - pretty: "from_fen/1 - Simple Position", - name: from_fen_simple, - test: { Ply::from_fen(&SIMPLE_POSITION_FEN) }, - where: -); - -bench!( - group: Ply, - pretty: "from_fen/1 - Start Position", - name: from_fen_start, - test: { Ply::from_fen(&START_POSITION_FEN) }, - where: -); - -bench!( - group: Ply, - pretty: "from_fen/1 - London Position", - name: from_fen_london, - test: { Ply::from_fen(&LONDON_POSITION_FEN) }, - where: -); diff --git a/doc/records/mutation-testing/10-NOV-2024/caught.txt b/doc/records/mutation-testing/10-NOV-2024/caught.txt new file mode 100644 index 0000000..262e711 --- /dev/null +++ b/doc/records/mutation-testing/10-NOV-2024/caught.txt @@ -0,0 +1,396 @@ +src/notation/fen/mod.rs:56:9: replace FEN::with_default_metadata -> Self with Default::default() +src/notation/square/from_into.rs:5:27: replace + with - in ::from +src/util/charray.rs:91:49: replace - with / in Charray::adjust_coordinates +src/notation/fen/position_metadata.rs:131:34: replace += with -= in PositionMetadata::update +src/types/direction.rs:23:33: replace + with * in Direction::index_shift +src/types/color.rs:68:14: replace == with != in Color::is_white +src/game/variation.rs:121:9: replace Variation::record -> &mut Self with Box::leak(Box::new(Default::default())) +src/notation/fen/position_metadata.rs:142:29: replace || with && in PositionMetadata::update +src/constants/file.rs:51:9: replace File::to_index -> usize with 0 +src/notation/fen/castle_rights.rs:46:44: replace != with == in ::from +src/notation/square/mod.rs:23:23: replace * with + in Square::set_rank +src/coup/rep/mod.rs:539:98: replace & with ^ in Move::is_double_pawn_push_for +src/types/occupant.rs:70:9: replace ::fmt -> std::fmt::Result with Ok(Default::default()) +src/notation/fen/position_metadata.rs:192:28: replace << with >> in ::from +src/coup/rep/mod.rs:539:49: replace - with + in Move::is_double_pawn_push_for +src/notation/fen/position_metadata.rs:222:47: replace & with | in ::from +src/types/pextboard/mod.rs:57:27: replace & with ^ in slow_attacks +src/coup/rep/move_type.rs:125:22: replace & with ^ in MoveType::is_en_passant +src/game/variation.rs:59:9: replace Variation::setup -> &mut Self with Box::leak(Box::new(Default::default())) +src/util/charray.rs:88:9: replace Charray::adjust_coordinates -> (usize, usize) with (1, 1) +src/constants/file.rs:59:9: replace File::to_pgn -> &'static str with "" +src/types/log/mod.rs:90:25: replace += with -= in Log::write +src/coup/rep/mod.rs:550:43: replace == with != in Move::is_short_castling_move_for +src/types/log/write_head.rs:49:9: replace WriteHead<'a, T>::next -> Option<&mut T> with None +src/board/interface/alteration.rs:45:9: replace Alteration::lit -> Vec with vec![] +src/notation/fen/position_metadata.rs:145:33: replace += with *= in PositionMetadata::update +src/notation/fen/position_metadata.rs:131:34: replace += with *= in PositionMetadata::update +src/notation/square/mod.rs:28:30: replace * with + in Square::set_file +src/coup/rep/mod.rs:511:9: replace Move::is_capture -> bool with false +src/notation/fen/position.rs:90:30: replace != with == in ::from +src/notation/fen/castle_rights.rs:47:44: replace != with == in ::from +src/board/interface/query.rs:39:48: replace + with - in display_board +src/coup/rep/move_type.rs:168:14: replace == with != in MoveType::is_ambiguous +src/util/charray.rs:89:38: replace - with / in Charray::adjust_coordinates +src/notation/fen/position.rs:79:35: replace != with == in ::from +src/notation/fen/position_metadata.rs:217:34: replace & with | in ::from +src/notation/square/mod.rs:45:9: replace Square::up -> Option with None +src/types/direction.rs:30:34: replace + with * in Direction::index_shift +src/types/direction.rs:29:33: replace - with / in Direction::index_shift +src/coup/rep/mod.rs:318:37: replace >> with << in Move::target_idx +src/types/log/write_head.rs:58:26: replace == with != in WriteHead<'a, T>::prev +src/notation/fen/position_metadata.rs:218:44: replace & with ^ in ::from +src/coup/rep/move_type.rs:104:14: replace == with != in MoveType::is_long_castle +src/notation/fen/position_metadata.rs:210:9: replace ::from -> Self with Default::default() +src/coup/rep/move_type.rs:112:9: replace MoveType::is_capture -> bool with true +src/engine/uci/mod.rs:94:9: replace UCIOption::is_keyword -> bool with false +src/util/charray.rs:89:45: replace - with + in Charray::adjust_coordinates +src/notation/square/iterator.rs:85:9: replace ::from -> Self with Default::default() +src/notation/square/mod.rs:37:16: replace % with + in Square::file +src/coup/rep/mod.rs:511:9: replace Move::is_capture -> bool with true +src/notation/fen/position_metadata.rs:48:9: replace ::fmt -> std::fmt::Result with Ok(Default::default()) +src/util/charray.rs:38:9: replace Charray::get -> u8 with 1 +src/notation/square/from_into.rs:5:23: replace * with / in ::from +src/notation/fen/mod.rs:28:9: replace ::fmt -> std::fmt::Result with Ok(Default::default()) +src/notation/fen/position_metadata.rs:224:33: replace & with | in ::from +src/types/log/transaction.rs:27:9: replace Transaction::commit -> Vec with vec![] +src/notation/uci.rs:57:43: replace > with < in ::try_from +src/types/occupant.rs:21:9: delete ! in Occupant::is_occupied +src/notation/fen/position.rs:79:25: replace == with != in ::from +src/types/direction.rs:26:34: replace - with + in Direction::index_shift +src/notation/fen/mod.rs:35:23: replace == with != in ::eq +src/notation/fen/castle_rights.rs:57:9: replace ::from -> u32 with 0 +src/constants/file.rs:37:21: replace & with ^ in File::from_index +src/coup/rep/mod.rs:292:18: replace & with ^ in Move::source_idx +src/notation/fen/position_metadata.rs:195:12: replace |= with &= in ::from +src/types/color.rs:64:9: replace Color::is_black -> bool with true +src/types/occupant.rs:21:9: replace Occupant::is_occupied -> bool with false +src/notation/fen/position_metadata.rs:189:12: replace |= with &= in ::from +src/notation/square/from_into.rs:17:9: replace ::from -> usize with 1 +src/notation/square/mod.rs:48:30: replace + with - in Square::up +src/board/simple/mod.rs:48:9: replace ::alter -> PieceBoard with Default::default() +src/util/mod.rs:13:27: replace << with >> in select_subset +src/types/color.rs:68:9: replace Color::is_white -> bool with false +src/notation/fen/position_metadata.rs:218:56: replace >> with << in ::from +src/coup/rep/mod.rs:292:9: replace Move::source_idx -> usize with 1 +src/notation/square/mod.rs:53:24: replace == with != in Square::down +src/notation/fen/mod.rs:128:9: replace ::fmt -> std::fmt::Result with Ok(Default::default()) +src/types/log/mod.rs:94:9: replace Log::get -> Option<&T> with None +src/board/interface/query.rs:54:26: replace != with == in to_fen +src/notation/fen/castle_rights.rs:65:22: replace |= with &= in ::from +src/notation/fen/position.rs:88:42: replace += with *= in ::from +src/coup/rep/mod.rs:380:30: replace & with | in Move::move_metadata +src/util/mod.rs:13:22: replace & with | in select_subset +src/board/interface/query.rs:63:22: replace != with == in to_fen +src/util/charray.rs:88:9: replace Charray::adjust_coordinates -> (usize, usize) with (0, 0) +src/coup/rep/mod.rs:529:9: replace Move::is_ambiguous -> bool with true +src/notation/square/mod.rs:72:30: replace + with - in Square::right +src/coup/rep/debug.rs:6:9: replace ::fmt -> Result with Ok(Default::default()) +src/types/log/write_head.rs:52:27: replace += with -= in WriteHead<'a, T>::next +src/util/charray.rs:92:39: replace - with / in Charray::adjust_coordinates +src/types/color.rs:64:9: replace Color::is_black -> bool with false +src/types/log/cursor.rs:24:51: replace + with - in Cursor<'a, T>::jump +src/board/interface/query.rs:34:5: replace display_board -> String with "xyzzy".into() +src/coup/rep/mod.rs:126:9: replace Move::disambiguate -> Option with None +src/board/interface/query.rs:34:5: replace display_board -> String with String::new() +src/types/color.rs:64:14: replace == with != in Color::is_black +src/notation/fen/position.rs:68:9: replace ::fmt -> std::fmt::Result with Ok(Default::default()) +src/notation/fen/position_metadata.rs:195:43: replace << with >> in ::from +src/board/simple/from_into.rs:19:9: replace PieceBoard::set_startpos with () +src/coup/rep/mod.rs:549:9: replace Move::is_short_castling_move_for -> bool with false +src/types/piece.rs:25:9: replace Piece::to_fen -> char with Default::default() +src/coup/rep/move_type.rs:112:22: replace & with | in MoveType::is_capture +src/coup/rep/move_type.rs:112:44: replace != with == in MoveType::is_capture +src/notation/square/mod.rs:37:16: replace % with / in Square::file +src/util/charray.rs:91:42: replace - with / in Charray::adjust_coordinates +src/types/occupant.rs:21:9: replace Occupant::is_occupied -> bool with true +src/notation/square/mod.rs:56:30: replace - with + in Square::down +src/notation/square/mod.rs:48:30: replace + with * in Square::up +src/notation/square/iterator.rs:13:20: replace += with -= in ::next +src/board/interface/query.rs:39:23: replace + with * in display_board +src/notation/square/mod.rs:69:24: replace == with != in Square::right +src/util/charray.rs:92:46: replace - with + in Charray::adjust_coordinates +src/notation/fen/position_metadata.rs:145:33: replace += with -= in PositionMetadata::update +src/types/direction.rs:22:9: replace Direction::index_shift -> usize with 1 +src/notation/fen/mod.rs:139:5: replace setup_mut with () +src/notation/fen/position.rs:126:9: replace ::into_iter -> Self::IntoIter with Default::default() +src/types/log/cursor.rs:47:26: replace == with != in Cursor<'a, T>::next +src/types/log/write_head.rs:27:9: replace WriteHead<'a, T>::jump -> Option<&mut T> with None +src/util/charray.rs:88:9: replace Charray::adjust_coordinates -> (usize, usize) with (0, 1) +src/coup/rep/mod.rs:250:9: replace Move::is_null -> bool with false +src/notation/square/mod.rs:33:9: replace Square::index -> usize with 0 +src/types/direction.rs:22:9: replace Direction::index_shift -> usize with 0 +src/notation/fen/castle_rights.rs:47:35: replace & with ^ in ::from +src/notation/square/mod.rs:28:34: replace + with - in Square::set_file +src/notation/fen/position.rs:80:26: replace != with == in ::from +src/notation/square/mod.rs:69:9: replace Square::right -> Option with None +src/coup/rep/move_type.rs:108:9: replace MoveType::is_short_castle -> bool with true +src/notation/square/from_into.rs:5:27: replace + with * in ::from +src/coup/rep/move_type.rs:125:9: replace MoveType::is_en_passant -> bool with false +src/notation/fen/castle_rights.rs:13:9: replace ::fmt -> std::fmt::Result with Ok(Default::default()) +src/notation/square/from_into.rs:68:26: replace + with * in ::try_from +src/notation/fen/mod.rs:34:23: replace == with != in ::eq +src/board/interface/query.rs:62:21: replace == with != in to_fen +src/types/direction.rs:29:33: replace - with + in Direction::index_shift +src/notation/square/mod.rs:41:9: replace Square::rank -> usize with 0 +src/types/direction.rs:23:33: replace + with - in Direction::index_shift +src/board/interface/query.rs:52:38: replace += with -= in to_fen +src/engine/uci/mod.rs:98:9: replace UCIOption::set with () +src/notation/fen/position_metadata.rs:222:47: replace & with ^ in ::from +src/types/log/mod.rs:90:25: replace += with *= in Log::write +src/coup/rep/mod.rs:318:18: replace & with ^ in Move::target_idx +src/types/color.rs:34:9: replace ::fmt -> std::fmt::Result with Ok(Default::default()) +src/notation/square/iterator.rs:226:9: replace RankFile::upward -> &mut Self with Box::leak(Box::new(Default::default())) +src/board/simple/display_debug.rs:16:9: replace ::fmt -> std::fmt::Result with Ok(Default::default()) +src/board/interface/query.rs:62:31: replace != with == in to_fen +src/notation/square/mod.rs:41:9: replace Square::rank -> usize with 1 +src/types/pextboard/mod.rs:27:44: replace / with % +src/notation/fen/castle_rights.rs:48:44: replace != with == in ::from +src/coup/rep/mod.rs:292:9: replace Move::source_idx -> usize with 0 +src/notation/fen/castle_rights.rs:48:35: replace & with | in ::from +src/board/simple/mod.rs:55:9: replace ::alter_mut -> &mut Self with Box::leak(Box::new(Default::default())) +src/notation/square/iterator.rs:180:9: replace RankFile::is_done -> bool with true +src/notation/square/mod.rs:56:30: replace - with / in Square::down +src/notation/fen/mod.rs:118:9: replace FEN::compile -> Vec with vec![] +src/types/direction.rs:24:34: replace + with * in Direction::index_shift +src/notation/fen/mod.rs:133:5: replace setup -> A with Default::default() +src/coup/rep/move_type.rs:37:9: replace MoveType::decode -> &'static str with "xyzzy" +src/engine/uci/mod.rs:63:9: replace ::fmt -> fmt::Result with Ok(Default::default()) +src/notation/square/display_debug.rs:14:9: replace ::fmt -> std::fmt::Result with Ok(Default::default()) +src/types/log/write_head.rs:21:9: replace WriteHead<'a, T>::seek -> Option<&mut T> with None +src/notation/square/iterator.rs:27:9: replace Square::fenwise -> RankFile with Default::default() +src/coup/rep/move_type.rs:125:39: replace != with == in MoveType::is_en_passant +src/coup/rep/mod.rs:380:30: replace & with ^ in Move::move_metadata +src/engine/uci/mod.rs:275:9: delete ! in UCIMessage::has_response +src/util/charray.rs:69:9: replace Charray::with_texture -> Self with Default::default() +src/engine/uci/mod.rs:225:67: replace != with == in UCIMessage::parse +src/coup/rep/mod.rs:318:18: replace & with | in Move::target_idx +src/notation/square/mod.rs:23:27: replace + with * in Square::set_rank +src/notation/fen/position_metadata.rs:196:41: replace << with >> in ::from +src/coup/rep/mod.rs:519:9: replace Move::is_long_castle -> bool with true +src/types/occupant.rs:40:9: replace Occupant::piece -> Option with None +src/notation/square/from_into.rs:68:26: replace + with - in ::try_from +src/notation/square/mod.rs:72:30: replace + with * in Square::right +src/notation/square/mod.rs:33:9: replace Square::index -> usize with 1 +src/board/simple/mod.rs:26:9: replace PieceBoard::set with () +src/notation/square/iterator.rs:66:9: replace ::eq -> bool with false +src/notation/square/iterator.rs:66:31: replace == with != in ::eq +src/types/direction.rs:25:33: replace + with - in Direction::index_shift +src/engine/uci/mod.rs:224:68: replace != with == in UCIMessage::parse +src/notation/fen/castle_rights.rs:49:35: replace & with ^ in ::from +src/util/charray.rs:89:38: replace - with + in Charray::adjust_coordinates +src/notation/fen/castle_rights.rs:46:35: replace & with ^ in ::from +src/types/direction.rs:26:34: replace - with / in Direction::index_shift +src/notation/square/iterator.rs:210:9: replace RankFile::right_to_left -> &mut Self with Box::leak(Box::new(Default::default())) +src/coup/rep/mod.rs:318:9: replace Move::target_idx -> usize with 0 +src/types/log/write_head.rs:61:27: replace -= with += in WriteHead<'a, T>::prev +src/coup/rep/mod.rs:538:9: replace Move::is_double_pawn_push_for -> bool with false +src/notation/square/from_into.rs:68:22: replace * with / in ::try_from +src/constants/file.rs:59:9: replace File::to_pgn -> &'static str with "xyzzy" +src/coup/rep/move_type.rs:120:9: replace MoveType::is_promotion -> bool with true +src/notation/square/iterator.rs:257:9: replace RankFile::start_on -> &mut Self with Box::leak(Box::new(Default::default())) +src/coup/rep/move_type.rs:120:9: replace MoveType::is_promotion -> bool with false +src/util/charray.rs:43:9: replace Charray::set with () +src/types/log/mod.rs:98:9: replace Log::get_mut -> Option<&mut T> with None +src/notation/fen/position_metadata.rs:217:46: replace >> with << in ::from +src/coup/rep/move_type.rs:120:22: replace & with ^ in MoveType::is_promotion +src/notation/fen/position_metadata.rs:217:34: replace & with ^ in ::from +src/types/occupant.rs:17:9: replace Occupant::is_empty -> bool with false +src/game/variation.rs:43:9: replace Variation::make -> &mut Self with Box::leak(Box::new(Default::default())) +src/types/log/cursor.rs:50:27: replace += with -= in Cursor<'a, T>::next +src/types/log/cursor.rs:24:51: replace + with * in Cursor<'a, T>::jump +src/board/interface/query.rs:39:37: replace * with + in display_board +src/coup/rep/move_type.rs:58:9: replace MoveType::to_uci -> &'static str with "" +src/types/piece.rs:31:19: replace & with | in ::from +src/engine/uci/mod.rs:280:42: replace == with != in UCIMessage::is_complete +src/notation/square/from_into.rs:5:23: replace * with + in ::from +src/notation/fen/position_metadata.rs:130:30: replace == with != in PositionMetadata::update +src/coup/rep/move_type.rs:72:9: replace MoveType::is_null -> bool with false +src/types/direction.rs:30:34: replace + with - in Direction::index_shift +src/notation/fen/position_metadata.rs:190:12: replace |= with &= in ::from +src/notation/fen/position_metadata.rs:222:64: replace >> with << in ::from +src/util/charray.rs:92:39: replace - with + in Charray::adjust_coordinates +src/coup/rep/move_type.rs:120:46: replace != with == in MoveType::is_promotion +src/util/charray.rs:92:53: replace - with / in Charray::adjust_coordinates +src/notation/fen/position.rs:79:30: replace && with || in ::from +src/notation/fen/position_metadata.rs:218:44: replace & with | in ::from +src/util/mod.rs:11:5: replace select_subset -> Vec with vec![] +src/engine/uci/mod.rs:228:45: replace == with != in UCIMessage::parse +src/notation/fen/castle_rights.rs:47:35: replace & with | in ::from +src/util/charray.rs:92:53: replace - with + in Charray::adjust_coordinates +src/notation/square/from_into.rs:68:22: replace * with + in ::try_from +src/types/log/write_head.rs:28:25: replace < with > in WriteHead<'a, T>::jump +src/engine/uci/mod.rs:275:9: replace UCIMessage::has_response -> bool with false +src/coup/rep/mod.rs:550:66: replace == with != in Move::is_short_castling_move_for +src/types/log/mod.rs:106:9: replace Log::len -> usize with 1 +src/engine/uci/mod.rs:94:9: replace UCIOption::is_keyword -> bool with true +src/util/mod.rs:13:22: replace & with ^ in select_subset +src/notation/fen/mod.rs:63:9: replace FEN::set_metadata with () +src/engine/uci/mod.rs:279:9: replace UCIMessage::is_complete -> bool with true +src/notation/fen/castle_rights.rs:57:9: replace ::from -> u32 with 1 +src/notation/square/mod.rs:64:30: replace - with + in Square::left +src/notation/square/iterator.rs:273:35: replace + with * in RankFile::current_square +src/coup/rep/mod.rs:385:9: replace Move::compile -> Vec with vec![] +src/engine/driver/hazel.rs:39:9: replace ::exec -> Vec with vec![] +src/notation/fen/position_metadata.rs:142:38: replace == with != in PositionMetadata::update +src/notation/fen/castle_rights.rs:49:35: replace & with | in ::from +src/notation/fen/position_metadata.rs:225:36: replace & with ^ in ::from +src/board/simple/from_into.rs:24:9: replace PieceBoard::set_fen with () +src/types/log/cursor.rs:24:9: replace Cursor<'a, T>::jump -> Option<&T> with None +src/types/pextboard/mod.rs:25:43: replace / with % +src/types/log/write_head.rs:28:25: replace < with == in WriteHead<'a, T>::jump +src/game/variation.rs:28:9: replace Variation::commit -> &mut Self with Box::leak(Box::new(Default::default())) +src/notation/square/iterator.rs:54:9: replace ::fmt -> std::fmt::Result with Ok(Default::default()) +src/types/piece.rs:31:19: replace & with ^ in ::from +src/types/log/write_head.rs:49:26: replace == with != in WriteHead<'a, T>::next +src/types/direction.rs:27:33: replace - with + in Direction::index_shift +src/notation/square/mod.rs:64:30: replace - with / in Square::left +src/coup/rep/mod.rs:292:18: replace & with | in Move::source_idx +src/board/simple/mod.rs:41:9: replace ::from -> Self with Default::default() +src/notation/fen/position_metadata.rs:128:9: replace PositionMetadata::update with () +src/notation/fen/position_metadata.rs:76:9: replace PositionMetadata::parse with () +src/types/color.rs:68:9: replace Color::is_white -> bool with true +src/util/charray.rs:92:46: replace - with / in Charray::adjust_coordinates +src/notation/fen/castle_rights.rs:48:35: replace & with ^ in ::from +src/notation/square/from_into.rs:37:24: replace != with == in ::try_from +src/types/log/write_head.rs:58:9: replace WriteHead<'a, T>::prev -> Option<&mut T> with None +src/util/charray.rs:91:42: replace - with + in Charray::adjust_coordinates +src/util/charray.rs:38:9: replace Charray::get -> u8 with 0 +src/types/log/cursor.rs:56:26: replace == with != in Cursor<'a, T>::prev +src/coup/rep/debug.rs:28:9: replace ::fmt -> Result with Ok(Default::default()) +src/notation/fen/castle_rights.rs:32:9: replace ::from -> u8 with 1 +src/game/variation.rs:49:9: replace Variation::new_game -> &mut Self with Box::leak(Box::new(Default::default())) +src/types/occupant.rs:17:9: replace Occupant::is_empty -> bool with true +src/types/log/cursor.rs:47:9: replace Cursor<'a, T>::next -> Option<&T> with None +src/engine/uci/mod.rs:158:53: replace == with != in ::fmt +src/notation/square/mod.rs:53:9: replace Square::down -> Option with None +src/coup/rep/mod.rs:561:43: replace == with != in Move::is_long_castling_move_for +src/types/log/mod.rs:60:9: replace Log::commit -> &mut Self with Box::leak(Box::new(Default::default())) +src/types/log/mod.rs:89:9: replace Log::write with () +src/notation/uci.rs:57:28: replace || with && in ::try_from +src/engine/uci/mod.rs:158:46: replace || with && in ::fmt +src/board/interface/query.rs:39:37: replace * with / in display_board +src/util/charray.rs:99:9: replace >::fmt -> std::fmt::Result with Ok(Default::default()) +src/coup/rep/move_type.rs:120:22: replace & with | in MoveType::is_promotion +src/types/log/cursor.rs:65:9: replace Cursor<'a, T>::read -> Option<&T> with None +src/util/charray.rs:92:60: replace - with + in Charray::adjust_coordinates +src/coup/rep/mod.rs:126:12: delete ! in Move::disambiguate +src/coup/rep/move_type.rs:58:9: replace MoveType::to_uci -> &'static str with "xyzzy" +src/notation/square/iterator.rs:273:31: replace * with / in RankFile::current_square +src/notation/square/mod.rs:41:16: replace / with % in Square::rank +src/notation/fen/castle_rights.rs:49:44: replace != with == in ::from +src/notation/fen/castle_rights.rs:46:35: replace & with | in ::from +src/notation/square/mod.rs:23:23: replace * with / in Square::set_rank +src/notation/fen/mod.rs:34:9: replace ::eq -> bool with false +src/types/log/mod.rs:49:9: replace Log::begin -> &mut Self with Box::leak(Box::new(Default::default())) +src/types/color.rs:50:9: replace Color::pawn_rank -> Bitboard with Default::default() +src/notation/fen/position_metadata.rs:196:12: replace |= with &= in ::from +src/notation/uci.rs:57:24: replace < with > in ::try_from +src/notation/fen/position.rs:109:30: replace == with != in ::eq +src/coup/rep/mod.rs:515:9: replace Move::is_short_castle -> bool with true +src/notation/fen/position.rs:109:9: replace ::eq -> bool with false +src/notation/fen/castle_rights.rs:68:22: replace |= with &= in ::from +src/notation/square/mod.rs:37:9: replace Square::file -> usize with 1 +src/types/direction.rs:24:34: replace + with - in Direction::index_shift +src/board/interface/query.rs:39:23: replace + with - in display_board +src/notation/square/mod.rs:61:24: replace == with != in Square::left +src/coup/rep/mod.rs:318:9: replace Move::target_idx -> usize with 1 +src/coup/rep/mod.rs:539:49: replace - with / in Move::is_double_pawn_push_for +src/notation/square/iterator.rs:194:9: replace RankFile::left_to_right -> &mut Self with Box::leak(Box::new(Default::default())) +src/notation/square/display_debug.rs:8:9: replace ::fmt -> std::fmt::Result with Ok(Default::default()) +src/board/simple/display_debug.rs:9:9: replace ::fmt -> std::fmt::Result with Ok(Default::default()) +src/board/interface/query.rs:62:26: replace && with || in to_fen +src/types/log/transaction.rs:23:9: replace Transaction::record with () +src/notation/fen/castle_rights.rs:59:22: replace |= with &= in ::from +src/notation/square/iterator.rs:11:25: replace < with == in ::next +src/coup/rep/mod.rs:348:9: replace Move::is_promotion -> bool with true +src/notation/square/iterator.rs:242:9: replace RankFile::downward -> &mut Self with Box::leak(Box::new(Default::default())) +src/util/mod.rs:13:33: replace > with == in select_subset +src/notation/uci.rs:57:24: replace < with == in ::try_from +src/types/log/write_head.rs:52:27: replace += with *= in WriteHead<'a, T>::next +src/board/simple/from_into.rs:6:9: replace ::from -> Self with Default::default() +src/types/color.rs:57:9: replace Color::promotion_rank -> Bitboard with Default::default() +src/util/mod.rs:13:33: replace > with < in select_subset +src/board/simple/mod.rs:34:9: replace ::get -> Occupant with Default::default() +src/board/interface/alteration.rs:21:9: replace ::fmt -> std::fmt::Result with Ok(Default::default()) +src/notation/fen/position.rs:100:18: replace != with == in ::from +src/coup/rep/move_type.rs:37:9: replace MoveType::decode -> &'static str with "" +src/board/interface/query.rs:39:48: replace + with * in display_board +src/types/log/cursor.rs:59:27: replace -= with /= in Cursor<'a, T>::prev +src/coup/rep/move_type.rs:168:9: replace MoveType::is_ambiguous -> bool with true +src/notation/square/iterator.rs:11:25: replace < with > in ::next +src/notation/square/from_into.rs:84:18: replace < with > in ::try_from +src/notation/square/iterator.rs:13:20: replace += with *= in ::next +src/board/interface/query.rs:52:38: replace += with *= in to_fen +src/coup/rep/mod.rs:292:37: replace >> with << in Move::source_idx +src/types/log/cursor.rs:59:27: replace -= with += in Cursor<'a, T>::prev +src/notation/fen/position_metadata.rs:224:33: replace & with ^ in ::from +src/util/charray.rs:91:49: replace - with + in Charray::adjust_coordinates +src/notation/square/mod.rs:41:16: replace / with * in Square::rank +src/notation/fen/position_metadata.rs:224:49: replace != with == in ::from +src/coup/rep/move_type.rs:104:9: replace MoveType::is_long_castle -> bool with false +src/game/variation.rs:82:9: replace Variation::current_position -> FEN with Default::default() +src/notation/fen/mod.rs:48:9: replace FEN::start_position -> Self with Default::default() +src/coup/rep/move_type.rs:112:22: replace & with ^ in MoveType::is_capture +src/types/log/write_head.rs:67:9: replace WriteHead<'a, T>::read -> Option<&mut T> with None +src/notation/fen/position_metadata.rs:189:20: replace << with >> in ::from +src/constants/file.rs:25:9: replace ::from -> Self with Default::default() +src/coup/rep/move_type.rs:72:14: replace == with != in MoveType::is_null +src/util/charray.rs:92:60: replace - with / in Charray::adjust_coordinates +src/notation/square/mod.rs:45:24: replace == with != in Square::up +src/types/log/write_head.rs:61:27: replace -= with /= in WriteHead<'a, T>::prev +src/coup/rep/mod.rs:348:9: replace Move::is_promotion -> bool with false +src/coup/rep/move_type.rs:112:9: replace MoveType::is_capture -> bool with false +src/notation/square/iterator.rs:273:31: replace * with + in RankFile::current_square +src/types/log/cursor.rs:26:25: replace < with == in Cursor<'a, T>::jump +src/types/direction.rs:27:33: replace - with / in Direction::index_shift +src/notation/fen/castle_rights.rs:62:22: replace |= with &= in ::from +src/types/occupant.rs:33:9: replace Occupant::color -> Option with None +src/notation/square/from_into.rs:17:9: replace ::from -> usize with 0 +src/coup/rep/mod.rs:560:9: replace Move::is_long_castling_move_for -> bool with false +src/notation/fen/position.rs:74:9: replace ::from -> Self with Default::default() +src/notation/uci.rs:70:32: replace == with != in ::try_from +src/types/log/cursor.rs:18:9: replace Cursor<'a, T>::seek -> Option<&T> with None +src/notation/fen/position_metadata.rs:192:46: replace | with & in ::from +src/types/log/write_head.rs:27:51: replace + with - in WriteHead<'a, T>::jump +src/notation/square/from_into.rs:84:18: replace < with == in ::try_from +src/notation/uci.rs:57:43: replace > with == in ::try_from +src/coup/rep/move_type.rs:72:9: replace MoveType::is_null -> bool with true +src/board/interface/query.rs:46:5: replace to_fen -> FEN with Default::default() +src/engine/uci/mod.rs:158:24: replace == with != in ::fmt +src/coup/rep/move_type.rs:154:9: replace MoveType::promotion_piece -> Option with None +src/types/log/mod.rs:106:9: replace Log::len -> usize with 0 +src/coup/rep/mod.rs:539:69: replace == with != in Move::is_double_pawn_push_for +src/types/direction.rs:28:34: replace - with / in Direction::index_shift +src/notation/fen/mod.rs:100:9: replace FEN::halfmove_clock -> u8 with 1 +src/coup/rep/move_type.rs:108:14: replace == with != in MoveType::is_short_castle +src/types/log/mod.rs:55:9: replace Log::record -> &mut Self with Box::leak(Box::new(Default::default())) +src/notation/square/mod.rs:37:9: replace Square::file -> usize with 0 +src/constants/file.rs:51:9: replace File::to_index -> usize with 1 +src/coup/rep/mod.rs:524:9: replace Move::is_en_passant -> bool with false +src/coup/rep/move_type.rs:108:9: replace MoveType::is_short_castle -> bool with false +src/notation/fen/position_metadata.rs:225:36: replace & with | in ::from +src/types/log/cursor.rs:26:25: replace < with > in Cursor<'a, T>::jump +src/coup/rep/move_type.rs:104:9: replace MoveType::is_long_castle -> bool with true +src/notation/square/mod.rs:61:9: replace Square::left -> Option with None +src/notation/square/mod.rs:28:34: replace + with * in Square::set_file +src/notation/fen/position.rs:88:42: replace += with -= in ::from +src/notation/square/iterator.rs:273:35: replace + with - in RankFile::current_square +src/notation/fen/castle_rights.rs:32:9: replace ::from -> u8 with 0 +src/types/direction.rs:25:33: replace + with * in Direction::index_shift +src/notation/fen/position_metadata.rs:185:9: replace ::from -> Self with Default::default() +src/coup/rep/mod.rs:539:98: replace & with | in Move::is_double_pawn_push_for +src/coup/rep/mod.rs:539:105: replace == with != in Move::is_double_pawn_push_for +src/coup/rep/mod.rs:561:66: replace == with != in Move::is_long_castling_move_for +src/types/log/write_head.rs:27:51: replace + with * in WriteHead<'a, T>::jump +src/notation/fen/mod.rs:105:9: replace FEN::fullmove_number -> u16 with 0 +src/coup/rep/mod.rs:515:9: replace Move::is_short_castle -> bool with false +src/coup/rep/mod.rs:519:9: replace Move::is_long_castle -> bool with false +src/constants/file.rs:37:21: replace & with | in File::from_index +src/notation/square/iterator.rs:11:9: replace ::next -> Option with None +src/types/log/cursor.rs:56:9: replace Cursor<'a, T>::prev -> Option<&T> with None +src/util/charray.rs:89:45: replace - with / in Charray::adjust_coordinates +src/types/direction.rs:28:34: replace - with + in Direction::index_shift +src/util/charray.rs:88:9: replace Charray::adjust_coordinates -> (usize, usize) with (1, 0) +src/notation/fen/mod.rs:110:9: replace FEN::setup -> A with Default::default() +src/notation/fen/position.rs:26:9: replace Position::compile -> Vec with vec![] diff --git a/doc/records/mutation-testing/10-NOV-2024/missed.txt b/doc/records/mutation-testing/10-NOV-2024/missed.txt new file mode 100644 index 0000000..3cf4242 --- /dev/null +++ b/doc/records/mutation-testing/10-NOV-2024/missed.txt @@ -0,0 +1,118 @@ +src/board/simple/from_into.rs:12:9: replace ::from -> Self with Default::default() +src/constants/file.rs:55:9: replace File::to_byte -> u8 with 0 +src/constants/file.rs:55:9: replace File::to_byte -> u8 with 1 +# src/coup/rep/mod.rs:134:25: delete ! in Move::disambiguate +# src/coup/rep/mod.rs:142:56: replace - with + in Move::disambiguate +# src/coup/rep/mod.rs:142:56: replace - with / in Move::disambiguate +# src/coup/rep/mod.rs:144:32: replace == with != in Move::disambiguate +# src/coup/rep/mod.rs:148:39: replace == with != in Move::disambiguate +# src/coup/rep/mod.rs:152:24: delete ! in Move::disambiguate +# src/coup/rep/mod.rs:174:42: replace == with != in Move::disambiguate +# src/coup/rep/mod.rs:176:49: replace == with != in Move::disambiguate +# src/coup/rep/mod.rs:181:42: replace == with != in Move::disambiguate +# src/coup/rep/mod.rs:183:49: replace == with != in Move::disambiguate +# src/coup/rep/mod.rs:202:9: replace Move::to_pgn -> String with "xyzzy".into() +# src/coup/rep/mod.rs:202:9: replace Move::to_pgn -> String with String::new() +# src/coup/rep/mod.rs:250:9: replace Move::is_null -> bool with true +# src/coup/rep/mod.rs:497:9: replace Move::to_uci -> String with "xyzzy".into() +# src/coup/rep/mod.rs:497:9: replace Move::to_uci -> String with String::new() +# src/coup/rep/mod.rs:524:9: replace Move::is_en_passant -> bool with true +# src/coup/rep/mod.rs:529:9: replace Move::is_ambiguous -> bool with false +# src/coup/rep/mod.rs:538:9: replace Move::is_double_pawn_push_for -> bool with true +# src/coup/rep/mod.rs:539:77: replace && with || in Move::is_double_pawn_push_for +# src/coup/rep/mod.rs:540:105: replace == with != in Move::is_double_pawn_push_for +# src/coup/rep/mod.rs:540:49: replace - with + in Move::is_double_pawn_push_for +# src/coup/rep/mod.rs:540:49: replace - with / in Move::is_double_pawn_push_for +# src/coup/rep/mod.rs:540:69: replace == with != in Move::is_double_pawn_push_for +# src/coup/rep/mod.rs:540:77: replace && with || in Move::is_double_pawn_push_for +# src/coup/rep/mod.rs:540:98: replace & with ^ in Move::is_double_pawn_push_for +# src/coup/rep/mod.rs:540:98: replace & with | in Move::is_double_pawn_push_for +# src/coup/rep/mod.rs:549:9: replace Move::is_short_castling_move_for -> bool with true +# src/coup/rep/mod.rs:550:49: replace && with || in Move::is_short_castling_move_for +# src/coup/rep/mod.rs:551:43: replace == with != in Move::is_short_castling_move_for +# src/coup/rep/mod.rs:551:49: replace && with || in Move::is_short_castling_move_for +# src/coup/rep/mod.rs:551:66: replace == with != in Move::is_short_castling_move_for +# src/coup/rep/mod.rs:560:9: replace Move::is_long_castling_move_for -> bool with true +# src/coup/rep/mod.rs:561:49: replace && with || in Move::is_long_castling_move_for +# src/coup/rep/mod.rs:562:43: replace == with != in Move::is_long_castling_move_for +# src/coup/rep/mod.rs:562:49: replace && with || in Move::is_long_castling_move_for +# src/coup/rep/mod.rs:562:66: replace == with != in Move::is_long_castling_move_for +src/coup/rep/move_type.rs:125:22: replace & with | in MoveType::is_en_passant +src/coup/rep/move_type.rs:125:9: replace MoveType::is_en_passant -> bool with true +src/coup/rep/move_type.rs:168:9: replace MoveType::is_ambiguous -> bool with false +src/engine/driver/hazel.rs:35:9: replace ::exec_message -> Vec with vec![] +src/engine/uci/connection.rs:21:5: replace run_with_io -> io::Result<()> with Ok(()) +src/engine/uci/mod.rs:275:9: replace UCIMessage::has_response -> bool with true +src/engine/uci/mod.rs:281:46: replace == with != in UCIMessage::is_complete +src/game/action/chess.rs:34:9: replace ::fmt -> std::fmt::Result with Ok(Default::default()) +src/game/variation.rs:36:9: replace Variation::commit_all -> &mut Self with Box::leak(Box::new(Default::default())) +src/game/variation.rs:54:9: replace Variation::halt -> &mut Self with Box::leak(Box::new(Default::default())) +src/game/variation.rs:64:9: replace Variation::variation -> &mut Self with Box::leak(Box::new(Default::default())) +src/notation/fen/castle_rights.rs:59:22: replace |= with ^= in ::from +src/notation/fen/castle_rights.rs:62:22: replace |= with ^= in ::from +src/notation/fen/castle_rights.rs:65:22: replace |= with ^= in ::from +src/notation/fen/castle_rights.rs:68:22: replace |= with ^= in ::from +src/notation/fen/mod.rs:100:9: replace FEN::halfmove_clock -> u8 with 0 +src/notation/fen/mod.rs:105:9: replace FEN::fullmove_number -> u16 with 1 +src/notation/fen/mod.rs:122:9: replace FEN::metadata -> PositionMetadata with Default::default() +src/notation/fen/mod.rs:34:41: replace && with || in ::eq +src/notation/fen/mod.rs:34:9: replace ::eq -> bool with true +src/notation/fen/mod.rs:95:9: replace FEN::en_passant -> Option with None +src/notation/fen/position.rs:109:9: replace ::eq -> bool with true +src/notation/fen/position_metadata.rs:162:35: replace == with != in PositionMetadata::update +src/notation/fen/position_metadata.rs:163:35: replace == with != in PositionMetadata::update +src/notation/fen/position_metadata.rs:164:35: replace == with != in PositionMetadata::update +src/notation/fen/position_metadata.rs:165:35: replace == with != in PositionMetadata::update +src/notation/fen/position_metadata.rs:189:12: replace |= with ^= in ::from +src/notation/fen/position_metadata.rs:190:12: replace |= with ^= in ::from +src/notation/fen/position_metadata.rs:192:46: replace | with ^ in ::from +src/notation/fen/position_metadata.rs:192:67: replace << with >> in ::from +src/notation/fen/position_metadata.rs:195:12: replace |= with ^= in ::from +src/notation/fen/position_metadata.rs:196:12: replace |= with ^= in ::from +src/notation/fen/position_metadata.rs:225:52: replace >> with << in ::from +src/notation/square/from_into.rs:11:32: replace * with + in ::from +src/notation/square/from_into.rs:11:32: replace * with / in ::from +src/notation/square/from_into.rs:11:36: replace + with * in ::from +src/notation/square/from_into.rs:11:36: replace + with - in ::from +src/notation/square/from_into.rs:23:9: replace ::from -> usize with 0 +src/notation/square/from_into.rs:23:9: replace ::from -> usize with 1 +src/notation/square/iterator.rs:180:9: replace RankFile::is_done -> bool with false +src/notation/square/iterator.rs:23:9: replace Square::by_rank_and_file -> RankFile with Default::default() +src/notation/square/iterator.rs:60:31: replace == with != in ::eq +src/notation/square/iterator.rs:60:9: replace ::eq -> bool with false +src/notation/square/iterator.rs:60:9: replace ::eq -> bool with true +src/notation/square/iterator.rs:66:9: replace ::eq -> bool with true +src/notation/square/mod.rs:23:27: replace + with - in Square::set_rank +src/notation/square/mod.rs:28:30: replace * with / in Square::set_file +src/notation/square/mod.rs:77:9: replace Square::backrank_for -> bool with false +src/notation/square/mod.rs:77:9: replace Square::backrank_for -> bool with true +src/notation/square/mod.rs:78:41: replace == with != in Square::backrank_for +src/notation/square/mod.rs:79:41: replace == with != in Square::backrank_for +src/notation/square/mod.rs:84:21: replace == with != in Square::backrank +src/notation/square/mod.rs:84:26: replace || with && in Square::backrank +src/notation/square/mod.rs:84:41: replace == with != in Square::backrank +src/notation/square/mod.rs:84:9: replace Square::backrank -> bool with false +src/notation/square/mod.rs:84:9: replace Square::backrank -> bool with true +src/types/color.rs:28:9: replace ::from -> Self with Default::default() +src/types/log/mod.rs:102:9: replace Log::is_empty -> bool with false +src/types/log/mod.rs:102:9: replace Log::is_empty -> bool with true +src/types/log/mod.rs:117:9: replace Log::write_head with () +src/types/log/mod.rs:137:9: replace >::into_iter -> Self::IntoIter with Default::default() +src/types/log/mod.rs:37:21: replace < with == in Log::seek +src/types/log/mod.rs:37:21: replace < with > in Log::seek +src/types/log/mod.rs:37:9: replace Log::seek with () +src/types/log/mod.rs:82:15: delete ! in Log::commit_all +src/types/log/mod.rs:82:9: replace Log::commit_all -> &mut Self with Box::leak(Box::new(Default::default())) +src/types/log/transaction.rs:19:9: replace Transaction::is_finished -> bool with false +src/types/log/transaction.rs:19:9: replace Transaction::is_finished -> bool with true +src/types/pextboard/mod.rs:25:43: replace / with * +src/types/pextboard/mod.rs:27:44: replace / with * +src/types/pextboard/mod.rs:47:5: replace slow_attacks -> Bitboard with Default::default() +src/types/pextboard/mod.rs:57:16: delete ! in slow_attacks +src/types/pextboard/mod.rs:57:27: replace & with | in slow_attacks +src/types/pextboard/mod.rs:67:5: replace slow_bishop_attacks -> Bitboard with Default::default() +src/types/pextboard/mod.rs:75:5: replace slow_rook_attacks -> Bitboard with Default::default() +src/types/pextboard/mod.rs:83:5: replace attacks_for -> Bitboard with Default::default() +src/types/pextboard/mod.rs:88:50: replace | with & in attacks_for +src/types/pextboard/mod.rs:88:50: replace | with ^ in attacks_for +src/util/charray.rs:48:9: replace Charray::set_origin with () diff --git a/doc/records/mutation-testing/10-NOV-2024/timeout.txt b/doc/records/mutation-testing/10-NOV-2024/timeout.txt new file mode 100644 index 0000000..eaf8df1 --- /dev/null +++ b/doc/records/mutation-testing/10-NOV-2024/timeout.txt @@ -0,0 +1,4 @@ +src/engine/uci/mod.rs:279:9: replace UCIMessage::is_complete -> bool with false +src/engine/uci/mod.rs:145:9: replace ::fmt -> fmt::Result with Ok(Default::default()) +src/types/log/mod.rs:28:9: replace Log::start -> Self with Default::default() +src/types/log/cursor.rs:50:27: replace += with *= in Cursor<'a, T>::next diff --git a/doc/records/mutation-testing/10-NOV-2024/unviable.txt b/doc/records/mutation-testing/10-NOV-2024/unviable.txt new file mode 100644 index 0000000..f13790c --- /dev/null +++ b/doc/records/mutation-testing/10-NOV-2024/unviable.txt @@ -0,0 +1,113 @@ +src/coup/rep/mod.rs:333:9: replace Move::target -> Square with Default::default() +src/notation/square/from_into.rs:5:9: replace ::from -> Self with Default::default() +src/types/log/write_head.rs:67:9: replace WriteHead<'a, T>::read -> Option<&mut T> with Some(Box::leak(Box::new(Default::default()))) +src/coup/rep/mod.rs:72:9: replace Move::from -> Move with Default::default() +src/notation/square/mod.rs:23:9: replace Square::set_rank -> Self with Default::default() +src/engine/uci/mod.rs:113:9: replace UCIOption::parse -> UCIOption with Default::default() +src/engine/driver/hazel.rs:35:9: replace ::exec_message -> Vec with vec![Default::default()] +src/notation/square/from_into.rs:76:9: replace ::try_from -> Result with Ok(Default::default()) +src/notation/fen/mod.rs:85:9: replace FEN::side_to_move -> Color with Default::default() +src/notation/square/iterator.rs:11:9: replace ::next -> Option with Some(Default::default()) +src/types/occupant.rs:62:67: replace Occupant::bishop -> Self with Default::default() +src/coup/rep/mod.rs:83:9: replace Move::null -> Move with Default::default() +src/notation/fen/position.rs:26:9: replace Position::compile -> Vec with vec![Default::default()] +src/types/log/cursor.rs:56:9: replace Cursor<'a, T>::prev -> Option<&T> with Some(&Default::default()) +src/engine/uci/mod.rs:83:9: replace UCIOption::empty -> UCIOption with Default::default() +src/types/occupant.rs:47:60: replace Occupant::white_queen -> Self with Default::default() +src/game/action/game.rs:17:9: replace GameAction::jump -> Self with Default::default() +src/coup/rep/mod.rs:303:9: replace Move::source -> Square with Default::default() +src/coup/rep/mod.rs:126:9: replace Move::disambiguate -> Option with Some(Default::default()) +src/types/occupant.rs:64:65: replace Occupant::king -> Self with Default::default() +src/notation/square/iterator.rs:162:9: replace RankFile::last_square -> Square with Default::default() +src/notation/square/mod.rs:28:9: replace Square::set_file -> Self with Default::default() +src/board/interface/play.rs:18:9: replace Play::unwind_mut -> &mut Self with Box::leak(Box::new(Default::default())) +src/constants/file.rs:19:9: replace ::from -> Self with Default::default() +src/types/occupant.rs:40:9: replace Occupant::piece -> Option with Some(Default::default()) +src/types/color.rs:43:9: replace Color::pawn_direction -> Direction with Default::default() +src/types/color.rs:18:9: replace ::from -> Self with Default::default() +src/board/interface/alteration.rs:53:9: replace Alteration::inverse -> Self with Default::default() +src/game/action/game.rs:37:9: replace GameAction::comment -> Self with Default::default() +src/game/action/game.rs:29:9: replace GameAction::set -> Self with Default::default() +src/types/log/write_head.rs:32:38: replace >= with < in WriteHead<'a, T>::jump +src/notation/square/mod.rs:53:9: replace Square::down -> Option with Some(Default::default()) +src/board/interface/alter.rs:10:9: replace Alter::alter_mut -> &mut Self with Box::leak(Box::new(Default::default())) +src/types/occupant.rs:49:61: replace Occupant::white_bishop -> Self with Default::default() +src/types/occupant.rs:46:59: replace Occupant::white_king -> Self with Default::default() +src/coup/rep/mod.rs:380:9: replace Move::move_metadata -> MoveType with Default::default() +src/types/occupant.rs:29:9: replace Occupant::black -> Self with Default::default() +src/board/interface/alteration.rs:33:9: replace Alteration::place -> Self with Default::default() +src/notation/uci.rs:57:9: replace ::try_from -> Result with Ok(Default::default()) +src/types/log/write_head.rs:49:9: replace WriteHead<'a, T>::next -> Option<&mut T> with Some(Box::leak(Box::new(Default::default()))) +src/notation/uci.rs:41:9: replace ::try_from -> Result with Ok(Default::default()) +src/notation/square/iterator.rs:273:9: replace RankFile::current_square -> Square with Default::default() +src/types/log/write_head.rs:21:9: replace WriteHead<'a, T>::seek -> Option<&mut T> with Some(Box::leak(Box::new(Default::default()))) +src/coup/rep/move_type.rs:135:9: replace MoveType::capture -> MoveType with Default::default() +src/types/occupant.rs:65:65: replace Occupant::pawn -> Self with Default::default() +src/board/interface/alteration.rs:49:9: replace Alteration::clear -> Self with Default::default() +src/types/occupant.rs:55:59: replace Occupant::black_rook -> Self with Default::default() +src/notation/square/mod.rs:61:9: replace Square::left -> Option with Some(Default::default()) +src/game/action/game.rs:21:9: replace GameAction::ponder -> Self with Default::default() +src/types/log/cursor.rs:30:38: replace >= with < in Cursor<'a, T>::jump +src/coup/rep/move_type.rs:139:9: replace MoveType::short_castle -> MoveType with Default::default() +src/board/interface/alteration.rs:45:9: replace Alteration::lit -> Vec with vec![Default::default()] +src/types/occupant.rs:33:9: replace Occupant::color -> Option with Some(Default::default()) +src/coup/rep/mod.rs:111:9: replace Move::from_notation -> Move with Default::default() +src/types/occupant.rs:48:59: replace Occupant::white_rook -> Self with Default::default() +src/notation/square/from_into.rs:29:9: replace ::from -> Square with Default::default() +src/types/occupant.rs:57:61: replace Occupant::black_knight -> Self with Default::default() +src/types/occupant.rs:54:60: replace Occupant::black_queen -> Self with Default::default() +src/types/log/cursor.rs:24:9: replace Cursor<'a, T>::jump -> Option<&T> with Some(&Default::default()) +src/constants/file.rs:37:9: replace File::from_index -> Self with Default::default() +src/types/log/cursor.rs:65:9: replace Cursor<'a, T>::read -> Option<&T> with Some(&Default::default()) +src/board/interface/alteration.rs:37:9: replace Alteration::remove -> Self with Default::default() +src/notation/square/from_into.rs:37:9: replace ::try_from -> Result with Ok(Default::default()) +src/engine/uci/mod.rs:190:9: replace UCIMessage::parse -> UCIMessage with Default::default() +src/types/occupant.rs:51:59: replace Occupant::white_pawn -> Self with Default::default() +src/game/compiles_to.rs:58:5: replace transitive_compile -> C with Default::default() +src/notation/square/mod.rs:69:9: replace Square::right -> Option with Some(Default::default()) +src/types/piece.rs:21:9: replace Piece::last_piece -> Piece with Default::default() +src/types/log/cursor.rs:18:9: replace Cursor<'a, T>::seek -> Option<&T> with Some(&Default::default()) +src/game/action/game.rs:13:9: replace GameAction::seek -> Self with Default::default() +src/types/log/transaction.rs:27:9: replace Transaction::commit -> Vec with vec![Default::default()] +src/game/action/game.rs:25:9: replace GameAction::idle -> Self with Default::default() +src/coup/rep/mod.rs:367:9: replace Move::promotion_piece -> Piece with Default::default() +src/coup/rep/move_type.rs:148:9: replace MoveType::null_move -> MoveType with Default::default() +src/coup/rep/mod.rs:385:9: replace Move::compile -> Vec with vec![Default::default()] +src/coup/rep/mod.rs:269:9: replace Move::short_castle -> Move with Default::default() +src/coup/rep/mod.rs:254:9: replace Move::long_castle -> Move with Default::default() +src/notation/fen/castle_rights.rs:45:9: replace ::from -> Self with Default::default() +src/notation/square/mod.rs:45:9: replace Square::up -> Option with Some(Default::default()) +src/types/occupant.rs:63:66: replace Occupant::queen -> Self with Default::default() +src/types/occupant.rs:56:61: replace Occupant::black_bishop -> Self with Default::default() +src/notation/uci.rs:27:9: replace ::from -> Self with Default::default() +src/util/mod.rs:11:5: replace select_subset -> Vec with vec![Default::default()] +src/coup/rep/mod.rs:48:9: replace Move::empty -> Move with Default::default() +src/notation/uci.rs:49:9: replace ::try_from -> Result with Ok(Default::default()) +src/coup/rep/move_type.rs:143:9: replace MoveType::long_castle -> MoveType with Default::default() +src/types/log/write_head.rs:58:9: replace WriteHead<'a, T>::prev -> Option<&mut T> with Some(Box::leak(Box::new(Default::default()))) +src/notation/fen/mod.rs:118:9: replace FEN::compile -> Vec with vec![Default::default()] +src/types/occupant.rs:60:65: replace Occupant::rook -> Self with Default::default() +src/coup/rep/move_type.rs:154:9: replace MoveType::promotion_piece -> Option with Some(Default::default()) +src/types/log/mod.rs:98:9: replace Log::get_mut -> Option<&mut T> with Some(Box::leak(Box::new(Default::default()))) +src/types/color.rs:79:9: replace ::not -> Self::Output with Default::default() +src/types/piece.rs:31:9: replace ::from -> Self with Default::default() +src/types/log/mod.rs:94:9: replace Log::get -> Option<&T> with Some(&Default::default()) +src/types/log/transaction.rs:12:9: replace Transaction::begin -> Self with Default::default() +src/types/occupant.rs:25:9: replace Occupant::white -> Self with Default::default() +src/types/occupant.rs:53:59: replace Occupant::black_king -> Self with Default::default() +src/notation/uci.rs:17:9: replace ::from -> Self with Default::default() +src/board/interface/alteration.rs:41:9: replace Alteration::tag -> Self with Default::default() +src/types/log/mod.rs:112:9: replace Log::cursor -> A with Default::default() +src/notation/fen/castle_rights.rs:39:9: replace ::from -> Self with Default::default() +src/notation/fen/mod.rs:95:9: replace FEN::en_passant -> Option with Some(Default::default()) +src/notation/fen/mod.rs:90:9: replace FEN::castling -> CastleRights with Default::default() +src/notation/square/from_into.rs:11:9: replace ::from -> Self with Default::default() +src/types/log/write_head.rs:27:9: replace WriteHead<'a, T>::jump -> Option<&mut T> with Some(Box::leak(Box::new(Default::default()))) +src/types/occupant.rs:61:67: replace Occupant::knight -> Self with Default::default() +src/types/occupant.rs:58:59: replace Occupant::black_pawn -> Self with Default::default() +src/board/interface/play.rs:14:9: replace Play::apply_mut -> &mut Self with Box::leak(Box::new(Default::default())) +src/engine/driver/hazel.rs:39:9: replace ::exec -> Vec with vec![Default::default()] +src/types/log/cursor.rs:47:9: replace Cursor<'a, T>::next -> Option<&T> with Some(&Default::default()) +src/notation/square/from_into.rs:84:9: replace ::try_from -> Result with Ok(Default::default()) +src/types/occupant.rs:50:61: replace Occupant::white_knight -> Self with Default::default() +src/types/occupant.rs:13:9: replace Occupant::empty -> Self with Default::default() diff --git a/doc/records/mutation-testing/8-OCT-20204/caught.txt b/doc/records/mutation-testing/8-OCT-2024/caught.txt similarity index 100% rename from doc/records/mutation-testing/8-OCT-20204/caught.txt rename to doc/records/mutation-testing/8-OCT-2024/caught.txt diff --git a/doc/records/mutation-testing/8-OCT-20204/missed.txt b/doc/records/mutation-testing/8-OCT-2024/missed.txt similarity index 100% rename from doc/records/mutation-testing/8-OCT-20204/missed.txt rename to doc/records/mutation-testing/8-OCT-2024/missed.txt diff --git a/doc/records/mutation-testing/8-OCT-20204/sha b/doc/records/mutation-testing/8-OCT-2024/sha similarity index 100% rename from doc/records/mutation-testing/8-OCT-20204/sha rename to doc/records/mutation-testing/8-OCT-2024/sha diff --git a/doc/records/mutation-testing/8-OCT-20204/timeout.txt b/doc/records/mutation-testing/8-OCT-2024/timeout.txt similarity index 100% rename from doc/records/mutation-testing/8-OCT-20204/timeout.txt rename to doc/records/mutation-testing/8-OCT-2024/timeout.txt diff --git a/doc/records/mutation-testing/8-OCT-20204/unviable.txt b/doc/records/mutation-testing/8-OCT-2024/unviable.txt similarity index 100% rename from doc/records/mutation-testing/8-OCT-20204/unviable.txt rename to doc/records/mutation-testing/8-OCT-2024/unviable.txt diff --git a/doc/records/mutation-testing/9-NOV-2024/missed.txt b/doc/records/mutation-testing/9-NOV-2024/missed.txt new file mode 100644 index 0000000..0d50998 --- /dev/null +++ b/doc/records/mutation-testing/9-NOV-2024/missed.txt @@ -0,0 +1,123 @@ +# src/board/interface/alteration.rs:21:9: replace ::fmt -> std::fmt::Result with Ok(Default::default()) +# src/board/simple/from_into.rs:12:9: replace ::from -> Self with Default::default() +# src/board/simple/mod.rs:48:9: replace ::alter -> PieceBoard with Default::default() +#src/constants/file.rs:55:9: replace File::to_byte -> u8 with 0 +#src/constants/file.rs:55:9: replace File::to_byte -> u8 with 1 +src/coup/rep/debug.rs:28:9: replace ::fmt -> Result with Ok(Default::default()) +src/coup/rep/mod.rs:134:25: delete ! in Move::disambiguate +src/coup/rep/mod.rs:142:56: replace - with + in Move::disambiguate +src/coup/rep/mod.rs:142:56: replace - with / in Move::disambiguate +src/coup/rep/mod.rs:144:32: replace == with != in Move::disambiguate +src/coup/rep/mod.rs:148:39: replace == with != in Move::disambiguate +src/coup/rep/mod.rs:152:24: delete ! in Move::disambiguate +src/coup/rep/mod.rs:174:42: replace == with != in Move::disambiguate +src/coup/rep/mod.rs:176:49: replace == with != in Move::disambiguate +src/coup/rep/mod.rs:181:42: replace == with != in Move::disambiguate +src/coup/rep/mod.rs:183:49: replace == with != in Move::disambiguate +src/coup/rep/mod.rs:202:9: replace Move::to_pgn -> String with "xyzzy".into() +src/coup/rep/mod.rs:202:9: replace Move::to_pgn -> String with String::new() +src/coup/rep/mod.rs:250:9: replace Move::is_null -> bool with false +src/coup/rep/mod.rs:250:9: replace Move::is_null -> bool with true +src/coup/rep/mod.rs:497:9: replace Move::to_uci -> String with "xyzzy".into() +src/coup/rep/mod.rs:497:9: replace Move::to_uci -> String with String::new() +src/coup/rep/mod.rs:524:9: replace Move::is_en_passant -> bool with true +src/coup/rep/mod.rs:529:9: replace Move::is_ambiguous -> bool with false +src/coup/rep/mod.rs:538:9: replace Move::is_double_pawn_push_for -> bool with true +src/coup/rep/mod.rs:539:77: replace && with || in Move::is_double_pawn_push_for +src/coup/rep/mod.rs:540:105: replace == with != in Move::is_double_pawn_push_for +src/coup/rep/mod.rs:540:49: replace - with + in Move::is_double_pawn_push_for +src/coup/rep/mod.rs:540:49: replace - with / in Move::is_double_pawn_push_for +src/coup/rep/mod.rs:540:69: replace == with != in Move::is_double_pawn_push_for +src/coup/rep/mod.rs:540:77: replace && with || in Move::is_double_pawn_push_for +src/coup/rep/mod.rs:540:98: replace & with ^ in Move::is_double_pawn_push_for +src/coup/rep/mod.rs:540:98: replace & with | in Move::is_double_pawn_push_for +src/coup/rep/mod.rs:549:9: replace Move::is_short_castling_move_for -> bool with true +src/coup/rep/mod.rs:550:49: replace && with || in Move::is_short_castling_move_for +src/coup/rep/mod.rs:551:43: replace == with != in Move::is_short_castling_move_for +src/coup/rep/mod.rs:551:49: replace && with || in Move::is_short_castling_move_for +src/coup/rep/mod.rs:551:66: replace == with != in Move::is_short_castling_move_for +src/coup/rep/mod.rs:560:9: replace Move::is_long_castling_move_for -> bool with true +src/coup/rep/mod.rs:561:49: replace && with || in Move::is_long_castling_move_for +src/coup/rep/mod.rs:562:43: replace == with != in Move::is_long_castling_move_for +src/coup/rep/mod.rs:562:49: replace && with || in Move::is_long_castling_move_for +src/coup/rep/mod.rs:562:66: replace == with != in Move::is_long_castling_move_for +src/coup/rep/move_type.rs:125:22: replace & with | in MoveType::is_en_passant +src/coup/rep/move_type.rs:125:9: replace MoveType::is_en_passant -> bool with true +src/coup/rep/move_type.rs:168:9: replace MoveType::is_ambiguous -> bool with false +src/engine/driver/hazel.rs:35:9: replace ::exec_message -> Vec with vec![] +src/engine/uci/connection.rs:21:5: replace run_with_io -> io::Result<()> with Ok(()) +src/engine/uci/mod.rs:275:9: replace UCIMessage::has_response -> bool with true +src/engine/uci/mod.rs:281:46: replace == with != in UCIMessage::is_complete +src/game/action/chess.rs:34:9: replace ::fmt -> std::fmt::Result with Ok(Default::default()) +src/game/variation.rs:36:9: replace Variation::commit_all -> &mut Self with Box::leak(Box::new(Default::default())) +src/game/variation.rs:54:9: replace Variation::halt -> &mut Self with Box::leak(Box::new(Default::default())) +src/game/variation.rs:64:9: replace Variation::variation -> &mut Self with Box::leak(Box::new(Default::default())) +src/notation/fen/castle_rights.rs:59:22: replace |= with ^= in ::from +src/notation/fen/castle_rights.rs:62:22: replace |= with ^= in ::from +src/notation/fen/castle_rights.rs:65:22: replace |= with ^= in ::from +src/notation/fen/castle_rights.rs:68:22: replace |= with ^= in ::from +src/notation/fen/mod.rs:100:9: replace FEN::halfmove_clock -> u8 with 0 +src/notation/fen/mod.rs:105:9: replace FEN::fullmove_number -> u16 with 1 +src/notation/fen/mod.rs:118:9: replace FEN::compile -> Vec with vec![] +src/notation/fen/mod.rs:122:9: replace FEN::metadata -> PositionMetadata with Default::default() +src/notation/fen/mod.rs:34:41: replace && with || in ::eq +src/notation/fen/mod.rs:34:9: replace ::eq -> bool with true +src/notation/fen/mod.rs:95:9: replace FEN::en_passant -> Option with None +src/notation/fen/position.rs:109:9: replace ::eq -> bool with true +src/notation/fen/position_metadata.rs:162:35: replace == with != in PositionMetadata::update +src/notation/fen/position_metadata.rs:163:35: replace == with != in PositionMetadata::update +src/notation/fen/position_metadata.rs:164:35: replace == with != in PositionMetadata::update +src/notation/fen/position_metadata.rs:165:35: replace == with != in PositionMetadata::update +src/notation/fen/position_metadata.rs:189:12: replace |= with ^= in ::from +src/notation/fen/position_metadata.rs:190:12: replace |= with ^= in ::from +src/notation/fen/position_metadata.rs:192:46: replace | with ^ in ::from +src/notation/fen/position_metadata.rs:192:67: replace << with >> in ::from +src/notation/fen/position_metadata.rs:195:12: replace |= with ^= in ::from +src/notation/fen/position_metadata.rs:196:12: replace |= with ^= in ::from +src/notation/fen/position_metadata.rs:225:52: replace >> with << in ::from +src/notation/square/from_into.rs:11:32: replace * with + in ::from +src/notation/square/from_into.rs:11:32: replace * with / in ::from +src/notation/square/from_into.rs:11:36: replace + with * in ::from +src/notation/square/from_into.rs:11:36: replace + with - in ::from +src/notation/square/from_into.rs:23:9: replace ::from -> usize with 0 +src/notation/square/from_into.rs:23:9: replace ::from -> usize with 1 +src/notation/square/iterator.rs:180:9: replace RankFile::is_done -> bool with false +src/notation/square/iterator.rs:23:9: replace Square::by_rank_and_file -> RankFile with Default::default() +src/notation/square/iterator.rs:60:31: replace == with != in ::eq +src/notation/square/iterator.rs:60:9: replace ::eq -> bool with false +src/notation/square/iterator.rs:60:9: replace ::eq -> bool with true +src/notation/square/iterator.rs:66:9: replace ::eq -> bool with true +src/notation/square/mod.rs:23:27: replace + with - in Square::set_rank +src/notation/square/mod.rs:28:30: replace * with / in Square::set_file +src/notation/square/mod.rs:77:9: replace Square::backrank_for -> bool with false +src/notation/square/mod.rs:77:9: replace Square::backrank_for -> bool with true +src/notation/square/mod.rs:78:41: replace == with != in Square::backrank_for +src/notation/square/mod.rs:79:41: replace == with != in Square::backrank_for +src/notation/square/mod.rs:84:21: replace == with != in Square::backrank +src/notation/square/mod.rs:84:26: replace || with && in Square::backrank +src/notation/square/mod.rs:84:41: replace == with != in Square::backrank +src/notation/square/mod.rs:84:9: replace Square::backrank -> bool with false +src/notation/square/mod.rs:84:9: replace Square::backrank -> bool with true +src/types/color.rs:28:9: replace ::from -> Self with Default::default() +src/types/log/mod.rs:102:9: replace Log::is_empty -> bool with false +src/types/log/mod.rs:102:9: replace Log::is_empty -> bool with true +src/types/log/mod.rs:117:9: replace Log::write_head with () +src/types/log/mod.rs:137:9: replace >::into_iter -> Self::IntoIter with Default::default() +src/types/log/mod.rs:37:21: replace < with == in Log::seek +src/types/log/mod.rs:37:21: replace < with > in Log::seek +src/types/log/mod.rs:37:9: replace Log::seek with () +src/types/log/mod.rs:82:15: delete ! in Log::commit_all +src/types/log/mod.rs:82:9: replace Log::commit_all -> &mut Self with Box::leak(Box::new(Default::default())) +src/types/log/transaction.rs:19:9: replace Transaction::is_finished -> bool with false +src/types/log/transaction.rs:19:9: replace Transaction::is_finished -> bool with true +src/types/pextboard/mod.rs:25:43: replace / with * +src/types/pextboard/mod.rs:27:44: replace / with * +src/types/pextboard/mod.rs:47:5: replace slow_attacks -> Bitboard with Default::default() +src/types/pextboard/mod.rs:57:16: delete ! in slow_attacks +src/types/pextboard/mod.rs:57:27: replace & with | in slow_attacks +src/types/pextboard/mod.rs:67:5: replace slow_bishop_attacks -> Bitboard with Default::default() +src/types/pextboard/mod.rs:75:5: replace slow_rook_attacks -> Bitboard with Default::default() +src/types/pextboard/mod.rs:83:5: replace attacks_for -> Bitboard with Default::default() +src/types/pextboard/mod.rs:88:50: replace | with & in attacks_for +src/types/pextboard/mod.rs:88:50: replace | with ^ in attacks_for +src/util/charray.rs:48:9: replace Charray::set_origin with () diff --git a/flake.lock b/flake.lock index 2414e60..7751722 100644 --- a/flake.lock +++ b/flake.lock @@ -7,11 +7,11 @@ "rust-overlay": "rust-overlay" }, "locked": { - "lastModified": 1726415396, - "narHash": "sha256-VHYut0Cb5MBpg1PJWZjGkPwoXqQfUypoRQqJJdCC4CM=", + "lastModified": 1729441100, + "narHash": "sha256-C5gSlimzGTQGxxWPU39N3EjZ/ceNYp/DsnwPk3Q9SeM=", "owner": "godzie44", "repo": "BugStalker", - "rev": "6ea95cc830c7ca0b11953434747ccff60fd243fe", + "rev": "31bdcf5633e72c9b0fa583fe61cd9c4e60e3c898", "type": "github" }, "original": { @@ -37,11 +37,11 @@ ] }, "locked": { - "lastModified": 1724232775, - "narHash": "sha256-6u2DycIEgrgNYlLxyGqdFVmBNiKIitnQKJ1pbRP5oko=", + "lastModified": 1726520618, + "narHash": "sha256-jOsaBmJ/EtX5t/vbylCdS7pWYcKGmWOKg4QKUzKr6dA=", "owner": "cachix", "repo": "cachix", - "rev": "03b6cb3f953097bff378fb8b9ea094bd091a4ec7", + "rev": "695525f9086542dfb09fde0871dbf4174abbf634", "type": "github" }, "original": { @@ -95,11 +95,11 @@ "pre-commit-hooks": "pre-commit-hooks_2" }, "locked": { - "lastModified": 1726671107, - "narHash": "sha256-XxQbcOSjvHsEC1hqZ5WU0aXtuomSikJnBCt38Qhk4b8=", + "lastModified": 1731081992, + "narHash": "sha256-1BlKyBDfkYQknlkqL6yAMKMG1NhSQ/6kq+oY1oWOQ+w=", "owner": "cachix", "repo": "devenv", - "rev": "5cac85ca427b5461a657472ffc4d15d0a61a5ef2", + "rev": "ad1cfd1055e6bb8e0d6f3b7aa7bf00e5b374f0a4", "type": "github" }, "original": { @@ -183,11 +183,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1726641202, - "narHash": "sha256-NrSmOWnr0bIudOLwXd7UwspaHCGwVp8F0jed+nleWpI=", + "lastModified": 1731047492, + "narHash": "sha256-F4h8YtTzPWv0/1Z6fc8fMSqKpn7YhOjlgp66cr15tEo=", "owner": "nix-community", "repo": "fenix", - "rev": "59e6cc52c6242800bb448c5a2b6427bd949385ad", + "rev": "da6332e801fbb0418f80f20cefa947c5fe5c18c9", "type": "github" }, "original": { @@ -456,11 +456,11 @@ "pre-commit-hooks": "pre-commit-hooks" }, "locked": { - "lastModified": 1725980365, - "narHash": "sha256-uDwWyizzlQ0HFzrhP6rVp2+2NNA+/TM5zT32dR8GUlg=", + "lastModified": 1727438425, + "narHash": "sha256-X8ES7I1cfNhR9oKp06F6ir4Np70WGZU5sfCOuNBEwMg=", "owner": "domenkozar", "repo": "nix", - "rev": "1e61e9f40673f84c3b02573145492d8af581bec5", + "rev": "f6c5ae4c1b2e411e6b1e6a8181cc84363d6a7546", "type": "github" }, "original": { @@ -644,11 +644,11 @@ }, "nixpkgs_6": { "locked": { - "lastModified": 1726463316, - "narHash": "sha256-gI9kkaH0ZjakJOKrdjaI/VbaMEo9qBbSUl93DnU7f4c=", + "lastModified": 1730785428, + "narHash": "sha256-Zwl8YgTVJTEum+L+0zVAWvXAGbWAuXHax3KzuejaDyo=", "owner": "nixos", "repo": "nixpkgs", - "rev": "99dc8785f6a0adac95f5e2ab05cc2e1bf666d172", + "rev": "4aa36568d413aca0ea84a1684d2d46f55dbabad7", "type": "github" }, "original": { @@ -660,16 +660,16 @@ }, "nixpkgs_7": { "locked": { - "lastModified": 1726463316, - "narHash": "sha256-gI9kkaH0ZjakJOKrdjaI/VbaMEo9qBbSUl93DnU7f4c=", + "lastModified": 1730883749, + "narHash": "sha256-mwrFF0vElHJP8X3pFCByJR365Q2463ATp2qGIrDUdlE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "99dc8785f6a0adac95f5e2ab05cc2e1bf666d172", + "rev": "dba414932936fde69f0606b4f1d87c5bc0003ede", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-unstable", + "ref": "nixos-24.05", "repo": "nixpkgs", "type": "github" } @@ -751,11 +751,11 @@ "nixpkgs-stable": "nixpkgs-stable" }, "locked": { - "lastModified": 1725513492, - "narHash": "sha256-tyMUA6NgJSvvQuzB7A1Sf8+0XCHyfSPRx/b00o6K0uo=", + "lastModified": 1726745158, + "narHash": "sha256-D5AegvGoEjt4rkKedmxlSEmC+nNLMBPWFxvmYnVLhjk=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "7570de7b9b504cfe92025dd1be797bf546f66528", + "rev": "4e743a6920eab45e8ba0fbe49dc459f1423a4b74", "type": "github" }, "original": { @@ -775,11 +775,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1726443025, - "narHash": "sha256-nCmG4NJpwI0IoIlYlwtDwVA49yuspA2E6OhfCOmiArQ=", + "lastModified": 1730989300, + "narHash": "sha256-ZWSta9893f/uF5PoRFn/BSUAxF4dKW+TIbdA6rZoGBg=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "94b526fc86eaa0e90fb4d54a5ba6313aa1e9b269", + "rev": "1042a8c22c348491a4bade4f664430b03d6f5b5c", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 6702c83..07389c8 100644 --- a/flake.nix +++ b/flake.nix @@ -1,6 +1,6 @@ { inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05"; fenix.url = "github:nix-community/fenix"; devenv.url = "github:cachix/devenv"; bugstalker.url = "github:godzie44/BugStalker"; @@ -32,18 +32,18 @@ packages = with pkgs; [ - gnuplot - bugstalker.packages."x86_64-linux".default - perf-tools - cloc - linuxKernel.packages.linux_6_6.perf - just + bacon cargo-llvm-cov - cargo-nextest cargo-mutants - stockfish - bacon + cargo-nextest + cloc + gnuplot + imhex + just + linuxKernel.packages.linux_6_6.perf mold + perf-tools + stockfish ]; }]; }; diff --git a/src/board/interface/alter.rs b/src/board/interface/alter.rs index 77a6cd0..4cc2515 100644 --- a/src/board/interface/alter.rs +++ b/src/board/interface/alter.rs @@ -11,3 +11,11 @@ pub trait Alter where Self: Sized { self } } + +// // TODO: Use this instead of the `compile` methods all over +// pub trait IntoAlter { +// fn into_alter(self) -> Vec; +// } +// +// +// All Query are IntoAlter, except they're missing Metadata. This IntoAlter doesn't quite capture what I want it to mean, that it is a vector that ensures there is a metadata set. diff --git a/src/board/interface/alteration.rs b/src/board/interface/alteration.rs index 179bb31..00f5df0 100644 --- a/src/board/interface/alteration.rs +++ b/src/board/interface/alteration.rs @@ -1,11 +1,31 @@ +use std::fmt::Debug; + +use fen::PositionMetadata; + use crate::types::Occupant; use crate::notation::*; -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Clone, Copy, PartialEq)] pub enum Alteration { Place { square: Square, occupant: Occupant }, Remove { square: Square, occupant: Occupant }, - Done + Assert(PositionMetadata), + Lit(u8), + Clear, +} + + + +impl Debug for Alteration { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Place { square, occupant } => write!(f, "Place {} @ {}", occupant, square), + Self::Remove { square, occupant } => write!(f, "Remove {} @ {}", occupant, square), + Self::Assert(metadata) => write!(f, "Assert {:?}", metadata), + Self::Clear => write!(f, "Clear"), + Self::Lit(byte) => write!(f, "Lit({:x})", byte) + } + } } impl Alteration { @@ -17,8 +37,16 @@ impl Alteration { Self::Remove { square, occupant } } - pub fn done() -> Self { - Self::Done + pub fn tag(byte: u8) -> Self { + Self::Lit(byte) + } + + pub fn lit(bytes: &[u8]) -> Vec { + bytes.iter().map(|byte| Self::Lit(*byte)).collect() + } + + pub fn clear() -> Self { + Self::Clear } pub fn inverse(&self) -> Self { @@ -34,6 +62,21 @@ impl Alteration { mod tests { use super::*; + #[test] + fn debug_display_is_correct() { + let alteration = Alteration::place(A1, Occupant::black_king()); + assert_eq!(format!("{:?}", alteration), "Place k @ a1"); + + let alteration = Alteration::remove(A1, Occupant::black_king()); + assert_eq!(format!("{:?}", alteration), "Remove k @ a1"); + } + + #[test] + fn clear() { + let alteration = Alteration::clear(); + assert_eq!(alteration, Alteration::Clear); + } + #[test] fn place() { let alteration = Alteration::place(A1, Occupant::black_king()); @@ -46,12 +89,6 @@ mod tests { assert_eq!(alteration, Alteration::Remove { square: A1, occupant: Occupant::black_king() }); } - #[test] - fn done() { - let alteration = Alteration::done(); - assert_eq!(alteration, Alteration::Done); - } - #[test] fn inverse() { let place = Alteration::place(A1, Occupant::black_king()); @@ -59,4 +96,16 @@ mod tests { assert_eq!(place.inverse(), remove); assert_eq!(remove.inverse(), place); } + + #[test] + fn tag() { + let alteration = Alteration::tag(0x01); + assert_eq!(alteration, Alteration::Lit(0x01)); + } + + #[test] + fn lit() { + let alterations = Alteration::lit(&[0x01, 0x02, 0x03]); + assert_eq!(alterations, vec![Alteration::Lit(0x01), Alteration::Lit(0x02), Alteration::Lit(0x03)]); + } } diff --git a/src/board/interface/mod.rs b/src/board/interface/mod.rs index 7e1ace8..ccf2995 100644 --- a/src/board/interface/mod.rs +++ b/src/board/interface/mod.rs @@ -2,6 +2,7 @@ pub mod alter; pub mod alteration; pub mod query; +pub mod play; pub use alter::Alter; diff --git a/src/board/interface/play.rs b/src/board/interface/play.rs new file mode 100644 index 0000000..27ee3f7 --- /dev/null +++ b/src/board/interface/play.rs @@ -0,0 +1,22 @@ +use crate::board::interface::{Alter, Query}; + + +pub trait Play where Self: Alter + Query + Clone { + type Rule; + type Metadata; + + fn apply(&self, rule: Self::Rule) -> Self; + fn unwind(&self) -> Self; + + fn metadata(&self) -> Self::Metadata; + + fn apply_mut(&mut self, rule: Self::Rule) -> &mut Self { + *self = self.apply(rule); + self + } + fn unwind_mut(&mut self) -> &mut Self { + *self = self.unwind(); + self + } + +} diff --git a/src/board/interface/query.rs b/src/board/interface/query.rs index 62a898c..d0940f1 100644 --- a/src/board/interface/query.rs +++ b/src/board/interface/query.rs @@ -8,7 +8,7 @@ use crate::{ /// board using standard 'index' notation with the 0th square being a1 and the 63rd square being /// h8. pub trait Query { - fn get(&self, square: S) -> Occupant where S : SquareNotation; + fn get(&self, square: impl Into) -> Occupant; } lazy_static! { @@ -46,34 +46,6 @@ pub fn to_fen(board: &impl Query) -> FEN { let mut f = String::new(); let mut empty = 0; - /* - for s in Square::by_rank_and_file() { - if s.index() % 8 == 0 && s != A1 { - if empty != 0 { - f.push_str(&empty.to_string()); - empty = 0; - } - f.push_str("/"); - } - let occ = board.get(s); - match occ { - Occupant::Empty => empty += 1, - _ => { - if empty > 0 { - f.push_str(&empty.to_string()); - empty = 0; - } - f.push_str(&occ.to_string()); - } - } - - if empty == 8 { - f.push_str("8"); - empty = 0; - } - } - */ - for s in Square::by_rank_and_file().downward() { let occ = board.get(s); match occ { diff --git a/src/board/simple/display_debug.rs b/src/board/simple/display_debug.rs index c827e52..b8beecc 100644 --- a/src/board/simple/display_debug.rs +++ b/src/board/simple/display_debug.rs @@ -1,6 +1,7 @@ use std::fmt::{Display, Debug}; -use super::*; +use crate::board::query::display_board; +use crate::board::PieceBoard; impl Debug for PieceBoard { @@ -21,12 +22,38 @@ impl Display for PieceBoard { mod tests { use super::*; + use crate::board::query::Query; use crate::types::Piece; use crate::types::Occupant; + use crate::notation::*; #[test] - pub fn bottom_left_is_a1() { + pub fn bottom_left_is_a1_display() { + let mut board = PieceBoard::default(); + board.set(A1, Occupant::white_rook()); + let rep = format!("{}", board); + let expected_rep = "8 . . . . . . . . +7 . . . . . . . . +6 . . . . . . . . +5 . . . . . . . . +4 . . . . . . . . +3 . . . . . . . . +2 . . . . . . . . +1 R . . . . . . . + a b c d e f g h +"; + println!("{}", rep); + println!("{}", expected_rep); + + // The board should find the rook + assert_eq!(board.get(A1), Occupant::white(Piece::Rook)); + // it should be in the bottom left of the representation + assert_eq!(rep, expected_rep); + } + + #[test] + pub fn bottom_left_is_a1_debug() { let mut board = PieceBoard::default(); board.set(A1, Occupant::white_rook()); let rep = format!("{:?}", board); diff --git a/src/board/simple/from_into.rs b/src/board/simple/from_into.rs index 91a0fb8..969b74d 100644 --- a/src/board/simple/from_into.rs +++ b/src/board/simple/from_into.rs @@ -69,6 +69,15 @@ mod tests { assert_eq!(fen, fen2); } + #[test] + pub fn converts_from_borrowed_reference_correctly() { + let fen = FEN::new(&START_POSITION_FEN); + let mut board = PieceBoard::default(); + board.set_fen(&fen); + let fen2 = query::to_fen(&board); + assert_eq!(fen, fen2); + } + /* For want of a FEN type and an Arbitrary instance #[quickcheck] pub fn converts_fen_to_board_correctly_quickcheck(fen: FEN) -> bool { diff --git a/src/board/simple/mod.rs b/src/board/simple/mod.rs index 6bf2160..a1f651c 100644 --- a/src/board/simple/mod.rs +++ b/src/board/simple/mod.rs @@ -1,16 +1,10 @@ -use tracing::{instrument, debug}; - -use crate::board::{alter::Alter, alteration::Alteration, query::{display_board , Query}}; +use crate::board::{alter::Alter, alteration::Alteration, query::Query}; use crate::constants::START_POSITION_FEN; -use crate::engine::Engine; -use crate::notation::*; -use crate::notation::uci::UCI; -use crate::notation::fen::{self, FEN}; use crate::types::Occupant; -use crate::engine::uci::UCIMessage; -use crate::game::interface::Chess; - +use crate::notation::*; +use crate::notation::fen::*; +use tracing::instrument; pub mod display_debug; pub mod from_into; @@ -27,9 +21,8 @@ impl Default for PieceBoard { } } - impl PieceBoard { - pub fn set(&mut self, square: S, occupant: Occupant) where S : SquareNotation { + pub fn set(&mut self, square: impl Into, occupant: Occupant) { let sq = square.into(); self.board[sq.index()] = occupant; @@ -37,12 +30,18 @@ impl PieceBoard { } impl Query for PieceBoard { - fn get(&self, square: S) -> Occupant where S: SquareNotation { + fn get(&self, square: impl Into) -> Occupant { let sq = square.into(); self.board[sq.index()] } } +impl From for FEN { + fn from(board: PieceBoard) -> Self { + super::query::to_fen(&board) + } +} + impl Alter for PieceBoard { #[instrument] fn alter(&self, alter: Alteration) -> PieceBoard { @@ -67,43 +66,6 @@ impl Alter for PieceBoard { } } -impl Engine for PieceBoard { - fn exec_message(&mut self, message: &str) -> Vec { - self.exec(&UCIMessage::parse(message)) - } - - #[instrument] - fn exec(&mut self, message: &UCIMessage) -> Vec { - let ret = match message { - UCIMessage::UCI => vec![UCIMessage::ID("name".to_string(), "Hazel Pieceboard".to_string()), UCIMessage::UCIOk], - UCIMessage::IsReady => vec![UCIMessage::ReadyOk], - UCIMessage::UCINewGame => { - self.set_startpos(); - vec![] - }, - UCIMessage::Position(fen, moves) => { - if fen == "startpos" { - self.set_startpos(); - } else { - self.set_fen(&FEN::new(fen)); - } - - debug!("Here"); - - for m in moves { - let uci = UCI::try_from(m).unwrap_or_else(|_| panic!("Invalid Move: {}", &m)); - self.make_mut(uci.into()); - } - - vec![] - }, - _ => vec![] - }; - debug!("Done With Exec"); - ret - } -} - #[cfg(test)] mod tests { @@ -137,6 +99,16 @@ mod tests { mod alter { use super::*; + #[test] + pub fn alter_returns_new_board() { + let b1 = PieceBoard::default(); + let b2 = b1.alter(Alteration::place(D5, Occupant::white_pawn())); + + assert!(b1 != b2); + assert_eq!(b1.get(D5), Occupant::empty()); + assert_eq!(b2.get(D5), Occupant::white_pawn()); + } + #[test] pub fn alters_board_correctly() { let mut board = PieceBoard::default(); @@ -162,22 +134,4 @@ mod tests { assert_eq!(board.get(E1), Occupant::empty()); } } - - mod engine { - use tracing_test::traced_test; - use crate::board::interface::query; - - use super::*; - - #[traced_test] - #[test] - pub fn executes_position_correctly() { - let mut board = PieceBoard::default(); - let moves = ["e2e4", "e7e5", "g1f3", "b8c6", "f1c4", "g8f6", "d2d3", "d7d6", "c1e3", "c8e6"]; - let message = UCIMessage::Position("startpos".to_string(), moves.iter().map(|s| s.to_string()).collect()); - board.exec(&message); - let fen = query::to_fen(&board); - assert_eq!(fen, FEN::with_default_metadata("r2qkb1r/ppp2ppp/2npbn2/4p3/2B1P3/3PBN2/PPP2PPP/RN1QK2R")); - } - } } diff --git a/src/constants/file.rs b/src/constants/file.rs index dd0305b..8ba9a1d 100644 --- a/src/constants/file.rs +++ b/src/constants/file.rs @@ -14,12 +14,26 @@ pub enum File { H = 7, } +impl From for File { + fn from(value: u8) -> Self { + File::from_index(value as usize) + } +} + +impl From for u8 { + fn from(file: File) -> Self { + file as u8 + } +} + + + impl File { pub fn to_bitboard(self) -> Bitboard { FILE_MASKS[self as usize] } - pub fn from_index(index: usize) -> Self { + pub const fn from_index(index: usize) -> Self { match index & 0o07 { 0 => File::A, 1 => File::B, @@ -33,10 +47,14 @@ impl File { } } - pub fn to_index(self) -> usize { + pub const fn to_index(self) -> usize { self as usize } + pub const fn to_byte(self) -> u8 { + self as u8 + } + pub fn to_pgn(self) -> &'static str { match self { File::A => "a", @@ -67,8 +85,16 @@ pub const NOT_H_FILE: u64 = 0x7f7f7f7f7f7f7f7f; #[cfg(test)] mod test { + use quickcheck::{Arbitrary, Gen}; + use super::*; + impl Arbitrary for File { + fn arbitrary(g: &mut Gen) -> File { + File::from_index(usize::arbitrary(g) % 8) + } + } + #[test] fn to_bitboard() { assert_eq!(File::A.to_bitboard(), Bitboard::from(0x0101010101010101u64)); @@ -116,4 +142,35 @@ mod test { assert_eq!(File::G.to_pgn(), "g"); assert_eq!(File::H.to_pgn(), "h"); } + + #[test] + fn to_u8() { + assert_eq!(u8::from(File::A), 0); + assert_eq!(u8::from(File::B), 1); + assert_eq!(u8::from(File::C), 2); + assert_eq!(u8::from(File::D), 3); + assert_eq!(u8::from(File::E), 4); + assert_eq!(u8::from(File::F), 5); + assert_eq!(u8::from(File::G), 6); + assert_eq!(u8::from(File::H), 7); + } + + #[test] + fn from_u8() { + assert_eq!(File::from(0), File::A); + assert_eq!(File::from(1), File::B); + assert_eq!(File::from(2), File::C); + assert_eq!(File::from(3), File::D); + assert_eq!(File::from(4), File::E); + assert_eq!(File::from(5), File::F); + assert_eq!(File::from(6), File::G); + assert_eq!(File::from(7), File::H); + } + + + #[quickcheck] + fn from_u8_to_u8_roundtrips(file: File) -> bool { + let byte = u8::from(file); + File::from(byte) == file + } } diff --git a/src/constants/mod.rs b/src/constants/mod.rs index be4e012..84a1faf 100644 --- a/src/constants/mod.rs +++ b/src/constants/mod.rs @@ -1,14 +1,12 @@ //! Various Constants and Tables -// pub mod conversion_tables; pub mod file; pub mod masks; pub mod move_tables; pub mod test; -// pub use conversion_tables::*; pub use file::*; pub use masks::*; pub use test::*; diff --git a/src/coup/gen/generator.rs b/src/coup/gen/generator.rs index 022ee6c..3af6300 100644 --- a/src/coup/gen/generator.rs +++ b/src/coup/gen/generator.rs @@ -180,8 +180,6 @@ impl Move { #[cfg(test)] mod tests { - use tracing_test::traced_test; - use super::*; use crate::{assert_is_subset, bitboard::Bitboard, constants::*, movement::MoveType}; diff --git a/src/coup/rep/debug.rs b/src/coup/rep/debug.rs index 032d7af..80b3d8d 100644 --- a/src/coup/rep/debug.rs +++ b/src/coup/rep/debug.rs @@ -34,7 +34,14 @@ mod test { use super::*; #[test] - fn displays_as_intended() { + fn display_displays_as_intended() { + let m = Move::from_notation("d2", "d4", MoveType::QUIET); + let debug_out = format!("{}", m); + assert_eq!(debug_out, "d2 (11) -> d4 (27) (QUIET) [0o026660]"); + } + + #[test] + fn debug_displays_as_intended() { let m = Move::from_notation("d2", "d4", MoveType::QUIET); let debug_out = format!("{:?}", m); assert_eq!(debug_out, "d2 (11) -> d4 (27) (QUIET) [0o026660]"); diff --git a/src/coup/rep/halfply.rs b/src/coup/rep/halfply.rs deleted file mode 100644 index 5fabba5..0000000 --- a/src/coup/rep/halfply.rs +++ /dev/null @@ -1,148 +0,0 @@ -use crate::board::interface::Query; -use crate::coup::rep::Move; -use crate::notation::uci::UCI; - - -// This contains the source notation, the calculated Move object, and the board state prior to the -// move. -#[derive(Debug, Clone, PartialEq)] -pub struct HalfPly { - notation: String, // this should be an Enum or otherwise encode the notation _type_ - mov: Move, -} - -impl From for HalfPly { - fn from(mov: Move) -> Self { - Self { - notation: mov.to_uci(), - mov, - } - } -} - -impl From<&str> for HalfPly { - /// Assumes string is UCI notation - /// TODO: Implement `Notation`, a type which tags a string with its notation type, allows - /// conversion to canonical type, and then this method can be implemented for `Notation`. - fn from(notation: &str) -> Self { - let m = UCI::try_from(notation).unwrap_or_else(|_| panic!("Invalid Move: {}", notation)); - Self { - notation: notation.to_string(), - mov: m.into(), - } - } -} - -impl From<&HalfPly> for Move { - fn from(half_ply: &HalfPly) -> Self { - half_ply.mov - } -} - -impl From for Move { - fn from(half_ply: HalfPly) -> Self { - half_ply.mov - } -} - -impl HalfPly { - pub fn notation(&self) -> &str { - &self.notation - } - - pub fn to_pgn(&self, context: &C) -> String where C: Query { - self.mov.to_pgn(context) - } -} - - -#[cfg(test)] -mod tests { - use super::*; - use crate::notation::*; - - mod creation { - use super::*; - - #[test] - fn from_notation() { - let half_ply = HalfPly::from("d2d4"); - // TODO: Demeter - assert_eq!(half_ply.mov.source(), D2); - assert_eq!(half_ply.mov.target(), D4); - } - } - - mod to_pgn { - use crate::types::{Occupant, Color}; - use crate::board::simple::PieceBoard; - use crate::engine::Engine; - - use super::*; - - - #[test] - fn to_pgn_quiet() { - let mut board = PieceBoard::default(); - board.set_startpos(); - - let half_ply = HalfPly::from("d2d3"); - assert_eq!(half_ply.to_pgn(&board), "d3"); - } - - #[test] - fn to_pgn_double_pawn() { - let mut board = PieceBoard::default(); - board.set_startpos(); - - - let half_ply = HalfPly::from("d2d4"); - assert_eq!(half_ply.to_pgn(&board), "d4"); - } - - #[test] - fn to_pgn_capture() { - let mut board = PieceBoard::default(); - board.exec_message("position startpos moves d2d4 e7e5"); - let half_ply = HalfPly::from("d4e5"); - assert_eq!(half_ply.to_pgn(&board), "dxe5"); - } - - #[test] - fn to_pgn_promotion() { - let mut board = PieceBoard::default(); - board.set(A7, Occupant::white_pawn()); - let half_ply = HalfPly::from("a7a8q"); - assert_eq!(half_ply.to_pgn(&board), "a8=Q"); - } - - #[test] - fn to_pgn_capture_promotion() { - let mut board = PieceBoard::default(); - board.set(A7, Occupant::white_pawn()); - board.set(B8, Occupant::black_queen()); - - - let half_ply = HalfPly::from("a7b8q"); - assert_eq!(half_ply.to_pgn(&board), "axb8=Q"); - } - - #[test] - fn to_pgn_kingside_castle() { - let mut board = PieceBoard::default(); - board.exec_message("position startpos moves e2e4 e7e5 g1f3 b8c6 f1c4 g8f6"); - - let half_ply = HalfPly::from("e1g1"); - assert_eq!(half_ply.to_pgn(&board), "O-O"); - } - - #[test] - fn to_pgn_queenside_castle() { - // 1. d4 h6 2. Bf4 g6 3. Nc3 f6 4. Qd3 e6 5. O-O-O - let mut board = PieceBoard::default(); - board.exec_message("position startpos moves d2d4 h7h6 c1f4 g7g6 b1c3 f7f6 d1d3 e7e6"); - let half_ply = HalfPly::from("e1c1"); - assert_eq!(half_ply.to_pgn(&board), "O-O-O"); - } - } -} diff --git a/src/coup/rep/mod.rs b/src/coup/rep/mod.rs index a55e4a0..2645dd5 100644 --- a/src/coup/rep/mod.rs +++ b/src/coup/rep/mod.rs @@ -22,13 +22,12 @@ use crate::board::interface::{Alteration, Query}; use crate::notation::*; use crate::types::{Color, Piece, Occupant}; -use crate::board::query::display_board; use crate::constants::File; use serde::{Deserialize, Serialize}; use tracing::instrument; -use tracing::{debug, trace}; +use tracing::trace; #[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Serialize, Deserialize)] pub struct Move(pub(crate) u16); @@ -42,9 +41,6 @@ pub struct Move(pub(crate) u16); mod debug; mod move_type; -pub mod halfply; - -pub use halfply::HalfPly; pub use move_type::*; impl Move { @@ -63,7 +59,7 @@ impl Move { /// assert!(!m.is_promotion()); /// assert!(m.move_metadata().is_quiet()); /// ``` - pub fn new(source: S, target: S, metadata: MoveType) -> Move where S : SquareNotation { + pub fn new(source: impl Into, target: impl Into, metadata: MoveType) -> Move { let s : Square = source.into(); let t : Square = target.into(); @@ -72,7 +68,7 @@ impl Move { | metadata as u16 ) } - pub fn from(source: S, target: S, metadata: MoveType) -> Move where S : SquareNotation { + pub fn from(source: impl Into, target: impl Into, metadata: MoveType) -> Move { trace!("Deprecated use of Move::from, use Move::new instead"); Move::new( source, @@ -98,7 +94,6 @@ impl Move { /// # use hazel::notation::*; /// # use hazel::constants::*; /// # use hazel::types::Piece; - /// # use either::Either; /// // the move from d2 -> d4 /// let m = Move::from_notation("d2", "d4", MoveType::DOUBLE_PAWN); /// @@ -133,16 +128,12 @@ impl Move { let source = context.get(self.source()); let target = context.get(self.target()); - debug!("DISPLAY:\n{}\nSource: should be {}, is {:?}", display_board(context), self.source(), source); - // If the source square is empty, we can't disambiguate if source.is_empty() { return None; } let capturing = !target.is_empty(); // If there is a piece on the target square, then we're capturing match source.piece().unwrap() { - // FIXME: ideally this'd look at the square notation and not the raw index, but that's - // a bigger refactor than is appropriate right now Piece::Pawn => { // we might still be capturing en passant, we can check to see if we're moving // diagonally. This can be done by checking the difference between the source @@ -176,30 +167,27 @@ impl Move { return Some(MoveType::QUIET); } }, - // FIXME: ideally this'd look at the square notation and not the raw index, but that's - // a bigger refactor than is appropriate right now Piece::King => { // Castling is a king move in UCI, so it's a king move as far as I'm concerned. - match self.source_idx() { - 0o04 => { - if self.target_idx() == 0o06 { + match self.source() { + E1 => { + if self.target() == G1 { return Some(MoveType::SHORT_CASTLE); - } else if self.target_idx() == 0o02 { + } else if self.target() == C1 { return Some(MoveType::LONG_CASTLE); } }, - 0o74 => { - if self.target_idx() == 0o76 { + E8 => { + if self.target() == G8 { return Some(MoveType::SHORT_CASTLE); - } else if self.target_idx() == 0o72 { + } else if self.target() == C8 { return Some(MoveType::LONG_CASTLE); } }, _ => { }, } }, - _ => { - }, + _ => { }, }; // Otherwise, moves are just captures or quiet, simple as. if capturing { @@ -265,13 +253,13 @@ impl Move { pub fn long_castle(color: Color) -> Move { match color { Color::WHITE => Move::from( - Square::try_from("e1").unwrap(), - Square::try_from("c1").unwrap(), + E1, + C1, MoveType::LONG_CASTLE, ), Color::BLACK => Move::from( - Square::try_from("e8").unwrap(), - Square::try_from("c8").unwrap(), + E8, + C8, MoveType::LONG_CASTLE, ), } @@ -280,13 +268,13 @@ impl Move { pub fn short_castle(color: Color) -> Move { match color { Color::WHITE => Move::from( - Square::try_from("e1").unwrap(), - Square::try_from("g1").unwrap(), + E1, + G1, MoveType::SHORT_CASTLE, ), Color::BLACK => Move::from( - Square::try_from("e8").unwrap(), - Square::try_from("g8").unwrap(), + E8, + G8, MoveType::SHORT_CASTLE, ), } @@ -324,7 +312,7 @@ impl Move { /// /// let m = Move::from(D2, D4, MoveType::DOUBLE_PAWN); /// - /// assert_eq!(m.target_idx(), D4.into()); + /// assert_eq!(m.target_idx(), usize::from(D4)); /// ``` pub fn target_idx(&self) -> usize { ((self.0 & TARGET_IDX_MASK) >> TARGET_IDX_SHIFT).into() @@ -400,38 +388,34 @@ impl Move { let source_occupant = context.get(source); let target_occupant = context.get(target); - let contextprime = self.disambiguate(context); + let contextprime = self.disambiguate(context).unwrap(); - let mut alterations = match contextprime.unwrap() { + let alterations = match contextprime { MoveType::QUIET => vec![ + Alteration::remove(source, source_occupant), Alteration::place(target, source_occupant), - Alteration::remove(source, source_occupant) ], MoveType::DOUBLE_PAWN => vec![ + Alteration::remove(source, source_occupant), Alteration::place(target, source_occupant), - Alteration::remove(source, source_occupant) ], MoveType::SHORT_CASTLE => { let color = source_occupant.color().unwrap(); let rook_source = match color { - Color::WHITE => Square::try_from("h1"), - Color::BLACK => Square::try_from("h8"), - }.unwrap(); + Color::WHITE => H1, + Color::BLACK => H8, + }; let rook_target = match color { - Color::WHITE => Square::try_from("f1"), - Color::BLACK => Square::try_from("f8"), - }.unwrap(); - return vec![ - // remove the rook + Color::WHITE => F1, + Color::BLACK => F8, + }; + vec![ Alteration::remove(rook_source, Occupant::rook(color)), - // remove the king Alteration::remove(source, source_occupant), - // place the king Alteration::place(target, source_occupant), - // place the rook - Alteration::place(rook_target, Occupant::rook(color)) - ]; + Alteration::place(rook_target, Occupant::rook(color)), + ] }, MoveType::LONG_CASTLE => { let color = source_occupant.color().unwrap(); @@ -443,7 +427,7 @@ impl Move { Color::WHITE => D1, Color::BLACK => D8 }; - return vec![ + vec![ // remove the rook Alteration::remove(rook_source, Occupant::rook(color)), // remove the king @@ -465,26 +449,45 @@ impl Move { Alteration::place(target, source_occupant), ], MoveType::PROMOTION_KNIGHT => vec![ + Alteration::remove(source, source_occupant), + Alteration::place(target, Occupant::knight(source_occupant.color().unwrap())), ], MoveType::PROMOTION_BISHOP => vec![ + Alteration::remove(source, source_occupant), + Alteration::place(target, Occupant::bishop(source_occupant.color().unwrap())), ], MoveType::PROMOTION_ROOK => vec![ + Alteration::remove(source, source_occupant), + Alteration::place(target, Occupant::rook(source_occupant.color().unwrap())), ], MoveType::PROMOTION_QUEEN => vec![ + Alteration::remove(source, source_occupant), + Alteration::place(target, Occupant::queen(source_occupant.color().unwrap())), ], MoveType::PROMOTION_CAPTURE_KNIGHT => vec![ + Alteration::remove(source, source_occupant), + Alteration::remove(target, target_occupant), + Alteration::place(target, Occupant::knight(source_occupant.color().unwrap())), ], MoveType::PROMOTION_CAPTURE_BISHOP => vec![ + Alteration::remove(source, source_occupant), + Alteration::remove(target, target_occupant), + Alteration::place(target, Occupant::bishop(source_occupant.color().unwrap())), ], MoveType::PROMOTION_CAPTURE_ROOK => vec![ + Alteration::remove(source, source_occupant), + Alteration::remove(target, target_occupant), + Alteration::place(target, Occupant::rook(source_occupant.color().unwrap())), ], MoveType::PROMOTION_CAPTURE_QUEEN => vec![ + Alteration::remove(source, source_occupant), + Alteration::remove(target, target_occupant), + Alteration::place(target, Occupant::queen(source_occupant.color().unwrap())), ], - _ => todo!() + MoveType::NULLMOVE => vec![], + _ => { unreachable!(); } }; - alterations.push(Alteration::done()); - return alterations; } @@ -565,6 +568,33 @@ impl Move { mod test { use super::*; + mod creation { + use super::*; + + #[test] + fn new_move() { + let m = Move::new(D2, D4, MoveType::DOUBLE_PAWN); + assert_eq!(m.source(), D2); + assert_eq!(m.target(), D4); + assert!(!m.is_promotion()); + assert!(!m.is_null()); + assert!(m.is_double_pawn_push_for(Color::WHITE)); + } + + #[test] + fn empty_move() { + let m = Move::empty(); + assert_eq!(m.0, 0); + } + + #[test] + fn null_move() { + let m = Move::null(); + assert!(m.is_null()); + } + + } + mod from_notation { use super::*; @@ -591,20 +621,224 @@ mod test { mod castling { use super::*; + mod white { + use super::*; + + #[test] + fn short_castle_parses_correctly() { + let m = Move::short_castle(Color::WHITE); + assert_eq!(m.source(), E1); + assert_eq!(m.target(), G1); + assert!(m.is_short_castle()); + } + + #[test] + fn long_castle_parses_correctly() { + let m = Move::long_castle(Color::WHITE); + assert_eq!(m.source(), E1); + assert_eq!(m.target(), C1); + assert!(m.is_long_castle()); + } + } + + mod black { + use super::*; + + #[test] + fn short_castle_parses_correctly() { + let m = Move::short_castle(Color::BLACK); + assert_eq!(m.source(), E8); + assert_eq!(m.target(), G8); + assert!(m.is_short_castle()); + } + + #[test] + fn long_castle_parses_correctly() { + let m = Move::long_castle(Color::BLACK); + assert_eq!(m.source(), E8); + assert_eq!(m.target(), C8); + assert!(m.is_long_castle()); + } + } + } + + mod disambiguate { + use crate::board::{Alter, PieceBoard}; + + use super::*; + #[test] - fn short_castle_parses_correctly() { - let m = Move::short_castle(Color::WHITE); - assert_eq!(m.source_idx(), 0o04); - assert_eq!(m.target_idx(), 0o06); - assert!(m.is_short_castle()); + fn quiet_move_disambiguates_correctly() { + let m = Move::from(D2, D3, MoveType::UCI_AMBIGUOUS); + let mut context = PieceBoard::default(); + context.set_startpos(); + + assert_eq!(m.disambiguate(&context).unwrap(), MoveType::QUIET); } #[test] - fn long_castle_parses_correctly() { - let m = Move::long_castle(Color::WHITE); - assert_eq!(m.source_idx(), 0o04); - assert_eq!(m.target_idx(), 0o02); - assert!(m.is_long_castle()); + fn capture_move_disambiguates_correctly() { + let m = Move::from(C3, D4, MoveType::UCI_AMBIGUOUS); + let mut context = PieceBoard::default(); + context.alter_mut(Alteration::place(C3, Occupant::pawn(Color::WHITE))); + context.alter_mut(Alteration::place(D4, Occupant::pawn(Color::BLACK))); + + assert_eq!(m.disambiguate(&context).unwrap(), MoveType::CAPTURE); + } + + #[test] + fn double_pawn_move_disambiguates_correctly() { + let m = Move::from(D2, D4, MoveType::UCI_AMBIGUOUS); + let mut context = PieceBoard::default(); + context.set_startpos(); + + assert_eq!(m.disambiguate(&context).unwrap(), MoveType::DOUBLE_PAWN); + } + + #[test] + fn short_castle_disambiguates_correctly() { + let m = Move::from(E1, G1, MoveType::UCI_AMBIGUOUS); + let mut context = PieceBoard::default(); + context.set_startpos(); + context.alter_mut(Alteration::remove(F1, Occupant::bishop(Color::WHITE))); + context.alter_mut(Alteration::remove(G1, Occupant::knight(Color::WHITE))); + + assert_eq!(m.disambiguate(&context).unwrap(), MoveType::SHORT_CASTLE); + } + + #[test] + fn long_castle_disambiguates_correctly() { + let m = Move::from(E1, C1, MoveType::UCI_AMBIGUOUS); + let mut context = PieceBoard::default(); + context.set_startpos(); + context.alter_mut(Alteration::remove(B1, Occupant::knight(Color::WHITE))); + context.alter_mut(Alteration::remove(C1, Occupant::bishop(Color::WHITE))); + + assert_eq!(m.disambiguate(&context).unwrap(), MoveType::LONG_CASTLE); + } + } + + mod to_star { + use crate::board::{Alter, PieceBoard}; + + use super::*; + + mod pgn { + use super::*; + + #[test] + fn to_pgn_quiet_move() { + let m = Move::from(D2, D3, MoveType::QUIET); + let mut context = PieceBoard::default(); + context.set_startpos(); + + assert_eq!(m.to_pgn(&context), "d3"); + } + + #[test] + fn to_pgn_capture_move() { + let m = Move::from(D4, E5, MoveType::CAPTURE); + let mut context = PieceBoard::default(); + context.alter_mut(Alteration::place(D4, Occupant::white_pawn())); + context.alter_mut(Alteration::place(E5, Occupant::black_pawn())); + + assert_eq!(m.to_pgn(&context), "dxe5"); + } + + #[test] + fn to_pgn_double_pawn_move() { + let m = Move::from(D2, D4, MoveType::DOUBLE_PAWN); + let mut context = PieceBoard::default(); + context.set_startpos(); + + assert_eq!(m.to_pgn(&context), "d4"); + } + + #[test] + fn to_pgn_short_castle() { + let m = Move::short_castle(Color::WHITE); + let mut context = PieceBoard::default(); + // NOTE: Context does not mean it checks to see if the move is legal, in this position + // white cannot castle, eppur si muove. + context.set_startpos(); + + assert_eq!(m.to_pgn(&context), "O-O"); + } + + #[test] + fn to_pgn_long_castle() { + let m = Move::long_castle(Color::WHITE); + let mut context = PieceBoard::default(); + // NOTE: same caveat as short castling. + context.set_startpos(); + + assert_eq!(m.to_pgn(&context), "O-O-O"); + } + + #[test] + fn to_pgn_promotion_move() { + let m = Move::from(D7, D8, MoveType::PROMOTION_QUEEN); + let mut context = PieceBoard::default(); + context.alter_mut(Alteration::place(D7, Occupant::white_pawn())); + + assert_eq!(m.to_pgn(&context), "d8=Q"); + } + + #[test] + fn to_pgn_capture_promotion_move() { + let m = Move::from(C7, D8, MoveType::PROMOTION_CAPTURE_QUEEN); + let mut context = PieceBoard::default(); + context.alter_mut(Alteration::place(C7, Occupant::white_pawn())); + context.alter_mut(Alteration::place(D8, Occupant::black_pawn())); + + assert_eq!(m.to_pgn(&context), "cxd8=Q"); + } + } + + mod uci { + use super::*; + + #[test] + fn to_uci_quiet_move() { + let m = Move::from(D2, D3, MoveType::QUIET); + assert_eq!(m.to_uci(), "d2d3"); + } + + #[test] + fn to_uci_capture_move() { + let m = Move::from(D4, E5, MoveType::CAPTURE); + assert_eq!(m.to_uci(), "d4e5"); + } + + #[test] + fn to_uci_double_pawn_move() { + let m = Move::from(D2, D4, MoveType::DOUBLE_PAWN); + assert_eq!(m.to_uci(), "d2d4"); + } + + #[test] + fn to_uci_short_castle() { + let m = Move::short_castle(Color::WHITE); + assert_eq!(m.to_uci(), "e1g1"); + } + + #[test] + fn to_uci_long_castle() { + let m = Move::long_castle(Color::WHITE); + assert_eq!(m.to_uci(), "e1c1"); + } + + #[test] + fn to_uci_promotion_move() { + let m = Move::from(D7, D8, MoveType::PROMOTION_QUEEN); + assert_eq!(m.to_uci(), "d7d8q"); + } + + #[test] + fn to_uci_capture_promotion_move() { + let m = Move::from(C7, D8, MoveType::PROMOTION_CAPTURE_QUEEN); + assert_eq!(m.to_uci(), "c7d8q"); + } } } diff --git a/src/coup/rep/move_type.rs b/src/coup/rep/move_type.rs index 901f2bd..a6303af 100644 --- a/src/coup/rep/move_type.rs +++ b/src/coup/rep/move_type.rs @@ -91,7 +91,7 @@ impl MoveType { 0b1101 => MoveType::PROMOTION_CAPTURE_BISHOP, 0b1110 => MoveType::PROMOTION_CAPTURE_ROOK, 0b1111 => MoveType::PROMOTION_CAPTURE_QUEEN, - _ => unimplemented!(), + _ => unreachable!(), } } @@ -143,6 +143,11 @@ impl MoveType { MoveType::LONG_CASTLE } + #[inline(always)] + pub fn null_move() -> MoveType { + MoveType::NULLMOVE + } + pub fn promotion_piece(&self) -> Option { // TODO: It may be faster to mask-and-cast the bits, they're arranged such that they correspond to the piece enum. // This is the KISS version @@ -163,3 +168,128 @@ impl MoveType { self == MoveType::UCI_AMBIGUOUS } } + + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + pub fn is_null_works() { + assert!(MoveType::null_move().is_null()); + assert!(!MoveType::quiet().is_null()); + } + + #[test] + pub fn is_long_castle_works() { + assert!(MoveType::long_castle().is_long_castle()); + assert!(!MoveType::short_castle().is_long_castle()); + } + + #[test] + pub fn is_short_castle_works() { + assert!(MoveType::short_castle().is_short_castle()); + assert!(!MoveType::long_castle().is_short_castle()); + } + + #[test] + pub fn is_capture_works() { + assert!(MoveType::capture().is_capture()); + assert!(!MoveType::quiet().is_capture()); + } + + #[test] + pub fn is_quiet_works() { + assert!(MoveType::quiet().is_quiet()); + assert!(!MoveType::capture().is_quiet()); + } + + mod promotion_piece { + use super::*; + + #[test] + pub fn promotion_piece_works() { + assert_eq!(MoveType::PROMOTION_KNIGHT.promotion_piece(), Some(Piece::Knight)); + assert_eq!(MoveType::PROMOTION_BISHOP.promotion_piece(), Some(Piece::Bishop)); + assert_eq!(MoveType::PROMOTION_ROOK.promotion_piece(), Some(Piece::Rook)); + assert_eq!(MoveType::PROMOTION_QUEEN.promotion_piece(), Some(Piece::Queen)); + assert_eq!(MoveType::PROMOTION_CAPTURE_KNIGHT.promotion_piece(), Some(Piece::Knight)); + assert_eq!(MoveType::PROMOTION_CAPTURE_BISHOP.promotion_piece(), Some(Piece::Bishop)); + assert_eq!(MoveType::PROMOTION_CAPTURE_ROOK.promotion_piece(), Some(Piece::Rook)); + assert_eq!(MoveType::PROMOTION_CAPTURE_QUEEN.promotion_piece(), Some(Piece::Queen)); + } + + #[test] + pub fn promotion_piece_none() { + assert_eq!(MoveType::QUIET.promotion_piece(), None); + assert_eq!(MoveType::CAPTURE.promotion_piece(), None); + assert_eq!(MoveType::SHORT_CASTLE.promotion_piece(), None); + assert_eq!(MoveType::LONG_CASTLE.promotion_piece(), None); + assert_eq!(MoveType::NULLMOVE.promotion_piece(), None); + assert_eq!(MoveType::UCI_AMBIGUOUS.promotion_piece(), None); + } + } + + mod uci { + use super::*; + + + #[test] + pub fn to_uci_works() { + assert_eq!(MoveType::PROMOTION_KNIGHT.to_uci(), "n"); + assert_eq!(MoveType::PROMOTION_BISHOP.to_uci(), "b"); + assert_eq!(MoveType::PROMOTION_ROOK.to_uci(), "r"); + assert_eq!(MoveType::PROMOTION_QUEEN.to_uci(), "q"); + assert_eq!(MoveType::PROMOTION_CAPTURE_KNIGHT.to_uci(), "n"); + assert_eq!(MoveType::PROMOTION_CAPTURE_BISHOP.to_uci(), "b"); + assert_eq!(MoveType::PROMOTION_CAPTURE_ROOK.to_uci(), "r"); + assert_eq!(MoveType::PROMOTION_CAPTURE_QUEEN.to_uci(), "q"); + } + } + + #[test] + pub fn new_works() { + assert_eq!(MoveType::new(0b0000), MoveType::QUIET); + assert_eq!(MoveType::new(0b0001), MoveType::DOUBLE_PAWN); + assert_eq!(MoveType::new(0b0010), MoveType::SHORT_CASTLE); + assert_eq!(MoveType::new(0b0011), MoveType::LONG_CASTLE); + assert_eq!(MoveType::new(0b0100), MoveType::CAPTURE); + assert_eq!(MoveType::new(0b0101), MoveType::EP_CAPTURE); + assert_eq!(MoveType::new(0b0110), MoveType::NULLMOVE); + assert_eq!(MoveType::new(0b0111), MoveType::UCI_AMBIGUOUS); + assert_eq!(MoveType::new(0b1000), MoveType::PROMOTION_KNIGHT); + assert_eq!(MoveType::new(0b1001), MoveType::PROMOTION_BISHOP); + assert_eq!(MoveType::new(0b1010), MoveType::PROMOTION_ROOK); + assert_eq!(MoveType::new(0b1011), MoveType::PROMOTION_QUEEN); + assert_eq!(MoveType::new(0b1100), MoveType::PROMOTION_CAPTURE_KNIGHT); + assert_eq!(MoveType::new(0b1101), MoveType::PROMOTION_CAPTURE_BISHOP); + assert_eq!(MoveType::new(0b1110), MoveType::PROMOTION_CAPTURE_ROOK); + assert_eq!(MoveType::new(0b1111), MoveType::PROMOTION_CAPTURE_QUEEN); + } + + mod decode { + use super::*; + + #[test] + pub fn decode_works() { + assert_eq!(MoveType::QUIET.decode(), "QUIET"); + assert_eq!(MoveType::DOUBLE_PAWN.decode(), "DOUBLE_PAWN"); + assert_eq!(MoveType::SHORT_CASTLE.decode(), "SHORT_CASTLE"); + assert_eq!(MoveType::LONG_CASTLE.decode(), "LONG_CASTLE"); + assert_eq!(MoveType::CAPTURE.decode(), "CAPTURE"); + assert_eq!(MoveType::EP_CAPTURE.decode(), "EP_CAPTURE"); + assert_eq!(MoveType::NULLMOVE.decode(), "NULLMOVE"); + assert_eq!(MoveType::UCI_AMBIGUOUS.decode(), "UCI_AMBIGUOUS"); + assert_eq!(MoveType::PROMOTION_KNIGHT.decode(), "PROMOTION_KNIGHT"); + assert_eq!(MoveType::PROMOTION_BISHOP.decode(), "PROMOTION_BISHOP"); + assert_eq!(MoveType::PROMOTION_ROOK.decode(), "PROMOTION_ROOK"); + assert_eq!(MoveType::PROMOTION_QUEEN.decode(), "PROMOTION_QUEEN"); + assert_eq!(MoveType::PROMOTION_CAPTURE_KNIGHT.decode(), "PROMOTION_CAPTURE_KNIGHT"); + assert_eq!(MoveType::PROMOTION_CAPTURE_BISHOP.decode(), "PROMOTION_CAPTURE_BISHOP"); + assert_eq!(MoveType::PROMOTION_CAPTURE_ROOK.decode(), "PROMOTION_CAPTURE_ROOK"); + assert_eq!(MoveType::PROMOTION_CAPTURE_QUEEN.decode(), "PROMOTION_CAPTURE_QUEEN"); + } + } +} + diff --git a/src/engine/driver/hazel.rs b/src/engine/driver/hazel.rs index 8f272e2..9e2bf19 100644 --- a/src/engine/driver/hazel.rs +++ b/src/engine/driver/hazel.rs @@ -5,7 +5,7 @@ // some basic parsing of the UCI Messages to Hazel types, but otherwise be pretty 'dumb' use tracing::*; -use crate::engine::uci::UCIMessage; +use crate::{engine::uci::UCIMessage, game::variation::Variation, notation::{fen::FEN, uci::UCI}}; pub use crate::engine::Engine; @@ -14,14 +14,14 @@ pub use crate::engine::Engine; #[derive(Default)] pub struct Driver { debug: bool, - game: Option<()> + game: Variation } impl Driver { pub fn new() -> Driver { Driver { debug: false, - game: None + game: Variation::new() } } } @@ -38,7 +38,9 @@ impl Engine for Driver { fn exec(&mut self, message: &UCIMessage) -> Vec { info!("Executing UCI instruction: {:?}", &message); - match message { + self.game.commit(); + + let ret = match message { // GUI -> Engine UCIMessage::IsReady => { vec![UCIMessage::ReadyOk] @@ -57,10 +59,17 @@ impl Engine for Driver { vec![] } UCIMessage::UCINewGame => { - self.game = None; + self.game.new_game(); vec![] } - UCIMessage::Position(_fen, _moves) => { + UCIMessage::Position(fen, moves) => { + + self.game.setup(FEN::new(fen)); + + for m_str in moves { + let m = UCI::try_from(m_str).expect("Invalid UCI Move"); + self.game.make(m.into()); + } vec![] } UCIMessage::Go(_) => { @@ -94,7 +103,11 @@ impl Engine for Driver { error!("Unexpected message: {:?}", message); panic!("Unexpected message"); } - } + }; + + self.game.commit(); + + ret } } @@ -102,9 +115,16 @@ impl Engine for Driver { #[cfg(test)] mod tests { use super::*; - use tracing_test::traced_test; + use crate::coup::rep::{Move, MoveType}; + use crate::notation::*; - use crate::constants::{START_POSITION_FEN, POS2_KIWIPETE_FEN}; + impl Driver { + pub fn log(&self) -> Vec { + self.game.log() + } + } + + use crate::{constants::{POS2_KIWIPETE_FEN, START_POSITION_FEN}, game::action::chess::ChessAction}; #[test] fn driver_parses_isready() { @@ -130,34 +150,41 @@ mod tests { assert!(driver.debug) } - #[ignore]// WIP as I refactor board rep #[test] fn driver_sets_up_start_position() { let mut driver = Driver::new(); let response = driver.exec_message("position startpos moves"); assert_eq!(response, vec![]); - assert!(driver.game.is_some()); - // assert!(driver.game.unwrap().to_fen() == START_POSITION_FEN); + assert_eq!(driver.game.log(), vec![ + ChessAction::Setup(FEN::start_position()) + ]); + assert_eq!(driver.game.current_position(), FEN::new(START_POSITION_FEN)); } - #[ignore]// WIP as I refactor board rep #[test] fn driver_sets_up_arbitrary_position() { let mut driver = Driver::new(); let response = driver.exec_message(&format!("position fen {} moves", POS2_KIWIPETE_FEN)); assert_eq!(response, vec![]); - assert!(driver.game.is_some()); - // assert!(driver.game.unwrap().to_fen() == POS2_KIWIPETE_FEN); + assert_eq!(driver.game.log(), vec![ + ChessAction::Setup(FEN::new(POS2_KIWIPETE_FEN)) + ]); + assert_eq!(driver.game.current_position(), FEN::new(POS2_KIWIPETE_FEN)); } - #[ignore] // WIP as I refactor board rep #[test] fn driver_plays_moves_specified_by_position() { let mut driver = Driver::new(); let response = driver.exec_message(&format!("position fen {} moves e2e4 e7e5", START_POSITION_FEN)); assert_eq!(response, vec![]); - assert!(driver.game.is_some()); - // assert_eq!(driver.game.unwrap().to_fen(), "rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq e6 0 2"); + assert_eq!(driver.game.log(), vec![ + ChessAction::Setup(FEN::new(START_POSITION_FEN)), + ChessAction::Make(Move::new(E2, E4, MoveType::UCI_AMBIGUOUS)), + ChessAction::Make(Move::new(E7, E5, MoveType::UCI_AMBIGUOUS)) + ]); + assert_eq!(driver.game.current_position(), FEN::new("rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq e6 0 2")); } } + + diff --git a/src/engine/driver/stockfish.rs b/src/engine/driver/stockfish.rs index e6f1d83..b08e219 100644 --- a/src/engine/driver/stockfish.rs +++ b/src/engine/driver/stockfish.rs @@ -69,7 +69,6 @@ impl Engine for Stockfish { #[instrument] fn exec(&mut self, message: &UCIMessage) -> Vec { - debug!("{}", message.to_string()); let cmd_str = message.to_string(); writeln!(self.stdin, "{}", cmd_str).expect("Failed to write to stockfish"); @@ -104,10 +103,8 @@ mod tests { use std::assert_matches::assert_matches; use super::*; - use tracing_test::traced_test; use crate::engine::uci::UCIMessage; - #[traced_test] #[test] fn stockfish_connects() { let mut stockfish = Stockfish::new(); diff --git a/src/engine/uci/connection.rs b/src/engine/uci/connection.rs index ab704e9..dcb45a8 100644 --- a/src/engine/uci/connection.rs +++ b/src/engine/uci/connection.rs @@ -10,6 +10,7 @@ use crate::engine::driver::Driver; use crate::engine::uci::UCIMessage; use crate::engine::Engine; +#[cfg_attr(test, mutants::skip)] pub fn run() -> io::Result<()> { run_with_io(io::stdin(), io::stdout()) } @@ -36,7 +37,6 @@ pub fn run_with_io(input: T, mut output: U) -> io::Re #[cfg(test)] mod tests { use super::*; - use tracing_test::traced_test; #[test] fn test_with_dummy_io() { diff --git a/src/engine/uci/mod.rs b/src/engine/uci/mod.rs index db8a2a7..d8e0d3d 100644 --- a/src/engine/uci/mod.rs +++ b/src/engine/uci/mod.rs @@ -292,8 +292,6 @@ impl UCIMessage { #[cfg(test)] mod tests { use super::*; - use tracing_test::traced_test; - mod display { use super::*; diff --git a/src/game/action/chess.rs b/src/game/action/chess.rs new file mode 100644 index 0000000..9a79b45 --- /dev/null +++ b/src/game/action/chess.rs @@ -0,0 +1,67 @@ +use std::fmt::{Debug, Formatter}; + +use crate::{coup::rep::Move, notation::fen::FEN, types::Color}; + +#[derive(Debug, Clone, PartialEq)] +pub enum Delim { + Start, + End +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Reason { + /// Checkmate by the given color + Winner(Color), + /// Draw for any reason + Stalemate, + /// Aborted for unspecified reason + Aborted, + /// Returned from an unfinished variation + Returned, +} + +#[derive(Clone, PartialEq)] +pub enum ChessAction { + NewGame, + Halted(Reason), + Variation(Delim), + Setup(FEN), + Make(Move), +} + +impl Debug for ChessAction { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + ChessAction::NewGame => write!(f, "NewGame"), + ChessAction::Halted(egs) => write!(f, "EndGame({:?})", egs), + ChessAction::Variation(Delim::Start) => write!(f, "Variation(Start)"), + ChessAction::Variation(Delim::End) => write!(f, "Variation(End)"), + ChessAction::Setup(fen) => write!(f, "Setup({})", fen), + ChessAction::Make(mov) => write!(f, "Make({:?})", mov), + } + } +} + +/* +impl CompilesTo> for ChessAction { + type Context = Q; + + fn compile(&self, context: &Self::Context) -> Vec where Self::Context : Query { + match self { + ChessAction::NewGame => vec![Alteration::Clear], + ChessAction::EndGame(_egs) => vec![ + ], + ChessAction::Variation(_) => vec![ + ], + ChessAction::Setup(fen) => fen.compile(), + ChessAction::Make(mov) => mov.compile(context), + } + } +} +*/ + +#[cfg(test)] +mod tests { + use super::*; + +} diff --git a/src/game/action/game.rs b/src/game/action/game.rs new file mode 100644 index 0000000..83e3c4f --- /dev/null +++ b/src/game/action/game.rs @@ -0,0 +1,39 @@ +pub enum GameAction { + // Seeks forward/back by specified number of actions + Seek(usize), + Jump(isize), + Ponder, + Idle, + Set(String, Option), + Comment(String) +} + +impl GameAction { + pub fn seek(amount: usize) -> Self { + GameAction::Seek(amount) + } + + pub fn jump(amount: isize) -> Self { + GameAction::Jump(amount) + } + + pub fn ponder() -> Self { + GameAction::Ponder + } + + pub fn idle() -> Self { + GameAction::Idle + } + + pub fn set(key: impl Into, value: Option>) -> Self { + let key_s : String = key.into(); + match value { + Some(value) => GameAction::Set(key_s, Some(value.into())), + None => GameAction::Set(key_s, None) + } + } + + pub fn comment(comment: impl Into) -> Self { + GameAction::Comment(comment.into()) + } +} diff --git a/src/game/action/mod.rs b/src/game/action/mod.rs new file mode 100644 index 0000000..6e39d50 --- /dev/null +++ b/src/game/action/mod.rs @@ -0,0 +1,3 @@ +pub mod chess; +pub mod game; + diff --git a/src/game/compiles_to.rs b/src/game/compiles_to.rs new file mode 100644 index 0000000..b2a56fa --- /dev/null +++ b/src/game/compiles_to.rs @@ -0,0 +1,80 @@ + +/* + + +Sketchy, but here's the idea. + +Every `CompilesTo` type has some implementation of this (perhaps generic in the target rep, instead of a trait object) + +A log contains a list of items which compile to the same target rep, and then can cache that compilation and seek through it dynamically. +The assumption is always that every action can be 'undone' -- either trivially by rerunning, or incremeentally via inverses. + +Then I can layer caches per representation level. + +CompilesTo is a kind of 'Context-Embedding' operation. Whatever context is necessary must be provided, then the target representation should stand without further context. + +*/ + +pub trait CompilesTo { + type Context = (); + + fn compile(&self, context: &Self::Context) -> R; +} + + + + +/* +* In principle this is transitive, but I think Context makes it tricky. +* +* Say you have some #compile/T (with T as the Context Type) to take A -> B +* and similarly you have a B -> C with S as the context type, then in theory: +* +* A.compile(context: &T).compile(context: &S) -> Vec +* +* should always exist, so the implementation would be: + +impl CompilesTo for A where B: CompilesTo, A: CompilesTo, A != B != C { + // I'm sure this is wrong, I need to know about the sum of each implementation's context. So if + // + // A --> B, under context T + // B --> C, under context S + // + // Then A --> C under context (T, S) + type Context = (A::Context, B::Context); + + fn compile(&self, context: &Self::Context) -> C { + let (a, b) = context; + self.compile(a).compile(b) + } +} + +* but this doesn't work because there is no sense in which I can declare `A,B,C` distinct. +* It's possible there is a way to express this, but I'm not sure how to do it. +* +* Instead, there is this function: +*/ +pub fn transitive_compile(a: &A, context: &(T, S)) -> C where A: CompilesTo, B: CompilesTo { + let (t, s) = context; + a.compile(t).compile(s) +} +/* +* This unfortunately requires you to specify everything, including the intermediate type and the +* context types; which would otherwise be calculated for you. But it does allow you to transitively +* compile along a type chain. +* +* The ideal generic implementation version would mean that an `fn foo(bar: &impl CompilesTo)` would +* be able to take any type which can compile to C, regardless of the intermediate types (I think), +* meaning that the context would be inferred from the type signature. There is one wrinkle, easily +* solved with another theoretical generic impl like "If A -> B under T, then Vec -> B under T. +* by compiling each A, collecting, and flattening" +* This constrains the context to be the same across all of the types in the vec, which means that +* if the moves trigger state change that will effect the subsequent compilation, it will not be +* able to compile because of the hidden gamestate change between compilations. So this generic impl +* is more like a fold with an additional operation to update the metadata. For Hazel, I don't want +* to get that generic (yet). +* +* The transitive compile should be enough to get the nested language thing working, so that's what +* I'm going for. +*/ + diff --git a/src/game/interface.rs b/src/game/interface.rs deleted file mode 100644 index b769aee..0000000 --- a/src/game/interface.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crate::board::interface::{Alter, Query}; -use crate::coup::rep::Move; - -/// The implementor understands the rules of chess and can make/unmake moves. -/// -/// implementing Chess states that the implementor can interpret and produce the result of -/// chess moves as represented by the `Move` type. The `make` and `unmake` methods should be -/// implemented to apply and reverse the move, respectively. -/// -/// implementors must also provide a `Default` implementation which represents the starting state -/// of an _empty_ chessboard (no pieces). -pub trait Chess where Self: Sized + Default + Alter + Query { - fn make(&self, mov: Move) -> Self; - fn unmake(&self, mov: Move) -> Self; - - fn make_mut(&mut self, mov: Move) -> &mut Self { - *self = self.make(mov); - self - } - - fn unmake_mut(&mut self, mov: Move) -> &mut Self { - *self = self.unmake(mov); - self - } -} - -/// The canonical implementation of Chess for any type which is Alterable and Queryable. The -/// algorithm is straightforward: -/// 1. Compile the move in the context of the board, yielding a vector of Alterations. -/// 2. Apply each alteration in sequence to the board, returning the final board state. -/// -/// Unmaking is trivial because Alterations are reversible. It's the same algorithm, but applying -/// `inverse` first. -impl Chess for T where T: Alter + Query + Clone + Default { - fn make(&self, mov: Move) -> T { - let alterations = mov.compile(self); - alterations.iter().fold(self.clone(), |board, alteration| board.alter(*alteration)) - } - - fn unmake(&self, mov: Move) -> T { - let alterations = mov.compile(self); - alterations.iter().fold(self.clone(), |board, alteration| board.alter(alteration.inverse())) - } -} diff --git a/src/game/line.rs b/src/game/line.rs deleted file mode 100644 index 704a028..0000000 --- a/src/game/line.rs +++ /dev/null @@ -1,214 +0,0 @@ -use std::fmt::{self, Display, Formatter}; - -use crate::board::interface::Query; -use crate::board::simple::PieceBoard; -use crate::constants::START_POSITION_FEN; -use crate::coup::rep::HalfPly; -use crate::game::interface::Chess; -use crate::types::Color; -use crate::notation::fen::FEN; - -/// A Line is a single sequence of moves starting from the provided initial position (via FEN, by -/// default, the standard start position). -#[derive(Clone, Debug)] -pub struct Line { - initial_position: FEN, - halfplies: Vec, -} - -impl Default for Line { - fn default() -> Self { - Line { - initial_position: FEN::new(START_POSITION_FEN), - halfplies: Vec::new(), - } - } -} - -// FIXME: This is in desparate want of that Notation type. -impl From> for Line { - fn from(moves: Vec<&str>) -> Self { - let mut line = Line::default(); - for move_str in moves { - let halfply = HalfPly::from(move_str); - line.push(halfply); - } - line - } -} - -impl Line { - fn push(&mut self, halfply: HalfPly) { - self.halfplies.push(halfply); - } - - fn pop(&mut self) -> Option { - self.halfplies.pop() - } - fn current_move(&self) -> Option { - self.halfplies.last().cloned() - } - - fn current_position(&self) -> impl Chess { - let mut board = PieceBoard::default(); - - board.set_fen(&self.initial_position); - - for halfply in &self.halfplies { - board = board.make(halfply.into()); - } - board - } - - fn current_color(&self) -> Color { - if self.halfplies() % 2 == 0 { - Color::WHITE - } else { - Color::BLACK - } - } - - fn halfplies(&self) -> usize { - self.halfplies.len() - } - - fn to_pgn(&self) -> String { - // PieceBoard is used to do the conversion to PGN since it's very simple, any `Chess` will - // do. - let mut board = PieceBoard::default(); - board.set_fen(&self.initial_position); - self.to_pgn_with_context(&board) - } - - fn to_pgn_with_context(&self, context: &C) -> String where C: Query { - //FIXME: The context is _completely_ wrong here, since I'm not actually making these moves. - //UCI moves and PGN moves are both ambiguous, you have to calculate to unpack the - //boardstate, which is understandable but pretty annoying. - let mut pgn = String::new(); - let line = self.halfplies.clone(); - for (move_number, halfply) in line.into_iter().enumerate() { - if move_number % 2 == 0 { - pgn.push_str(&format!("{}. {}", move_number / 2 + 1, &halfply.to_pgn(context))); - } else { - pgn.push_str(&format!(" {}\n", &halfply.to_pgn(context))); - } - } - pgn - } - - /// Clones the line into a new line, suitable for making a variation from the current move. - /// DEPRECATED: This is going to be a whole struct thing... - fn make_variation(&self) -> Line { - let mut variation = Line::default(); - for halfply in &self.halfplies { - variation.push(halfply.clone()); - } - variation - } -} - -impl Display for Line { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - // PieceBoard is used to do the conversion to PGN since it's very simple, and `Chess` will - // do. - write!(f, "{}", self.to_pgn()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use crate::board::simple::PieceBoard; - use crate::constants::START_POSITION_FEN; - - - #[test] - fn renders_game_pgn_correctly() { - let line = Line::from(vec![ - "d2d4", - "d7d5", - "c1f4", - "g8f6", - "g1f3", - "e7e6", - ]); - assert_eq!(line.to_pgn(), "1. d4 d5\n2. Bf4 Nf6\n3. Nf3 e6\n"); - } - - #[test] - fn renders_game_with_check_correctly() { - /* - let line = Line::from(vec![ - ]); - */ - } - - #[test] - fn renders_game_with_checkmate_correctly() { - } - - #[test] - fn renders_game_with_longcastling_correctly() { - } - - #[test] - fn renders_game_with_promotion_correctly() { - } - - #[test] - fn renders_game_with_en_passant_correctly() { - } - - #[test] - fn renders_game_with_disambiguation_correctly() { - } - - #[test] - fn renders_game_with_capture_correctly() { - } - - #[test] - fn renders_game_with_shortcastling_correctly() { - } - - #[test] - fn renders_game_with_promotion_and_checkmate_correctly() { - } - - - #[test] - fn line_push_pop() { - let mut line = Line::default(); - let halfply = HalfPly::from("e2e4"); - line.push(halfply.clone()); - assert_eq!(line.current_move(), Some(halfply.clone())); - assert_eq!(line.pop(), Some(halfply.clone())); - assert_eq!(line.pop(), None); - } - - #[test] - fn line_current_color() { - let mut line = Line::default(); - let halfply = HalfPly::from("e2e4"); - assert_eq!(line.current_color(), Color::WHITE); - line.push(halfply.clone()); - assert_eq!(line.current_color(), Color::BLACK); - line.push(halfply.clone()); - assert_eq!(line.current_color(), Color::WHITE); - line.push(halfply.clone()); - assert_eq!(line.current_color(), Color::BLACK); - } - - #[test] - fn line_to_pgn() { - let mut line = Line::default(); - let halfply = HalfPly::from("e2e4"); - line.push(halfply.clone()); - assert_eq!(line.to_pgn(), "1. e4"); - let halfply = HalfPly::from("e7e5"); - line.push(halfply.clone()); - assert_eq!(line.to_pgn(), "1. e4 e5\n"); - } -} - diff --git a/src/game/mod.rs b/src/game/mod.rs index 69c7282..cf6fa73 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -1,13 +1,3 @@ -#![allow(dead_code, unused_imports)] - -pub mod line; +pub mod action; pub mod variation; -pub mod interface; - -use line::Line; -use variation::Variation; - -struct Game { - mainline: Line, - variations: Vec, -} +pub mod compiles_to; diff --git a/src/game/variation.rs b/src/game/variation.rs index 7be3fde..ff9ecfd 100644 --- a/src/game/variation.rs +++ b/src/game/variation.rs @@ -1,46 +1,271 @@ -// this is a stub still, don't worry that it doesn't work. -use crate::coup::rep::HalfPly; -use crate::game::line::Line; -use crate::constants::START_POSITION_FEN; +use crate::{board::Alter, types::log::Log}; + +use crate::{board::PieceBoard, coup::rep::Move, notation::fen::{PositionMetadata, FEN}}; + +use super::action::chess::{ChessAction, Delim, Reason}; + +#[derive(Debug, Default, Clone)] pub struct Variation { - parent: Box, - initial_position: String, - previous_move_index: usize, - continuation: Line, + // Active Data + /// A record of every action in the game + log: Log, + halted: bool + + // Caches / Derived Data } -/* -impl From for Variation { - fn from(line: Line) -> Self { - let continuation = Line::default(); - // FIXME: This is not really correct - // continuation.initial_position = line.current_position().to_fen(); - - let length = line.halfplies(); - Variation { - parent: Box::new(line), - initial_position: START_POSITION_FEN.to_string(), - previous_move_index: length, - continuation, +impl Variation { + pub fn new() -> Self { + Self { + log: Log::start(), + halted: false + } + } + + pub fn commit(&mut self) -> &mut Self { + if self.halted { return self; } + + // update cached state? + self.log.commit(); + self + } + + pub fn commit_all(&mut self) -> &mut Self { + if self.halted { return self; } + + self.log.commit_all(); + self + } + + pub fn make(&mut self, mov: Move) -> &mut Self { + self.record(ChessAction::Make(mov)); + self + } + + pub fn new_game(&mut self) -> &mut Self { + self.record(ChessAction::NewGame); + self + } + + pub fn halt(&mut self, state: Reason) -> &mut Self { + self.record(ChessAction::Halted(state)); + self + } + + pub fn setup(&mut self, fen: FEN) -> &mut Self { + self.record(ChessAction::Setup(fen.clone())); + self + } + + pub fn variation(&mut self, block: impl Fn(&mut Variation)) -> &mut Self { + self.log.begin(); + + let mut variation = Variation::new(); + + block(&mut variation); + + variation.commit_all(); + + self.record(ChessAction::Variation(Delim::Start)); + for action in variation.log.into_iter() { + self.record(action); } + self.record(ChessAction::Variation(Delim::End)); + + self.log.commit(); + + self + } + + pub fn current_position(&self) -> FEN { + self.log.cursor(|cursor| { + let mut board = PieceBoard::default(); + let mut metadata = PositionMetadata::default(); + while let Some(action) = cursor.next() { + match action { + ChessAction::NewGame => { + board = PieceBoard::default(); + metadata = PositionMetadata::default(); + }, + ChessAction::Halted(_) => { + todo!(); + }, + ChessAction::Variation(_) => { + // This is a variation, so we don't need to do anything. Only reading the + // mainline + }, + ChessAction::Setup(fen) => { + board.set_fen(fen); + }, + ChessAction::Make(mov) => { + metadata.update(mov, &board); + for alter in mov.compile(&board) { + board.alter_mut(alter); + } + }, + } + } + + // Now board and metadata are caught up, so we just ask board to write it's fen + let mut ret = FEN::from(board); + ret.set_metadata(metadata); + ret + }) + } + + fn record(&mut self, action: ChessAction) -> &mut Self { + if self.halted { return self; } + + self.log.record(action); + self } } -*/ + + #[cfg(test)] mod tests { + use crate::notation::*; + use crate::{coup::rep::MoveType, types::Occupant}; + use crate::board::interface::*; + use super::*; - /* + impl Variation { + pub fn log(&self) -> Vec { + self.log.log() + } + } + #[test] - fn line_make_variation() { - let mut line = Line::default(); - let halfply = HalfPly::from("e2e4"); - line.push(halfply.clone()); - let variation = line.make_variation(); - assert_eq!(variation.current_move(), Some(halfply.clone())); - } - */ -} + fn fen_correct_after_one_move_from_start_pos() { + let mut game = Variation::default(); + game.new_game() + .setup(FEN::start_position()) + .make(Move::new(D2, D4, MoveType::DOUBLE_PAWN)) + .commit(); + + let actual_fen = game.current_position(); + + assert_eq!(actual_fen, FEN::new("rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR b KQkq d3 0 2")); + } + + #[test] + fn fen_correct_after_castling() { + let mut game = Variation::default(); + game.new_game() + .setup(FEN::start_position()) + .make(Move::new(E2, E4, MoveType::DOUBLE_PAWN)) + .make(Move::new(E7, E5, MoveType::DOUBLE_PAWN)) + .make(Move::new(G1, F3, MoveType::QUIET)) + .make(Move::new(B8, C6, MoveType::QUIET)) + .make(Move::new(F1, E2, MoveType::QUIET)) + .make(Move::new(G8, F6, MoveType::QUIET)) + .make(Move::new(E1, G1, MoveType::SHORT_CASTLE)) + .commit(); + + let actual_fen = game.current_position(); + + assert_eq!(actual_fen, FEN::new("r1bqkb1r/pppp1ppp/2n2n2/4p3/4P3/5N2/PPPPBPPP/RNBQ1RK1 b kq - 5 5")); + } + #[test] + fn fen_correct_mainline_position_when_variation_present() { + let mut game = Variation::default(); + game.new_game() + .setup(FEN::start_position()) + .make(Move::new(E2, E4, MoveType::DOUBLE_PAWN)) + .commit() + .variation(|v| { + v.make(Move::new(D2, D4, MoveType::DOUBLE_PAWN)); + }).make(Move::new(E7, E5, MoveType::DOUBLE_PAWN)) + .make(Move::new(G1, F3, MoveType::QUIET)) + .make(Move::new(B8, C6, MoveType::QUIET)) + .make(Move::new(F1, E2, MoveType::QUIET)) + .make(Move::new(G8, F6, MoveType::QUIET)) + .make(Move::new(E1, G1, MoveType::SHORT_CASTLE)) + .commit(); + + let actual_fen = game.current_position(); + + + assert_eq!(actual_fen, FEN::new("r1bqkb1r/pppp1ppp/2n2n2/4p3/4P3/5N2/PPPPBPPP/RNBQ1RK1 b kq - 5 5")); + } + + #[test] + fn a_cursor_can_follow_a_variation() { + let mut game = Variation::default(); + game.new_game() + .setup(FEN::start_position()) + .commit() + .variation(|v| { + v.make(Move::new(D2, D4, MoveType::DOUBLE_PAWN)).commit(); + }) + .make(Move::new(E7, E5, MoveType::DOUBLE_PAWN)) + .make(Move::new(E2, E4, MoveType::DOUBLE_PAWN)) + .make(Move::new(G1, F3, MoveType::QUIET)) + .make(Move::new(B8, C6, MoveType::QUIET)) + .make(Move::new(F1, E2, MoveType::QUIET)) + .make(Move::new(G8, F6, MoveType::QUIET)) + .make(Move::new(E1, G1, MoveType::SHORT_CASTLE)) + .commit(); + + + // HACK: This is a prototype of sorts, eventually there should be a cursor that takes a + // list of move numbers and variation numbers, e.g: + // + // vec![{move: 1, variation: 0}, {move: 2, variation: 0}, {move: 3, variation: 1}] + // + // would specify following the mainline for the first two moves, then following the first + // variation of the third. For simplicity, this should assume every move is the mainline + // unless otherwise specified. When a particular variation is reached, the cursor should + // switch to that variation and halt after reading the first 'end' delimiter. + // + // nested variations will have many end delimiters, so this quitting after finding the + // first one I think is correct, but remains to be seen. + // + // This test, for now, should cover the variation case in for now. + let line = game.log.cursor(|cursor| { + let mut board = PieceBoard::default(); + let mut metadata = PositionMetadata::default(); + while let Some(action) = cursor.next() { + match action { + ChessAction::NewGame => { + board = PieceBoard::default(); + metadata = PositionMetadata::default(); + }, + ChessAction::Halted(_) => { + todo!("In Halt"); + }, + ChessAction::Variation(v) => { + match v { + Delim::Start => { }, + Delim::End => { + break; + } + } + }, + ChessAction::Setup(fen) => { + board.set_fen(fen); + }, + ChessAction::Make(mov) => { + metadata.update(mov, &board); + for alter in mov.compile(&board) { + board.alter_mut(alter); + } + }, + } + } + + // Now board and metadata are caught up, so we just ask board to write it's fen + let mut ret = FEN::from(board); + ret.set_metadata(metadata); + ret + }); + + + assert_eq!(line, FEN::new("rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR b KQkq d3 0 2")); + + } +} diff --git a/src/lib.rs b/src/lib.rs index 134582d..c58b75a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,8 @@ -#![feature(stmt_expr_attributes,assert_matches,const_trait_impl,const_for)] +#![feature(stmt_expr_attributes,assert_matches,const_trait_impl,const_for,associated_type_defaults)] +// The Squares being consts means that sometimes when I use them as a reference, I trigger this +// warning. I generally don't mind the temporary value being created, and in fact want it (see the +// PositionMetadata to/from u32 impls for an example). +#![allow(const_item_mutation)] #![cfg_attr(test, allow(unused_imports))] // NOTE: These lints are disabled for the following reasons: // @@ -20,7 +24,6 @@ extern crate quickcheck_macros; #[macro_use] extern crate lazy_static; -extern crate either; extern crate rand; #[cfg(test)] @@ -29,15 +32,16 @@ pub use tracing_test; pub mod board; pub mod brain; pub mod constants; +pub mod game; pub mod coup; pub mod engine; -pub mod game; pub mod notation; #[macro_use] pub mod types; pub mod ui; pub mod util; + /// passes if the left is a subset of the right #[macro_export] macro_rules! assert_is_subset { diff --git a/src/notation/fen/castle_rights.rs b/src/notation/fen/castle_rights.rs index b6f64e1..892d005 100644 --- a/src/notation/fen/castle_rights.rs +++ b/src/notation/fen/castle_rights.rs @@ -1,6 +1,6 @@ use std::fmt::Display; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct CastleRights { pub white_short: bool, pub white_long: bool, @@ -27,11 +27,75 @@ impl Display for CastleRights { } } +impl From for u8 { + #[inline(always)] fn from(rights: CastleRights) -> u8 { + let u32_castling: u32 = rights.into(); + u32_castling as u8 + } +} + +impl From for CastleRights { + #[inline(always)] fn from(castling: u8) -> Self { + CastleRights::from(castling as u32) + } +} + +impl From for CastleRights { + fn from(castling: u32) -> Self { + CastleRights { + white_short: castling & 0b1000 != 0, + white_long: castling & 0b0100 != 0, + black_short: castling & 0b0010 != 0, + black_long: castling & 0b0001 != 0, + } + } +} + + +impl From for u32 { + fn from(rights: CastleRights) -> u32 { + let mut castling = 0; + if rights.white_short { + castling |= 0b1000; + } + if rights.white_long { + castling |= 0b0100; + } + if rights.black_short { + castling |= 0b0010; + } + if rights.black_long { + castling |= 0b0001; + } + castling + } +} + #[cfg(test)] mod tests { + use quickcheck::{Arbitrary, Gen}; + use super::*; + impl Arbitrary for CastleRights { + fn arbitrary(g: &mut Gen) -> Self { + CastleRights { + white_short: bool::arbitrary(g), + white_long: bool::arbitrary(g), + black_short: bool::arbitrary(g), + black_long: bool::arbitrary(g), + } + } + } + + + #[quickcheck] + fn roundtrips_correctly(castling: CastleRights) -> bool { + let castling2 = CastleRights::from(u8::from(castling)); + castling == castling2 + } + #[test] fn display_test() { let rights = CastleRights { diff --git a/src/notation/fen/mod.rs b/src/notation/fen/mod.rs index ead2075..ffe3b05 100644 --- a/src/notation/fen/mod.rs +++ b/src/notation/fen/mod.rs @@ -1,42 +1,42 @@ mod position_metadata; +mod position; mod castle_rights; -use std::fmt::Display; +use std::fmt::{Debug, Display}; use tracing::instrument; -use crate::board::{Alter, Alteration}; -use crate::constants::EMPTY_POSITION_FEN; -use crate::types::{Color, Occupant, Piece}; -use crate::game::interface::Chess; +use crate::board::Alter; +use crate::board::Alteration; +use crate::constants::{EMPTY_POSITION_FEN, START_POSITION_FEN}; +use crate::types::Color; use crate::notation::*; -use position_metadata::PositionMetadata; -use castle_rights::CastleRights; +pub use position_metadata::PositionMetadata; +pub use castle_rights::CastleRights; +use position::Position; -// NOTE: There exists a metadata type in `Ply` which might be useful here. I intended it to be -// packed into 4 bytes, but I think I implemented it as a plain struct, either way, it can come in -// here. -// I need to start reorganizing things more aggressively, and pruning out the stuff I won't need -// anymore. It's messy in here. - -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct FEN { - original_fen: String, - position: Vec, + position: Position, metadata: PositionMetadata } +impl Debug for FEN { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{} {}", self.position, self.metadata) + } +} impl PartialEq for FEN { fn eq(&self, other: &Self) -> bool { - self.original_fen == other.original_fen + self.position == other.position && + self.metadata == other.metadata } } impl Eq for FEN {} - impl Default for FEN { fn default() -> Self { Self::new(EMPTY_POSITION_FEN) @@ -44,21 +44,25 @@ impl Default for FEN { } impl FEN { + pub fn start_position() -> Self { + Self::new(START_POSITION_FEN) + } + /// Sometimes you just want to specify the position without all the metadata, this /// assumes you are describing a position with white-to-move, all castling rights, no en /// passant square. #[instrument] pub fn with_default_metadata(fen: &str) -> Self { - let fenprime = format!("{} {}", fen, PositionMetadata::default()); - let position = Self::compile(&fenprime); - Self { - original_fen: fenprime, - position, + position: Position::new(fen), metadata: PositionMetadata::default(), } } + pub fn set_metadata(&mut self, metadata: PositionMetadata) { + self.metadata = metadata; + } + /// Expects a full FEN string with all metadata. #[instrument] pub fn new(fen: &str) -> Self { @@ -66,12 +70,11 @@ impl FEN { let mut parts = fen.split_whitespace(); let position_str = parts.next().unwrap(); - let position = Self::compile(position_str); + let position = Position::new(position_str); metadata.parse(&mut parts); Self { - original_fen: fen.to_string(), position, metadata } @@ -93,73 +96,36 @@ impl FEN { } #[instrument] - pub fn halfmove_clock(&self) -> usize { + pub fn halfmove_clock(&self) -> u8 { self.metadata.halfmove_clock } #[instrument] - pub fn fullmove_number(&self) -> usize { + pub fn fullmove_number(&self) -> u16 { self.metadata.fullmove_number } #[instrument] - pub fn setup(&self) -> C where C : Chess { - let mut board = C::default(); - for alteration in &self.position { - board.alter_mut(*alteration); + pub fn setup(&self) -> A where A : Alter + Default { + let mut board = A::default(); + for alteration in self.position.clone().into_iter() { + board.alter_mut(alteration); } board } - #[instrument] - fn compile(fen: &str) -> Vec { - let mut alterations = Vec::new(); - let mut cursor = Square::by_rank_and_file(); - cursor.downward(); - for c in fen.chars() { - if cursor.is_done() { break; } - - match c { - '1'..='8' => { - let skip = c.to_digit(10).unwrap() as usize; - for _ in 0..skip { cursor.next(); } - } - '/' => { - continue; - } - _ => { - let color = if c.is_uppercase() { Color::WHITE } else { Color::BLACK }; - let piece = match c.to_ascii_lowercase() { - 'p' => Piece::Pawn, - 'n' => Piece::Knight, - 'b' => Piece::Bishop, - 'r' => Piece::Rook, - 'q' => Piece::Queen, - 'k' => Piece::King, - _ => { - continue; - }, - }; - let occupant = Occupant::Occupied(piece, color); - alterations.push(Alteration::Place { square: cursor.current_square(), occupant } ); - - cursor.next(); - } - } - - } - - + pub fn compile(&self) -> Vec { + self.position.clone().into_iter().collect() + } - return alterations; + pub fn metadata(&self) -> PositionMetadata { + self.metadata.clone() } } impl Display for FEN { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", - self.original_fen, - ) + write!(f, "{} {}", self.position, self.metadata) } } @@ -170,8 +136,8 @@ pub fn setup(fen: &FEN) -> A { } pub fn setup_mut(fen: &FEN, board: &mut A) { - for alteration in &fen.position { - board.alter_mut(*alteration); + for alteration in fen.position.clone().into_iter() { + board.alter_mut(alteration); } } @@ -180,14 +146,62 @@ mod tests { use crate::board::simple::PieceBoard; use crate::constants::{POS2_KIWIPETE_FEN, START_POSITION_FEN}; use crate::types::Color; + use crate::types::Occupant; use super::*; + #[test] + fn compile_is_correct_for_empty_pos() { + let fen = FEN::new(EMPTY_POSITION_FEN); + let alterations = fen.compile(); + assert_eq!(alterations.len(), 0); + } + + #[test] + fn compile_is_correct_for_start_pos() { + let fen = FEN::new(START_POSITION_FEN); + let alterations = fen.compile(); + assert_eq!(alterations.len(), 32); + assert_eq!(alterations, vec![ + Alteration::Place { square: A8, occupant: Occupant::black_rook() }, + Alteration::Place { square: B8, occupant: Occupant::black_knight() }, + Alteration::Place { square: C8, occupant: Occupant::black_bishop() }, + Alteration::Place { square: D8, occupant: Occupant::black_queen() }, + Alteration::Place { square: E8, occupant: Occupant::black_king() }, + Alteration::Place { square: F8, occupant: Occupant::black_bishop() }, + Alteration::Place { square: G8, occupant: Occupant::black_knight() }, + Alteration::Place { square: H8, occupant: Occupant::black_rook() }, + Alteration::Place { square: A7, occupant: Occupant::black_pawn() }, + Alteration::Place { square: B7, occupant: Occupant::black_pawn() }, + Alteration::Place { square: C7, occupant: Occupant::black_pawn() }, + Alteration::Place { square: D7, occupant: Occupant::black_pawn() }, + Alteration::Place { square: E7, occupant: Occupant::black_pawn() }, + Alteration::Place { square: F7, occupant: Occupant::black_pawn() }, + Alteration::Place { square: G7, occupant: Occupant::black_pawn() }, + Alteration::Place { square: H7, occupant: Occupant::black_pawn() }, + Alteration::Place { square: A2, occupant: Occupant::white_pawn() }, + Alteration::Place { square: B2, occupant: Occupant::white_pawn() }, + Alteration::Place { square: C2, occupant: Occupant::white_pawn() }, + Alteration::Place { square: D2, occupant: Occupant::white_pawn() }, + Alteration::Place { square: E2, occupant: Occupant::white_pawn() }, + Alteration::Place { square: F2, occupant: Occupant::white_pawn() }, + Alteration::Place { square: G2, occupant: Occupant::white_pawn() }, + Alteration::Place { square: H2, occupant: Occupant::white_pawn() }, + Alteration::Place { square: A1, occupant: Occupant::white_rook() }, + Alteration::Place { square: B1, occupant: Occupant::white_knight() }, + Alteration::Place { square: C1, occupant: Occupant::white_bishop() }, + Alteration::Place { square: D1, occupant: Occupant::white_queen() }, + Alteration::Place { square: E1, occupant: Occupant::white_king() }, + Alteration::Place { square: F1, occupant: Occupant::white_bishop() }, + Alteration::Place { square: G1, occupant: Occupant::white_knight() }, + Alteration::Place { square: H1, occupant: Occupant::white_rook() }, + ]); + } + #[test] fn fen_startpos() { let fen = FEN::new(START_POSITION_FEN); - assert_eq!(fen.original_fen, START_POSITION_FEN); // We test the position part below in the #setup test assert_eq!(fen.side_to_move(), Color::WHITE); assert!(fen.castling().white_short); @@ -202,7 +216,6 @@ mod tests { #[test] fn fen_kiwipete_position() { let fen = FEN::new(POS2_KIWIPETE_FEN); - assert_eq!(fen.original_fen, POS2_KIWIPETE_FEN); // We test the position part below in the #setup test assert_eq!(fen.side_to_move(), Color::WHITE); assert!(fen.castling().white_short); @@ -253,8 +266,6 @@ mod tests { #[test] fn fen_empty_board() { let fen = FEN::new("8/8/8/8/8/8/8/8 w KQkq - 0 1"); - assert_eq!(fen.original_fen, "8/8/8/8/8/8/8/8 w KQkq - 0 1"); - // We test the position part below in the #setup test assert_eq!(fen.side_to_move(), Color::WHITE); assert!(fen.castling().white_short); assert!(fen.castling().white_long); @@ -272,5 +283,12 @@ mod tests { assert_eq!(format!("{}", fen), expected); } + #[test] + fn fen_parses_ep_square() { + let problem = FEN::new("rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR b KQkq d2 0 2"); + // This round-trips the string into our structures and back out. + assert_eq!(format!("{:?}", problem), "rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR b KQkq d2 0 2"); + } + } diff --git a/src/notation/fen/position.rs b/src/notation/fen/position.rs new file mode 100644 index 0000000..64535e8 --- /dev/null +++ b/src/notation/fen/position.rs @@ -0,0 +1,172 @@ +use std::fmt::{Display, Formatter}; + +use crate::board::{Alteration, Query}; +use crate::constants::EMPTY_POSITION_FEN; +use crate::types::{Color, Piece}; +use crate::notation::*; +use crate::types::Occupant; + +#[derive(Debug, Clone)] +pub(super) struct Position { + position_string: String, + position: Vec +} + +impl Position { + pub fn new(fen: &str) -> Self { + let position = Self::compile(&fen); + + Self { + position_string: fen.to_string(), + position, + } + } + + fn compile(fen: &str) -> Vec { + let mut alterations = Vec::new(); + let mut cursor = Square::by_rank_and_file(); + cursor.downward(); + for c in fen.chars() { + if cursor.is_done() { break; } + + match c { + '1'..='8' => { + let skip = c.to_digit(10).unwrap() as usize; + for _ in 0..skip { cursor.next(); } + } + '/' => { + continue; + } + c => { + let color = if c.is_uppercase() { Color::WHITE } else { Color::BLACK }; + let piece = match c.to_ascii_lowercase() { + 'p' => Piece::Pawn, + 'n' => Piece::Knight, + 'b' => Piece::Bishop, + 'r' => Piece::Rook, + 'q' => Piece::Queen, + 'k' => Piece::King, + _ => { + continue; + }, + }; + let occupant = Occupant::Occupied(piece, color); + alterations.push(Alteration::Place { square: cursor.current_square(), occupant } ); + + cursor.next(); + } + } + + } + + alterations + } +} + +impl Display for Position { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.position_string) + } +} + +impl From for Position { + fn from(c: C) -> Self { + let mut rep = String::new(); + let mut empty = 0; + for s in Square::fenwise() { + let occ = c.get(s); + + if s.file() == 0 && s != A8 { + if empty != 0 { + rep.push_str(&empty.to_string()); + empty = 0; + } + rep.push('/'); + } + + match occ { + Occupant::Empty => empty += 1, + Occupant::Occupied(p, c) => { + if empty != 0 { + rep.push_str(&empty.to_string()); + empty = 0; + } + rep.push(p.to_fen(c)); + // rep.push_str(&c.to_string()); + } + } + } + + if empty != 0 { + rep.push_str(&empty.to_string()); + } + + Position::new(&rep) + } +} +impl PartialEq for Position { + fn eq(&self, other: &Self) -> bool { + self.position_string == other.position_string + } +} + +impl Eq for Position {} + +impl Default for Position { + fn default() -> Self { + Self::new(EMPTY_POSITION_FEN.split_whitespace().next().unwrap()) + } +} + +impl IntoIterator for Position { + type Item = Alteration; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.position.into_iter() + } +} + + + +#[cfg(test)] +mod tests { + use crate::board::PieceBoard; + + use super::*; + + #[test] + fn converts_from_query_correctly() { + let mut pb = PieceBoard::default(); + pb.set_startpos(); + + let pos = Position::from(pb); + + assert_eq!(pos.position_string, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"); + } + + #[test] + fn converts_from_empty_position() { + let pb = PieceBoard::default(); + + let pos = Position::from(pb); + + assert_eq!(pos.position_string, "8/8/8/8/8/8/8/8"); + } + + #[test] + fn empty_position_is_equivalent_to_a_real_empty_pos() { + let pos = Position::default(); + assert_eq!(pos.position_string, "8/8/8/8/8/8/8/8"); + } + + #[test] + fn compile_fen_compiles_to_expected_output() { + let fen = "p7/8/8/8/8/8/8/8"; + let alters = Position::compile(fen); + + assert_eq!(alters.len(), 1); + assert_eq!(alters[0], Alteration::place(A8, Occupant::black_pawn())); + } + +} diff --git a/src/notation/fen/position_metadata.rs b/src/notation/fen/position_metadata.rs index 559bd2f..69c1751 100644 --- a/src/notation/fen/position_metadata.rs +++ b/src/notation/fen/position_metadata.rs @@ -1,24 +1,32 @@ use std::fmt::Display; use std::str::SplitWhitespace; -use tracing::{instrument, debug}; - +use crate::board::Query; +use crate::constants::File; +use crate::coup::rep::Move; use crate::notation::fen::castle_rights::CastleRights; use crate::notation::*; -use crate::types::Color; +use crate::types::{Color, Occupant, Piece}; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct PositionMetadata { - pub side_to_move: Color, - pub castling: CastleRights, - // The index of the square containing the en passant target square, or None if there is none - pub en_passant: Option, - pub halfmove_clock: usize, - pub fullmove_number: usize, + /// Color of the Current Player + pub side_to_move: Color, // u1 + /// Bitfield of Castle Rights + pub castling: CastleRights, // u4 + /// The index of the square containing the en passant target square, or None if there is none + pub en_passant: Option, // u4 for flag + file, flag might be a halfmove clock of 0 after T1? + /// The number of halfmoves since the last pawn advance or capture + pub halfmove_clock: u8, // u6 is enough + pub fullmove_number: u16, // u16 + // + // layout of the metadata: + // 00000000 00000000 00000000 00000000 + // CCCCEEEE HHHHHHSx FFFFFFFF FFFFFFFF } + impl Default for PositionMetadata { - #[instrument] fn default() -> Self { Self { side_to_move: Color::WHITE, @@ -52,8 +60,18 @@ impl Display for PositionMetadata { } } +const CASTLING_MASK: u8 = 0b1111_0000; +const CASTLING_SHIFT: u8 = 4; +const EP_FLAG_MASK: u8 = 0b0000_1000; +const EP_FLAG_SHIFT: u8 = 3; +const EP_FILE_MASK: u8 = 0b0000_0111; +const EP_FILE_SHIFT: u8 = 0; +const STM_MASK: u8 = 0b0000_0010; +const STM_SHIFT: u8 = 1; +const HMC_MASK: u8 = 0b1111_1100; +const HMC_SHIFT: u8 = 2; + impl PositionMetadata { - #[instrument] pub fn parse(&mut self, parts: &mut SplitWhitespace<'_>) { let side_to_move = parts.next(); let castling = parts.next(); @@ -61,7 +79,6 @@ impl PositionMetadata { let halfmove_clock = parts.next(); let fullmove_number = parts.next(); - debug!("Side to move: {:?}", side_to_move); let side_to_move = match side_to_move { Some("w") => Color::WHITE, Some("b") => Color::BLACK, @@ -87,7 +104,13 @@ impl PositionMetadata { let en_passant = match en_passant { Some("-") => None, - Some(square) => Some(Square::new(square.parse().unwrap())), + Some(square) => { + let sq = Square::try_from(square); + match sq { + Ok(sq) => Some(sq), + Err(_) => None, + } + }, None => panic!("Invalid en passant square"), }; @@ -100,12 +123,192 @@ impl PositionMetadata { self.fullmove_number = fullmove_number.unwrap().parse().unwrap(); } + pub fn update(&mut self, mov: &Move, board: &impl Query) { + // Clear the EP square, we'll re-set it if necessary later. + self.en_passant = None; + + if self.side_to_move == Color::WHITE { + self.fullmove_number += 1; + self.side_to_move = Color::BLACK; + } else { + self.side_to_move = Color::WHITE; + } + + // rely on the color of the piece being moved, rather than reasoning about the side-to-move + // or delaying it till the end. + let Occupant::Occupied(piece, color) = board.get(mov.source()) else { panic!("Move has no source piece"); }; + + + if mov.is_capture() || piece == Piece::Pawn { + self.halfmove_clock = 0; + } else { + self.halfmove_clock += 1; + } + + let source = mov.source(); + match piece { + Piece::King => { + match color { + Color::WHITE => { + self.castling.white_short = false; + self.castling.white_long = false; + } + Color::BLACK => { + self.castling.black_short = false; + self.castling.black_long = false; + } + } + } + Piece::Rook if source == H1 => { self.castling.white_short = false; } + Piece::Rook if source == H8 => { self.castling.black_short = false; } + Piece::Rook if source == A1 => { self.castling.white_long = false; } + Piece::Rook if source == A8 => { self.castling.black_long = false; } + Piece::Rook => {} + Piece::Pawn => { + if mov.is_double_pawn_push_for(color) { + self.en_passant = match color { + Color::BLACK => mov.target().up(), + Color::WHITE => mov.target().down(), + } + } + } + _ => {} + } + } } +impl From for u32 { + fn from(data: PositionMetadata) -> Self { + // Layout of the metadata: + // 00000000 00000000 00000000 00000000 + // CCCCeEEE HHHHHHSx FFFFFFFF FFFFFFFF + let mut b1 : u8 = 0; + let mut b2 : u8 = 0; + + let from = u8::from(data.castling); + b1 |= from << CASTLING_SHIFT; + b1 |= match data.en_passant { + None => 0, + Some(sq) => (1 << EP_FLAG_SHIFT) | ((sq.file() as u8) << EP_FILE_SHIFT), + }; + + b2 |= (data.halfmove_clock as u8) << HMC_SHIFT; + b2 |= (data.side_to_move as u8) << STM_SHIFT; + + + let [b3, b4] = data.fullmove_number.to_ne_bytes(); + + u32::from_ne_bytes([b1, b2, b3, b4]) + } +} + +impl From for PositionMetadata { + fn from(data: u32) -> Self { + // Layout of the metadata: + // 00000000 00000000 00000000 00000000 + // CCCCeEEE HHHHHHSx FFFFFFFF FFFFFFFF + let [b1, b2, b3, b4] = data.to_ne_bytes(); + + // It is convenient to work on the second byte first. + + // b2 contains the halfmove clock (in the upper 6 bits) and the STM indicator in the second + // lowest bit. the LSB is unused. + // Shifts again to kill unused bits. + let halfmove_clock = (b2 & HMC_MASK) >> HMC_SHIFT; + let side_to_move = Color::from((b2 & STM_MASK) >> STM_SHIFT); + + // b1 contains the Castling Information and EP square: + // magic numbers are just shifting off the unused portions. + let castling = CastleRights::from((b1 & CASTLING_MASK) >> CASTLING_SHIFT); + + let en_passant = if (b1 & EP_FLAG_MASK) != 0 { + let ep_file_data = (b1 & EP_FILE_MASK) >> EP_FILE_SHIFT; + let ep_file = File::from_index(ep_file_data as usize); + + Some(match side_to_move { + // color is the _side to move_, so the EP square would be on the opposite side if + // it exists + Color::WHITE => A6.set_file(ep_file as usize), + Color::BLACK => A3.set_file(ep_file as usize), + }) + } else { + None + }; + + + // b3 and b4 contain the fullmove number as a u16 + let fullmove_number = u16::from_ne_bytes([b3, b4]); + + assert_eq!(u32::from_ne_bytes([b1, b2, b3, b4]), data); + + Self { + side_to_move, + castling, + en_passant, + halfmove_clock, + fullmove_number, + } + } +} + + + #[cfg(test)] mod tests { use super::*; + use quickcheck::{Arbitrary, Gen}; + use tracing::debug; + + impl Arbitrary for PositionMetadata { + fn arbitrary(g: &mut Gen) -> Self { + let should_ep = bool::arbitrary(g); + let color = Color::arbitrary(g); + let ep_square = if should_ep { + let file = File::arbitrary(g); + + let sq = if color == Color::WHITE { + A6.set_file(file as usize) + } else { + A3.set_file(file as usize) + }; + + Some(sq) + } else { + None + }; + + Self { + side_to_move: color, + castling: CastleRights::arbitrary(g), + en_passant: ep_square, + halfmove_clock: u8::arbitrary(g) % 64, + fullmove_number: u16::arbitrary(g), + } + } + } + + #[test] + fn ep_square_is_converts_to_u32_correctly() { + let metadata = PositionMetadata { + en_passant: Some(G3), + ..Default::default() + }; + let [mut b1, _, _, _] = u32::from(metadata).to_ne_bytes(); + + let mask = 0b00001111; + + b1 &= mask; + + assert_eq!(b1, 0b00001110); + } + + #[quickcheck] + fn roundtrips_correctly(metadata: PositionMetadata) -> bool { + metadata == PositionMetadata::from(u32::from(metadata)) + } + + // TODO: These should be quickcheck #[test] fn parse() { let mut metadata = PositionMetadata::default(); @@ -122,6 +325,43 @@ mod tests { assert_eq!(metadata.fullmove_number, 1); } + #[test] + fn parse_2() { + let mut metadata = PositionMetadata::default(); + let mut parts = "w kq - 1 1".split_whitespace(); + metadata.parse(&mut parts); + + assert_eq!(metadata.side_to_move, Color::WHITE); + assert!(!metadata.castling.white_short); + assert!(!metadata.castling.white_long); + assert!(metadata.castling.black_short); + assert!(metadata.castling.black_long); + assert_eq!(metadata.en_passant, None); + assert_eq!(metadata.halfmove_clock, 1); + assert_eq!(metadata.fullmove_number, 1); + } + + #[test] + fn to_and_from_u32() { + let metadata = PositionMetadata { + side_to_move: Color::WHITE, + castling: CastleRights { + white_short: true, + white_long: true, + black_short: true, + black_long: true, + }, + en_passant: None, + halfmove_clock: 0, + fullmove_number: 1, + }; + + let u32_data = u32::from(metadata); + let metadata2 = PositionMetadata::from(u32_data); + + assert_eq!(metadata, metadata2); + } + #[test] fn print() { let metadata = PositionMetadata { @@ -139,4 +379,89 @@ mod tests { assert_eq!(metadata.to_string(), "w KQkq - 0 1"); } + + #[test] + fn parses_metadata_with_ep_square() { + let mut metadata = PositionMetadata::default(); + let mut parts = "w KQkq e3 0 1".split_whitespace(); + metadata.parse(&mut parts); + + assert_eq!(metadata.side_to_move, Color::WHITE); + assert!(metadata.castling.white_short); + assert!(metadata.castling.white_long); + assert!(metadata.castling.black_short); + assert!(metadata.castling.black_long); + assert_eq!(metadata.en_passant, Some(E3)); + assert_eq!(metadata.halfmove_clock, 0); + assert_eq!(metadata.fullmove_number, 1); + } + + #[test] + fn test_default() { + let metadata = PositionMetadata::default(); + assert_eq!(metadata.side_to_move, Color::WHITE); + assert_eq!(metadata.castling, CastleRights { + white_short: true, + white_long: true, + black_short: true, + black_long: true, + }); + assert_eq!(metadata.en_passant, None); + assert_eq!(metadata.halfmove_clock, 0); + assert_eq!(metadata.fullmove_number, 1); + } + + #[test] + fn test_parse() { + let mut metadata = PositionMetadata::default(); + let mut parts = "w KQkq - 0 1".split_whitespace(); + metadata.parse(&mut parts); + + assert_eq!(metadata.side_to_move, Color::WHITE); + assert_eq!(metadata.castling, CastleRights { + white_short: true, + white_long: true, + black_short: true, + black_long: true, + }); + assert_eq!(metadata.en_passant, None); + assert_eq!(metadata.halfmove_clock, 0); + assert_eq!(metadata.fullmove_number, 1); + } + + #[test] + fn test_parse_2() { + let mut metadata = PositionMetadata::default(); + let mut parts = "w kq - 1 1".split_whitespace(); + metadata.parse(&mut parts); + + assert_eq!(metadata.side_to_move, Color::WHITE); + assert_eq!(metadata.castling, CastleRights { + white_short: false, + white_long: false, + black_short: true, + black_long: true, + }); + assert_eq!(metadata.en_passant, None); + assert_eq!(metadata.halfmove_clock, 1); + assert_eq!(metadata.fullmove_number, 1); + } + + #[test] + fn test_parse_3() { + let mut metadata = PositionMetadata::default(); + let mut parts = "w kq - 1 1".split_whitespace(); + metadata.parse(&mut parts); + + assert_eq!(metadata.side_to_move, Color::WHITE); + assert_eq!(metadata.castling, CastleRights { + white_short: false, + white_long: false, + black_short: true, + black_long: true, + }); + assert_eq!(metadata.en_passant, None); + assert_eq!(metadata.halfmove_clock, 1); + assert_eq!(metadata.fullmove_number, 1); + } } diff --git a/src/notation/interface.rs b/src/notation/interface.rs deleted file mode 100644 index 14dd645..0000000 --- a/src/notation/interface.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::{coup::rep::Move, game::line::Line, notation::Square}; - - -/// Implementors must provide a bijective mapping to a Square -pub trait SquareNotation where Self : Into + From { - - fn index(self) -> usize { - let s : Square = self.into(); - s._index() - } - - fn file(self) -> usize { - let s : Square = self.into(); - s._file() - } - - fn rank(self) -> usize { - let s : Square = self.into(); - s._rank() - } -} - -/// Implementors must provide a bijective mapping to a Move -pub trait MoveNotation where Self : Into + From { } - -/// Implementors must provide a bijective mapping to a Line -pub trait LineNotation where Self : Into + From { } - -/* equivalent to above, but also imports variations -pub trait StudyNotation { -} -*/ - diff --git a/src/notation/mod.rs b/src/notation/mod.rs index 709ee83..129466a 100644 --- a/src/notation/mod.rs +++ b/src/notation/mod.rs @@ -1,4 +1,3 @@ - // Square Notations pub mod square; // Represent a Square as an index with 0 at a1 and 63 at h8 // pub mod bitsquare; // Represent a Square as a bitboard with 1 at a1 and 2^63 at h8 @@ -13,9 +12,5 @@ pub mod uci; // Canonical Move Notation, Universal Chess Interface uses 'Long Al // Board Notations pub mod fen; -// Traits -pub mod interface; - // Re-exports pub use square::*; -pub use interface::*; diff --git a/src/notation/square/iterator.rs b/src/notation/square/iterator.rs index d8b9c22..e9a8314 100644 --- a/src/notation/square/iterator.rs +++ b/src/notation/square/iterator.rs @@ -95,6 +95,7 @@ impl From for RankFile { impl Iterator for RankFile { type Item = Square; + #[cfg_attr(test, mutants::skip)] fn next(&mut self) -> Option { if self.done { return None; } @@ -277,6 +278,8 @@ impl RankFile { mod tests { use super::*; + + mod iterator { use super::*; @@ -309,6 +312,13 @@ mod tests { mod rankfile { use super::*; + #[quickcheck] + fn from_square(s: usize) -> bool { + let square = Square::new(s % 64); + let rankfile = RankFile::from(square); + rankfile == square + } + mod touches_all_squares { use super::*; diff --git a/src/notation/square/mod.rs b/src/notation/square/mod.rs index d8e735d..79f076e 100644 --- a/src/notation/square/mod.rs +++ b/src/notation/square/mod.rs @@ -1,4 +1,4 @@ -use crate::{notation::SquareNotation, types::Color}; +use crate::types::Color; mod constants; mod display_debug; @@ -14,34 +14,74 @@ pub use iterator::*; #[derive(Clone, Copy, PartialEq, Eq)] pub struct Square(usize); -impl SquareNotation for Square { } - impl Square { pub const fn new(index: usize) -> Self { Self(index) } - pub(crate) const fn _index(&self) -> usize { + pub const fn set_rank(&mut self, rank: usize) -> Self { + self.0 = rank * 8 + self.file(); + *self + } + + pub const fn set_file(&mut self, file: usize) -> Self { + self.0 = self.rank() * 8 + file; + *self + } + + pub const fn index(&self) -> usize { self.0 } - pub(crate) const fn _file(&self) -> usize { + pub const fn file(&self) -> usize { self.0 % 8 } - pub(crate) const fn _rank(&self) -> usize { + pub const fn rank(&self) -> usize { self.0 / 8 } + pub const fn up(&self) -> Option { + if self.rank() == 7 { + None + } else { + Some(Self(self.0 + 8)) + } + } + + pub const fn down(&self) -> Option { + if self.rank() == 0 { + None + } else { + Some(Self(self.0 - 8)) + } + } + + pub const fn left(&self) -> Option { + if self.file() == 0 { + None + } else { + Some(Self(self.0 - 1)) + } + } + + pub const fn right(&self) -> Option { + if self.file() == 7 { + None + } else { + Some(Self(self.0 + 1)) + } + } + pub const fn backrank_for(&self, color: Color) -> bool { match color { - Color::WHITE => self._rank() == 0, - Color::BLACK => self._rank() == 7, + Color::WHITE => self.rank() == 0, + Color::BLACK => self.rank() == 7, } } pub const fn backrank(&self) -> bool { - self._rank() == 0 || self._rank() == 7 + self.rank() == 0 || self.rank() == 7 } } @@ -50,31 +90,77 @@ impl Square { mod tests { use super::*; + use quickcheck::{Arbitrary, Gen}; + + impl Arbitrary for Square { + fn arbitrary(g: &mut Gen) -> Self { + Square(usize::arbitrary(g) % 64) + } + } + #[test] fn rank_is_correct() { - assert_eq!(A1._rank(), 0); - assert_eq!(A2._rank(), 1); - assert_eq!(A3._rank(), 2); - assert_eq!(A4._rank(), 3); - assert_eq!(A5._rank(), 4); - assert_eq!(A6._rank(), 5); - assert_eq!(A7._rank(), 6); - assert_eq!(A8._rank(), 7); - - assert_eq!(H1._rank(), 0); + assert_eq!(A1.rank(), 0); + assert_eq!(A2.rank(), 1); + assert_eq!(A3.rank(), 2); + assert_eq!(A4.rank(), 3); + assert_eq!(A5.rank(), 4); + assert_eq!(A6.rank(), 5); + assert_eq!(A7.rank(), 6); + assert_eq!(A8.rank(), 7); + + assert_eq!(H1.rank(), 0); } #[test] fn file_is_correct() { - assert_eq!(A1._file(), 0); - assert_eq!(B1._file(), 1); - assert_eq!(C1._file(), 2); - assert_eq!(D1._file(), 3); - assert_eq!(E1._file(), 4); - assert_eq!(F1._file(), 5); - assert_eq!(G1._file(), 6); - assert_eq!(H1._file(), 7); - - assert_eq!(H8._file(), 7); + assert_eq!(A1.file(), 0); + assert_eq!(B1.file(), 1); + assert_eq!(C1.file(), 2); + assert_eq!(D1.file(), 3); + assert_eq!(E1.file(), 4); + assert_eq!(F1.file(), 5); + assert_eq!(G1.file(), 6); + assert_eq!(H1.file(), 7); + + assert_eq!(H8.file(), 7); + } + + #[test] + fn up_is_correct() { + assert_eq!(A1.up(), Some(A2)); + assert_eq!(A8.up(), None); + } + + #[test] + fn down_is_correct() { + assert_eq!(A1.down(), None); + assert_eq!(A8.down(), Some(A7)); + } + + #[test] + fn left_is_correct() { + assert_eq!(A1.left(), None); + assert_eq!(H1.left(), Some(G1)); + } + + #[test] + fn right_is_correct() { + assert_eq!(H1.right(), None); + assert_eq!(A1.right(), Some(B1)); + } + + #[test] + fn set_file_is_correct() { + let mut square = A1; + assert_eq!(square.set_file(7), H1); + assert_eq!(square.set_file(0), A1); + } + + #[test] + fn set_rank_is_correct() { + let mut square = A1; + assert_eq!(square.set_rank(7), A8); + assert_eq!(square.set_rank(0), A1); } } diff --git a/src/notation/uci.rs b/src/notation/uci.rs index 2b22706..ca5f4fb 100644 --- a/src/notation/uci.rs +++ b/src/notation/uci.rs @@ -3,8 +3,6 @@ use crate::notation::square::*; use crate::coup::rep::{Move, MoveType}; use crate::types::Piece; -use super::MoveNotation; - /// Represents a move in UCI format. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct UCI { @@ -86,10 +84,6 @@ impl TryFrom<&str> for UCI { } } -impl MoveNotation for UCI { -} - - #[cfg(test)] mod tests { use super::*; diff --git a/src/types/bitboard/creation.rs b/src/types/bitboard/creation.rs index 8b4fa6b..31ce1dd 100644 --- a/src/types/bitboard/creation.rs +++ b/src/types/bitboard/creation.rs @@ -59,7 +59,6 @@ impl Default for Bitboard { #[cfg(test)] mod tests { use super::*; - use crate::notation::SquareNotation; #[test] fn empty() { diff --git a/src/types/bitboard/from_into.rs b/src/types/bitboard/from_into.rs index 06afdc4..f7d2849 100644 --- a/src/types/bitboard/from_into.rs +++ b/src/types/bitboard/from_into.rs @@ -21,7 +21,7 @@ impl From for usize { } } -impl From for Bitboard { +impl> From for Bitboard { fn from(n: N) -> Bitboard { let s : Square = n.into(); Bitboard(1 << s.index()) diff --git a/src/types/bitboard/intrinsics.rs b/src/types/bitboard/intrinsics.rs index 475c98f..608c8d8 100644 --- a/src/types/bitboard/intrinsics.rs +++ b/src/types/bitboard/intrinsics.rs @@ -8,7 +8,6 @@ impl Bitboard { unsafe { core::arch::x86_64::_pext_u64(self.0, mask.0) } } - #[cfg_attr(test, mutants::skip)] // This is a panic instead of failing back to slower methods, I am lazy. #[cfg(not(target_feature = "bmi2"))] pub fn pext(&self, _mask: Bitboard) -> u64 { // TODO: Write CPU-REQUIREMENTS @@ -36,7 +35,6 @@ impl Bitboard { } } - #[cfg_attr(test, mutants::skip)] // This is a panic instead of failing back to slower methods, I am lazy. #[cfg(not(target_feature = "bmi1"))] pub fn first_index(&self) -> usize { // TODO: Write CPU-REQUIREMENTS diff --git a/src/types/bitboard/mod.rs b/src/types/bitboard/mod.rs index e6f3bef..837cf33 100644 --- a/src/types/bitboard/mod.rs +++ b/src/types/bitboard/mod.rs @@ -110,8 +110,8 @@ impl Bitboard { /// assert!(b.is_set(A1)); /// ``` #[inline] - pub fn set(&mut self, square: S) { - self.0 |= 1 << square.index(); + pub fn set(&mut self, square: impl Into) { + self.0 |= 1 << square.into().index(); } /// Return a vector containing all the indices which are set @@ -145,8 +145,8 @@ impl Bitboard { /// b.unset(A1); /// assert!(!b.is_set(A1)); /// ``` - pub fn unset(&mut self, square: S) { - self.0 &= !(1 << square.index()); + pub fn unset(&mut self, square: impl Into) { + self.0 &= !(1 << square.into().index()); } /// Logically 'moves' a piece from the 'from' square to the 'to' square @@ -166,8 +166,8 @@ impl Bitboard { /// b.flip(A1); /// assert!(!b.is_set(A1)); /// ``` - pub fn flip(&mut self, square: S) { - self.0 ^= 1 << square.index(); + pub fn flip(&mut self, square: impl Into) { + self.0 ^= 1 << square.into().index(); } /// True if the given bit is set @@ -181,8 +181,8 @@ impl Bitboard { /// assert!(!b.is_set(A1)); /// ``` #[inline] - pub fn is_set(&self, square: S) -> bool { - self.0 & (1 << square.index()) != 0 + pub fn is_set(&self, square: impl Into) -> bool { + self.0 & (1 << square.into().index()) != 0 } /// Count the number of set squares diff --git a/src/types/color.rs b/src/types/color.rs index 725c77b..a6bfaff 100644 --- a/src/types/color.rs +++ b/src/types/color.rs @@ -13,6 +13,22 @@ pub enum Color { BLACK = 1, } +impl From for Color { + fn from(value: u8) -> Self { + match value { + 0 => Color::WHITE, + 1 => Color::BLACK, + _ => panic!("Invalid color index"), + } + } +} + +impl From for u8 { + fn from(color: Color) -> Self { + color as u8 + } +} + impl Display for Color { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -70,8 +86,20 @@ impl Not for Color { #[cfg(test)] mod tests { + use quickcheck::{Arbitrary, Gen}; + use super::*; + impl Arbitrary for Color { + fn arbitrary(g: &mut Gen) -> Self { + if bool::arbitrary(g) { + Color::WHITE + } else { + Color::BLACK + } + } + } + #[test] fn is_white() { assert!(Color::WHITE.is_white()); diff --git a/src/types/log/cursor.rs b/src/types/log/cursor.rs new file mode 100644 index 0000000..70da004 --- /dev/null +++ b/src/types/log/cursor.rs @@ -0,0 +1,185 @@ +use super::Log; + + +pub struct Cursor<'a, T> where T: Clone { + log: &'a Log, + position: Option +} + +impl<'a, T> Cursor<'a, T> where T: Clone { + pub fn new(log: &'a Log) -> Self { + Self { + log, + position: None + } + } + + pub fn seek(&mut self, position: usize) -> Option<&T> { + self.position = Some(position); + self.read() + } + + pub fn jump(&mut self, offset: isize) -> Option<&T> { + // FIXME: I think this should probably not be None, but I don't know what the convenient + // API should be, so for now this is what it is. + if self.position.is_none() { return None } + + let new_position = self.position.unwrap() as isize + offset; + + if new_position < 0 { + self.position = Some(0); + None + } else { + if new_position as usize >= self.log.log.len() { + self.position = Some(self.log.len()); + } else { + self.position = Some(new_position as usize); + } + self.read() + } + } + + /// Now listen for a second, I know this looks bad. + /// + /// I'm not implementing `Iterator` because of weird lifetime stuff that happens that I'm too + /// scared to try to fix. + /// + /// It looks bad because it is bad. Don't be like me, be brave. + #[allow(clippy::should_implement_trait)] + pub fn next(&mut self) -> Option<&T> { + match self.position { + None => { + self.position = Some(0); + self.read() + } + Some(position) => { + if position == self.log.len() { + None + } else { + self.position = Some(position + 1); + self.read() + } + } + } + } + + pub fn prev(&mut self) -> Option<&T> { + match self.position { + None => { + self.position = Some(self.log.len() - 1); + self.read() + } + Some(position) => { + if position == 0 { + None + } else { + self.position = Some(position - 1); + self.read() + } + } + } + } + + pub fn read(&mut self) -> Option<&T> { + match self.position { + None => None, + Some(position) => self.log.get(position) + } + } +} + +#[cfg(test)] +mod tests { + mod cursor { + use crate::types::log::Log; + + use super::*; + + #[test] + fn cursor_seeks() { + let mut log = Log::default(); + + log.record(1).record(2).commit(); + + log.cursor(|cursor| { + assert_eq!(*cursor.seek(0).unwrap(), 1); + assert_eq!(*cursor.seek(1).unwrap(), 2); + assert_eq!(cursor.seek(2), None); + assert_eq!(cursor.seek(3), None); + }); + + log.record(3).record(4).commit(); + + log.cursor(|cursor| { + assert_eq!(*cursor.seek(0).unwrap(), 1); + assert_eq!(*cursor.seek(1).unwrap(), 2); + assert_eq!(*cursor.seek(3).unwrap(), 4); + assert_eq!(*cursor.seek(2).unwrap(), 3); + assert_eq!(cursor.seek(4), None); + }); + } + + #[test] + fn cursor_prev_and_next() { + let mut log = Log::default(); + + log.record(1).record(2).commit(); + + log.cursor(|cursor| { + assert_eq!(*cursor.next().unwrap(), 1); + assert_eq!(*cursor.next().unwrap(), 2); + assert_eq!(cursor.next(), None); + assert_eq!(*cursor.prev().unwrap(), 2); + assert_eq!(*cursor.prev().unwrap(), 1); + assert_eq!(cursor.prev(), None); + }); + + log.record(3).record(4).commit(); + + log.cursor(|cursor| { + assert_eq!(*cursor.next().unwrap(), 1); + assert_eq!(*cursor.next().unwrap(), 2); + assert_eq!(*cursor.next().unwrap(), 3); + assert_eq!(*cursor.next().unwrap(), 4); + assert_eq!(cursor.next(), None); + assert_eq!(*cursor.prev().unwrap(), 4); + assert_eq!(*cursor.prev().unwrap(), 3); + assert_eq!(*cursor.prev().unwrap(), 2); + assert_eq!(*cursor.prev().unwrap(), 1); + assert_eq!(cursor.prev(), None); + }); + } + + + #[test] + fn cursor_jumps() { + let mut log = Log::default(); + + log.record(1).record(2).commit(); + + log.cursor(|cursor| { + assert_eq!(*cursor.seek(0).unwrap(), 1); + assert_eq!(*cursor.jump(1).unwrap(), 2); + assert_eq!(cursor.jump(1), None); + assert_eq!(*cursor.jump(-1).unwrap(), 2); + assert_eq!(*cursor.jump(-1).unwrap(), 1); + assert_eq!(cursor.jump(-1), None); + }); + + log.record(3).record(4).commit(); + + log.cursor(|cursor| { + assert_eq!(*cursor.seek(0).unwrap(), 1); + assert_eq!(*cursor.jump(1).unwrap(), 2); + assert_eq!(*cursor.jump(1).unwrap(), 3); + assert_eq!(*cursor.jump(1).unwrap(), 4); + assert_eq!(cursor.jump(1), None); + assert_eq!(*cursor.jump(-1).unwrap(), 4); + assert_eq!(*cursor.jump(-1).unwrap(), 3); + assert_eq!(*cursor.jump(-1).unwrap(), 2); + assert_eq!(*cursor.jump(-1).unwrap(), 1); + assert_eq!(cursor.jump(-1), None); + }); + } + } +} diff --git a/src/types/log/mod.rs b/src/types/log/mod.rs new file mode 100644 index 0000000..dea84f1 --- /dev/null +++ b/src/types/log/mod.rs @@ -0,0 +1,151 @@ +use std::fmt::Debug; +use transaction::Transaction; +use write_head::WriteHead; +use cursor::Cursor; + +pub mod cursor; +pub mod transaction; +pub mod write_head; + + +#[derive(Clone)] +pub struct Log where T: Clone { + log: Vec, + // TODO: Extract this + current_txn: Transaction, + stack: Vec>, + + write_head: usize +} + +impl Debug for Log where T: Debug + Clone { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Log: {:?}", self.log) + } +} + +impl Default for Log where T: Clone { + fn default() -> Self { + Self::start() + } +} + +impl Log where T: Clone { + pub fn start() -> Self { + Self { + log: vec![], + current_txn: Transaction::begin(), + stack: vec![], + write_head: 0 + } + } + + pub fn seek(&mut self, position: usize) { + if position < self.log.len() { + self.write_head = position; + } else { + // FIXME: I think this should actually not constrain the write head to the length of + // the current buffer, but rather store it as a sparse log or some kind of tree. This + // would allow writing to arbitrary positions in an 'infinite' log, and then jumping + // around to those positions. + panic!("Attempted to seek past the end of the log."); + } + } + + pub fn begin(&mut self) -> &mut Self { + self.stack.push(self.current_txn.clone()); + self.current_txn = Transaction::begin(); + self + } + + pub fn record(&mut self, action: T) -> &mut Self { + self.current_txn.record(action); + self + } + + pub fn commit(&mut self) -> &mut Self { + if self.stack.is_empty() { + // then we aren't nested. + let actions = self.current_txn.commit(); + for action in actions.into_iter() { + self.write(action); + } + + self.current_txn = Transaction::begin(); + } else { + // then we are nested + let actions = self.current_txn.commit(); + + self.current_txn = self.stack.pop().unwrap(); + + for action in actions.into_iter() { + self.record(action); + } + } + self + } + + pub fn commit_all(&mut self) -> &mut Self { + while !self.stack.is_empty() { + self.commit(); + } + self + } + + fn write(&mut self, cache: T) { + self.log.insert(self.write_head, cache); + self.write_head += 1; + } + + pub fn get(&self, position: usize) -> Option<&T> { + self.log.get(position) + } + + pub fn get_mut(&mut self, position: usize) -> Option<&mut T> { + self.log.get_mut(position) + } + + pub fn is_empty(&self) -> bool { + self.log.is_empty() + } + + pub fn len(&self) -> usize { + self.log.len() + } + + /// Cursor offers a readonly view of the current state of the log. The cursor object lives for + /// as long as the provided block. + pub fn cursor(&self, block: impl Fn(&mut Cursor) -> A) -> A { + let mut cursor = Cursor::new(self); + block(&mut cursor) + } + + pub fn write_head(&mut self, block: impl Fn(&mut WriteHead)) { + let mut write_head = WriteHead::new(self); + block(&mut write_head); + } +} + +impl IntoIterator for Log where T: Clone { + type Item = T; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.log.into_iter() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + impl Log where T : Clone { + pub fn log(&self) -> Vec { + self.log.clone() + } + + pub fn txn_state(&self) -> Vec { + self.current_txn.content().clone() + } + } +} diff --git a/src/types/log/transaction.rs b/src/types/log/transaction.rs new file mode 100644 index 0000000..46f0691 --- /dev/null +++ b/src/types/log/transaction.rs @@ -0,0 +1,98 @@ + +// TODO: Change this to have a block-passing API? + +#[derive(Clone)] +pub struct Transaction { + content: Vec, + finished: bool +} + +impl Transaction { + pub fn begin() -> Self { + Self { + content: vec![], + finished: false + } + } + + pub fn is_finished(&self) -> bool { + self.finished + } + + pub fn record(&mut self, action: T) { + self.content.push(action); + } + + pub fn commit(&mut self) -> Vec { + self.finished = true; + self.content.clone() + } + + #[cfg(test)] + pub fn content(&self) -> Vec { + self.content.clone() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + mod txn { + use crate::types::log::Log; + + use super::*; + + #[test] + fn commit_records_changes() { + let mut log = Log::default(); + log.record(1).record(2); + assert_eq!(log.log(), vec![]); + log.commit(); + assert_eq!(log.log(), vec![1, 2]); + } + + #[test] + fn multiple_txns() { + let mut log = Log::default(); + log.record(1).record(2); + log.commit(); + log.record(3).record(4); + log.commit(); + assert_eq!(log.log(), vec![1, 2, 3, 4]); + } + + #[test] + fn nested_txns() { + let mut log = Log::default(); + + assert_eq!(log.log(), vec![]); + + log.record(1).record(2); + log.commit(); + + assert_eq!(log.log(), vec![1, 2]); + + log.record(3); + + assert_eq!(log.txn_state(), vec![3]); + + log.begin() + .record(4); + + assert_eq!(log.txn_state(), vec![4]); + + assert_eq!(log.log(), vec![1, 2]); + + log.commit(); + + assert_eq!(log.txn_state(), vec![3, 4]); + + assert_eq!(log.log(), vec![1, 2]); + + log.commit(); + + assert_eq!(log.log(), vec![1, 2, 3, 4]); + } + } +} diff --git a/src/types/log/write_head.rs b/src/types/log/write_head.rs new file mode 100644 index 0000000..1c9048b --- /dev/null +++ b/src/types/log/write_head.rs @@ -0,0 +1,183 @@ +use super::Log; + +// Cursor should be a read-only version of this, which can then be used as a component of a replay +// engine that can interpret a log of chessactions including choosing between variations, etc. + + +pub struct WriteHead<'a, T> where T: Clone { + log: &'a mut Log, + position: usize +} + +impl<'a, T> WriteHead<'a, T> where T: Clone { + pub fn new(log: &'a mut Log) -> Self { + Self { + log, + position: 0 + } + } + + pub fn seek(&mut self, position: usize) -> Option<&mut T> { + self.position = position; + self.read() + } + + // FIXME: Should this be conditional? + pub fn jump(&mut self, offset: isize) -> Option<&mut T> { + let new_position = self.position as isize + offset; + if new_position < 0 { + self.position = 0; + None + } else { + if new_position as usize >= self.log.len() { + self.position = self.log.len(); + } else { + self.position = new_position as usize; + } + self.read() + } + } + + /// Now listen for a second, I know this looks bad. + /// + /// I'm not implementing `Iterator` because of weird lifetime stuff that happens that I'm too + /// scared to try to fix. + /// + /// It looks bad because it is bad. Don't be like me, be brave. + #[allow(clippy::should_implement_trait)] + pub fn next(&mut self) -> Option<&mut T> { + if self.position == self.log.len() { + None + } else { + self.position += 1; + self.read() + } + } + + pub fn prev(&mut self) -> Option<&mut T> { + if self.position == 0 { + None + } else { + self.position -= 1; + self.read() + } + } + + pub fn read(&mut self) -> Option<&mut T> { + self.log.get_mut(self.position) + } + + // FIXME: I think these are going to be necessary, but I haven't adapted the tests below to + // exercise the writer part of this thing since pulling it from cursor. Cursor and WriteHead + // themselves are probably some common underlying abstract class that differs only in + // mutability, but mutability generics aren't a thing so *shrug* + #[cfg(test)] + #[allow(dead_code)] + fn position(&self) -> usize { + self.position + } + + #[cfg(test)] + #[allow(dead_code)] + fn log(&self) -> &Log { + self.log + } +} + +#[cfg(test)] +mod tests { + mod cursor { + use crate::types::log::Log; + + use super::*; + + + + #[test] + fn write_head_seeks() { + let mut log = Log::default(); + + log.record(1).record(2).commit(); + + log.write_head(|write_head| { + assert_eq!(*write_head.seek(0).unwrap(), 1); + assert_eq!(*write_head.seek(1).unwrap(), 2); + assert_eq!(write_head.seek(2), None); + assert_eq!(write_head.seek(3), None); + }); + + log.record(3).record(4).commit(); + + log.write_head(|write_head| { + assert_eq!(*write_head.seek(0).unwrap(), 1); + assert_eq!(*write_head.seek(1).unwrap(), 2); + assert_eq!(*write_head.seek(3).unwrap(), 4); + assert_eq!(*write_head.seek(2).unwrap(), 3); + assert_eq!(write_head.seek(4), None); + }); + } + + #[test] + fn write_head_prev_and_next() { + let mut log = Log::default(); + + log.record(1).record(2).commit(); + + log.write_head(|write_head| { + assert_eq!(*write_head.read().unwrap(), 1); + assert_eq!(*write_head.next().unwrap(), 2); + assert_eq!(write_head.next(), None); + assert_eq!(*write_head.prev().unwrap(), 2); + assert_eq!(*write_head.prev().unwrap(), 1); + assert_eq!(write_head.prev(), None); + }); + + log.record(3).record(4).commit(); + + log.write_head(|write_head| { + assert_eq!(*write_head.read().unwrap(), 1); + assert_eq!(*write_head.next().unwrap(), 2); + assert_eq!(*write_head.next().unwrap(), 3); + assert_eq!(*write_head.next().unwrap(), 4); + assert_eq!(write_head.next(), None); + assert_eq!(*write_head.prev().unwrap(), 4); + assert_eq!(*write_head.prev().unwrap(), 3); + assert_eq!(*write_head.prev().unwrap(), 2); + assert_eq!(*write_head.prev().unwrap(), 1); + assert_eq!(write_head.prev(), None); + }); + } + + + #[test] + fn write_head_jumps() { + let mut log = Log::default(); + + log.record(1).record(2).commit(); + + log.write_head(|write_head| { + assert_eq!(*write_head.seek(0).unwrap(), 1); + assert_eq!(*write_head.jump(1).unwrap(), 2); + assert_eq!(write_head.jump(1), None); + assert_eq!(*write_head.jump(-1).unwrap(), 2); + assert_eq!(*write_head.jump(-1).unwrap(), 1); + assert_eq!(write_head.jump(-1), None); + }); + + log.record(3).record(4).commit(); + + log.write_head(|cursor| { + assert_eq!(*cursor.seek(0).unwrap(), 1); + assert_eq!(*cursor.jump(1).unwrap(), 2); + assert_eq!(*cursor.jump(1).unwrap(), 3); + assert_eq!(*cursor.jump(1).unwrap(), 4); + assert_eq!(cursor.jump(1), None); + assert_eq!(*cursor.jump(-1).unwrap(), 4); + assert_eq!(*cursor.jump(-1).unwrap(), 3); + assert_eq!(*cursor.jump(-1).unwrap(), 2); + assert_eq!(*cursor.jump(-1).unwrap(), 1); + assert_eq!(cursor.jump(-1), None); + }); + } + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs index e49bba4..214e7c6 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -13,6 +13,10 @@ pub mod color; // Used for shifting bitboards around mostly pub mod direction; +// A freely moving cursor-based log object for recording and replaying actions. +pub mod log; + + pub use bitboard::Bitboard; pub use pextboard::PEXTBoard; pub use piece::Piece; diff --git a/src/ui/app.rs b/src/ui/app.rs index 219ce35..660a1ad 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -5,11 +5,10 @@ use ratatui::crossterm::event::{Event, KeyCode}; use ratatui::prelude::*; use ratatui::widgets::{Block, Borders}; -use tracing::{debug, instrument}; +use tracing::instrument; -use crate::engine::uci::UCIMessage; -use crate::ui::model::entry::{Entry, stockfish}; -use crate::engine::Engine; +use crate::board::PieceBoard; +use crate::notation::fen::FEN; use super::widgets::tile::Tile; @@ -20,7 +19,6 @@ enum Mode { pub struct Hazel { flags: HashMap, - entry: Entry, // UI mode: Mode, tile: Tile, @@ -37,24 +35,19 @@ impl Debug for Hazel { impl Hazel { #[instrument] pub fn new() -> Self { - let mut s = Self { + let s = Self { flags: HashMap::new(), mode: Mode::Command, - entry: stockfish(), tile: Tile::new(), }; + /* let startup_commands = vec![ UCIMessage::UCI, UCIMessage::IsReady, UCIMessage::Position("startpos".to_string(), vec!["d2d4".to_string()]), ]; - - - for command in startup_commands { - debug!("{}", &command); - s.entry.exec(&command); - } + */ return s; } @@ -120,14 +113,16 @@ impl Hazel { } - #[instrument] + #[instrument(skip(self, frame))] pub fn render(&mut self, frame: &mut Frame) { - self.tile.set_state(self.entry.boardstate); + let p = PieceBoard::from(FEN::start_position()); + self.tile.set_state(p); frame.render_widget(&self.tile, Rect::new(0,0,64,32)); } } #[cfg(test)] +#[cfg_attr(test, mutants::skip)] mod tests { use std::process::Termination; @@ -135,9 +130,6 @@ mod tests { use super::*; - use tracing_test::traced_test; - - #[traced_test] #[test] fn renders_as_expected() { let mut hazel = Hazel::new(); @@ -156,18 +148,18 @@ mod tests { "│ Placeholder ││ Placeholder │a6 b6 c6 d6 e6 f6 g6 h6 ", "│ Placeholder ││ Placeholder │ ", "│ Placeholder ││ Placeholder │a5 b5 c5 d5 e5 f5 g5 h5 ", - "│ Placeholder ││ Placeholder │ P ", + "│ Placeholder ││ Placeholder │ ", "│ Placeholder ││ Placeholder │a4 b4 c4 d4 e4 f4 g4 h4 ", "│ Placeholder ││ Placeholder │ ", "│ Placeholder ││ Placeholder │a3 b3 c3 d3 e3 f3 g3 h3 ", - "│ Placeholder ││ Placeholder │ P P P P P P P ", + "│ Placeholder ││ Placeholder │ P P P P P P P P ", "│ Placeholder ││ Placeholder │a2 b2 c2 d2 e2 f2 g2 h2 ", "│ Placeholder ││ Placeholder │ R N B Q K B N R ", "│ Placeholder ││ Placeholder │a1 b1 c1 d1 e1 f1 g1 h1 ", "│ Placeholder ││ Placeholder │ Placeholder ", "│ Placeholder ││ Placeholder │ Placeholder ", "└──────────────────┘└──────────────────┘ Placeholder ", - "│ rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR w KQkq - 0 1 │", + "│ rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 │", "┌──────────────────────────────────────────────────────────────┐", "│ │", "│ │", @@ -184,15 +176,15 @@ mod tests { let actual = t.backend().buffer().clone(); - // NOTE: This is going to be turned off most of the time, except when I need a snapshot of the UI - // to cheat the test. - // assert_eq!(actual, expected); + //assert_eq!(actual, expected); // Ignore style differences for now, by... turning everything into a big list of chars // wrapped in &strs wrapped in my pain and suffering. let expected_content : Vec = expected.content().iter().map(|x| x.symbol().to_string()).collect(); let actual_content : Vec = actual.content().iter().map(|x| x.symbol().to_string()).collect(); - assert_eq!(actual_content.join(""), expected_content.join("")); + for (i, (expected_line, actual_line)) in expected_content.iter().zip(actual_content.iter()).enumerate() { + assert_eq!(expected_line, actual_line, "Line {} does not match", i); + } } } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index d784887..99ce6dc 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -17,6 +17,7 @@ mod widgets; use app::Hazel; /// Boilerplate to get the app started. +#[cfg_attr(test, mutants::skip)] pub fn run() -> Result<(), Box> { enable_raw_mode()?; // Reroute to stderr since we want to talk on stdout for UCI potentially @@ -36,6 +37,7 @@ pub fn run() -> Result<(), Box> { Ok(()) } +#[cfg_attr(test, mutants::skip)] fn run_app(terminal: &mut Terminal, app: &mut Hazel) -> io::Result { loop { if app.check_flag("exit") { return Ok(true); } diff --git a/src/ui/model/entry.rs b/src/ui/model/entry.rs deleted file mode 100644 index 78a9407..0000000 --- a/src/ui/model/entry.rs +++ /dev/null @@ -1,92 +0,0 @@ -#![allow(dead_code)] - -use crate::board::simple::PieceBoard; -use crate::engine::Engine; -use crate::engine::driver::Driver; -use crate::engine::driver::stockfish::Stockfish; -use crate::engine::uci::UCIMessage; - - -use std::fmt::{Debug, Formatter}; - -use std::collections::HashMap; - - -pub struct Entry { - pub config: Configuration, // for the current configuration options for the engine. - pub boardstate: PieceBoard, - pub engine: Box>, -} - -impl Debug for Entry { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Entry") - .field("config", &self.config) - .field("boardstate", &self.boardstate) - .finish() - } -} - -type Configuration = HashMap; - -impl Entry { - fn new(engine: Box>) -> Self { - Self { - config: HashMap::new(), - boardstate: PieceBoard::default(), - engine - } - } -} - -pub fn stockfish() -> Entry { - Entry::new(Box::new(Stockfish::new())) -} - -pub fn hazel() -> Entry { - Entry::new(Box::new(Driver::new())) -} - -/* -* TODO: I don't want to debug this right now, but I think there is some kind of combinator logic to -* be had here. Proxying groups of engines seems like a good idea. I may want to have it return a -* richer structure, though, as right now it would not distinguish between which engine except by -* order. -pub struct EntryPair { - pub left: Entry, - pub right: Entry, -} - -impl EntryPair { - pub fn new(left: Entry, right: Entry) -> Self { - Self { left, right } - } -} - -impl Engine for EntryPair { - fn exec(&mut self, message: UCIMessage) -> Vec { - let left = self.left.exec(message.clone()); - let right = self.right.exec(message); - left.into_iter().chain(right.into_iter()).collect() - } - - fn exec_message(&mut self, message: &str) -> Vec { - let left = self.left.exec_message(message); - let right = self.right.exec_message(message); - left.into_iter().chain(right.into_iter()).collect() - } -} -*/ - -impl Engine for Entry { - fn exec(&mut self, message: &UCIMessage) -> Vec { - // update the boardstate - self.boardstate.exec(message); - self.engine.exec(message) - } - - fn exec_message(&mut self, message: &str) -> Vec { - self.boardstate.exec_message(message); - self.engine.exec_message(message) - } -} diff --git a/src/ui/model/grid.rs b/src/ui/model/grid.rs deleted file mode 100644 index 37d2358..0000000 --- a/src/ui/model/grid.rs +++ /dev/null @@ -1,15 +0,0 @@ -#![allow(dead_code)] -use crate::ui::model::entry::Entry; - -#[derive(Debug, Default)] -pub struct Grid { - entries: Vec -} - -impl Grid { - pub fn new() -> Self { - Self { - entries: vec![] - } - } -} diff --git a/src/ui/model/mod.rs b/src/ui/model/mod.rs index a9e7930..e69de29 100644 --- a/src/ui/model/mod.rs +++ b/src/ui/model/mod.rs @@ -1,3 +0,0 @@ -pub mod entry; -pub mod grid; -pub mod race_control; diff --git a/src/ui/model/race_control.rs b/src/ui/model/race_control.rs deleted file mode 100644 index 3464924..0000000 --- a/src/ui/model/race_control.rs +++ /dev/null @@ -1,16 +0,0 @@ -#![allow(dead_code)] - -use crate::ui::model::grid::Grid; - -#[derive(Debug, Default)] -pub struct RaceControl { - grid: Grid, -} - -impl RaceControl { - pub fn new() -> Self { - Self { - grid: Grid::new(), - } - } -} diff --git a/src/ui/widgets/fen.rs b/src/ui/widgets/fen.rs index 8d04c59..c50269e 100644 --- a/src/ui/widgets/fen.rs +++ b/src/ui/widgets/fen.rs @@ -7,7 +7,6 @@ use tracing::{debug, instrument}; use ratatui::buffer::Buffer; -use crate::ui::model::entry::Entry; use crate::board::simple::PieceBoard; use crate::board::interface::query; @@ -22,18 +21,6 @@ pub struct FEN { alignment: Alignment } -impl From<&Entry> for FEN { - fn from(entry: &Entry) -> Self { - Self::new(query::to_fen(&entry.boardstate)) - } -} - -impl From for FEN { - fn from(entry: Entry) -> Self { - Self::new(query::to_fen(&entry.boardstate)) - } -} - impl From<&PieceBoard> for FEN { fn from(board: &PieceBoard) -> Self { Self::new(query::to_fen(board)) diff --git a/src/ui/widgets/tile/mod.rs b/src/ui/widgets/tile/mod.rs index 86e10a1..90b8340 100644 --- a/src/ui/widgets/tile/mod.rs +++ b/src/ui/widgets/tile/mod.rs @@ -28,7 +28,6 @@ const HEIGHT : u16 = 32; #[derive(Default)] pub struct Tile { /* - game_section: GameSection, query_line: Query, */ engine_io_section: EngineIOSection, @@ -39,9 +38,6 @@ pub struct Tile { impl Tile { pub fn new() -> Self { Self { - /* - game_section: GameSection::new(), - */ engine_io_section: EngineIOSection::default(), state: PieceBoard::default(), } @@ -80,14 +76,12 @@ impl Widget for &Tile { let game_section = GameSectionLayout::new(self.state); game_section.render(chunks[0], buf); - //Placeholder::of_size(chunks[0].width, chunks[0].height).render(chunks[0], buf); Placeholder::of_size(chunks[1].width, chunks[1].height).borders(Borders::LEFT | Borders::RIGHT).render(chunks[1], buf); self.engine_io_section.render(chunks[2], buf); - // self.game_section.render(chunks[0], buf, state); self.query_line().render(chunks[1], buf); - // self.engine_io_section.render(chunks[2], buf, state); + self.engine_io_section.render(chunks[2], buf); } } diff --git a/src/util/mask.rs b/src/util/mask.rs index 3d52ae4..99e4225 100644 --- a/src/util/mask.rs +++ b/src/util/mask.rs @@ -100,7 +100,6 @@ mod tests { use super::*; mod mask { - use tracing_test::traced_test; use super::*; diff --git a/tests/fixtures/no-variations.pgn b/tests/fixtures/no-variations-and-halts.pgn similarity index 93% rename from tests/fixtures/no-variations.pgn rename to tests/fixtures/no-variations-and-halts.pgn index 32476fc..3582f6d 100644 --- a/tests/fixtures/no-variations.pgn +++ b/tests/fixtures/no-variations-and-halts.pgn @@ -1,4 +1,4 @@ -[Event "Example Chess"] +[Event "No Variations, Includes Halt"] [White "white"] [Black "black"] [Result "0-1"] diff --git a/tests/fixtures/no-variations-and-no-halt.pgn b/tests/fixtures/no-variations-and-no-halt.pgn new file mode 100644 index 0000000..e5b40fc --- /dev/null +++ b/tests/fixtures/no-variations-and-no-halt.pgn @@ -0,0 +1,12 @@ +[Event "No Variations, No Halt"] +[White "white"] +[Black "black"] +[Result "0-1"] +[CurrentPosition "3r2k1/5rp1/p3Q2p/1p2Bp2/8/PP1q4/4RPbP/4K3 w - -"] +[TimeControl "900+10"] + +1. e4 c6 2. d4 d5 3. exd5 cxd5 4. c4 Nf6 5. Nc3 Nc6 6. Nf3 e6 7. Be2 Bd6 8. O-O +dxc4 9. Bxc4 O-O 10. b3 a6 11. Bd3 h6 12. Bc2 b5 13. Ne4 Nxe4 14. Bxe4 Bb7 15. +Bc2 Nb4 16. Bb1 Qd7 17. a3 Nd5 18. Ne5 Qc7 19. Qd3 f5 20. Qg3 Qc3 21. Bd3 Qxd4 +22. Re1 Qxa1 23. Kf1 Bxe5 24. Qg6 Nf4 25. Bxf4 Qd4 26. Qxe6+ Rf7 27. Bxe5 Qxd3+ +28. Re2 Bxg2+ 29. Ke1 Rd8 diff --git a/tests/fixtures/with-nested-variations-halts.pgn b/tests/fixtures/with-nested-variations-halts.pgn new file mode 100644 index 0000000..491b04f --- /dev/null +++ b/tests/fixtures/with-nested-variations-halts.pgn @@ -0,0 +1,8 @@ +[Event "Nested Variations"] +[White "white"] +[Black "black"] +[Result "0-1"] +[TimeControl "900+10"] + +1. d4 d5 2. Bf4 c6 3. Nf3 Nf6 4. e3 Nh5 (4... Bf5 5. c4 a5 (5... e6 6. Qb3 b6 7. Nc3) 6. Nc3 e6) 5. Be5 f6 6. Bxb8 (6. +Bg3 Nxg3 7. hxg3) 6... Rxb8 7. c4 g6 0-1 diff --git a/tests/fixtures/with-nested-variations-no-halt.pgn b/tests/fixtures/with-nested-variations-no-halt.pgn new file mode 100644 index 0000000..a935777 --- /dev/null +++ b/tests/fixtures/with-nested-variations-no-halt.pgn @@ -0,0 +1,7 @@ +[Event "Nested Variations"] +[White "white"] +[Black "black"] +[Result "0-1"] +[TimeControl "900+10"] + +1. d4 d5 2. Bf4 c6 3. Nf3 Nf6 4. e3 Nh5 (4... Bf5 5. c4 a5 (5... e6 6. Qb3 b6 7. Nc3) 6. Nc3 e6) 5. Be5 f6 6. Bxb8 (6. Bg3 Nxg3 7. hxg3) 6... Rxb8 7. c4 g6 diff --git a/tests/fixtures/with-variations-halts.pgn b/tests/fixtures/with-variations-halts.pgn new file mode 100644 index 0000000..8d51ce9 --- /dev/null +++ b/tests/fixtures/with-variations-halts.pgn @@ -0,0 +1,8 @@ +[Event "With Variations"] +[White "white"] +[Black "black"] +[Result "0-1"] +[TimeControl "900+10"] + +1. d4 d5 2. Bf4 c6 3. Nf3 Nf6 4. e3 Nh5 (4... Bf5 5. c4 a5 6. Nc3 e6) 5. Be5 f6 6. Bxb8 (6. Bg3 Nxg3 7. hxg3) 6... Rxb8 7. c4 g6 0-1 + diff --git a/tests/fixtures/with-variations-no-halt.pgn b/tests/fixtures/with-variations-no-halt.pgn new file mode 100644 index 0000000..fb158a7 --- /dev/null +++ b/tests/fixtures/with-variations-no-halt.pgn @@ -0,0 +1,8 @@ +[Event "With Variations"] +[White "white"] +[Black "black"] +[Result "0-1"] +[TimeControl "900+10"] + +1. d4 d5 2. Bf4 c6 3. Nf3 Nf6 4. e3 Nh5 (4... Bf5 5. c4 a5 6. Nc3 e6) 5. Be5 f6 6. Bxb8 (6. Bg3 Nxg3 7. hxg3) 6... Rxb8 7. c4 g6 +