diff --git a/Cargo.lock b/Cargo.lock index 76a1a66..09f8dcc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,77 +17,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anstream" -version = "0.6.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" - -[[package]] -name = "anstyle-parse" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.59.0", -] - [[package]] name = "arrayvec" version = "0.5.2" @@ -159,7 +88,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix 1.0.7", + "rustix", "slab", "tracing", "windows-sys 0.59.0", @@ -214,12 +143,6 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - [[package]] name = "backtrace" version = "0.3.75" @@ -260,30 +183,6 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" -[[package]] -name = "cassowary" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" - -[[package]] -name = "castaway" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" -dependencies = [ - "rustversion", -] - -[[package]] -name = "cc" -version = "1.2.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" -dependencies = [ - "shlex", -] - [[package]] name = "cfg-if" version = "1.0.1" @@ -302,103 +201,12 @@ dependencies = [ "rand", ] -[[package]] -name = "chess-tui" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9862cc00a6baeae4869d124c8ae0733833cb90305a1c7d7d16b8eab39881ff49" -dependencies = [ - "chrono", - "clap", - "dirs", - "log", - "ratatui", - "simplelog", - "toml", - "uci", -] - [[package]] name = "chess_ai_rust" version = "0.1.0" dependencies = [ "async-std", "chess", - "chess-tui", -] - -[[package]] -name = "chrono" -version = "0.4.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "wasm-bindgen", - "windows-link", -] - -[[package]] -name = "clap" -version = "4.5.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "clap_lex" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" - -[[package]] -name = "colorchoice" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" - -[[package]] -name = "compact_str" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" -dependencies = [ - "castaway", - "cfg-if", - "itoa", - "rustversion", - "ryu", - "static_assertions", ] [[package]] @@ -410,120 +218,12 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" -[[package]] -name = "crossterm" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" -dependencies = [ - "bitflags", - "crossterm_winapi", - "mio", - "parking_lot", - "rustix 0.38.44", - "signal-hook", - "signal-hook-mio", - "winapi", -] - -[[package]] -name = "crossterm_winapi" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" -dependencies = [ - "winapi", -] - -[[package]] -name = "darling" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.104", -] - -[[package]] -name = "darling_macro" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "deranged" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" -dependencies = [ - "powerfmt", -] - -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.48.0", -] - -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - [[package]] name = "errno" version = "0.3.13" @@ -589,18 +289,6 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "futures-channel" version = "0.3.31" @@ -635,17 +323,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "getrandom" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - [[package]] name = "gimli" version = "0.31.1" @@ -664,99 +341,12 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "hashbrown" -version = "0.15.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - [[package]] name = "hermit-abi" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" -[[package]] -name = "iana-time-zone" -version = "0.1.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "indoc" -version = "2.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" - -[[package]] -name = "instability" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf9fed6d91cfb734e7476a06bde8300a1b94e217e1b523b6f0cd1a01998c71d" -dependencies = [ - "darling", - "indoc", - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" - -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - [[package]] name = "js-sys" version = "0.3.77" @@ -782,38 +372,12 @@ version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" -[[package]] -name = "libredox" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" -dependencies = [ - "bitflags", - "libc", -] - -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - [[package]] name = "linux-raw-sys" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" -[[package]] -name = "lock_api" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" -dependencies = [ - "autocfg", - "scopeguard", -] - [[package]] name = "log" version = "0.4.27" @@ -823,15 +387,6 @@ dependencies = [ "value-bag", ] -[[package]] -name = "lru" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" -dependencies = [ - "hashbrown", -] - [[package]] name = "memchr" version = "2.7.5" @@ -847,48 +402,12 @@ dependencies = [ "adler2", ] -[[package]] -name = "mio" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" -dependencies = [ - "libc", - "log", - "wasi", - "windows-sys 0.59.0", -] - [[package]] name = "nodrop" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_threads" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" -dependencies = [ - "libc", -] - [[package]] name = "object" version = "0.36.7" @@ -904,52 +423,11 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" -[[package]] -name = "once_cell_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - [[package]] name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parking_lot" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.52.6", -] - -[[package]] -name = "paste" -version = "1.0.15" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "pin-project-lite" @@ -984,17 +462,11 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix 1.0.7", + "rustix", "tracing", "windows-sys 0.59.0", ] -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - [[package]] name = "ppv-lite86" version = "0.2.21" @@ -1068,66 +540,12 @@ dependencies = [ "rand_core", ] -[[package]] -name = "ratatui" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdef7f9be5c0122f890d58bdf4d964349ba6a6161f705907526d891efabba57d" -dependencies = [ - "bitflags", - "cassowary", - "compact_str", - "crossterm", - "instability", - "itertools", - "lru", - "paste", - "strum", - "strum_macros", - "unicode-segmentation", - "unicode-truncate", - "unicode-width", -] - -[[package]] -name = "redox_syscall" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" -dependencies = [ - "bitflags", -] - -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom", - "libredox", - "thiserror", -] - [[package]] name = "rustc-demangle" version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - [[package]] name = "rustix" version = "1.0.7" @@ -1137,7 +555,7 @@ dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys 0.9.4", + "linux-raw-sys", "windows-sys 0.59.0", ] @@ -1147,131 +565,12 @@ version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "serde" -version = "1.0.219" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.219" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[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.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-mio" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" -dependencies = [ - "libc", - "mio", - "signal-hook", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" -dependencies = [ - "libc", -] - -[[package]] -name = "simplelog" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16257adbfaef1ee58b1363bdc0664c9b8e1e30aed86049635fb5f147d065a9c0" -dependencies = [ - "log", - "termcolor", - "time", -] - [[package]] name = "slab" version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.104", -] - [[package]] name = "syn" version = "1.0.109" @@ -1306,77 +605,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "time" -version = "0.3.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" -dependencies = [ - "deranged", - "itoa", - "libc", - "num-conv", - "num_threads", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" - -[[package]] -name = "time-macros" -version = "0.2.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde", -] - [[package]] name = "tracing" version = "0.1.41" @@ -1393,68 +621,24 @@ version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" -[[package]] -name = "uci" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2601884034b66d65ed33b7d04eb554342be16f7f8730b9ce5c6717156fe20290" -dependencies = [ - "log", -] - [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "unicode-truncate" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" -dependencies = [ - "itertools", - "unicode-segmentation", - "unicode-width", -] - -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - [[package]] name = "unicode-xid" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - [[package]] name = "value-bag" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -1536,105 +720,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-implement" -version = "0.60.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "windows-interface" -version = "0.59.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.104", -] - -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.59.0" @@ -1653,21 +738,6 @@ dependencies = [ "windows-targets 0.53.2", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -1700,12 +770,6 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -1718,12 +782,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -1736,12 +794,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -1766,12 +818,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -1784,12 +830,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -1802,12 +842,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -1820,12 +854,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index ea0dea9..7c5449b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,4 +8,3 @@ edition = "2021" [dependencies] async-std = "1.12.0" chess = "3.2" -chess-tui = "1.6.1" diff --git a/README.md b/README.md index 87dcba7..6bcc415 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,118 @@ -# Chess-AI-Rust +# Chess AI Rust -My [Chess-AI](https://github.com/mehulrao/Chess-AI/tree/main) Rewritten in Rust +A chess AI engine implemented in Rust with UCI (Universal Chess Interface) support. + +## Features + +- **Minimax Search Algorithm** with alpha-beta pruning +- **Iterative Deepening** for time-controlled searches +- **Transposition Tables** for move caching and optimization +- **Move Ordering** for better search efficiency +- **UCI Protocol Support** - Compatible with any UCI chess interface +- **Interactive Mode** - Play directly in the terminal + +## UCI Mode + +The engine supports the Universal Chess Interface (UCI) protocol, making it compatible with popular chess GUIs and analysis tools. + +### Usage + +To run the engine in UCI mode: + +```bash +cargo run --release -- uci +``` + +### Compatible Software + +The engine works with any UCI-compatible chess software, including: + +- **chess-tui** - Terminal-based chess interface +- **Arena** - Chess GUI +- **ChessBase** - Professional chess software +- **Lichess** - Online chess platform (analysis) +- **cutechess-cli** - Command-line tournament manager + +### Using with chess-tui + +1. Build the engine: + ```bash + cargo build --release + ``` + +2. Run chess-tui with your engine: + ```bash + chess-tui -e ./target/release/chess_ai_rust + ``` + +### UCI Commands Supported + +- `uci` - Initialize UCI mode +- `isready` - Check if engine is ready +- `ucinewgame` - Start a new game +- `position [fen | startpos] moves ...` - Set position +- `go [depth ] [movetime ] [wtime ] [btime ] [winc ] [binc ]` - Start searching +- `stop` - Stop current search +- `setoption name value ` - Set engine options +- `quit` - Exit the engine + +### Engine Options + +- **Hash** - Transposition table size in MB (default: 64, range: 1-1024) +- **MaxDepth** - Maximum search depth (default: 10, range: 1-100) +- **UseSecondSearch** - Enable quiescence search (default: true) + +## Interactive Mode + +To play directly in the terminal: + +```bash +cargo run --release +``` + +This mode provides a simple text-based interface for playing against the AI. + +## Building + +```bash +cargo build --release +``` + +## Testing + +To test the UCI implementation: + +```bash +# Start the engine in UCI mode +cargo run --release -- uci + +# Send UCI commands manually: +# uci +# isready +# position startpos moves e2e4 +# go depth 5 +# quit +``` + +## Performance + +The engine uses several optimization techniques: + +- **Alpha-beta pruning** - Reduces search tree size +- **Iterative deepening** - Provides anytime results +- **Transposition tables** - Avoids re-computing positions +- **Move ordering** - Improves pruning efficiency +- **Quiescence search** - Handles tactical positions + +## Architecture + +- `src/main.rs` - Mode selection and interactive interface +- `src/uci/` - UCI protocol implementation +- `src/searcher.rs` - Search algorithm implementation +- `src/evaluation.rs` - Position evaluation +- `src/move_ordering.rs` - Move ordering heuristics +- `src/entry.rs` - Transposition table entries + +## License + +This project is licensed under the MIT License. diff --git a/UCI_EXAMPLE.md b/UCI_EXAMPLE.md new file mode 100644 index 0000000..f0c12e7 --- /dev/null +++ b/UCI_EXAMPLE.md @@ -0,0 +1,76 @@ +# UCI Implementation Example + +This chess engine now supports the Universal Chess Interface (UCI) protocol. + +## Quick Start + +1. **Build the engine:** + ```bash + cargo build --release + ``` + +2. **Run in UCI mode:** + ```bash + cargo run --release -- uci + ``` + +3. **Use with chess-tui:** + ```bash + chess-tui -e ./target/release/chess_ai_rust + ``` + +## Manual UCI Testing + +You can test the UCI implementation manually: + +```bash +# Start the engine +cargo run --release -- uci + +# The engine will respond with: +# id name Chess AI Rust +# id author Chess AI Rust Developer +# option name Hash type spin default 64 min 1 max 1024 +# option name MaxDepth type spin default 10 min 1 max 100 +# option name UseSecondSearch type check default true +# uciok + +# Now you can send commands: +isready +# readyok + +position startpos moves e2e4 +go depth 5 +# info depth 1 score cp 23 nodes 45 time 1 pv d7d6 +# info depth 2 score cp 18 nodes 127 time 3 pv d7d6 +# ... +# bestmove d7d6 + +quit +``` + +## UCI Commands Supported + +- `uci` - Initialize UCI mode +- `isready` - Check if engine is ready +- `ucinewgame` - Start a new game +- `position [fen | startpos] moves ` - Set position +- `go [depth ] [movetime ] [time controls...]` - Start searching +- `stop` - Stop current search +- `setoption name value ` - Set engine options +- `quit` - Exit + +## Engine Options + +- **Hash**: Transposition table size in MB (1-1024, default: 64) +- **MaxDepth**: Maximum search depth (1-100, default: 10) +- **UseSecondSearch**: Enable quiescence search (true/false, default: true) + +## Interactive Mode + +The original interactive mode is still available: + +```bash +cargo run --release +# (runs without UCI, direct terminal play) +``` diff --git a/src/main.rs b/src/main.rs index 7bc6e5e..fb06621 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,85 +1,10 @@ -use chess::{CacheTable, ChessMove, Color, Game}; -mod searcher; -use crate::{entry::Entry, searcher::Searcher}; mod entry; mod evaluation; mod move_ordering; - -const TARGET_DEPTH: usize = 5; -const PLAYER: Color = Color::White; -const TT_SIZE: usize = 67108864; -const TIME: u64 = 5000; +mod searcher; +mod uci; fn main() { - let mut game = Game::new(); - //let mut game = Game::from_str("r3kb1r/pqp1n1p1/2p1b2p/4Bp2/Q3p3/P1N4N/2P2PPP/3R1RK1 w kq - 0 1").unwrap(); - if !game.result().is_none() { - println!("Mate!"); - return; - } - - let mut tt: CacheTable = CacheTable::new(TT_SIZE, Entry::new_default()); - println!("--------------------------------"); - println!("{}", game.current_position()); - while game.result().is_none() { - if PLAYER == Color::Black { - do_search(&mut game, &mut tt); - user_move(&mut game); - } else { - loop { - user_move(&mut game); - do_search(&mut game, &mut tt); - } - } - } -} - -fn user_move(game: &mut Game) { - loop { - let mut move_text = String::new(); - println!("Enter your move: "); - std::io::stdin().read_line(&mut move_text).unwrap(); - if move_text.trim_end() == "O-O" { - if PLAYER == Color::White { - move_text = String::from("e1g1"); - } else { - move_text = String::from("e8g8"); - } - } else if move_text == "O-O-O" { - if PLAYER == Color::White { - move_text = String::from("e1c1"); - } else { - move_text = String::from("e8c8"); - } - } - let _move = - match ChessMove::from_san(&game.current_position(), &move_text.trim().to_string()) { - Ok(m) => { - game.make_move(m); - break; - } - Err(_) => { - println!("Invalid Move: {}", move_text); - continue; - } - }; - } - println!("--------------------------------"); - println!("{}", game.current_position()); -} - -fn do_search(game: &mut Game, tt: &mut CacheTable) { - let mut searcher: Searcher = Searcher::new(game.current_position(), true); - searcher.do_iterative_deepening_search(TARGET_DEPTH, tt); - - println!( - "Best Move: {}{}", - searcher.get_best_move().unwrap().get_source(), - searcher.get_best_move().unwrap().get_dest() - ); - println!("Best Eval: {}", searcher.get_best_eval()); - println!("TT Hits: {}", searcher.get_num_tt()); - game.make_move(searcher.get_best_move().unwrap()); - println!("--------------------------------"); - println!("{}", game.current_position()); + // Always run UCI mode + uci::run_uci_loop(); } diff --git a/src/searcher.rs b/src/searcher.rs index b9754a9..41d8f09 100644 --- a/src/searcher.rs +++ b/src/searcher.rs @@ -2,20 +2,19 @@ use crate::{entry::Entry, evaluation::evaluate, move_ordering::order_moves}; use chess::BitBoard; use chess::Board; use chess::BoardStatus; -use chess::ChessMove; use chess::CacheTable; -use chess::MoveGen; +use chess::ChessMove; use chess::Color; +use chess::MoveGen; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; const IMMEDIATE_MATE_SCORE: i32 = 100000; const POS_INF: i32 = 9999999; const NEG_INF: i32 = -POS_INF; const MAX_MATE_DEPTH: i32 = 1000; -#[derive(Clone)] -#[derive(Copy)] -#[derive(PartialEq)] -#[derive(PartialOrd)] +#[derive(Clone, Copy, PartialEq, PartialOrd)] pub enum EvalType { Exact, LowerBound, @@ -23,9 +22,6 @@ pub enum EvalType { } const INVALID_MOVE: Option = None; -#[derive(Clone)] -#[derive(Copy)] -#[derive(PartialEq)] pub struct Searcher { use_second_search: bool, board: Board, @@ -39,10 +35,10 @@ pub struct Searcher { best_eval: i32, best_move_this_iter: Option, best_eval_this_iter: i32, + external_stop_flag: Option>, } impl Searcher { - pub fn new(board: Board, use_second_search: bool) -> Searcher { Searcher { use_second_search, @@ -57,19 +53,59 @@ impl Searcher { best_eval: 0, best_move_this_iter: None, best_eval_this_iter: 0, + external_stop_flag: None, } } - pub fn search_moves(&mut self, depth: u8, ply_from_root: i32, mut alpha: i32, mut beta: i32, tt: &mut CacheTable) -> i32 { - if self.abort_search {return 0} + pub fn new_with_stop_flag( + board: Board, + use_second_search: bool, + stop_flag: Arc, + ) -> Searcher { + Searcher { + use_second_search, + board, + best_move: None, + num_nodes: 0, + num_pos: 0, + num_safe_pos: 0, + num_prunes: 0, + num_tt: 0, + abort_search: false, + best_eval: 0, + best_move_this_iter: None, + best_eval_this_iter: 0, + external_stop_flag: Some(stop_flag), + } + } + + pub fn search_moves( + &mut self, + depth: u8, + ply_from_root: i32, + mut alpha: i32, + mut beta: i32, + tt: &mut CacheTable, + ) -> i32 { + if self.abort_search { + return 0; + } + + // Check external stop flag if available + if let Some(ref stop_flag) = self.external_stop_flag { + if !stop_flag.load(Ordering::Relaxed) { + self.abort_search = true; + return 0; + } + } if ply_from_root > 0 { if self.board.status() == chess::BoardStatus::Stalemate { - return 0 + return 0; } alpha = alpha.max(-IMMEDIATE_MATE_SCORE + ply_from_root); beta = beta.min(IMMEDIATE_MATE_SCORE - ply_from_root); if alpha >= beta { - return alpha + return alpha; } } let board_hash = self.board.get_hash(); @@ -80,14 +116,14 @@ impl Searcher { self.best_move_this_iter = get_stored_move(tt, board_hash); self.best_eval_this_iter = tt_eval.unwrap(); } - return tt_eval.unwrap() + return tt_eval.unwrap(); } if depth == 0 { return if self.use_second_search { self.search_captures(alpha, beta, tt) } else { evaluate(&self.board) - } + }; } let move_list = MoveGen::new_legal(&self.board); let sorted_move_list = order_moves(&self.board, tt, move_list, true); @@ -108,9 +144,19 @@ impl Searcher { let evaluation = -self.search_moves(depth - 1, ply_from_root + 1, -beta, -alpha, tt); self.board = board_backup; self.num_nodes += 1; - if self.abort_search {return 0} + if self.abort_search { + return 0; + } if evaluation >= beta { - self.store_eval(self.board.get_hash(), depth, ply_from_root, evaluation, EvalType::LowerBound, this_move, tt); + self.store_eval( + self.board.get_hash(), + depth, + ply_from_root, + evaluation, + EvalType::LowerBound, + this_move, + tt, + ); return beta; } if evaluation > alpha { @@ -123,11 +169,23 @@ impl Searcher { } } } - self.store_eval(self.board.get_hash(), depth, ply_from_root, alpha, eval_type, best_move_this_pos.unwrap_or_default(), tt); - return alpha + self.store_eval( + self.board.get_hash(), + depth, + ply_from_root, + alpha, + eval_type, + best_move_this_pos.unwrap_or_default(), + tt, + ); + return alpha; } - pub fn do_iterative_deepening_search(&mut self, mut target_depth: usize, tt: &mut CacheTable) { + pub fn do_iterative_deepening_search( + &mut self, + mut target_depth: usize, + tt: &mut CacheTable, + ) { self.num_nodes = 0; self.num_pos = 0; self.num_safe_pos = 0; @@ -147,7 +205,7 @@ impl Searcher { self.best_eval_this_iter = NEG_INF; self.search_moves(depth as u8, 0, NEG_INF, POS_INF, tt); current_iter_search_depth = depth; - println!("Current Depth: {} Num Posititon: {}", current_iter_search_depth, self.num_nodes); + // Debug output removed for UCI compliance - use eprintln! for debugging if needed if !self.best_move_this_iter.is_none() { self.best_move = self.best_move_this_iter; self.best_eval = self.best_eval_this_iter; @@ -181,20 +239,28 @@ impl Searcher { self.best_eval } - fn lookup_evaluation(&self, hash: u64, depth: u8, ply_from_root: i32, alpha: i32, beta: i32, tt: &CacheTable) -> Option { + fn lookup_evaluation( + &self, + hash: u64, + depth: u8, + ply_from_root: i32, + alpha: i32, + beta: i32, + tt: &CacheTable, + ) -> Option { let tt_eval = tt.get(hash); if tt_eval.is_some() { let tt_ = tt_eval.unwrap(); if tt_.depth >= depth { let corrected_score = self.correct_retrieved_mate_score(tt_.value, ply_from_root); if tt_.node_type == EvalType::Exact { - return Some(corrected_score) + return Some(corrected_score); } if tt_.node_type == EvalType::UpperBound && corrected_score <= alpha { - return Some(corrected_score) + return Some(corrected_score); } if tt_.node_type == EvalType::LowerBound && corrected_score >= beta { - return Some(corrected_score) + return Some(corrected_score); } } } @@ -204,7 +270,7 @@ impl Searcher { fn correct_retrieved_mate_score(&self, score: i32, num_ply_searched: i32) -> i32 { if self.is_mate_score(score) { let sign = score.signum(); - return(score * sign - num_ply_searched) * sign; + return (score * sign - num_ply_searched) * sign; } score } @@ -221,6 +287,10 @@ impl Searcher { self.num_tt } + pub fn get_num_nodes(&self) -> i32 { + self.num_nodes + } + fn search_captures(&mut self, mut alpha: i32, beta: i32, tt: &CacheTable) -> i32 { let evaluation = evaluate(&self.board); self.num_pos += 1; @@ -259,8 +329,22 @@ impl Searcher { alpha } - fn store_eval(&mut self, hash: u64, depth: u8, ply_from_root: i32, eval: i32, eval_type: EvalType, this_move: ChessMove, tt: &mut CacheTable) { - let entry: Entry = Entry::new(self.correct_score_to_store(eval, ply_from_root), this_move, depth, eval_type); + fn store_eval( + &mut self, + hash: u64, + depth: u8, + ply_from_root: i32, + eval: i32, + eval_type: EvalType, + this_move: ChessMove, + tt: &mut CacheTable, + ) { + let entry: Entry = Entry::new( + self.correct_score_to_store(eval, ply_from_root), + this_move, + depth, + eval_type, + ); tt.add(hash, entry); } } @@ -269,7 +353,7 @@ pub fn get_stored_move(tt: &CacheTable, hash: u64) -> Option { let tt_eval = tt.get(hash); if tt_eval.is_some() { let tt_ = tt_eval.unwrap(); - return Some(tt_.move_) + return Some(tt_.move_); } None -} \ No newline at end of file +} diff --git a/src/uci/commands.rs b/src/uci/commands.rs new file mode 100644 index 0000000..e5a7d62 --- /dev/null +++ b/src/uci/commands.rs @@ -0,0 +1,2 @@ +// This module can be used for future UCI command extensions +// Currently all commands are handled in the engine module diff --git a/src/uci/engine.rs b/src/uci/engine.rs new file mode 100644 index 0000000..59ddb95 --- /dev/null +++ b/src/uci/engine.rs @@ -0,0 +1,273 @@ +use std::io::{self, Write}; +use std::str::FromStr; + +use crate::entry::Entry; +use crate::uci::protocol::{GoParams, PositionParams}; +use crate::uci::search_manager::SearchManager; +use crate::uci::utils::parse_uci_move; +use chess::{CacheTable, ChessMove, Game, MoveGen}; + +pub struct EngineOptions { + pub hash_size: usize, + pub max_depth: u8, + pub use_second_search: bool, +} + +impl Default for EngineOptions { + fn default() -> Self { + Self { + hash_size: 67108864, // 64MB default + max_depth: 10, + use_second_search: true, + } + } +} + +pub struct UciEngine { + game: Game, + tt: CacheTable, + options: EngineOptions, + search_manager: SearchManager, + debug_mode: bool, +} + +impl UciEngine { + pub fn new() -> Self { + let options = EngineOptions::default(); + let tt = CacheTable::new(options.hash_size, Entry::new_default()); + + Self { + game: Game::new(), + tt, + options, + search_manager: SearchManager::new(), + debug_mode: false, + } + } + + pub fn send_options(&self) { + println!( + "option name Hash type spin default {} min 1 max 1024", + self.options.hash_size / 1024 / 1024 + ); + + println!( + "option name MaxDepth type spin default {} min 1 max 100", + self.options.max_depth + ); + + println!( + "option name UseSecondSearch type check default {}", + self.options.use_second_search + ); + io::stdout().flush().unwrap(); + } + + pub fn handle_command(&mut self, command: &str) { + let tokens: Vec<&str> = command.split_whitespace().collect(); + + if tokens.is_empty() { + return; + } + + match tokens[0] { + "isready" => { + println!("readyok"); + io::stdout().flush().unwrap(); + } + "debug" => { + if tokens.len() > 1 { + match tokens[1] { + "on" => { + self.debug_mode = true; + if self.debug_mode { + println!("info string Debug mode enabled"); + io::stdout().flush().unwrap(); + } + } + "off" => { + self.debug_mode = false; + } + _ => {} // Ignore invalid debug parameters + } + } + } + "ucinewgame" => { + self.handle_new_game(); + } + "position" => { + self.handle_position(command); + } + "go" => { + self.handle_go(command); + } + "stop" => { + self.handle_stop(); + } + "setoption" => { + self.handle_setoption(command); + } + "d" | "display" => { + // Debug command to display current position + println!("{}", self.game.current_position()); + io::stdout().flush().unwrap(); + } + _ => { + // Unknown command, ignore as per UCI spec + if self.debug_mode { + println!("info string Unknown command: {}", command); + io::stdout().flush().unwrap(); + } + } + } + } + + fn handle_new_game(&mut self) { + self.game = Game::new(); + // Clear TT for new game (positions from previous game are not relevant) + self.tt = CacheTable::new(self.options.hash_size, Entry::new_default()); + } + + fn handle_position(&mut self, command: &str) { + let params = PositionParams::from_command(command); + + // Set up the game + if let Some(fen) = params.fen { + match Game::from_str(&fen) { + Ok(game) => { + self.game = game; + if self.debug_mode { + println!("info string Position set from FEN: {}", fen); + io::stdout().flush().unwrap(); + } + } + Err(_) => { + // Send UCI error info instead of stderr + println!("info string Error: Invalid FEN: {}", fen); + io::stdout().flush().unwrap(); + return; + } + } + } else { + self.game = Game::new(); + if self.debug_mode { + println!("info string Position set to startpos"); + io::stdout().flush().unwrap(); + } + } + + // Apply moves + for move_str in params.moves { + if self.debug_mode { + println!("info string Applying move: {}", move_str); + io::stdout().flush().unwrap(); + } + + match ChessMove::from_san(&self.game.current_position(), &move_str) { + Ok(chess_move) => { + self.game.make_move(chess_move); + if self.debug_mode { + println!("info string Move applied successfully (SAN): {}", move_str); + io::stdout().flush().unwrap(); + } + } + Err(_) => { + // Try UCI format (e.g., "e2e4") + if let Some(chess_move) = + parse_uci_move(&move_str, &self.game.current_position()) + { + // Validate the move is legal + let legal_moves: Vec = + MoveGen::new_legal(&self.game.current_position()).collect(); + if legal_moves.contains(&chess_move) { + self.game.make_move(chess_move); + if self.debug_mode { + println!( + "info string Move applied successfully (UCI): {}", + move_str + ); + io::stdout().flush().unwrap(); + } + } else { + println!("info string Error: Illegal move: {}", move_str); + io::stdout().flush().unwrap(); + return; + } + } else { + println!("info string Error: Invalid move format: {}", move_str); + io::stdout().flush().unwrap(); + return; + } + } + } + } + + if self.debug_mode { + println!( + "info string Final position: {}", + self.game.current_position().to_string() + ); + io::stdout().flush().unwrap(); + } + } + + fn handle_go(&mut self, command: &str) { + let params = GoParams::from_command(command); + + // Stop any existing search and retrieve TT if available + self.handle_stop(); + if let Some(updated_tt) = self.search_manager.get_tt_after_search() { + self.tt = updated_tt; + } + + // Move our TT to the search manager (temporarily) + let tt_for_search = std::mem::replace( + &mut self.tt, + CacheTable::new(self.options.hash_size, Entry::new_default()), + ); + + // Start new search with our TT + self.search_manager.start_search( + self.game.current_position(), + params, + tt_for_search, + self.options.max_depth, + self.options.use_second_search, + self.debug_mode, + ); + } + + fn handle_stop(&mut self) { + self.search_manager.stop_search(); + + // Retrieve TT back from search manager if available + if let Some(updated_tt) = self.search_manager.get_tt_after_search() { + self.tt = updated_tt; + } + } + + fn handle_setoption(&mut self, command: &str) { + if let Some((name, value)) = crate::uci::protocol::parse_setoption(command) { + match name.as_str() { + "Hash" => { + if let Ok(hash_mb) = value.parse::() { + self.options.hash_size = hash_mb * 1024 * 1024; + // Recreate TT with new size (this clears previous entries, which is correct) + self.tt = CacheTable::new(self.options.hash_size, Entry::new_default()); + } + } + "MaxDepth" => { + if let Ok(depth) = value.parse::() { + self.options.max_depth = depth; + } + } + "UseSecondSearch" => { + self.options.use_second_search = value.to_lowercase() == "true"; + } + _ => { + // Unknown option, ignore + } + } + } + } +} diff --git a/src/uci/mod.rs b/src/uci/mod.rs new file mode 100644 index 0000000..c40b2c1 --- /dev/null +++ b/src/uci/mod.rs @@ -0,0 +1,77 @@ +pub mod commands; +pub mod engine; +pub mod protocol; +pub mod search_manager; +pub mod utils; + +use crate::uci::engine::UciEngine; +use std::io::{self, BufRead, BufReader, Write}; +use std::sync::mpsc::{self, Receiver, TryRecvError}; +use std::thread; + +pub fn run_uci_loop() { + let mut engine = UciEngine::new(); + + // Create a channel for command processing + let (tx, rx): (mpsc::Sender, Receiver) = mpsc::channel(); + + // Spawn input reading thread + thread::spawn(move || { + let stdin = io::stdin(); + let reader = BufReader::new(stdin); + + for line in reader.lines() { + match line { + Ok(command) => { + if tx.send(command).is_err() { + break; // Main thread has quit + } + } + Err(_) => break, + } + } + }); + + // Main UCI processing loop - can handle commands even during search + loop { + match rx.try_recv() { + Ok(command) => { + let command = command.trim(); + if command.is_empty() { + continue; + } + + if command == "quit" { + break; + } + + // Handle uci command specially to send identification + if command == "uci" { + // Send each response on a separate line with proper newlines + println!("id name Chess AI Rust"); + io::stdout().flush().unwrap(); + + println!("id author Chess AI Rust Developer"); + io::stdout().flush().unwrap(); + + // Send options + engine.send_options(); + + // Send uciok last + println!("uciok"); + io::stdout().flush().unwrap(); + } else { + engine.handle_command(command); + } + } + Err(TryRecvError::Empty) => { + // No command available, sleep briefly to avoid busy waiting + thread::sleep(std::time::Duration::from_millis(1)); + } + Err(TryRecvError::Disconnected) => { + // Input thread has quit + break; + } + } + } +} diff --git a/src/uci/protocol.rs b/src/uci/protocol.rs new file mode 100644 index 0000000..2a69985 --- /dev/null +++ b/src/uci/protocol.rs @@ -0,0 +1,179 @@ +use std::time::Duration; + +#[derive(Debug, Clone)] +pub struct GoParams { + pub wtime: Option, + pub btime: Option, + pub winc: Option, + pub binc: Option, + pub movestogo: Option, + pub depth: Option, + pub nodes: Option, + pub mate: Option, + pub movetime: Option, + pub infinite: bool, +} + +impl Default for GoParams { + fn default() -> Self { + Self { + wtime: None, + btime: None, + winc: None, + binc: None, + movestogo: None, + depth: None, + nodes: None, + mate: None, + movetime: None, + infinite: false, + } + } +} + +impl GoParams { + pub fn from_command(command: &str) -> Self { + let mut params = GoParams::default(); + let tokens: Vec<&str> = command.split_whitespace().collect(); + + let mut i = 1; // Skip "go" + while i < tokens.len() { + match tokens[i] { + "wtime" if i + 1 < tokens.len() => { + if let Ok(ms) = tokens[i + 1].parse::() { + params.wtime = Some(Duration::from_millis(ms)); + } + i += 2; + } + "btime" if i + 1 < tokens.len() => { + if let Ok(ms) = tokens[i + 1].parse::() { + params.btime = Some(Duration::from_millis(ms)); + } + i += 2; + } + "winc" if i + 1 < tokens.len() => { + if let Ok(ms) = tokens[i + 1].parse::() { + params.winc = Some(Duration::from_millis(ms)); + } + i += 2; + } + "binc" if i + 1 < tokens.len() => { + if let Ok(ms) = tokens[i + 1].parse::() { + params.binc = Some(Duration::from_millis(ms)); + } + i += 2; + } + "movestogo" if i + 1 < tokens.len() => { + if let Ok(moves) = tokens[i + 1].parse::() { + params.movestogo = Some(moves); + } + i += 2; + } + "depth" if i + 1 < tokens.len() => { + if let Ok(depth) = tokens[i + 1].parse::() { + params.depth = Some(depth); + } + i += 2; + } + "nodes" if i + 1 < tokens.len() => { + if let Ok(nodes) = tokens[i + 1].parse::() { + params.nodes = Some(nodes); + } + i += 2; + } + "mate" if i + 1 < tokens.len() => { + if let Ok(mate) = tokens[i + 1].parse::() { + params.mate = Some(mate); + } + i += 2; + } + "movetime" if i + 1 < tokens.len() => { + if let Ok(ms) = tokens[i + 1].parse::() { + params.movetime = Some(Duration::from_millis(ms)); + } + i += 2; + } + "infinite" => { + params.infinite = true; + i += 1; + } + _ => i += 1, + } + } + + params + } +} + +#[derive(Debug, Clone)] +pub struct PositionParams { + pub fen: Option, + pub moves: Vec, +} + +impl PositionParams { + pub fn from_command(command: &str) -> Self { + let mut params = PositionParams { + fen: None, + moves: Vec::new(), + }; + + let tokens: Vec<&str> = command.split_whitespace().collect(); + + if tokens.len() < 2 { + return params; + } + + let mut i = 1; // Skip "position" + + if tokens[i] == "startpos" { + i += 1; + } else if tokens[i] == "fen" { + i += 1; + let mut fen_parts = Vec::new(); + while i < tokens.len() && tokens[i] != "moves" { + fen_parts.push(tokens[i]); + i += 1; + } + if !fen_parts.is_empty() { + params.fen = Some(fen_parts.join(" ")); + } + } + + // Process moves + if i < tokens.len() && tokens[i] == "moves" { + i += 1; + while i < tokens.len() { + params.moves.push(tokens[i].to_string()); + i += 1; + } + } + + params + } +} + +pub fn parse_setoption(command: &str) -> Option<(String, String)> { + let tokens: Vec<&str> = command.split_whitespace().collect(); + + if tokens.len() < 4 || tokens[1] != "name" { + return None; + } + + let name_start = 2; + let mut name_end = name_start; + + // Find "value" keyword + while name_end < tokens.len() && tokens[name_end] != "value" { + name_end += 1; + } + + if name_end >= tokens.len() || tokens[name_end] != "value" { + return None; + } + + let name = tokens[name_start..name_end].join(" "); + let value = tokens[name_end + 1..].join(" "); + + Some((name, value)) +} diff --git a/src/uci/search_manager.rs b/src/uci/search_manager.rs new file mode 100644 index 0000000..6d3c3a1 --- /dev/null +++ b/src/uci/search_manager.rs @@ -0,0 +1,367 @@ +use std::io::{self, Write}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::mpsc; +use std::sync::Arc; +use std::thread; +use std::time::{Duration, Instant}; + +use crate::entry::Entry; +use crate::searcher::Searcher; +use crate::uci::protocol::GoParams; +use crate::uci::utils::{build_pv_line, move_to_uci_string}; +use chess::{Board, CacheTable, MoveGen}; + +pub struct SearchManager { + pub is_searching: Arc, + pub search_handle: Option>, + pub tt_receiver: Option>>, +} + +impl SearchManager { + pub fn new() -> Self { + Self { + is_searching: Arc::new(AtomicBool::new(false)), + search_handle: None, + tt_receiver: None, + } + } + + pub fn start_search( + &mut self, + board: Board, + params: GoParams, + mut tt: CacheTable, + max_depth: u8, + use_second_search: bool, + debug_mode: bool, + ) { + if debug_mode { + println!( + "info string Starting search on position: {}", + board.to_string() + ); + + // Check if there are legal moves + let legal_moves: Vec = MoveGen::new_legal(&board).collect(); + println!("info string Legal moves count: {}", legal_moves.len()); + if legal_moves.len() > 0 { + println!( + "info string First few legal moves: {:?}", + &legal_moves[..legal_moves.len().min(3)] + ); + } else { + println!("info string No legal moves available!"); + } + io::stdout().flush().unwrap(); + } + + // Use the provided transposition table instead of creating new one + let is_searching = self.is_searching.clone(); + + // Determine search parameters + let search_depth = params.depth.unwrap_or(max_depth) as usize; + let time_limit = calculate_time_limit(¶ms, &board); + + is_searching.store(true, Ordering::Relaxed); + + let (tx, rx) = mpsc::channel(); + self.tt_receiver = Some(rx); + + let handle = thread::spawn(move || { + // Create searcher with appropriate stop flag for infinite search + let searcher = if params.infinite { + Searcher::new_with_stop_flag(board, use_second_search, is_searching.clone()) + } else { + Searcher::new(board, use_second_search) + }; + let start_time = Instant::now(); + + if params.infinite { + execute_infinite_search( + searcher, + &mut tt, + start_time, + is_searching.clone(), + ¶ms, + ); + } else if let Some(time_limit) = time_limit { + execute_time_based_search( + searcher, + &mut tt, + start_time, + time_limit, + search_depth, + ¶ms, + ); + } else { + execute_depth_based_search(searcher, &mut tt, start_time, search_depth, ¶ms); + } + + io::stdout().flush().unwrap(); + is_searching.store(false, Ordering::Relaxed); + tx.send(tt).unwrap(); + }); + + self.search_handle = Some(handle); + } + + pub fn stop_search(&mut self) { + self.is_searching.store(false, Ordering::Relaxed); + + if let Some(handle) = self.search_handle.take() { + let _ = handle.join(); + } + } + + pub fn get_tt_after_search(&mut self) -> Option> { + if let Some(receiver) = self.tt_receiver.take() { + receiver.try_recv().ok() + } else { + None + } + } +} + +fn execute_infinite_search( + mut searcher: Searcher, + tt: &mut CacheTable, + start_time: Instant, + is_searching: Arc, + params: &GoParams, +) { + // Use individual depth searches with proper UCI output + for depth in 1..=50 { + if !is_searching.load(Ordering::Relaxed) { + break; + } + + // Check node limit if specified + if let Some(node_limit) = params.nodes { + if searcher.get_num_nodes() as u64 >= node_limit { + break; + } + } + + // Search to this exact depth (this resets searcher each time but gives us proper depth results) + searcher.do_iterative_deepening_search(depth, tt); + + if !is_searching.load(Ordering::Relaxed) { + break; + } + + // Check node limit again after search + if let Some(node_limit) = params.nodes { + if searcher.get_num_nodes() as u64 >= node_limit { + break; + } + } + + // Output UCI info for this depth + if let Some(best_move) = searcher.get_best_move() { + output_search_info(depth, &searcher, tt, start_time, best_move); + } + + // Check for mate - stop if found + if searcher.get_best_eval().abs() > (100000 - 1000) { + break; + } + + // Brief pause to allow stop command processing + thread::sleep(Duration::from_millis(10)); + } + + // Send final bestmove when stopped + output_final_bestmove(&searcher); +} + +fn execute_time_based_search( + mut searcher: Searcher, + tt: &mut CacheTable, + start_time: Instant, + time_limit: Duration, + search_depth: usize, + params: &GoParams, +) { + println!( + "info string Starting time-based search ({}ms allocated)", + time_limit.as_millis() + ); + io::stdout().flush().unwrap(); + + // Create a timer thread to stop search when time runs out + let search_time_up = Arc::new(AtomicBool::new(false)); + let search_time_up_clone = search_time_up.clone(); + + let timer_handle = thread::spawn(move || { + thread::sleep(time_limit); + search_time_up_clone.store(true, Ordering::Relaxed); + }); + + // Use iterative deepening with time checks + for depth in 1..=search_depth { + // Check if time is up + if search_time_up.load(Ordering::Relaxed) || start_time.elapsed() >= time_limit { + break; + } + + // Check node limit if specified + if let Some(node_limit) = params.nodes { + if searcher.get_num_nodes() as u64 >= node_limit { + break; + } + } + + // Search to this depth + searcher.do_iterative_deepening_search(depth, tt); + + // Check time again after search + if search_time_up.load(Ordering::Relaxed) || start_time.elapsed() >= time_limit { + break; + } + + // Check node limit again after search + if let Some(node_limit) = params.nodes { + if searcher.get_num_nodes() as u64 >= node_limit { + break; + } + } + + // Output UCI info for this depth + if let Some(best_move) = searcher.get_best_move() { + output_search_info(depth, &searcher, tt, start_time, best_move); + } + + // Check for mate - stop if found + if searcher.get_best_eval().abs() > (100000 - 1000) { + break; + } + + // Time management: if we used more than 80% of time, stop + if start_time.elapsed() > time_limit * 4 / 5 { + break; + } + } + + // Clean up timer thread + search_time_up.store(true, Ordering::Relaxed); + let _ = timer_handle.join(); + + // Send final bestmove + output_final_bestmove(&searcher); +} + +fn execute_depth_based_search( + mut searcher: Searcher, + tt: &mut CacheTable, + start_time: Instant, + search_depth: usize, + params: &GoParams, +) { + // If mate parameter is specified, search for mate in N moves + if let Some(mate_in_n) = params.mate { + println!( + "info string Starting mate search (mate in {} moves)", + mate_in_n + ); + io::stdout().flush().unwrap(); + + // For mate search, we search progressively deeper up to mate_in_n * 2 plies + let max_mate_depth = (mate_in_n as usize) * 2; + for depth in 1..=max_mate_depth { + // Check node limit if specified + if let Some(node_limit) = params.nodes { + if searcher.get_num_nodes() as u64 >= node_limit { + break; + } + } + + searcher.do_iterative_deepening_search(depth, tt); + + if let Some(best_move) = searcher.get_best_move() { + output_search_info(depth, &searcher, tt, start_time, best_move); + + // Check if we found mate + if searcher.get_best_eval().abs() > (100000 - 1000) { + break; + } + } + } + } else { + println!( + "info string Starting depth-based search (depth {})", + search_depth + ); + io::stdout().flush().unwrap(); + + searcher.do_iterative_deepening_search(search_depth, tt); + + if let Some(best_move) = searcher.get_best_move() { + output_search_info(search_depth, &searcher, tt, start_time, best_move); + } + } + + output_final_bestmove(&searcher); +} + +fn output_search_info( + depth: usize, + searcher: &Searcher, + tt: &CacheTable, + start_time: Instant, + best_move: chess::ChessMove, +) { + let elapsed_ms = start_time.elapsed().as_millis() as u64; + let nodes = searcher.get_num_nodes() as u64; + let nps = if elapsed_ms > 0 { + (nodes * 1000) / elapsed_ms + } else { + 0 + }; + + // Build PV line + let pv_line = build_pv_line(&searcher.get_board(), best_move, tt, 3); + + println!( + "info depth {} seldepth {} time {} nodes {} score cp {} nps {} tbhits 0 pv {}", + depth, + depth, + elapsed_ms, + nodes, + searcher.get_best_eval(), + nps, + pv_line + ); + io::stdout().flush().unwrap(); +} + +fn output_final_bestmove(searcher: &Searcher) { + if let Some(best_move) = searcher.get_best_move() { + println!("bestmove {}", move_to_uci_string(best_move)); + } else { + println!("bestmove 0000"); // UCI spec: nullmove is "0000" + } +} + +fn calculate_time_limit(params: &GoParams, board: &Board) -> Option { + if let Some(movetime) = params.movetime { + return Some(movetime); + } + + let side_to_move = board.side_to_move(); + let (time_left, increment) = match side_to_move { + chess::Color::White => (params.wtime, params.winc), + chess::Color::Black => (params.btime, params.binc), + }; + + if let Some(time_left) = time_left { + let base_time = time_left.as_millis() as f64; + let increment_time = increment.map(|i| i.as_millis() as f64).unwrap_or(0.0); + let moves_to_go = params.movestogo.unwrap_or(30) as f64; + + // Simple time management: use time_left / moves_to_go + increment + let allocated_time = (base_time / moves_to_go + increment_time * 0.8) as u64; + return Some(Duration::from_millis(allocated_time)); + } + + None +} diff --git a/src/uci/utils.rs b/src/uci/utils.rs new file mode 100644 index 0000000..b937985 --- /dev/null +++ b/src/uci/utils.rs @@ -0,0 +1,87 @@ +use crate::entry::Entry; +use chess::{Board, CacheTable, ChessMove, MoveGen, Square}; +use std::str::FromStr; + +/// Convert a ChessMove to UCI string format (e.g., "e2e4", "e7e8q") +pub fn move_to_uci_string(chess_move: ChessMove) -> String { + let promotion = match chess_move.get_promotion() { + Some(piece) => match piece { + chess::Piece::Queen => "q", + chess::Piece::Rook => "r", + chess::Piece::Bishop => "b", + chess::Piece::Knight => "n", + _ => "", + }, + None => "", + }; + format!( + "{}{}{}", + chess_move.get_source(), + chess_move.get_dest(), + promotion + ) +} + +/// Parse a UCI move string (e.g., "e2e4") into a ChessMove +pub fn parse_uci_move(move_str: &str, board: &Board) -> Option { + if move_str.len() < 4 { + return None; + } + + let from_str = &move_str[0..2]; + let to_str = &move_str[2..4]; + + let from_square = Square::from_str(from_str).ok()?; + let to_square = Square::from_str(to_str).ok()?; + + let promotion = if move_str.len() == 5 { + match move_str.chars().nth(4)? { + 'q' => Some(chess::Piece::Queen), + 'r' => Some(chess::Piece::Rook), + 'b' => Some(chess::Piece::Bishop), + 'n' => Some(chess::Piece::Knight), + _ => None, + } + } else { + None + }; + + Some(ChessMove::new(from_square, to_square, promotion)) +} + +/// Build a principal variation line by following transposition table entries +pub fn build_pv_line( + board: &Board, + first_move: ChessMove, + tt: &CacheTable, + max_depth: usize, +) -> String { + let mut pv_moves = Vec::new(); + let mut current_board = *board; + let mut current_move = first_move; + + // Add the first move + pv_moves.push(move_to_uci_string(current_move)); + + // Try to follow the PV using the transposition table + for _ in 1..max_depth { + // Apply the current move + current_board = current_board.make_move_new(current_move); + + // Look up the best response in the transposition table + if let Some(stored_move) = crate::searcher::get_stored_move(tt, current_board.get_hash()) { + // Verify this move is legal in the current position + let legal_moves: Vec = MoveGen::new_legal(¤t_board).collect(); + if legal_moves.contains(&stored_move) { + pv_moves.push(move_to_uci_string(stored_move)); + current_move = stored_move; + } else { + break; // Invalid move in TT, stop here + } + } else { + break; // No move found in TT, stop here + } + } + + pv_moves.join(" ") +}