commit 2fd10446b6eab657e772dfe97db00fcd9768ba46 Author: Ashhhleyyy Date: Mon Oct 31 13:25:32 2022 +0000 feat: initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bc41248 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +/node_modules +/dist diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..a22d09f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1374 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "async-trait" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "axum" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acee9fd5073ab6b045a275b3e709c163dd36c90685219cb21804a147b58dba43" +dependencies = [ + "async-trait", + "axum-core", + "base64", + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "sha-1", + "sync_wrapper", + "tokio", + "tokio-tungstenite", + "tower", + "tower-http", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e5939e02c56fecd5c017c37df4238c0a839fa76b7f97acdd7efb804fd181cc" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "tower-layer", + "tower-service", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" + +[[package]] +name = "catty" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf0adb3cc1c06945672f8dcc827e42497ac6d0aff49f459ec918132b82a5cbc" +dependencies = [ + "spin", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chs" +version = "0.1.0" +dependencies = [ + "axum", + "futures", + "mime_guess", + "rayon", + "rust-embed", + "serde", + "serde_json", + "thiserror", + "tokio", + "tower-http", + "tracing", + "tracing-subscriber", + "uuid", + "xtra", +] + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" + +[[package]] +name = "futures-executor" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" + +[[package]] +name = "futures-macro" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" + +[[package]] +name = "futures-task" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" + +[[package]] +name = "futures-util" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "itoa" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55edcf6c0bb319052dea84732cf99db461780fd5e8d3eb46ab6ff312ab31f197" + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "mio" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pin-project" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro2" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rayon" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + +[[package]] +name = "rust-embed" +version = "6.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "283ffe2f866869428c92e0d61c2f35dfb4355293cdfdc48f49e895c15f1333d1" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "6.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31ab23d42d71fb9be1b643fe6765d292c5e14d46912d13f3ae2815ca048ea04d" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "7.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1669d81dfabd1b5f8e2856b8bbe146c6192b0ba22162edc738ac0a5de18f054" +dependencies = [ + "sha2", + "walkdir", +] + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" + +[[package]] +name = "syn" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" + +[[package]] +name = "thiserror" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c530c8675c1dbf98facee631536fa116b5fb6382d7dd6dc1b118d970eafe3ba" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "httpdate", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "tungstenite" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +dependencies = [ + "base64", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand", + "sha-1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "uuid" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feb41e78f93363bb2df8b0e86a2ca30eed7806ea16ea0c790d757cf93f79be83" +dependencies = [ + "getrandom", + "serde", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[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-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" + +[[package]] +name = "xtra" +version = "0.6.0" +source = "git+https://github.com/Restioson/xtra.git#ca061b4090eaa2a563dbe582ec3b48a018729cf7" +dependencies = [ + "async-trait", + "catty", + "event-listener", + "futures-core", + "futures-sink", + "futures-util", + "pin-project-lite", + "spin", + "tokio", + "tracing", + "xtra-macros", +] + +[[package]] +name = "xtra-macros" +version = "0.1.0" +source = "git+https://github.com/Restioson/xtra.git#ca061b4090eaa2a563dbe582ec3b48a018729cf7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..c2a8e58 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "chs" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +axum = { version = "0.5.17", features = ["ws"] } +futures = "0.3.25" +mime_guess = "2.0.4" +rayon = "1.5.3" +rust-embed = "6.4.2" +serde = { version = "1.0.147", features = ["derive"] } +serde_json = "1.0.87" +thiserror = "1.0.37" +tokio = { version = "1.21.2", features = ["full"] } +tower-http = { version = "0.3.4", features = ["trace", "fs"] } +tracing = "0.1.37" +tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } +uuid = { version = "1.2.1", features = ["v4", "serde"] } +xtra = { git = "https://github.com/Restioson/xtra.git", version = "0.6.0", features = ["tokio", "instrumentation", "macros", "sink"] } diff --git a/Makefile.toml b/Makefile.toml new file mode 100644 index 0000000..c7089b8 --- /dev/null +++ b/Makefile.toml @@ -0,0 +1,8 @@ +[tasks.build-ui] +command = "pnpm" +args = ["build"] + +[tasks.build-release] +command = "cargo" +args = ["build", "--release"] +dependencies = ["build-ui"] diff --git a/examples/perft_test.rs b/examples/perft_test.rs new file mode 100644 index 0000000..2396527 --- /dev/null +++ b/examples/perft_test.rs @@ -0,0 +1,78 @@ +use chs::constants::*; +use chs::chess::perft::run_perft; + +#[derive(Default)] +struct TestRunner { + passed: usize, + failed: usize, +} + +impl TestRunner { + fn run_test bool>(&mut self, f: F) { + let result = f(); + if result { + self.passed += 1; + } else { + self.failed += 1; + } + } + + fn summarise(&self) { + println!("Ran {total} tests: {passed} passed, {failed} failed", total = self.passed + self.failed, passed = self.passed, failed = self.failed); + } +} + +fn main() { + let mut runner = TestRunner::default(); + let mut perft = |fen: &str, depth: u64, expected_positions: u64| runner.run_test(|| run_perft(fen, depth, expected_positions)); + + // Start position + println!("Start position"); + perft(START_FEN, 0, 1); + perft(START_FEN, 1, 20); + perft(START_FEN, 2, 400); + perft(START_FEN, 3, 8_902); + perft(START_FEN, 4, 197_281); + + // Other test positions (https://www.chessprogramming.org/Perft_Results) + + // Position 2 + println!("Position 2"); + perft(POSITION_2, 1, 48); + perft(POSITION_2, 2, 2_039); + perft(POSITION_2, 3, 97_862); + perft(POSITION_2, 4, 4_085_603); + + // Position 3 + println!("Position 3"); + perft(POSITION_3, 1, 14); + perft(POSITION_3, 2, 191); + perft(POSITION_3, 3, 2_812); + perft(POSITION_3, 4, 43_238); + perft(POSITION_3, 5, 674_624); + perft(POSITION_3, 6, 11_030_083); + + // Position 4 + println!("Position 4"); + perft(POSITION_4, 1, 6); + perft(POSITION_4, 2, 264); + perft(POSITION_4, 3, 9_467); + perft(POSITION_4, 4, 422_333); + perft(POSITION_4, 5, 15_833_292); + + // Position 5 + println!("Position 5"); + perft(POSITION_5, 1, 44); + perft(POSITION_5, 2, 1_486); + perft(POSITION_5, 3, 62_379); + perft(POSITION_5, 4, 2_103_487); + + // Position 6 + println!("Position 6"); + perft(POSITION_6, 1, 46); + perft(POSITION_6, 2, 2_079); + perft(POSITION_6, 3, 89_890); + perft(POSITION_6, 4, 3_894_594); + + runner.summarise(); +} diff --git a/examples/single_perft_test.rs b/examples/single_perft_test.rs new file mode 100644 index 0000000..a4d3d5b --- /dev/null +++ b/examples/single_perft_test.rs @@ -0,0 +1,5 @@ +use chs::{chess::perft::run_perft, constants::POSITION_6}; + +fn main() { + run_perft("rnb2k1r/pp1Pbppp/2p5/q7/2B5/P7/1PP1NnPP/RNBQK2R w KQ - 1 9", 1, 9); +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..b7820db --- /dev/null +++ b/index.html @@ -0,0 +1,15 @@ + + + + + + + Chs + + + +
+ + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..b511b78 --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "chs", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@types/node": "^18.11.7", + "typescript": "^4.8.4", + "vite": "^3.2.1", + "vite-plugin-solid": "^2.3.10" + }, + "dependencies": { + "@fontsource/noto-sans-symbols-2": "^4.5.10", + "@solid-primitives/websocket": "^0.3.3", + "solid-devtools": "^0.20.1", + "solid-js": "^1.6.0", + "zod": "^3.19.1" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..c5d191b --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1076 @@ +lockfileVersion: 5.4 + +specifiers: + '@fontsource/noto-sans-symbols-2': ^4.5.10 + '@solid-primitives/websocket': ^0.3.3 + '@types/node': ^18.11.7 + solid-devtools: ^0.20.1 + solid-js: ^1.6.0 + typescript: ^4.8.4 + vite: ^3.2.1 + vite-plugin-solid: ^2.3.10 + zod: ^3.19.1 + +dependencies: + '@fontsource/noto-sans-symbols-2': 4.5.10 + '@solid-primitives/websocket': 0.3.3_solid-js@1.6.0 + solid-devtools: 0.20.1_solid-js@1.6.0+vite@3.2.1 + solid-js: 1.6.0 + zod: 3.19.1 + +devDependencies: + '@types/node': 18.11.7 + typescript: 4.8.4 + vite: 3.2.1 + vite-plugin-solid: 2.3.10_solid-js@1.6.0+vite@3.2.1 + +packages: + + /@ampproject/remapping/2.2.0: + resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/gen-mapping': 0.1.1 + '@jridgewell/trace-mapping': 0.3.17 + + /@babel/code-frame/7.18.6: + resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/highlight': 7.18.6 + + /@babel/compat-data/7.20.0: + resolution: {integrity: sha512-Gt9jszFJYq7qzXVK4slhc6NzJXnOVmRECWcVjF/T23rNXD9NtWQ0W3qxdg+p9wWIB+VQw3GYV/U2Ha9bRTfs4w==} + engines: {node: '>=6.9.0'} + + /@babel/core/7.19.6: + resolution: {integrity: sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg==} + engines: {node: '>=6.9.0'} + dependencies: + '@ampproject/remapping': 2.2.0 + '@babel/code-frame': 7.18.6 + '@babel/generator': 7.20.0 + '@babel/helper-compilation-targets': 7.20.0_@babel+core@7.19.6 + '@babel/helper-module-transforms': 7.19.6 + '@babel/helpers': 7.20.0 + '@babel/parser': 7.20.0 + '@babel/template': 7.18.10 + '@babel/traverse': 7.20.0 + '@babel/types': 7.20.0 + convert-source-map: 1.9.0 + debug: 4.3.4 + gensync: 1.0.0-beta.2 + json5: 2.2.1 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + + /@babel/generator/7.20.0: + resolution: {integrity: sha512-GUPcXxWibClgmYJuIwC2Bc2Lg+8b9VjaJ+HlNdACEVt+Wlr1eoU1OPZjZRm7Hzl0gaTsUZNQfeihvZJhG7oc3w==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.20.0 + '@jridgewell/gen-mapping': 0.3.2 + jsesc: 2.5.2 + + /@babel/helper-annotate-as-pure/7.18.6: + resolution: {integrity: sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.20.0 + dev: true + + /@babel/helper-compilation-targets/7.20.0_@babel+core@7.19.6: + resolution: {integrity: sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/compat-data': 7.20.0 + '@babel/core': 7.19.6 + '@babel/helper-validator-option': 7.18.6 + browserslist: 4.21.4 + semver: 6.3.0 + + /@babel/helper-create-class-features-plugin/7.19.0_@babel+core@7.19.6: + resolution: {integrity: sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.19.6 + '@babel/helper-annotate-as-pure': 7.18.6 + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-function-name': 7.19.0 + '@babel/helper-member-expression-to-functions': 7.18.9 + '@babel/helper-optimise-call-expression': 7.18.6 + '@babel/helper-replace-supers': 7.19.1 + '@babel/helper-split-export-declaration': 7.18.6 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/helper-environment-visitor/7.18.9: + resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==} + engines: {node: '>=6.9.0'} + + /@babel/helper-function-name/7.19.0: + resolution: {integrity: sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.18.10 + '@babel/types': 7.20.0 + + /@babel/helper-hoist-variables/7.18.6: + resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.20.0 + + /@babel/helper-member-expression-to-functions/7.18.9: + resolution: {integrity: sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.20.0 + dev: true + + /@babel/helper-module-imports/7.16.0: + resolution: {integrity: sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.20.0 + dev: true + + /@babel/helper-module-imports/7.18.6: + resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.20.0 + + /@babel/helper-module-transforms/7.19.6: + resolution: {integrity: sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-module-imports': 7.18.6 + '@babel/helper-simple-access': 7.19.4 + '@babel/helper-split-export-declaration': 7.18.6 + '@babel/helper-validator-identifier': 7.19.1 + '@babel/template': 7.18.10 + '@babel/traverse': 7.20.0 + '@babel/types': 7.20.0 + transitivePeerDependencies: + - supports-color + + /@babel/helper-optimise-call-expression/7.18.6: + resolution: {integrity: sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.20.0 + dev: true + + /@babel/helper-plugin-utils/7.19.0: + resolution: {integrity: sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==} + engines: {node: '>=6.9.0'} + + /@babel/helper-replace-supers/7.19.1: + resolution: {integrity: sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-member-expression-to-functions': 7.18.9 + '@babel/helper-optimise-call-expression': 7.18.6 + '@babel/traverse': 7.20.0 + '@babel/types': 7.20.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/helper-simple-access/7.19.4: + resolution: {integrity: sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.20.0 + + /@babel/helper-split-export-declaration/7.18.6: + resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.20.0 + + /@babel/helper-string-parser/7.19.4: + resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==} + engines: {node: '>=6.9.0'} + + /@babel/helper-validator-identifier/7.19.1: + resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} + engines: {node: '>=6.9.0'} + + /@babel/helper-validator-option/7.18.6: + resolution: {integrity: sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==} + engines: {node: '>=6.9.0'} + + /@babel/helpers/7.20.0: + resolution: {integrity: sha512-aGMjYraN0zosCEthoGLdqot1oRsmxVTQRHadsUPz5QM44Zej2PYRz7XiDE7GqnkZnNtLbOuxqoZw42vkU7+XEQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.18.10 + '@babel/traverse': 7.20.0 + '@babel/types': 7.20.0 + transitivePeerDependencies: + - supports-color + + /@babel/highlight/7.18.6: + resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-validator-identifier': 7.19.1 + chalk: 2.4.2 + js-tokens: 4.0.0 + + /@babel/parser/7.20.0: + resolution: {integrity: sha512-G9VgAhEaICnz8iiJeGJQyVl6J2nTjbW0xeisva0PK6XcKsga7BIaqm4ZF8Rg1Wbaqmy6znspNqhPaPkyukujzg==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.20.0 + + /@babel/plugin-syntax-jsx/7.18.6_@babel+core@7.19.6: + resolution: {integrity: sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.6 + '@babel/helper-plugin-utils': 7.19.0 + dev: true + + /@babel/plugin-syntax-typescript/7.20.0_@babel+core@7.19.6: + resolution: {integrity: sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.6 + '@babel/helper-plugin-utils': 7.19.0 + + /@babel/plugin-transform-typescript/7.20.0_@babel+core@7.19.6: + resolution: {integrity: sha512-xOAsAFaun3t9hCwZ13Qe7gq423UgMZ6zAgmLxeGGapFqlT/X3L5qT2btjiVLlFn7gWtMaVyceS5VxGAuKbgizw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.6 + '@babel/helper-create-class-features-plugin': 7.19.0_@babel+core@7.19.6 + '@babel/helper-plugin-utils': 7.19.0 + '@babel/plugin-syntax-typescript': 7.20.0_@babel+core@7.19.6 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/preset-typescript/7.18.6_@babel+core@7.19.6: + resolution: {integrity: sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.19.6 + '@babel/helper-plugin-utils': 7.19.0 + '@babel/helper-validator-option': 7.18.6 + '@babel/plugin-transform-typescript': 7.20.0_@babel+core@7.19.6 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/template/7.18.10: + resolution: {integrity: sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.18.6 + '@babel/parser': 7.20.0 + '@babel/types': 7.20.0 + + /@babel/traverse/7.20.0: + resolution: {integrity: sha512-5+cAXQNARgjRUK0JWu2UBwja4JLSO/rBMPJzpsKb+oBF5xlUuCfljQepS4XypBQoiigL0VQjTZy6WiONtUdScQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.18.6 + '@babel/generator': 7.20.0 + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-function-name': 7.19.0 + '@babel/helper-hoist-variables': 7.18.6 + '@babel/helper-split-export-declaration': 7.18.6 + '@babel/parser': 7.20.0 + '@babel/types': 7.20.0 + debug: 4.3.4 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + /@babel/types/7.20.0: + resolution: {integrity: sha512-Jlgt3H0TajCW164wkTOTzHkZb075tMQMULzrLUoUeKmO7eFL96GgDxf7/Axhc5CAuKE3KFyVW1p6ysKsi2oXAg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.19.4 + '@babel/helper-validator-identifier': 7.19.1 + to-fast-properties: 2.0.0 + + /@esbuild/android-arm/0.15.12: + resolution: {integrity: sha512-IC7TqIqiyE0MmvAhWkl/8AEzpOtbhRNDo7aph47We1NbE5w2bt/Q+giAhe0YYeVpYnIhGMcuZY92qDK6dQauvA==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + optional: true + + /@esbuild/linux-loong64/0.15.12: + resolution: {integrity: sha512-tZEowDjvU7O7I04GYvWQOS4yyP9E/7YlsB0jjw1Ycukgr2ycEzKyIk5tms5WnLBymaewc6VmRKnn5IJWgK4eFw==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + optional: true + + /@fontsource/noto-sans-symbols-2/4.5.10: + resolution: {integrity: sha512-3sDgCDm3zNijKoQ4UVSg5PgGk2Drp9QIF73RKZzfiZ4Ksgsu6h4XXng2i+f6cXHpSaP2CEOLgTu7EwH298djMg==} + dev: false + + /@jridgewell/gen-mapping/0.1.1: + resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/set-array': 1.1.2 + '@jridgewell/sourcemap-codec': 1.4.14 + + /@jridgewell/gen-mapping/0.3.2: + resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/set-array': 1.1.2 + '@jridgewell/sourcemap-codec': 1.4.14 + '@jridgewell/trace-mapping': 0.3.17 + + /@jridgewell/resolve-uri/3.1.0: + resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} + engines: {node: '>=6.0.0'} + + /@jridgewell/set-array/1.1.2: + resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} + engines: {node: '>=6.0.0'} + + /@jridgewell/sourcemap-codec/1.4.14: + resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} + + /@jridgewell/trace-mapping/0.3.17: + resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==} + dependencies: + '@jridgewell/resolve-uri': 3.1.0 + '@jridgewell/sourcemap-codec': 1.4.14 + + /@solid-devtools/debugger/0.13.1_solid-js@1.6.0+vite@3.2.1: + resolution: {integrity: sha512-o44QBeepxRSSCUrTT9nUzs3uGfE/yN3S8uekWdpbouhrXUPa+t6Hdw6lh15I/U+yOB6WJ+q6PvUx/0Iv8clD3w==} + peerDependencies: + solid-js: ^1.5.5 + dependencies: + '@solid-devtools/shared': 0.9.2_solid-js@1.6.0 + '@solid-primitives/bounds': 0.0.104_solid-js@1.6.0 + '@solid-primitives/cursor': 0.0.101_solid-js@1.6.0 + '@solid-primitives/event-bus': 0.1.4_solid-js@1.6.0 + '@solid-primitives/event-listener': 2.2.4_solid-js@1.6.0 + '@solid-primitives/keyboard': 1.0.4_solid-js@1.6.0 + '@solid-primitives/platform': 0.0.102_solid-js@1.6.0 + '@solid-primitives/scheduled': 1.1.0_solid-js@1.6.0 + '@solid-primitives/utils': 3.1.0_solid-js@1.6.0 + object-observer: 5.1.6 + solid-js: 1.6.0 + type-fest: 3.1.0 + optionalDependencies: + '@solid-devtools/transform': 0.7.5_solid-js@1.6.0+vite@3.2.1 + transitivePeerDependencies: + - supports-color + - vite + dev: false + + /@solid-devtools/shared/0.9.2_solid-js@1.6.0: + resolution: {integrity: sha512-KP9LLjNNnfovwvc3zAQzM1Sy12exk9hWwAPMxwg4IeDR2Szz5s583PJ1Wx7HVFC1mOU3HDXjqt0OWGYgEZQURA==} + peerDependencies: + solid-js: ^1.5.0 + dependencies: + '@solid-primitives/event-bus': 0.1.4_solid-js@1.6.0 + '@solid-primitives/event-listener': 2.2.4_solid-js@1.6.0 + '@solid-primitives/media': 2.0.4_solid-js@1.6.0 + '@solid-primitives/rootless': 1.2.1_solid-js@1.6.0 + '@solid-primitives/scheduled': 1.1.0_solid-js@1.6.0 + '@solid-primitives/styles': 0.0.100_solid-js@1.6.0 + '@solid-primitives/utils': 3.1.0_solid-js@1.6.0 + solid-js: 1.6.0 + type-fest: 3.1.0 + dev: false + + /@solid-devtools/transform/0.7.5_solid-js@1.6.0+vite@3.2.1: + resolution: {integrity: sha512-Zn2eK/1DGoc7sCFdLS/18GsZNRFkdbY75WfcnL3GUmiFJ5E5EU3P+3UcbWN5vn+//+HMNQ4NPo/M/lcDZTw7WQ==} + peerDependencies: + solid-js: ^1.5.0 + vite: ^2.2.3 || ^3.0.0 + dependencies: + '@babel/core': 7.19.6 + '@babel/plugin-syntax-typescript': 7.20.0_@babel+core@7.19.6 + '@babel/types': 7.20.0 + '@solid-devtools/shared': 0.9.2_solid-js@1.6.0 + solid-js: 1.6.0 + vite: 3.2.1 + transitivePeerDependencies: + - supports-color + dev: false + + /@solid-primitives/bounds/0.0.104_solid-js@1.6.0: + resolution: {integrity: sha512-p5KXzNWzocrlHtd5Zi0OnudlQiw1DCBKrGjMfbhqvZb7YxmLaF6pR1bG4PBcL7Ud5PH420UL5SieDG5CsrSH9A==} + peerDependencies: + solid-js: ^1.5.0 + dependencies: + '@solid-primitives/event-listener': 2.2.4_solid-js@1.6.0 + '@solid-primitives/resize-observer': 2.0.6_solid-js@1.6.0 + '@solid-primitives/utils': 3.1.0_solid-js@1.6.0 + solid-js: 1.6.0 + dev: false + + /@solid-primitives/cursor/0.0.101_solid-js@1.6.0: + resolution: {integrity: sha512-BGRxYf+j23khlih4Xbk5t5zEBC7c2kR0ASMwEEQpNnp9jQB21A8vSS9GVhRQj09HNcQ03Rvyvn6Sl9HzXM40Pg==} + peerDependencies: + solid-js: ^1.5.0 + dependencies: + '@solid-primitives/utils': 3.1.0_solid-js@1.6.0 + solid-js: 1.6.0 + dev: false + + /@solid-primitives/event-bus/0.1.4_solid-js@1.6.0: + resolution: {integrity: sha512-fpj5/b/x5EZGmey7iUm6d/oc52ff8HbTr7yO/+WJUheMSCyY6dQz7INth7ovx57a3SDKTqZJGY3wRZijdBVFdg==} + peerDependencies: + solid-js: ^1.6.0 + dependencies: + '@solid-primitives/immutable': 0.1.4_solid-js@1.6.0 + '@solid-primitives/utils': 4.0.0_solid-js@1.6.0 + solid-js: 1.6.0 + dev: false + + /@solid-primitives/event-listener/2.2.4_solid-js@1.6.0: + resolution: {integrity: sha512-O/ppM0SpXWtNC7AHv1bQA9Dy6pj3NUM06MhSV9xwVv4N06PmlNYhGLDSPT1Esesm6b0fDgCXB5V+AgCSEzQd/w==} + peerDependencies: + solid-js: ^1.6.0 + dependencies: + '@solid-primitives/utils': 4.0.0_solid-js@1.6.0 + solid-js: 1.6.0 + dev: false + + /@solid-primitives/immutable/0.1.4_solid-js@1.6.0: + resolution: {integrity: sha512-9oLK8ihIjG5FZv74KoXXyKErxgGxGZsdevsIKB0ugTreBBmozHPcYTjoYFL/sHoqs2ZNMlmfNQ3kduvrvKG2RQ==} + peerDependencies: + solid-js: ^1.6.0 + dependencies: + '@solid-primitives/utils': 4.0.0_solid-js@1.6.0 + solid-js: 1.6.0 + dev: false + + /@solid-primitives/keyboard/1.0.4_solid-js@1.6.0: + resolution: {integrity: sha512-V2aNnmyLJrFie1BrvzO3HUjE80pisgGwZ7K5h8S5suYEkJRGb5Q2Jbh9YJrrz1Qj1dunEDajGFxD6gDHVloVEg==} + peerDependencies: + solid-js: ^1.6.0 + dependencies: + '@solid-primitives/event-listener': 2.2.4_solid-js@1.6.0 + '@solid-primitives/rootless': 1.2.1_solid-js@1.6.0 + '@solid-primitives/utils': 4.0.0_solid-js@1.6.0 + solid-js: 1.6.0 + dev: false + + /@solid-primitives/media/2.0.4_solid-js@1.6.0: + resolution: {integrity: sha512-MZkdUlV3qJQts4b7ZfAldbFGB1neH64rwHsnTmIeF2Zj8gWjYcYtJ36SwkRs3NjHQ53uQliZ+DtMXiCeapNw/g==} + peerDependencies: + solid-js: ^1.6.0 + dependencies: + '@solid-primitives/event-listener': 2.2.4_solid-js@1.6.0 + '@solid-primitives/rootless': 1.2.1_solid-js@1.6.0 + '@solid-primitives/utils': 4.0.0_solid-js@1.6.0 + solid-js: 1.6.0 + dev: false + + /@solid-primitives/platform/0.0.102_solid-js@1.6.0: + resolution: {integrity: sha512-1eZA1/HYOhmlZ9LrrGot+LUi/ypO2NXqfB+9F1WY98dGNDMz9pG9k+X7kg2YDJTUHDGbzY7WrsBRyAE8LurE7Q==} + peerDependencies: + solid-js: ^1.5.0 + dependencies: + solid-js: 1.6.0 + dev: false + + /@solid-primitives/resize-observer/2.0.6_solid-js@1.6.0: + resolution: {integrity: sha512-PbYmBFJBx1/WcrTZepcr6fABOrUP6CeXxehy2AKPCJInX3LKQ/elHLsM1g6KwVbvqpZ0aQ3a/3I7sRYk6BSrpw==} + peerDependencies: + solid-js: ^1.6.0 + dependencies: + '@solid-primitives/event-listener': 2.2.4_solid-js@1.6.0 + '@solid-primitives/rootless': 1.2.1_solid-js@1.6.0 + '@solid-primitives/utils': 4.0.0_solid-js@1.6.0 + solid-js: 1.6.0 + dev: false + + /@solid-primitives/rootless/1.2.1_solid-js@1.6.0: + resolution: {integrity: sha512-8RpdyS1e58PQbDjgjpyCh+IGoX3QEs/2LauMfl94eXJ5d/o1y/c6P61z9XqQm+Bx1Otdgx4nbFCoF7HPqa0mwg==} + peerDependencies: + solid-js: ^1.6.0 + dependencies: + '@solid-primitives/utils': 4.0.0_solid-js@1.6.0 + solid-js: 1.6.0 + dev: false + + /@solid-primitives/scheduled/1.1.0_solid-js@1.6.0: + resolution: {integrity: sha512-2A2dAiXe+lN0/KRUbIPXEcLoONRJXJmmvVda+y+qiSTbYmcoU2TtNlC04uhQBQJs8Yq2ElJuQhmFladcGLtE9g==} + peerDependencies: + solid-js: ^1.5.0 + dependencies: + solid-js: 1.6.0 + dev: false + + /@solid-primitives/styles/0.0.100_solid-js@1.6.0: + resolution: {integrity: sha512-9OPLQX3dbi26ur0f/VayIvW8FZ+wMMxRYLj6xnxtgUayVX8ZqmPqRBsBSBDWEL4C0YihEhAsYYUzXhiL76jiWw==} + peerDependencies: + solid-js: ^1.5.0 + dependencies: + '@solid-primitives/rootless': 1.2.1_solid-js@1.6.0 + solid-js: 1.6.0 + dev: false + + /@solid-primitives/utils/3.1.0_solid-js@1.6.0: + resolution: {integrity: sha512-/rerChcwgFtHEgVCCBY7BXGHh7a83HcIAzR8QhXJ789geIVbBs2YxHF4UUZlG7ec00NKSfvO3+sQquN/xKQLMw==} + peerDependencies: + solid-js: ^1.5.0 + dependencies: + solid-js: 1.6.0 + dev: false + + /@solid-primitives/utils/4.0.0_solid-js@1.6.0: + resolution: {integrity: sha512-fGsJy8Z8YiwogpiezD7TWjI62UCb0JAHJWdoXWGrggrn4bfToZotKkabiB0IVFMkWVE1ZcrkvZT3bkmqGnK0ng==} + peerDependencies: + solid-js: ^1.6.0 + dependencies: + solid-js: 1.6.0 + dev: false + + /@solid-primitives/websocket/0.3.3_solid-js@1.6.0: + resolution: {integrity: sha512-FlaBN07UDsu1u42xUO/776+s7Tip4EIp3l/SUzVNnJqVL0ryDOMLgvlYXkheVu1+8FntPCek1g6Nd8uS5L6qwQ==} + peerDependencies: + solid-js: ^1.5.0 + dependencies: + solid-js: 1.6.0 + dev: false + + /@types/node/18.11.7: + resolution: {integrity: sha512-LhFTglglr63mNXUSRYD8A+ZAIu5sFqNJ4Y2fPuY7UlrySJH87rRRlhtVmMHplmfk5WkoJGmDjE9oiTfyX94CpQ==} + dev: true + + /ansi-styles/3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + dependencies: + color-convert: 1.9.3 + + /babel-plugin-jsx-dom-expressions/0.35.1_@babel+core@7.19.6: + resolution: {integrity: sha512-OnSFhoYE+tfuhiNCBtC4ZZvc/4kuEaJzhVnH/FfNVkCIGCPr+lG8PLeeFejkW8GSY88Ryt9T75TFABNb7y405g==} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.19.6 + '@babel/helper-module-imports': 7.16.0 + '@babel/plugin-syntax-jsx': 7.18.6_@babel+core@7.19.6 + '@babel/types': 7.20.0 + html-entities: 2.3.2 + dev: true + + /babel-preset-solid/1.6.0_@babel+core@7.19.6: + resolution: {integrity: sha512-Unv2mU+H+AQ0PiGMlwywqAJqmDRZy8kfRkTDnTup3SYIp1UFkP5KcHg76O/2+wZ7mEgY071BsgeVcV1Cw96Fvg==} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.19.6 + babel-plugin-jsx-dom-expressions: 0.35.1_@babel+core@7.19.6 + dev: true + + /browserslist/4.21.4: + resolution: {integrity: sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + dependencies: + caniuse-lite: 1.0.30001426 + electron-to-chromium: 1.4.284 + node-releases: 2.0.6 + update-browserslist-db: 1.0.10_browserslist@4.21.4 + + /caniuse-lite/1.0.30001426: + resolution: {integrity: sha512-n7cosrHLl8AWt0wwZw/PJZgUg3lV0gk9LMI7ikGJwhyhgsd2Nb65vKvmSexCqq/J7rbH3mFG6yZZiPR5dLPW5A==} + + /chalk/2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + /color-convert/1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + dependencies: + color-name: 1.1.3 + + /color-name/1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + /convert-source-map/1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + + /csstype/3.1.1: + resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==} + + /debug/4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + + /electron-to-chromium/1.4.284: + resolution: {integrity: sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==} + + /esbuild-android-64/0.15.12: + resolution: {integrity: sha512-MJKXwvPY9g0rGps0+U65HlTsM1wUs9lbjt5CU19RESqycGFDRijMDQsh68MtbzkqWSRdEtiKS1mtPzKneaAI0Q==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + optional: true + + /esbuild-android-arm64/0.15.12: + resolution: {integrity: sha512-Hc9SEcZbIMhhLcvhr1DH+lrrec9SFTiRzfJ7EGSBZiiw994gfkVV6vG0sLWqQQ6DD7V4+OggB+Hn0IRUdDUqvA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + optional: true + + /esbuild-darwin-64/0.15.12: + resolution: {integrity: sha512-qkmqrTVYPFiePt5qFjP8w/S+GIUMbt6k8qmiPraECUWfPptaPJUGkCKrWEfYFRWB7bY23FV95rhvPyh/KARP8Q==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + optional: true + + /esbuild-darwin-arm64/0.15.12: + resolution: {integrity: sha512-z4zPX02tQ41kcXMyN3c/GfZpIjKoI/BzHrdKUwhC/Ki5BAhWv59A9M8H+iqaRbwpzYrYidTybBwiZAIWCLJAkw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + optional: true + + /esbuild-freebsd-64/0.15.12: + resolution: {integrity: sha512-XFL7gKMCKXLDiAiBjhLG0XECliXaRLTZh6hsyzqUqPUf/PY4C6EJDTKIeqqPKXaVJ8+fzNek88285krSz1QECw==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + optional: true + + /esbuild-freebsd-arm64/0.15.12: + resolution: {integrity: sha512-jwEIu5UCUk6TjiG1X+KQnCGISI+ILnXzIzt9yDVrhjug2fkYzlLbl0K43q96Q3KB66v6N1UFF0r5Ks4Xo7i72g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + optional: true + + /esbuild-linux-32/0.15.12: + resolution: {integrity: sha512-uSQuSEyF1kVzGzuIr4XM+v7TPKxHjBnLcwv2yPyCz8riV8VUCnO/C4BF3w5dHiVpCd5Z1cebBtZJNlC4anWpwA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + optional: true + + /esbuild-linux-64/0.15.12: + resolution: {integrity: sha512-QcgCKb7zfJxqT9o5z9ZUeGH1k8N6iX1Y7VNsEi5F9+HzN1OIx7ESxtQXDN9jbeUSPiRH1n9cw6gFT3H4qbdvcA==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + optional: true + + /esbuild-linux-arm/0.15.12: + resolution: {integrity: sha512-Wf7T0aNylGcLu7hBnzMvsTfEXdEdJY/hY3u36Vla21aY66xR0MS5I1Hw8nVquXjTN0A6fk/vnr32tkC/C2lb0A==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + optional: true + + /esbuild-linux-arm64/0.15.12: + resolution: {integrity: sha512-HtNq5xm8fUpZKwWKS2/YGwSfTF+339L4aIA8yphNKYJckd5hVdhfdl6GM2P3HwLSCORS++++7++//ApEwXEuAQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + optional: true + + /esbuild-linux-mips64le/0.15.12: + resolution: {integrity: sha512-Qol3+AvivngUZkTVFgLpb0H6DT+N5/zM3V1YgTkryPYFeUvuT5JFNDR3ZiS6LxhyF8EE+fiNtzwlPqMDqVcc6A==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + optional: true + + /esbuild-linux-ppc64le/0.15.12: + resolution: {integrity: sha512-4D8qUCo+CFKaR0cGXtGyVsOI7w7k93Qxb3KFXWr75An0DHamYzq8lt7TNZKoOq/Gh8c40/aKaxvcZnTgQ0TJNg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + optional: true + + /esbuild-linux-riscv64/0.15.12: + resolution: {integrity: sha512-G9w6NcuuCI6TUUxe6ka0enjZHDnSVK8bO+1qDhMOCtl7Tr78CcZilJj8SGLN00zO5iIlwNRZKHjdMpfFgNn1VA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + optional: true + + /esbuild-linux-s390x/0.15.12: + resolution: {integrity: sha512-Lt6BDnuXbXeqSlVuuUM5z18GkJAZf3ERskGZbAWjrQoi9xbEIsj/hEzVnSAFLtkfLuy2DE4RwTcX02tZFunXww==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + optional: true + + /esbuild-netbsd-64/0.15.12: + resolution: {integrity: sha512-jlUxCiHO1dsqoURZDQts+HK100o0hXfi4t54MNRMCAqKGAV33JCVvMplLAa2FwviSojT/5ZG5HUfG3gstwAG8w==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + optional: true + + /esbuild-openbsd-64/0.15.12: + resolution: {integrity: sha512-1o1uAfRTMIWNOmpf8v7iudND0L6zRBYSH45sofCZywrcf7NcZA+c7aFsS1YryU+yN7aRppTqdUK1PgbZVaB1Dw==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + optional: true + + /esbuild-sunos-64/0.15.12: + resolution: {integrity: sha512-nkl251DpoWoBO9Eq9aFdoIt2yYmp4I3kvQjba3jFKlMXuqQ9A4q+JaqdkCouG3DHgAGnzshzaGu6xofGcXyPXg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + optional: true + + /esbuild-windows-32/0.15.12: + resolution: {integrity: sha512-WlGeBZHgPC00O08luIp5B2SP4cNCp/PcS+3Pcg31kdcJPopHxLkdCXtadLU9J82LCfw4TVls21A6lilQ9mzHrw==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + optional: true + + /esbuild-windows-64/0.15.12: + resolution: {integrity: sha512-VActO3WnWZSN//xjSfbiGOSyC+wkZtI8I4KlgrTo5oHJM6z3MZZBCuFaZHd8hzf/W9KPhF0lY8OqlmWC9HO5AA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + optional: true + + /esbuild-windows-arm64/0.15.12: + resolution: {integrity: sha512-Of3MIacva1OK/m4zCNIvBfz8VVROBmQT+gRX6pFTLPngFYcj6TFH/12VveAqq1k9VB2l28EoVMNMUCcmsfwyuA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + optional: true + + /esbuild/0.15.12: + resolution: {integrity: sha512-PcT+/wyDqJQsRVhaE9uX/Oq4XLrFh0ce/bs2TJh4CSaw9xuvI+xFrH2nAYOADbhQjUgAhNWC5LKoUsakm4dxng==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.15.12 + '@esbuild/linux-loong64': 0.15.12 + esbuild-android-64: 0.15.12 + esbuild-android-arm64: 0.15.12 + esbuild-darwin-64: 0.15.12 + esbuild-darwin-arm64: 0.15.12 + esbuild-freebsd-64: 0.15.12 + esbuild-freebsd-arm64: 0.15.12 + esbuild-linux-32: 0.15.12 + esbuild-linux-64: 0.15.12 + esbuild-linux-arm: 0.15.12 + esbuild-linux-arm64: 0.15.12 + esbuild-linux-mips64le: 0.15.12 + esbuild-linux-ppc64le: 0.15.12 + esbuild-linux-riscv64: 0.15.12 + esbuild-linux-s390x: 0.15.12 + esbuild-netbsd-64: 0.15.12 + esbuild-openbsd-64: 0.15.12 + esbuild-sunos-64: 0.15.12 + esbuild-windows-32: 0.15.12 + esbuild-windows-64: 0.15.12 + esbuild-windows-arm64: 0.15.12 + + /escalade/3.1.1: + resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + engines: {node: '>=6'} + + /escape-string-regexp/1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + /fsevents/2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + optional: true + + /function-bind/1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + + /gensync/1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + /globals/11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + /has-flag/3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + /has/1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} + dependencies: + function-bind: 1.1.1 + + /html-entities/2.3.2: + resolution: {integrity: sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ==} + dev: true + + /is-core-module/2.11.0: + resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==} + dependencies: + has: 1.0.3 + + /is-what/4.1.7: + resolution: {integrity: sha512-DBVOQNiPKnGMxRMLIYSwERAS5MVY1B7xYiGnpgctsOFvVDz9f9PFXXxMcTOHuoqYp4NK9qFYQaIC1NRRxLMpBQ==} + engines: {node: '>=12.13'} + dev: true + + /js-tokens/4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + /jsesc/2.5.2: + resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} + engines: {node: '>=4'} + hasBin: true + + /json5/2.2.1: + resolution: {integrity: sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==} + engines: {node: '>=6'} + hasBin: true + + /merge-anything/5.0.4: + resolution: {integrity: sha512-YFsDeY5A9SLXhL21Qn15wCWewRUW6wMTxQF4SuPe9bNdr1wsjiE44Rp8FQUTCtwO0WLdlKiFzhAVE5tlf857Tg==} + engines: {node: '>=12.13'} + dependencies: + is-what: 4.1.7 + ts-toolbelt: 9.6.0 + dev: true + + /ms/2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + /nanoid/3.3.4: + resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + /node-releases/2.0.6: + resolution: {integrity: sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==} + + /object-observer/5.1.6: + resolution: {integrity: sha512-3Lcp1Q9BW0ELoObPTCx6qOJlqpHo1d1TZHugoY50RjQRMLBOcGa9Dwy7FQtMYALmb/zo3GjEWjV09FJ4Ss/lQg==} + dev: false + + /path-parse/1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + /picocolors/1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + + /postcss/8.4.18: + resolution: {integrity: sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.4 + picocolors: 1.0.0 + source-map-js: 1.0.2 + + /resolve/1.22.1: + resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} + hasBin: true + dependencies: + is-core-module: 2.11.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + /rollup/2.79.1: + resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==} + engines: {node: '>=10.0.0'} + hasBin: true + optionalDependencies: + fsevents: 2.3.2 + + /semver/6.3.0: + resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} + hasBin: true + + /solid-devtools/0.20.1_solid-js@1.6.0+vite@3.2.1: + resolution: {integrity: sha512-sSnW66Ya+sQ5HuKD4ijBhJHIlRhSt2Sghj/G9yeOlvMUD15k3rs5M3ELi7MDe6eD7s7bW7skBw491tJnHM+mcQ==} + peerDependencies: + solid-js: ^1.5.5 + dependencies: + '@solid-devtools/debugger': 0.13.1_solid-js@1.6.0+vite@3.2.1 + '@solid-devtools/shared': 0.9.2_solid-js@1.6.0 + '@solid-devtools/transform': 0.7.5_solid-js@1.6.0+vite@3.2.1 + '@solid-primitives/utils': 3.1.0_solid-js@1.6.0 + solid-js: 1.6.0 + type-fest: 3.1.0 + transitivePeerDependencies: + - supports-color + - vite + dev: false + + /solid-js/1.6.0: + resolution: {integrity: sha512-db5s65ErgZnBhapPx77qauzul8akHlMCvirS+Y86U4abMa3uizMVNW9ql3UxbO0yMzMGNpFJwUiOlXmJCbwVpA==} + dependencies: + csstype: 3.1.1 + + /solid-refresh/0.4.1_solid-js@1.6.0: + resolution: {integrity: sha512-v3tD/OXQcUyXLrWjPW1dXZyeWwP7/+GQNs8YTL09GBq+5FguA6IejJWUvJDrLIA4M0ho9/5zK2e9n+uy+4488g==} + peerDependencies: + solid-js: ^1.3 + dependencies: + '@babel/generator': 7.20.0 + '@babel/helper-module-imports': 7.18.6 + '@babel/types': 7.20.0 + solid-js: 1.6.0 + dev: true + + /source-map-js/1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + + /supports-color/5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + dependencies: + has-flag: 3.0.0 + + /supports-preserve-symlinks-flag/1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + /to-fast-properties/2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + + /ts-toolbelt/9.6.0: + resolution: {integrity: sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==} + dev: true + + /type-fest/3.1.0: + resolution: {integrity: sha512-StmrZmK3eD9mDF9Vt7UhqthrDSk66O9iYl5t5a0TSoVkHjl0XZx/xuc/BRz4urAXXGHOY5OLsE0RdJFIApSFmw==} + engines: {node: '>=14.16'} + dev: false + + /typescript/4.8.4: + resolution: {integrity: sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==} + engines: {node: '>=4.2.0'} + hasBin: true + dev: true + + /update-browserslist-db/1.0.10_browserslist@4.21.4: + resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + dependencies: + browserslist: 4.21.4 + escalade: 3.1.1 + picocolors: 1.0.0 + + /vite-plugin-solid/2.3.10_solid-js@1.6.0+vite@3.2.1: + resolution: {integrity: sha512-5jMF+QFk2TQaPLlDl7LvZZ99D4kO1X+rD9LR78p9sx9O+XisVSQaHFPLrCsyW/lXuBwub+ox/pNaZdCUZJwd3Q==} + peerDependencies: + solid-js: ^1.3.17 + vite: ^3.0.0 + dependencies: + '@babel/core': 7.19.6 + '@babel/preset-typescript': 7.18.6_@babel+core@7.19.6 + babel-preset-solid: 1.6.0_@babel+core@7.19.6 + merge-anything: 5.0.4 + solid-js: 1.6.0 + solid-refresh: 0.4.1_solid-js@1.6.0 + vite: 3.2.1 + transitivePeerDependencies: + - supports-color + dev: true + + /vite/3.2.1: + resolution: {integrity: sha512-ADtMkfHuWq4tskJsri2n2FZkORO8ZyhI+zIz7zTrDAgDEtct1jdxOg3YsZBfHhKjmMoWLOSCr+64qrEDGo/DbQ==} + engines: {node: ^14.18.0 || >=16.0.0} + hasBin: true + peerDependencies: + less: '*' + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + less: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + esbuild: 0.15.12 + postcss: 8.4.18 + resolve: 1.22.1 + rollup: 2.79.1 + optionalDependencies: + fsevents: 2.3.2 + + /zod/3.19.1: + resolution: {integrity: sha512-LYjZsEDhCdYET9ikFu6dVPGp2YH9DegXjdJToSzD9rO6fy4qiRYFoyEYwps88OseJlPyl2NOe2iJuhEhL7IpEA==} + dev: false diff --git a/src-web/App.tsx b/src-web/App.tsx new file mode 100644 index 0000000..102129e --- /dev/null +++ b/src-web/App.tsx @@ -0,0 +1,61 @@ +import { Component, createEffect, createSignal, Match, Switch } from 'solid-js'; +import createWebsocket from '@solid-primitives/websocket'; +import { wsUrl } from './constants'; +import Spinner from './components/Spinner'; +import Welcome from './components/Welcome'; +import { Board as BoardData, Move, ServerChessEvent } from './events'; +import Board from './components/Board'; + +interface Props { + gameId: string; +} + +const App: Component = (props) => { + const [board, setBoard] = createSignal(Array(64).fill(null)); + const [possibleMoves, setPossibleMoves] = createSignal([]); + + function handleEvent(e: MessageEvent) { + const data = JSON.parse(e.data); + const event = ServerChessEvent.parse(data); + if (event.event === 'BoardUpdate') { + console.log(event.data.board); + setBoard(event.data.board); + } else if (event.event === 'PossibleMoves') { + console.log(event.data.moves); + setPossibleMoves(event.data.moves); + } + } + + createEffect(() => { + console.log('board is now', board()); + }); + + createEffect(() => { + console.log('moves is now', possibleMoves()); + }); + + const [connect, disconnect, send, state, socket] = createWebsocket(wsUrl(props.gameId), handleEvent, console.error); + + function makeMove(move: Move) { + send(JSON.stringify({ + event: 'MakeMove', + data: { + move, + }, + })); + } + + return

Hello, World!

}> + + connect()} /> + + + + + + + +
+}; + +export default App; diff --git a/src-web/components/Board.css b/src-web/components/Board.css new file mode 100644 index 0000000..281c1d8 --- /dev/null +++ b/src-web/components/Board.css @@ -0,0 +1,62 @@ +.board { + font-family: 'Noto Sans Symbols 2', sans-serif; + display: grid; + grid-template-columns: repeat(8, auto); +} + +.square { + width: 64px; + height: 64px; + font-size: 48px; + display: flex; + flex-direction: row; + align-items: baseline; + justify-content: center; + user-select: none; + cursor: default; + padding: auto; + position: relative; + border-radius: 8px; +} + +.square.selectable { + cursor: pointer; +} + +.light { + background-color: #c8a2c8; + /* color: #5d375d; */ +} + +.dark { + background-color: #5d375d; + /* color: #C8A2C8; */ +} + +.white { + color: white; +} + +.black { + color: black; +} + +.borderSelected { + position: absolute; + width: 64px; + height: 64px; + top: 0; + left: 0; + border: 4px solid yellowgreen; + border-radius: 8px; +} + +.borderTarget { + position: absolute; + width: 32px; + height: 32px; + top: 16px; + left: 16px; + border: 4px solid yellowgreen; + border-radius: 999px; +} diff --git a/src-web/components/Board.tsx b/src-web/components/Board.tsx new file mode 100644 index 0000000..7b60ccb --- /dev/null +++ b/src-web/components/Board.tsx @@ -0,0 +1,73 @@ +import { Accessor, Component, createSignal, For, Show } from "solid-js"; +import { Board as BoardData, Coordinate, Move, PieceType } from "../events"; +import './Board.css'; + +interface Props { + board: Accessor; + moves: Accessor; + makeMove: (move: Move) => void; +} + +const PIECE_CHARS: Record = { + 'king': '♔', + 'queen': '♕', + 'rook': '♜', + 'knight': '♞', + 'bishop': '♝', + 'pawn': '♙', +}; + +const Board: Component = (props) => { + const [selectedSquare, setSelectedSquare] = createSignal(null); + + const validMoves = () => { + const selected = selectedSquare(); + if (selected !== null) { + return props.moves().filter(move => (move.from.rank * 8 + move.from.file) === selected); + } else { + return []; + } + }; + + return
+ {props.board().map((piece, i) => { + const coord: Coordinate = { + rank: Math.floor(i / 8), + file: i % 8, + }; + const isLight = () => ((i % 8) + Math.floor(i / 8)) % 2 == 0; + const hasMoves = () => props.moves().find((move) => move.from.rank === coord.rank && move.from.file === coord.file) !== undefined; + const targetMove = () => validMoves().find(move => move.to.rank === coord.rank && move.to.file === coord.file); + const isTarget = () => targetMove() !== undefined; + + return
{ + if (hasMoves()) { + if (selectedSquare() === i) { + setSelectedSquare(null); + } else { + setSelectedSquare(i); + } + } else { + const target = targetMove(); + if (target && !target.promotions) { + props.makeMove(target); + setSelectedSquare(null); + } + } + }}> +
+
+ {PIECE_CHARS[piece!.ty]} +
+ })} +
+} + +export default Board; diff --git a/src-web/components/Button.css b/src-web/components/Button.css new file mode 100644 index 0000000..478dfdd --- /dev/null +++ b/src-web/components/Button.css @@ -0,0 +1,16 @@ +.button { + width: 100%; + font-size: 1.35rem; + padding: 8px; + border-radius: 8px; + border: 1px solid black; + background-color: #ddd; +} + +.button:hover { + filter: brightness(.9); +} + +.button:active { + filter: brightness(.8); +} diff --git a/src-web/components/Button.tsx b/src-web/components/Button.tsx new file mode 100644 index 0000000..b84a833 --- /dev/null +++ b/src-web/components/Button.tsx @@ -0,0 +1,16 @@ +import { children, Component, JSX } from "solid-js"; +import './Button.css'; + +interface Props { + onClick?: () => void; + children: JSX.Element; +} + +const Button: Component = (props) => { + const c = children(() => props.children) + return +} + +export default Button; diff --git a/src-web/components/Connecting.css b/src-web/components/Connecting.css new file mode 100644 index 0000000..e69de29 diff --git a/src-web/components/Connecting.tsx b/src-web/components/Connecting.tsx new file mode 100644 index 0000000..940d698 --- /dev/null +++ b/src-web/components/Connecting.tsx @@ -0,0 +1,11 @@ +import { Component } from 'solid-js'; +import './Connecting.css'; +import Spinner from './Spinner'; + +const Connecting: Component = () => { + return
+ +
+} + +export default Connecting; diff --git a/src-web/components/Spinner.css b/src-web/components/Spinner.css new file mode 100644 index 0000000..188d3e1 --- /dev/null +++ b/src-web/components/Spinner.css @@ -0,0 +1,19 @@ +.spinner { + display: inline-block; + width: var(--spinner-size); + height: var(--spinner-size); + border: 4px solid; + border-color: transparent transparent var(--spinner-colour) var(--spinner-colour); + border-radius: 99999px; + transform-origin: center; + animation: spinner-spin 500ms linear infinite; +} + +@keyframes spinner-spin { + from { + transform: rotateZ(0deg); + } + to { + transform: rotateZ(360deg); + } +} diff --git a/src-web/components/Spinner.tsx b/src-web/components/Spinner.tsx new file mode 100644 index 0000000..bbdcdda --- /dev/null +++ b/src-web/components/Spinner.tsx @@ -0,0 +1,16 @@ +import { Component } from "solid-js"; +import './Spinner.css'; + +interface Props { + colour?: string; + size?: string; +} + +const Spinner: Component = (props) => { + return
+} + +export default Spinner; diff --git a/src-web/components/Welcome.tsx b/src-web/components/Welcome.tsx new file mode 100644 index 0000000..c21a443 --- /dev/null +++ b/src-web/components/Welcome.tsx @@ -0,0 +1,22 @@ +import { Component } from "solid-js"; +import Button from "./Button"; + +interface Props { + gameId: string; + joinGame: () => void; +} + +const Welcome: Component = (props) => { + console.log(props.gameId); + return
+

Welcome

+
+ Game ID:
{props.gameId}
+
+ +
; +} + +export default Welcome; diff --git a/src-web/constants.ts b/src-web/constants.ts new file mode 100644 index 0000000..2bd22b5 --- /dev/null +++ b/src-web/constants.ts @@ -0,0 +1,12 @@ +// const WS_BASE = 'ws://localhost:3000/ws/'; + +export function wsUrl(id: string) { + if (import.meta.env.WS_BASE) { + return import.meta.env.WS_BASE + id; + } else { + const loc = window.location; + let newUri = loc.protocol === "https:" ? "wss://" : "ws://"; + newUri += loc.host + '/ws/' + id; + return newUri; + } +} diff --git a/src-web/events.ts b/src-web/events.ts new file mode 100644 index 0000000..4159bff --- /dev/null +++ b/src-web/events.ts @@ -0,0 +1,59 @@ +import { z } from 'zod'; + +export const Coordinate = z.object({ + rank: z.number(), + file: z.number(), +}); + +export type Coordinate = z.infer; + +export const Side = z.enum(['white', 'black']); +export type Side = z.infer; + +export const PieceType = z.enum(['king', 'queen', 'rook', 'bishop', 'knight', 'pawn']); +export type PieceType = z.infer; + +export const Piece = z.object({ + side: Side, + ty: PieceType, +}); +export type Piece = z.infer; + +export interface Move { + from: Coordinate; + to: Coordinate; + set_en_passant: Coordinate | null; + other: Move | null; + promotions: Piece[] | null; +} + +export const Move: z.ZodType = z.lazy(() => z.object({ + from: Coordinate, + to: Coordinate, + set_en_passant: z.nullable(Coordinate), + other: z.nullable(Move), + promotions: z.nullable(z.array(Piece)), +})); + +export const Board = z.array(z.nullable(Piece)); + +export type Board = z.infer; + +export const BoardUpdateEvent = z.object({ + board: Board, +}); + +export type BoardUpdateEvent = z.infer; + +export const PossibleMovesEvent = z.object({ + moves: z.array(Move), +}); + +export type PossibleMovesEvent = z.infer; + +export const ServerChessEvent = z.discriminatedUnion("event", [ + z.object({ event: z.literal('BoardUpdate'), data: BoardUpdateEvent }), + z.object({ event: z.literal('PossibleMoves'), data: PossibleMovesEvent }), +]); + +export type ServerChessEvent = z.infer; diff --git a/src-web/index.tsx b/src-web/index.tsx new file mode 100644 index 0000000..e602e6b --- /dev/null +++ b/src-web/index.tsx @@ -0,0 +1,14 @@ +/* @refresh reload */ +import { render } from 'solid-js/web'; +import 'solid-devtools'; + +import App from './App'; +import './main.css'; +import "@fontsource/noto-sans-symbols-2"; + +const search = new URLSearchParams(window.location.search); +if (search.has('game_id')) { + render(() => , document.getElementById('root') as HTMLElement); +} else { + console.error('no game id'); +} diff --git a/src-web/main.css b/src-web/main.css new file mode 100644 index 0000000..a620c0d --- /dev/null +++ b/src-web/main.css @@ -0,0 +1,19 @@ +html { + box-sizing: border-box; +} + +html, body { + padding: 0; + margin: 0; +} + +* { + box-sizing: inherit; +} + +body { + display: grid; + place-items: center; + width: 100%; + min-height: 100vh; +} diff --git a/src/assets.rs b/src/assets.rs new file mode 100644 index 0000000..979489b --- /dev/null +++ b/src/assets.rs @@ -0,0 +1,26 @@ +use axum::{response::{IntoResponse, Response}, body::{boxed, Full}, http::{header, StatusCode}}; +use rust_embed::RustEmbed; + +#[derive(RustEmbed)] +#[folder = "dist/"] +struct Asset; + +pub struct StaticFile(pub T); + +impl IntoResponse for StaticFile +where + T: Into, +{ + fn into_response(self) -> Response { + let path = self.0.into(); + + match Asset::get(path.as_str()) { + Some(content) => { + let body = boxed(Full::from(content.data)); + let mime = mime_guess::from_path(path).first_or_octet_stream(); + Response::builder().header(header::CONTENT_TYPE, mime.as_ref()).body(body).unwrap() + } + None => StatusCode::NOT_FOUND.into_response(), + } + } +} diff --git a/src/chess.rs b/src/chess.rs new file mode 100644 index 0000000..c20e811 --- /dev/null +++ b/src/chess.rs @@ -0,0 +1,636 @@ +use std::fmt::Display; + +use serde::{Deserialize, Serialize}; + +use crate::error::{FENParseError, NotationError}; + +pub mod mv; +pub mod perft; + +#[derive(Copy, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct Coordinate { + rank: isize, + file: isize, +} + +impl Coordinate { + pub fn is_board_position(self) -> bool { + self.rank >= 0 && self.rank < 8 && self.file >= 0 && self.file < 8 + } + + pub fn to_index(self) -> usize { + if !self.is_board_position() { + panic!("called to_index on a coordinate with out-of-bounds values: {self:?}"); + } + (self.rank as usize) * 8 + (self.file as usize) + } + + pub fn from_index(index: usize) -> Self { + let rank = (index / 8) as isize; + let file = (index % 8) as isize; + return Self { rank, file }; + } + + pub fn parse_algebraic(s: &str) -> Result { + if s.len() != 2 { + return Err(NotationError::InvalidLength { + length: s.len(), + expected: 2, + }); + } + + let file = s + .chars() + .next() + .unwrap() + .to_digit(18) + .map(|v| v - 10) + .ok_or_else(|| NotationError::Other(s.to_owned()))? as isize; + + let rank = s + .chars() + .skip(1) + .next() + .unwrap() + .to_digit(9) + .map(|v| v - 1) + .ok_or_else(|| NotationError::Other(s.to_owned()))? as isize; + + if file < 0 || rank < 0 { + return Err(NotationError::Other(s.to_owned())); + } + + Ok(Self { file, rank }) + } + + pub fn between(from: Coordinate, to: Coordinate) -> BetweenCoordsIter { + if from.rank != to.rank && from.file != to.file { + panic!("Coordinate::between must be passed two values in the same rank or file"); + } + + if from.rank != to.rank { + let min_rank = from.rank.min(to.rank); + let max_rank = from.rank.max(to.rank); + BetweenCoordsIter { + start_file: from.file, + start_rank: min_rank, + + rank: true, + + offset: 1, + max_offset: (max_rank - min_rank) - 1, + } + } else { + let min_file = from.file.min(to.file); + let max_file = from.file.max(to.file); + BetweenCoordsIter { + start_rank: from.rank, + start_file: min_file, + + rank: false, + + offset: 1, + max_offset: (max_file - min_file) - 1, + } + } + } +} + +const FILES: [char; 8] = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']; + +impl Display for Coordinate { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if !self.is_board_position() { + panic!("cannot format non-board Coordinate in algebraic notation"); + } + write!(f, "{}", FILES[self.file as usize])?; + write!(f, "{}", self.rank + 1)?; + Ok(()) + } +} + +#[derive(Debug)] +pub struct BetweenCoordsIter { + start_rank: isize, + start_file: isize, + + rank: bool, + + offset: isize, + max_offset: isize, +} + +impl Iterator for BetweenCoordsIter { + type Item = Coordinate; + + fn next(&mut self) -> Option { + if self.offset > self.max_offset { + return None; + } + + let rank = if self.rank { + self.start_rank + self.offset + } else { + self.start_rank + }; + let file = if self.rank { + self.start_file + } else { + self.start_file + self.offset + }; + + self.offset += 1; + Some(Coordinate { rank, file }) + } +} + +impl std::ops::Add for Coordinate { + type Output = Coordinate; + + fn add(self, rhs: Coordinate) -> Self::Output { + Self { + file: self.file + rhs.file, + rank: self.rank + rhs.rank, + } + } +} + +impl std::ops::Mul for Coordinate { + type Output = Coordinate; + + fn mul(self, rhs: isize) -> Self::Output { + Self { + rank: self.rank * rhs, + file: self.file * rhs, + } + } +} + +#[derive(Clone, Debug)] +pub struct Board { + pub board: [Option; 64], + pub to_move: Side, + pub castling: BySide, + pub en_passant_target: Option, + pub halfmove_clock: u32, + pub fullmove_number: u32, +} + +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +pub struct Castling { + pub king: bool, + pub queen: bool, +} + + +/// FEN parsing implementation +impl Board { + pub fn from_fen(fen: &str) -> Result { + let mut board = [None; 64]; + let sections = fen.split(' ').collect::>(); + + if sections.len() < 6 { + return Err(FENParseError::NotEnoughSections); + } + + Self::parse_fen_board(&mut board, sections[0])?; + + let to_move = match sections[1] { + "w" => Side::White, + "b" => Side::Black, + c => return Err(NotationError::InvalidSide(c.to_owned()).into()), + }; + + let castling = Self::parse_fen_castling(sections[2])?; + + let en_passant_target = match sections[3] { + "-" => None, + s => Some(Coordinate::parse_algebraic(s)?), + }; + + let halfmove_clock = sections[4].parse::()?; + let fullmove_number = sections[5].parse::()?; + + Ok(Self { + board, + to_move, + castling, + en_passant_target, + halfmove_clock, + fullmove_number, + }) + } + + fn parse_fen_castling(fen: &str) -> Result, NotationError> { + let mut castling = BySide::::default(); + + if fen != "-" { + for c in fen.chars() { + let side = Side::from(c); + let castling = castling.get_mut(side); + match c { + 'q' | 'Q' => { + castling.queen = true; + } + 'k' | 'K' => { + castling.king = true; + } + _ => return Err(NotationError::InvalidPiece(c)), + } + } + } + + Ok(castling) + } + + fn parse_fen_board(target: &mut [Option; 64], board: &str) -> Result<(), FENParseError> { + enum State { + ParsingRank { file: usize }, + WaitingForSlash, + EndOfString, + } + + let mut state = State::ParsingRank { file: 0 }; + let mut rank = 7; + + for c in board.chars() { + match &mut state { + State::ParsingRank { file } => { + match c { + '0'..='8' => { + *file += c.to_digit(9).unwrap() as usize; + } + c => { + target[rank * 8 + *file] = Some(Piece::try_from(c)?); + *file += 1; + } + } + if *file == 8 { + state = if rank == 0 { + State::EndOfString + } else { + State::WaitingForSlash + }; + } + } + State::WaitingForSlash => { + if c != '/' { + return Err(FENParseError::ExpectedSlash); + } else { + state = State::ParsingRank { file: 0 }; + rank -= 1; + } + } + State::EndOfString => { + // This case should never be reached on a valid FEN string + return Err(FENParseError::BoardTooLarge); + } + } + } + + Ok(()) + } +} + +/// Accessors +impl Board { + pub fn calc_check_state(&self) -> BySide { + let white_moves = { + let mut moves = vec![]; + mv::generate_pseudolegal_captures(&Board { + to_move: Side::White, + ..*self + }, &mut moves); + moves + }; + let black_moves = { + let mut moves = vec![]; + mv::generate_pseudolegal_captures(&Board { + to_move: Side::Black, + ..*self + }, &mut moves); + moves + }; + let black_checked = white_moves.into_iter().any(|m| { + self.board[m.to.to_index()] == Some(Side::Black | PieceType::King) + }); + let white_checked = black_moves.into_iter().any(|m| { + self.board[m.to.to_index()] == Some(Side::White | PieceType::King) + }); + return BySide { + black: black_checked, + white: white_checked, + } + } + + pub fn get(&self, c: Coordinate) -> &Option { + &self.board[c.to_index()] + } + + pub fn get_mut(&mut self, c: Coordinate) -> &mut Option { + &mut self.board[c.to_index()] + } + + pub fn print_display(&self) { + for rank in (0..8).rev() { + for file in 0..8 { + if let Some(piece) = self.get(Coordinate { rank, file }) { + print!("{}", piece.to_char()); + } else { + print!(" "); + } + } + println!(); + } + } +} + +#[derive(Copy, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum Side { + Black, + White, +} + +impl Side { + pub fn other(&self) -> Self { + match self { + Self::White => Self::Black, + Self::Black => Self::White, + } + } + + pub fn back_rank(&self) -> isize { + match self { + Self::Black => 7, + Self::White => 0, + } + } + + pub fn pawn_rank(&self) -> isize { + match self { + Self::Black => 6, + Self::White => 1, + } + } +} + +impl From for Side { + fn from(c: char) -> Self { + if c.is_uppercase() { + Self::White + } else { + Self::Black + } + } +} + +#[derive(Copy, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum PieceType { + King, + Queen, + Rook, + Bishop, + Knight, + Pawn, +} + +impl PieceType { + pub fn to_char(self) -> char { + match self { + Self::King => 'k', + Self::Queen => 'q', + Self::Rook => 'r', + Self::Bishop => 'b', + Self::Knight => 'n', + Self::Pawn => 'p', + } + } +} + +impl TryFrom for PieceType { + type Error = NotationError; + + fn try_from(value: char) -> Result { + match value.to_ascii_lowercase() { + 'k' => Ok(Self::King), + 'q' => Ok(Self::Queen), + 'r' => Ok(Self::Rook), + 'b' => Ok(Self::Bishop), + 'n' => Ok(Self::Knight), + 'p' => Ok(Self::Pawn), + _ => Err(NotationError::InvalidPiece(value)), + } + } +} + +#[derive(Copy, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct Piece { + pub side: Side, + pub ty: PieceType, +} + +impl Piece { + pub fn to_char(&self) -> char { + match self.side { + Side::Black => self.ty.to_char(), + Side::White => self.ty.to_char().to_ascii_uppercase(), + } + } +} + +impl TryFrom for Piece { + type Error = FENParseError; + + fn try_from(value: char) -> Result { + let side = Side::from(value); + let ty = PieceType::try_from(value)?; + Ok(side | ty) + } +} + +impl Display for Piece { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.to_char()) + } +} + +impl std::ops::BitOr for Side { + type Output = Piece; + + fn bitor(self, rhs: PieceType) -> Self::Output { + Piece { + side: self, + ty: rhs, + } + } +} + +#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)] +pub struct BySide { + pub white: T, + pub black: T, +} + +impl BySide { + pub fn get(&self, side: Side) -> &T { + match side { + Side::Black => &self.black, + Side::White => &self.white, + } + } + + pub fn get_mut(&mut self, side: Side) -> &mut T { + match side { + Side::Black => &mut self.black, + Side::White => &mut self.white, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::prelude::*; + + // === FEN LAYOUT PARSER === + #[test] + fn start_position_board() { + let mut target = [None; 64]; + let res = + Board::parse_fen_board(&mut target, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"); + assert!(res.is_ok()); + } + + #[test] + fn start_position_board_extra() { + let mut target = [None; 64]; + let res = Board::parse_fen_board( + &mut target, + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNRaaaaaaaaa", + ); + assert!(matches!(res, Err(FENParseError::BoardTooLarge))); + } + + #[test] + fn start_position_board_invalid_piece() { + let mut target = [None; 64]; + let res = + Board::parse_fen_board(&mut target, "rnbqkbnr/vvvvvvvv/8/8/8/8/PPPPPPPP/RNBQKBNR"); + assert!(matches!( + res, + Err(FENParseError::InvalidNotation(NotationError::InvalidPiece( + _ + ))) + )); + } + + #[test] + fn start_position_board_rank_too_short() { + let mut target = [None; 64]; + let res = Board::parse_fen_board(&mut target, "rnbqkbnr/pp/8/8/8/8/PPPPPPPP/RNBQKBNR"); + assert!(matches!( + res, + Err(FENParseError::InvalidNotation(NotationError::InvalidPiece( + '/' + ))) + )); + } + + // === FULL FEN PARSER === + #[test] + fn start_position() { + let board = + Board::from_fen(crate::constants::START_FEN).expect("FEN string should be parsed"); + + assert_eq!( + board.board, + [ + Some(White | Rook), + Some(White | Knight), + Some(White | Bishop), + Some(White | Queen), + Some(White | King), + Some(White | Bishop), + Some(White | Knight), + Some(White | Rook), + Some(White | Pawn), + Some(White | Pawn), + Some(White | Pawn), + Some(White | Pawn), + Some(White | Pawn), + Some(White | Pawn), + Some(White | Pawn), + Some(White | Pawn), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + Some(Black | Pawn), + Some(Black | Pawn), + Some(Black | Pawn), + Some(Black | Pawn), + Some(Black | Pawn), + Some(Black | Pawn), + Some(Black | Pawn), + Some(Black | Pawn), + Some(Black | Rook), + Some(Black | Knight), + Some(Black | Bishop), + Some(Black | Queen), + Some(Black | King), + Some(Black | Bishop), + Some(Black | Knight), + Some(Black | Rook), + ] + ); + + assert_eq!(board.to_move, White); + assert_eq!( + board.castling, + BySide { + white: Castling { + king: true, + queen: true, + }, + black: Castling { + king: true, + queen: true, + }, + } + ); + + assert_eq!(board.en_passant_target, None); + assert_eq!(board.halfmove_clock, 0); + assert_eq!(board.fullmove_number, 1); + } +} diff --git a/src/chess/mv.rs b/src/chess/mv.rs new file mode 100644 index 0000000..84f8300 --- /dev/null +++ b/src/chess/mv.rs @@ -0,0 +1,404 @@ +use serde::{Serialize, Deserialize}; + +use crate::{prelude::*, chess::Side}; +use super::{Board, Coordinate, Piece, PieceType, Castling}; + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub struct Move { + pub from: Coordinate, + pub to: Coordinate, + pub set_en_passant: Option, + pub other: Option>, + pub promotions: Option>, +} + +impl Move { + pub fn new(from: Coordinate, to: Coordinate) -> Self { + Self { + from, + to, + set_en_passant: None, + other: None, + promotions: None, + } + } + + pub fn make(&self, board: &Board) -> Board { + let mut board = self.make_inner(board); + board.to_move = board.to_move.other(); + board + } + + fn make_inner(&self, board: &Board) -> Board { + let mut board = board.clone(); + let piece = board.get(self.from).expect("cannot make a move with no piece"); + if piece.ty == King { + *board.castling.get_mut(board.to_move) = Castling { + king: false, + queen: false, + }; + } + + let start_rank = board.to_move.back_rank(); + + if piece.ty == Rook && self.from.rank == start_rank { + if self.from.file == 0 { + board.castling.get_mut(board.to_move).queen = false; + } + if self.from.file == 7 { + board.castling.get_mut(board.to_move).king = false; + } + } + + *board.get_mut(self.from) = None; + { + let captured_piece = board.get(self.to); + if matches!(captured_piece, Some(piece) if piece.ty == Rook && self.to.rank == board.to_move.other().back_rank()) { + let castling = board.castling.get_mut(board.to_move.other()); + match self.to.file { + 0 => castling.queen = false, + 7 => castling.king = false, + _ => {} + }; + } + } + + let new_piece = if let Some(promotions) = &self.promotions { + match promotions.len() { + 0 => piece, + 1 => promotions[0], + _ => panic!("tried to make a move with more than one promotion"), + } + } else { + piece + }; + + *board.get_mut(self.to) = Some(new_piece); + + if piece.ty == Pawn { + if let Some(en_passant_target) = board.en_passant_target { + if en_passant_target == self.to { + *board.get_mut(Coordinate { + rank: self.from.rank, + file: self.to.file, + }) = None; + } + } + } + + board.en_passant_target = self.set_en_passant; + + if let Some(other) = &self.other { + other.make_inner(&board) + } else { + board + } + } +} + +const CARDINALS: [Coordinate; 4] = [ + Coordinate { rank: 1, file: 0 }, + Coordinate { rank: -1, file: 0 }, + Coordinate { rank: 0, file: 1 }, + Coordinate { rank: 0, file: -1 }, +]; + +const DIAGONALS: [Coordinate; 4] = [ + Coordinate { rank: 1, file: 1 }, + Coordinate { rank: -1, file: -1 }, + Coordinate { rank: -1, file: 1 }, + Coordinate { rank: 1, file: -1 }, +]; + +// Wish there was a way to statically concatenate CARDIANALS and DIAGONALS so I didn't have to copy-paste these. +const ALL_DIRECTIONS: [Coordinate; 8] = [ + Coordinate { rank: 1, file: 0 }, + Coordinate { rank: -1, file: 0 }, + Coordinate { rank: 0, file: 1 }, + Coordinate { rank: 0, file: -1 }, + Coordinate { rank: 1, file: 1 }, + Coordinate { rank: -1, file: -1 }, + Coordinate { rank: -1, file: 1 }, + Coordinate { rank: 1, file: -1 }, +]; +// And no I can't be bothered to write a macro lol + +const KNIGHT: [Coordinate; 8] = [ + Coordinate { rank: 1, file: 2 }, + Coordinate { rank: 2, file: 1 }, + Coordinate { rank: -1, file: -2 }, + Coordinate { rank: -2, file: -1 }, + Coordinate { rank: -1, file: 2 }, + Coordinate { rank: -2, file: 1 }, + Coordinate { rank: 1, file: -2 }, + Coordinate { rank: 2, file: -1 }, +]; + +const PAWN_CAPTURES: [Coordinate; 2] = [ + Coordinate { rank: 0, file: 1 }, + Coordinate { rank: 0, file: -1 }, +]; + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +enum MoveResult { + NoCapture, + Invalid, + Capture, +} + +fn add_if bool>(board: &Board, moves: &mut Vec, mv: Move, predicate: F) -> MoveResult { + if !mv.to.is_board_position() { + return MoveResult::Invalid; + } + + let target_piece = board.board[mv.to.to_index()]; + + let result = if let Some(target_piece) = target_piece { + if target_piece.side == board.to_move { + MoveResult::Invalid + } else { + MoveResult::Capture + } + } else if matches!(board.get(mv.from), Some(piece) if piece.ty == Pawn) && matches!(board.en_passant_target, Some(target) if target == mv.to) { + MoveResult::Capture + } else { + MoveResult::NoCapture + }; + + if predicate(result) { + moves.push(mv); + } + + result +} + +fn add_if_valid(board: &Board, moves: &mut Vec, mv: Move) -> MoveResult { + add_if(board, moves, mv, |r| r != MoveResult::Invalid) +} + +fn add_if_not_capture(board: &Board, moves: &mut Vec, mv: Move) -> MoveResult { + add_if(board, moves, mv, |r| r == MoveResult::NoCapture) +} + +fn add_if_capture(board: &Board, moves: &mut Vec, mv: Move) -> MoveResult { + add_if(board, moves, mv, |r| r == MoveResult::Capture) +} + +fn generate_castling_moves(board: &Board, moves: &mut Vec) { + fn test_move(board: &Board, king_from: Coordinate, king_to: Coordinate, rook_from: Coordinate, rook_to: Coordinate) -> Option { + let king = board.get(king_from); + let rook = board.get(rook_from); + let (king, rook) = match (king, rook) { + (Some(king), Some(rook)) => (*king, *rook), + _ => return None, + }; + if king.ty != King || rook.ty != Rook { + return None; + } + + if board.get(king_to).is_some() || board.get(rook_to).is_some() { + return None; + } + + for mid in Coordinate::between(king_from, king_to) { + if board.get(mid).is_some() { + return None; + } + let test_state = Move::new(king_from, mid).make(&board); + if *test_state.calc_check_state().get(board.to_move) { + return None; + } + } + + for mid in Coordinate::between(rook_from, rook_to) { + if board.get(mid).is_some() { + return None; + } + } + + Some(Move { + from: king_from, + to: king_to, + other: Some(Box::new(Move::new(rook_from, rook_to))), + promotions: None, + set_en_passant: None, + }) + } + + let castling = board.castling.get(board.to_move); + + // Cannot castle out of check + if (castling.queen || castling.king) && *board.calc_check_state().get(board.to_move) { + return; + } + + let rank = match board.to_move { + Black => 7, + White => 0, + }; + let king = Coordinate { + rank, + file: 4, + }; + if castling.queen { + let rook = Coordinate { + rank, + file: 0, + }; + let king_target = Coordinate { + rank, + file: 2, + }; + let rook_target = Coordinate { + rank, + file: 3, + }; + if let Some(mv) = test_move(board, king, king_target, rook, rook_target) { + moves.push(mv); + } + } + if castling.king { + let rook = Coordinate { + rank, + file: 7, + }; + let king_target = Coordinate { + rank, + file: 6, + }; + let rook_target = Coordinate { + rank, + file: 5, + }; + if let Some(mv) = test_move(board, king, king_target, rook, rook_target) { + moves.push(mv); + } + } +} + +fn generate_pawn_moves(board: &Board, moves: &mut Vec) { + fn pawn_move(side: Side, from: Coordinate, to: Coordinate, set_en_passant: Option) -> Move { + let promotion_rank = match side { + Black => 0, + White => 7, + }; + + let promotions = if to.rank == promotion_rank { + Some(vec![ + side | Queen, + side | Rook, + side | Bishop, + side | Knight, + ]) + } else { + None + }; + + let mv = Move { + from, + to, + other: None, + promotions, + set_en_passant, + }; + + mv + } + + let (start_rank, direction) = match board.to_move { + Black => (48..56, Coordinate { rank: -1, file: 0 }), + White => (8..16, Coordinate { rank: 1, file: 0 }), + }; + let pawn = board.to_move | Pawn; + for (index, piece) in board.board.iter().enumerate() { + match piece { + None => continue, + Some(p) if *p != pawn => continue, + _ => {} + } + let from = Coordinate::from_index(index); + let forward_res = add_if_not_capture(board, moves, pawn_move(board.to_move, from, from + direction, None)); + if forward_res == MoveResult::NoCapture && start_rank.contains(&index) { + add_if_not_capture(board, moves, pawn_move(board.to_move, from, from + (direction * 2), Some(from + direction))); + } + + for capture in PAWN_CAPTURES { + add_if_capture(board, moves, pawn_move(board.to_move, from, from + direction + capture, None)); + } + } +} + +fn generate_line_moves( + board: &Board, + moves: &mut Vec, + ty: PieceType, + directions: &[Coordinate], + distance_limit: isize, +) { + let target_piece = board.to_move | ty; + for (index, piece) in board.board.iter().enumerate() { + if let Some(piece) = piece { + if piece != &target_piece { + continue; + } + let from = Coordinate::from_index(index); + for direction in directions { + let mut multiplier = 1; + while add_if_valid( + board, + moves, + Move::new(from, from + (*direction * multiplier)), + ) == MoveResult::NoCapture + && multiplier < distance_limit + { + multiplier += 1; + } + } + } + } +} + +/// Same as `generate_pseudolegal`, but excludes castling moves +pub fn generate_pseudolegal_captures(board: &Board, moves: &mut Vec) { + generate_line_moves(board, moves, King, &ALL_DIRECTIONS, 1); + generate_line_moves(board, moves, Queen, &ALL_DIRECTIONS, 8); + generate_line_moves(board, moves, Rook, &CARDINALS, 8); + generate_line_moves(board, moves, Knight, &KNIGHT, 1); + generate_line_moves(board, moves, Bishop, &DIAGONALS, 8); + generate_pawn_moves(board, moves); +} + +pub fn generate_pseudolegal(board: &Board, moves: &mut Vec) { + generate_pseudolegal_captures(board, moves); + generate_castling_moves(board, moves); +} + +pub fn generate_legal(board: &Board) -> Vec { + let mut moves = vec![]; + generate_pseudolegal(board, &mut moves); + moves.into_iter().filter(|mv| { + if mv.other.is_some() { + // Cannot castle out of check + if *board.calc_check_state().get(board.to_move) { + return false; + } + } + let test_board = mv.make(board); + !*test_board.calc_check_state().get(board.to_move) + }).collect::>() +} + +#[cfg(test)] +mod test { + use super::*; + + use crate::constants::START_FEN; + + #[test] + fn start_position_pseudolegal() { + let board = Board::from_fen(START_FEN).expect("valid board"); + let mut moves = vec![]; + generate_pseudolegal(&board, &mut moves); + assert_eq!(moves.len(), 20); + } +} diff --git a/src/chess/perft.rs b/src/chess/perft.rs new file mode 100644 index 0000000..b9ceade --- /dev/null +++ b/src/chess/perft.rs @@ -0,0 +1,66 @@ +use crate::prelude::*; +use std::io::Write; +use rayon::prelude::*; + +fn perft_board(board: &Board, depth: u64, start_depth: u64) -> u64 { + if depth == 0 { + return 1; + } + let mut moves = vec![]; + crate::chess::mv::generate_pseudolegal(board, &mut moves); + + let count = moves.into_par_iter().map(|mv| { + let mut count = 0; + if let Some(promotions) = mv.promotions { + for promotion in promotions { + let mv = Move { + promotions: Some(vec![promotion]), + other: mv.other.clone(), + ..mv + }; + let new_board = mv.make(board); + let check = new_board.calc_check_state(); + if *check.get(board.to_move) { + continue; + } + let this_count = perft_board(&new_board, depth - 1, start_depth); + count += this_count; + #[cfg(test)] + if depth == start_depth { + println!("{from}{to}: {this_count}", from = mv.from, to = mv.to); + } + } + } else { + let new_board = mv.make(board); + if *new_board.calc_check_state().get(board.to_move) { + return 0; + } + let this_count = perft_board(&new_board, depth - 1, start_depth); + count += this_count; + #[cfg(test)] + if depth == start_depth { + println!("{from}{to}: {this_count}", from = mv.from, to = mv.to); + // println!("mv={mv:?} {piece:?}", piece = board.get(mv.from)); + } + } + count + }).sum(); + + count +} + +pub fn run_perft(fen: &str, depth: u64, expected_positions: u64) -> bool { + let board = Board::from_fen(fen).expect("failed to parse position"); + + println!("Running perft on position {fen:?} with depth {depth}, expecting {expected_positions} positions..."); + std::io::stdout().flush().unwrap(); + + let positions = perft_board(&board, depth, depth); + if positions == expected_positions { + println!("Passed perft on position {fen:?} with depth {depth}, expecting {expected_positions} positions! "); + } else { + println!("Failed perft on position {fen:?} with depth {depth}, expecting {expected_positions} positions, found {positions}!"); + } + + positions == expected_positions +} diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 0000000..6ac3192 --- /dev/null +++ b/src/constants.rs @@ -0,0 +1,6 @@ +pub const START_FEN: &str = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; +pub const POSITION_2: &str = "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 0"; +pub const POSITION_3: &str = "8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 0"; +pub const POSITION_4: &str = "r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1"; +pub const POSITION_5: &str = "rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8"; +pub const POSITION_6: &str = "r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10"; diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..3c4c463 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,25 @@ +#[derive(Debug, thiserror::Error)] +pub enum FENParseError { + #[error("not enough sections")] + NotEnoughSections, + #[error("expected slash")] + ExpectedSlash, + #[error("board too large")] + BoardTooLarge, + #[error("invalid notation: {0}")] + InvalidNotation(#[from] NotationError), + #[error("{0}")] + InvalidCount(#[from] std::num::ParseIntError), +} + +#[derive(Debug, thiserror::Error)] +pub enum NotationError { + #[error("expected length {length} for notation, expected: {expected}")] + InvalidLength { length: usize, expected: usize }, + #[error("invalid piece character: {0}")] + InvalidPiece(char), + #[error("invalid side: {0}")] + InvalidSide(String), + #[error("{0:?} is invalid notation")] + Other(String), +} diff --git a/src/game.rs b/src/game.rs new file mode 100644 index 0000000..8f61d74 --- /dev/null +++ b/src/game.rs @@ -0,0 +1,144 @@ +use std::collections::HashMap; + +use uuid::Uuid; +use xtra::{prelude::*, WeakAddress}; + +use crate::{prelude::{Board, Move}, constants::START_FEN, chess::{Side, mv::generate_legal}, player::{Player, OutgoingPlayerEvent, IncomingPlayerEvent}}; + +#[derive(Actor)] +pub struct GameManager { + games: HashMap>, +} + +impl GameManager { + pub fn new() -> Self { + Self { + games: HashMap::new(), + } + } +} + +#[derive(Clone)] +pub struct JoinGame { + pub game_id: Uuid, + pub player: WeakAddress, +} + +pub enum JoinGameResponse { + Success { + side: Side, + game: Address, + }, + Full, +} + +#[async_trait] +impl Handler for GameManager { + type Return = Option; + + async fn handle(&mut self, join_game: JoinGame, _ctx: &mut Context) -> Self::Return { + if let Some(game) = self.games.get(&join_game.game_id) { + let res = game.send(join_game.clone()).await.ok() + .map(|r| match r { + Some(side) => JoinGameResponse::Success { side, game: game.clone() }, + None => JoinGameResponse::Full, + }); + if res.is_none() { + self.games.remove(&join_game.game_id); + } + res + } else { + let game = ChessGame::new(join_game.player); + let game = xtra::spawn_tokio(game, Mailbox::unbounded()); + self.games.insert(join_game.game_id, game.clone()); + Some(JoinGameResponse::Success { + side: Side::White, + game, + }) + } + } +} + +#[derive(Actor)] +pub struct ChessGame { + board: Board, + white: WeakAddress, + black: Option>, +} + +#[async_trait] +impl Handler for ChessGame { + type Return = Option; + + async fn handle(&mut self, join_game: JoinGame, _ctx: &mut Context) -> Self::Return { + if self.black.is_some() { + None + } else { + self.black = Some(join_game.player); + self.broadcast_new_board(); + self.send_possible_moves(); + Some(Side::Black) + } + } +} + +#[derive(Debug, serde::Deserialize)] +pub struct IncomingEvent { + pub data: IncomingPlayerEvent, + pub side: Side, +} + +#[async_trait] +impl Handler for ChessGame { + type Return = (); + + async fn handle(&mut self, incoming_event: IncomingEvent, _ctx: &mut Context) -> Self::Return { + match incoming_event.data { + IncomingPlayerEvent::MakeMove { mv } => { + if incoming_event.side != self.board.to_move { + tracing::warn!(?incoming_event.side, ?mv, "other player tried to make move"); + return; + } + let legal_moves = generate_legal(&self.board); + if legal_moves.contains(&mv) { + self.board = mv.make(&self.board); + self.broadcast_new_board(); + self.send_possible_moves(); + } + }, + } + } +} + +impl ChessGame { + pub fn new(white: WeakAddress) -> Self { + Self { + board: Board::from_fen(START_FEN).expect("start fen is invalid"), + white, + black: None, + } + } + + fn broadcast_new_board(&self) { + // TODO: Handle players disconnecting + tokio::spawn(self.white.send(OutgoingPlayerEvent::BoardUpdate { + board: self.board.board.to_vec(), + })); + if let Some(black) = &self.black { + tokio::spawn(black.send(OutgoingPlayerEvent::BoardUpdate { + board: self.board.board.to_vec(), + })); + } + } + + fn send_possible_moves(&self) { + let (current, other) = match &self.board.to_move { + Side::Black => (self.black.clone().unwrap(), self.white.clone()), + Side::White => (self.white.clone(), self.black.clone().unwrap()), + }; + let moves = generate_legal(&self.board); + // TODO: handle player disconnects + tokio::spawn(current.send(OutgoingPlayerEvent::PossibleMoves { moves })); + tokio::spawn(other.send(OutgoingPlayerEvent::PossibleMoves { moves: Vec::new() })); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..99122a9 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,18 @@ +#[cfg(not(debug_assertions))] +pub mod assets; +pub mod chess; +pub mod constants; +pub mod error; +pub mod game; +pub mod player; +pub mod routes; + +pub mod prelude { + pub use crate::chess::Board; + pub use crate::chess::Piece; + + pub use crate::chess::PieceType::*; + pub use crate::chess::Side::*; + + pub use crate::chess::mv::Move; +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..d5ae5b9 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,29 @@ +use axum::Extension; +use chs::{prelude::*, game::GameManager}; +use tower_http::trace::TraceLayer; +use tracing_subscriber::prelude::*; +use xtra::Mailbox; + +#[tokio::main] +async fn main() { + tracing_subscriber::registry() + .with(tracing_subscriber::filter::EnvFilter::new( + std::env::var("RUST_LOG").unwrap_or_else(|_| "debug,hyper=info".into()), + )) + .with(tracing_subscriber::fmt::layer()) + .init(); + + tracing::info!("Hello, world!"); + + let game_manager = GameManager::new(); + let game_manager = xtra::spawn_tokio(game_manager, Mailbox::unbounded()); + + let app = chs::routes::routes() + .layer(Extension(game_manager)) + .layer(TraceLayer::new_for_http()); + + axum::Server::bind(&format!("0.0.0.0:3000").parse().unwrap()) + .serve(app.into_make_service()) + .await + .unwrap(); +} diff --git a/src/player.rs b/src/player.rs new file mode 100644 index 0000000..6af8ccf --- /dev/null +++ b/src/player.rs @@ -0,0 +1,78 @@ +use futures::{channel::mpsc::UnboundedSender, SinkExt}; +use serde::{Deserialize, Serialize}; +use xtra::prelude::*; + +use crate::{prelude::*, chess::Side, game::{ChessGame, IncomingEvent}}; + +#[derive(Actor)] +pub struct Player { + sink: UnboundedSender, + game: Option, +} + +pub struct GameInfo { + pub side: Side, + pub game: Address, +} + +impl Player { + pub fn new(sink: UnboundedSender) -> Self { + Self { + sink, + game: None, + } + } +} + +#[derive(Debug, Serialize)] +#[serde(tag = "event", content = "data")] +pub enum OutgoingPlayerEvent { + BoardUpdate { + board: Vec>, + }, + PossibleMoves { + moves: Vec, + }, +} + +#[async_trait] +impl Handler for Player { + type Return = (); + + async fn handle(&mut self, outgoing_event: OutgoingPlayerEvent, _ctx: &mut Context) -> Self::Return { + // TODO: Better error handling + self.sink.send(outgoing_event).await.expect("failed to send outgoing event"); + } +} + +#[derive(Debug, Deserialize)] +#[serde(tag = "event", content = "data")] +pub enum IncomingPlayerEvent { + MakeMove { + #[serde(rename = "move")] + mv: Move, + } +} + +#[async_trait] +impl Handler for Player { + type Return = (); + + async fn handle(&mut self, incoming_event: IncomingPlayerEvent, _ctx: &mut Context) -> Self::Return { + if let Some(game) = &self.game { + game.game.send(IncomingEvent { + data: incoming_event, + side: game.side, + }).await.expect("game disconnected"); + } + } +} + +#[async_trait] +impl Handler for Player { + type Return = (); + + async fn handle(&mut self, game_info: GameInfo, _ctx: &mut Context) -> Self::Return { + self.game = Some(game_info); + } +} diff --git a/src/routes.rs b/src/routes.rs new file mode 100644 index 0000000..273f833 --- /dev/null +++ b/src/routes.rs @@ -0,0 +1,121 @@ +use axum::{Router, extract::{WebSocketUpgrade, Path, ws::{Message, WebSocket}}, response::IntoResponse, routing::{get, get_service}, http::{StatusCode, Uri}, Extension}; +use futures::{StreamExt, SinkExt, stream::{SplitStream, SplitSink}, channel::mpsc::{self, UnboundedSender}}; +use tokio::task::JoinHandle; +use tower_http::services::ServeDir; +use uuid::Uuid; +use xtra::{Mailbox, Address}; + +use crate::{player::{Player, IncomingPlayerEvent, OutgoingPlayerEvent, GameInfo}, game::{GameManager, JoinGame, JoinGameResponse}}; +#[cfg(not(debug_assertions))] +use crate::assets::StaticFile; + +pub fn routes() -> Router { + let router = Router::new() + .route("/ws/:id", get(ws_handler)); + + #[cfg(not(debug_assertions))] + let router = router + .route("/", get(index)) + .fallback(get(fallback)); + + router +} + +#[cfg(not(debug_assertions))] +async fn index() -> impl IntoResponse { + StaticFile("index.html") +} + +#[cfg(not(debug_assertions))] +async fn fallback(uri: Uri) -> impl IntoResponse { + StaticFile(uri.path().trim_start_matches('/').to_owned()) +} + +async fn ws_handler( + ws: WebSocketUpgrade, + Path(id): Path, + Extension(game_manager): Extension>, +) -> impl IntoResponse { + ws.on_upgrade(move |ws| handle_socket(ws, id, game_manager)) +} + +async fn handle_socket(socket: WebSocket, id: Uuid, game_manager: Address) { + let (tx, rx) = socket.split(); + + let (tx, tx_task) = socket_send(tx); + + let player = Player::new(tx); + let player = xtra::spawn_tokio(player, Mailbox::unbounded()); + + let res = game_manager.send(JoinGame { game_id: id, player: player.downgrade() }).await + .expect("game manager disconnected"); + + if let Some(res) = res { + if let JoinGameResponse::Success { side, game } = res { + player.send(GameInfo { side, game }).await.expect("player disconnected"); + let rx_task = socket_recv(rx, player); + + tokio::select! { + _ = rx_task => {}, + _ = tx_task => {}, + } + } + } +} + +fn socket_send(mut tx: SplitSink) -> (UnboundedSender, JoinHandle<()>) { + let (message_tx, mut rx) = mpsc::unbounded(); + + let task = tokio::spawn(async move { + while let Some(message) = rx.next().await { + match serde_json::to_string(&message) { + Ok(json) => { + if tx.send(Message::Text(json)).await.is_err() { + return; + } + } + Err(e) => { + tracing::error!(?e, ?message, "failed to encode outgoing message"); + } + } + } + }); + + (message_tx, task) +} + +fn socket_recv(mut rx: SplitStream, player: Address) -> JoinHandle<()> { + let task = tokio::spawn(async move { + while let Some(msg) = rx.next().await { + if let Ok(msg) = msg { + match msg { + Message::Text(t) => { + let message = serde_json::from_str::(&t); + match message { + Ok(message) => { + if player.send(message).await.is_err() { + return; + } + } + Err(e) => { + tracing::error!(?e, "client send invalid data"); + } + } + } + Message::Close(_) => { + tracing::info!("client disconnected"); + return; + } + frame => { + tracing::warn!(?frame, "client sent invalid frame type"); + } + } + } else { + tracing::info!("client disconnected"); + return; + } + } + }); + + task +} diff --git a/tests/perft.rs b/tests/perft.rs new file mode 100644 index 0000000..09d51e5 --- /dev/null +++ b/tests/perft.rs @@ -0,0 +1,48 @@ +macro_rules! perft_test { + ($name:ident, $fen:expr, $depth:expr, $expected:expr) => { + #[test] + fn $name() { + let result = chs::chess::perft::run_perft($fen, $depth, $expected); + assert!(result); + } + }; +} + +mod perft { + use chs::constants::*; + + perft_test!(start_fen_depth_0, START_FEN, 0, 1); + perft_test!(start_fen_depth_1, START_FEN, 1, 20); + perft_test!(start_fen_depth_2, START_FEN, 2, 400); + perft_test!(start_fen_depth_3, START_FEN, 3, 8_902); + perft_test!(start_fen_depth_4, START_FEN, 4, 197_281); + perft_test!(start_fen_depth_5, START_FEN, 5, 4_865_609); + + perft_test!(position_2_depth_1, POSITION_2, 1, 48); + perft_test!(position_2_depth_2, POSITION_2, 2, 2_039); + perft_test!(position_2_depth_3, POSITION_2, 3, 97_862); + perft_test!(position_2_depth_4, POSITION_2, 4, 4_085_603); + + perft_test!(position_3_depth_1, POSITION_3, 1, 14); + perft_test!(position_3_depth_2, POSITION_3, 2, 191); + perft_test!(position_3_depth_3, POSITION_3, 3, 2_812); + perft_test!(position_3_depth_4, POSITION_3, 4, 43_238); + perft_test!(position_3_depth_5, POSITION_3, 5, 674_624); + perft_test!(position_3_depth_6, POSITION_3, 6, 11_030_083); + + perft_test!(position_4_depth_1, POSITION_4, 1, 6); + perft_test!(position_4_depth_2, POSITION_4, 2, 264); + perft_test!(position_4_depth_3, POSITION_4, 3, 9_467); + perft_test!(position_4_depth_4, POSITION_4, 4, 422_333); + perft_test!(position_4_depth_5, POSITION_4, 5, 15_833_292); + + perft_test!(position_5_depth_1, POSITION_5, 1, 44); + perft_test!(position_5_depth_2, POSITION_5, 2, 1_486); + perft_test!(position_5_depth_3, POSITION_5, 3, 62_379); + perft_test!(position_5_depth_4, POSITION_5, 4, 2_103_487); + + perft_test!(position_6_depth_1, POSITION_6, 1, 46); + perft_test!(position_6_depth_2, POSITION_6, 2, 2_079); + perft_test!(position_6_depth_3, POSITION_6, 3, 89_890); + perft_test!(position_6_depth_4, POSITION_6, 4, 3_894_594); +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..78f6ded --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "types": ["vite/client"], + "noEmit": true, + "isolatedModules": true, + "strict": true, + } +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..9ffae89 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "composite": true, + "module": "esnext", + "moduleResolution": "node" + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..6343c5f --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vite'; +import solidPlugin from 'vite-plugin-solid'; +import devtools from 'solid-devtools/vite'; + +export default defineConfig({ + plugins: [ + solidPlugin(), + devtools({ + // Will automatically add names when creating signals, memos, stores, or mutables + name: true, + }), + ], + server: { + hmr: { + clientPort: parseInt(process.env.CLIENT_PORT || '5173'), + }, + }, + build: { + target: 'esnext', + }, +});