Compare commits
67 Commits
v1.0.0
...
v2.0.0-rc3
9
.gitignore
vendored
9
.gitignore
vendored
@@ -24,7 +24,14 @@ target
|
|||||||
# Added by cargo
|
# Added by cargo
|
||||||
|
|
||||||
/target
|
/target
|
||||||
|
/__pycache__
|
||||||
|
|
||||||
.env
|
.env
|
||||||
|
|
||||||
node_modules/
|
/node_modules
|
||||||
|
|
||||||
|
bracket_pairings.txt
|
||||||
|
|
||||||
|
.cursor/
|
||||||
|
.claude/
|
||||||
|
.codex/
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
combine_control_expr = false
|
combine_control_expr = false
|
||||||
chain_width = 100
|
chain_width = 100
|
||||||
|
tab_spaces = 2
|
||||||
554
Cargo.lock
generated
554
Cargo.lock
generated
@@ -4,9 +4,9 @@ version = 4
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.100"
|
version = "1.0.102"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
@@ -35,10 +35,16 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "byteorder"
|
||||||
version = "1.10.1"
|
version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytes"
|
||||||
|
version = "1.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
@@ -46,6 +52,17 @@ version = "1.0.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chacha20"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures 0.3.0",
|
||||||
|
"rand_core 0.10.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "connect4-moderator-server"
|
name = "connect4-moderator-server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -53,7 +70,8 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"rand",
|
"local-ip-address",
|
||||||
|
"rand 0.10.1",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-tungstenite",
|
"tokio-tungstenite",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -69,6 +87,15 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpufeatures"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
@@ -79,12 +106,78 @@ dependencies = [
|
|||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling"
|
||||||
|
version = "0.20.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core",
|
||||||
|
"darling_macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_core"
|
||||||
|
version = "0.20.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
|
||||||
|
dependencies = [
|
||||||
|
"fnv",
|
||||||
|
"ident_case",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"strsim",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_macro"
|
||||||
|
version = "0.20.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "data-encoding"
|
name = "data-encoding"
|
||||||
version = "2.9.0"
|
version = "2.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
|
checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_builder"
|
||||||
|
version = "0.20.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
|
||||||
|
dependencies = [
|
||||||
|
"derive_builder_macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_builder_core"
|
||||||
|
version = "0.20.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
|
||||||
|
dependencies = [
|
||||||
|
"darling",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_builder_macro"
|
||||||
|
version = "0.20.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
|
||||||
|
dependencies = [
|
||||||
|
"derive_builder_core",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.7"
|
version = "0.10.7"
|
||||||
@@ -95,6 +188,18 @@ dependencies = [
|
|||||||
"crypto-common",
|
"crypto-common",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equivalent"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fnv"
|
name = "fnv"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
@@ -102,16 +207,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-core"
|
name = "foldhash"
|
||||||
version = "0.3.31"
|
version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-core"
|
||||||
|
version = "0.3.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-macro"
|
name = "futures-macro"
|
||||||
version = "0.3.31"
|
version = "0.3.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -120,28 +231,27 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-sink"
|
name = "futures-sink"
|
||||||
version = "0.3.31"
|
version = "0.3.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-task"
|
name = "futures-task"
|
||||||
version = "0.3.31"
|
version = "0.3.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-util"
|
name = "futures-util"
|
||||||
version = "0.3.31"
|
version = "0.3.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-macro",
|
"futures-macro",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"pin-utils",
|
|
||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -163,10 +273,57 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"r-efi",
|
"r-efi 5.3.0",
|
||||||
"wasip2",
|
"wasip2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"r-efi 6.0.0",
|
||||||
|
"rand_core 0.10.0",
|
||||||
|
"wasip2",
|
||||||
|
"wasip3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getset"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9cf0fc11e47561d47397154977bc219f4cf809b2974facc3ccb3b89e2436f912"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-error2",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.15.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||||
|
dependencies = [
|
||||||
|
"foldhash",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.16.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "1.3.1"
|
version = "1.3.1"
|
||||||
@@ -184,6 +341,30 @@ version = "1.10.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "id-arena"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ident_case"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "2.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
||||||
|
dependencies = [
|
||||||
|
"equivalent",
|
||||||
|
"hashbrown 0.16.1",
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.15"
|
version = "1.0.15"
|
||||||
@@ -196,12 +377,29 @@ version = "1.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "leb128fmt"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.177"
|
version = "0.2.177"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "local-ip-address"
|
||||||
|
version = "0.6.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "79ef8c257c92ade496781a32a581d43e3d512cf8ce714ecf04ea80f93ed0ff4a"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"neli",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.14"
|
version = "0.4.14"
|
||||||
@@ -217,6 +415,12 @@ version = "0.4.28"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -228,6 +432,35 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "neli"
|
||||||
|
version = "0.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22f9786d56d972959e1408b6a93be6af13b9c1392036c5c1fafa08a1b0c6ee87"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"byteorder",
|
||||||
|
"derive_builder",
|
||||||
|
"getset",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"neli-proc-macros",
|
||||||
|
"parking_lot",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "neli-proc-macros"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "05d8d08c6e98f20a62417478ebf7be8e1425ec9acecc6f63e22da633f6b71609"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"serde",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-ansi-term"
|
name = "nu-ansi-term"
|
||||||
version = "0.50.3"
|
version = "0.50.3"
|
||||||
@@ -272,12 +505,6 @@ version = "0.2.16"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pin-utils"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.21"
|
version = "0.2.21"
|
||||||
@@ -287,6 +514,38 @@ dependencies = [
|
|||||||
"zerocopy",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prettyplease"
|
||||||
|
version = "0.2.37"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error-attr2"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error2"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-error-attr2",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.103"
|
version = "1.0.103"
|
||||||
@@ -311,6 +570,12 @@ version = "5.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "r-efi"
|
||||||
|
version = "6.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.9.2"
|
version = "0.9.2"
|
||||||
@@ -318,7 +583,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rand_chacha",
|
"rand_chacha",
|
||||||
"rand_core",
|
"rand_core 0.9.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
|
||||||
|
dependencies = [
|
||||||
|
"chacha20",
|
||||||
|
"getrandom 0.4.2",
|
||||||
|
"rand_core 0.10.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -328,7 +604,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ppv-lite86",
|
"ppv-lite86",
|
||||||
"rand_core",
|
"rand_core 0.9.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -337,9 +613,15 @@ version = "0.9.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom 0.3.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.5.18"
|
version = "0.5.18"
|
||||||
@@ -355,6 +637,55 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "semver"
|
||||||
|
version = "1.0.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.228"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||||
|
dependencies = [
|
||||||
|
"serde_core",
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_core"
|
||||||
|
version = "1.0.228"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.228"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.149"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"memchr",
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
|
"zmij",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha1"
|
name = "sha1"
|
||||||
version = "0.10.6"
|
version = "0.10.6"
|
||||||
@@ -362,7 +693,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cpufeatures",
|
"cpufeatures 0.2.17",
|
||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -406,6 +737,12 @@ dependencies = [
|
|||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.110"
|
version = "2.0.110"
|
||||||
@@ -448,9 +785,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.48.0"
|
version = "1.50.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
|
checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -488,9 +825,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing"
|
name = "tracing"
|
||||||
version = "0.1.43"
|
version = "0.1.44"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647"
|
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"tracing-attributes",
|
"tracing-attributes",
|
||||||
@@ -510,9 +847,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-core"
|
name = "tracing-core"
|
||||||
version = "0.1.35"
|
version = "0.1.36"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c"
|
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"valuable",
|
"valuable",
|
||||||
@@ -554,7 +891,7 @@ dependencies = [
|
|||||||
"http",
|
"http",
|
||||||
"httparse",
|
"httparse",
|
||||||
"log",
|
"log",
|
||||||
"rand",
|
"rand 0.9.2",
|
||||||
"sha1",
|
"sha1",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"utf-8",
|
"utf-8",
|
||||||
@@ -572,6 +909,12 @@ version = "1.0.22"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-xid"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf-8"
|
name = "utf-8"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
@@ -602,7 +945,50 @@ version = "1.0.1+wasi-0.2.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wit-bindgen",
|
"wit-bindgen 0.46.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasip3"
|
||||||
|
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen 0.51.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-encoder"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
|
||||||
|
dependencies = [
|
||||||
|
"leb128fmt",
|
||||||
|
"wasmparser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-metadata"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"indexmap",
|
||||||
|
"wasm-encoder",
|
||||||
|
"wasmparser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmparser"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"hashbrown 0.15.5",
|
||||||
|
"indexmap",
|
||||||
|
"semver",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -700,6 +1086,94 @@ version = "0.46.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen"
|
||||||
|
version = "0.51.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen-rust-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-core"
|
||||||
|
version = "0.51.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"heck",
|
||||||
|
"wit-parser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-rust"
|
||||||
|
version = "0.51.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"heck",
|
||||||
|
"indexmap",
|
||||||
|
"prettyplease",
|
||||||
|
"syn",
|
||||||
|
"wasm-metadata",
|
||||||
|
"wit-bindgen-core",
|
||||||
|
"wit-component",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-rust-macro"
|
||||||
|
version = "0.51.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"prettyplease",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wit-bindgen-core",
|
||||||
|
"wit-bindgen-rust",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-component"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"bitflags",
|
||||||
|
"indexmap",
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"wasm-encoder",
|
||||||
|
"wasm-metadata",
|
||||||
|
"wasmparser",
|
||||||
|
"wit-parser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-parser"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"id-arena",
|
||||||
|
"indexmap",
|
||||||
|
"log",
|
||||||
|
"semver",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"unicode-xid",
|
||||||
|
"wasmparser",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.27"
|
version = "0.8.27"
|
||||||
@@ -719,3 +1193,9 @@ dependencies = [
|
|||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zmij"
|
||||||
|
version = "1.0.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||||
|
|||||||
15
Cargo.toml
15
Cargo.toml
@@ -4,11 +4,12 @@ version = "0.1.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tokio = { version = "1.48", features = ["full"] }
|
tokio = { version = "1.50", features = ["full"] }
|
||||||
tokio-tungstenite = "0.28"
|
tokio-tungstenite = "0.28"
|
||||||
futures-util = "0.3.31"
|
futures-util = "0.3"
|
||||||
tracing = "0.1.43"
|
tracing = "0.1"
|
||||||
tracing-subscriber = "0.3.22"
|
tracing-subscriber = "0.3"
|
||||||
anyhow = "1.0.100"
|
anyhow = "1.0"
|
||||||
rand = "0.9.2"
|
rand = "0.10"
|
||||||
async-trait = "0.1.89"
|
async-trait = "0.1"
|
||||||
|
local-ip-address = "0.6"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM rust:1.91.1 AS build
|
FROM rust:1.92 AS build
|
||||||
|
|
||||||
RUN rustup target add x86_64-unknown-linux-musl && \
|
RUN rustup target add x86_64-unknown-linux-musl && \
|
||||||
apt update && \
|
apt update && \
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ Download the [`gameloop.py`](https://github.com/joshuafhiggins/connect4-moderato
|
|||||||
|
|
||||||
In order to run your AI, you'll need:
|
In order to run your AI, you'll need:
|
||||||
- Python 3
|
- Python 3
|
||||||
- `pip install websockets` (Windows) or `pip3 install websockets` (Linux/macOS)
|
- `pip install websockets pip-system-certs wakepy` (Windows) or `pip3 install websockets pip-system-certs wakepy` (Linux/macOS)
|
||||||
- `pip install pip-system-certs` (Windows) or `pip3 install pip-system-certs` (Linux/macOS)
|
|
||||||
|
|
||||||
To run the example, run `python gameloop.py` (Windows) or `python3 gameloop.py` (Linux/macOS).
|
To run the example, run `python gameloop.py` (Windows) or `python3 gameloop.py` (Linux/macOS).
|
||||||
|
|
||||||
@@ -34,4 +33,4 @@ A JavaScript debug client that takes raw text as input and prints responses is p
|
|||||||
- `npm i`
|
- `npm i`
|
||||||
- `node debug_client.js`
|
- `node debug_client.js`
|
||||||
|
|
||||||
I also apologize in advance for the code you'll go on to read.
|
I also apologize in advance for the code you'll go on to read.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
const WebSocket = require("ws");
|
const WebSocket = require("ws");
|
||||||
const readline = require("readline");
|
const readline = require("readline");
|
||||||
|
|
||||||
const DEFAULT_URL = "wss://connect4.abunchofknowitalls.com";
|
const DEFAULT_URL = "ws://localhost:8080";
|
||||||
|
|
||||||
let ws;
|
let ws;
|
||||||
let pingInterval;
|
let pingInterval;
|
||||||
|
|||||||
9
docker-compose.yml
Normal file
9
docker-compose.yml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
services:
|
||||||
|
connect4-server:
|
||||||
|
env_file:
|
||||||
|
- ./.env
|
||||||
|
container_name: connect4-server
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- 5102:8080
|
||||||
|
image: joshuafhiggins/connect4-server
|
||||||
@@ -1 +1 @@
|
|||||||
docker build . -t joshuafhiggins/connect4-moderator-server
|
docker build . -t joshuafhiggins/connect4-server
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
docker run -d \
|
|
||||||
--env-file ./.env \
|
|
||||||
--name=connect4-moderator-server \
|
|
||||||
--restart unless-stopped \
|
|
||||||
-p 5102:8080 \
|
|
||||||
joshuafhiggins/connect4-moderator-server
|
|
||||||
76
gameloop.py
76
gameloop.py
@@ -1,19 +1,28 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import websockets
|
import websockets
|
||||||
|
from websockets.exceptions import ConnectionClosed
|
||||||
DEFAULT_SERVER_URL = "wss://connect4.abunchofknowitalls.com"
|
from wakepy import keep
|
||||||
|
|
||||||
from agent import Agent
|
from agent import Agent
|
||||||
|
|
||||||
|
DEFAULT_SERVER_URL = "wss://connect4.abunchofknowitalls.com/ws"
|
||||||
|
RECONNECT_INTERVAL_SECONDS = 5
|
||||||
|
RECONNECT_TIMEOUT_SECONDS = 60
|
||||||
|
MAX_RECONNECT_ATTEMPTS = (
|
||||||
|
(RECONNECT_TIMEOUT_SECONDS + RECONNECT_INTERVAL_SECONDS - 1)
|
||||||
|
// RECONNECT_INTERVAL_SECONDS
|
||||||
|
)
|
||||||
|
|
||||||
async def gameloop(socket):
|
async def gameloop(socket):
|
||||||
player = Agent()
|
player = Agent()
|
||||||
|
|
||||||
while True: # While game is active, continually anticipate messages
|
while True: # Receive messages until the connection closes
|
||||||
message = (await socket.recv()).split(":") # Receive message from server
|
message = (await socket.recv()).split(":") # Receive message from server
|
||||||
|
|
||||||
match message[0]:
|
match message[0]:
|
||||||
case "CONNECT":
|
case "CONNECT":
|
||||||
await socket.send("READY")
|
await socket.send("READY")
|
||||||
|
case "RECONNECT":
|
||||||
|
await socket.send("READY")
|
||||||
|
|
||||||
case "GAME":
|
case "GAME":
|
||||||
if message[1] == "START":
|
if message[1] == "START":
|
||||||
@@ -37,19 +46,54 @@ async def gameloop(socket):
|
|||||||
case "ERROR":
|
case "ERROR":
|
||||||
print(f"{message[0]}: {':'.join(message[1:])}")
|
print(f"{message[0]}: {':'.join(message[1:])}")
|
||||||
|
|
||||||
await socket.close()
|
|
||||||
|
|
||||||
|
|
||||||
async def join_server(username, server_url):
|
async def join_server(username, server_url):
|
||||||
async with websockets.connect(server_url, ping_interval=30, ping_timeout=30) as socket:
|
reconnecting = False
|
||||||
await socket.send(f"CONNECT:{username}")
|
reconnect_deadline = None
|
||||||
await gameloop(socket)
|
reconnect_attempt = 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
async with websockets.connect(server_url, ping_interval=30, ping_timeout=30) as socket:
|
||||||
|
if reconnecting:
|
||||||
|
await socket.send(f"RECONNECT:{username}")
|
||||||
|
print("Reconnected to server.")
|
||||||
|
else:
|
||||||
|
await socket.send(f"CONNECT:{username}")
|
||||||
|
|
||||||
|
reconnect_deadline = None
|
||||||
|
reconnect_attempt = 0
|
||||||
|
await gameloop(socket)
|
||||||
|
except (ConnectionClosed, OSError) as error:
|
||||||
|
print(f"Connection lost ({error}).")
|
||||||
|
else:
|
||||||
|
print("Connection closed.")
|
||||||
|
|
||||||
|
now = asyncio.get_running_loop().time()
|
||||||
|
if reconnect_deadline is None:
|
||||||
|
reconnect_deadline = now + RECONNECT_TIMEOUT_SECONDS
|
||||||
|
print(f"Attempting to reconnect every {RECONNECT_INTERVAL_SECONDS} seconds for up to {RECONNECT_TIMEOUT_SECONDS} seconds...")
|
||||||
|
|
||||||
|
remaining = reconnect_deadline - now
|
||||||
|
if remaining <= 0:
|
||||||
|
print("Failed to reconnect within 60 seconds. Exiting.")
|
||||||
|
return
|
||||||
|
|
||||||
|
reconnecting = True
|
||||||
|
reconnect_attempt += 1
|
||||||
|
wait_time = min(RECONNECT_INTERVAL_SECONDS, remaining)
|
||||||
|
print(
|
||||||
|
f"Reconnect attempt {reconnect_attempt}/{MAX_RECONNECT_ATTEMPTS}: "
|
||||||
|
f"retrying in {wait_time:.0f}s "
|
||||||
|
f"({remaining:.0f}s remaining)."
|
||||||
|
)
|
||||||
|
await asyncio.sleep(wait_time)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
server_url = (
|
with keep.presenting():
|
||||||
input(f"Enter server address [{DEFAULT_SERVER_URL}]: ").strip()
|
server_url = (
|
||||||
or DEFAULT_SERVER_URL
|
input(f"Enter server address [{DEFAULT_SERVER_URL}]: ").strip()
|
||||||
)
|
or DEFAULT_SERVER_URL
|
||||||
username = input("Enter username: ")
|
)
|
||||||
asyncio.run(join_server(username, server_url))
|
username = input("Enter username: ")
|
||||||
|
asyncio.run(join_server(username, server_url))
|
||||||
|
|||||||
57
src/lib.rs
Normal file
57
src/lib.rs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
use std::{collections::HashMap, net::SocketAddr, sync::Arc};
|
||||||
|
|
||||||
|
use rand::RngExt;
|
||||||
|
use tokio::sync::{
|
||||||
|
mpsc::{error::SendError, UnboundedSender},
|
||||||
|
RwLock,
|
||||||
|
};
|
||||||
|
use tokio_tungstenite::tungstenite::Message;
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
tournaments::Tournament,
|
||||||
|
types::{Client, Color, Match},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod server;
|
||||||
|
pub mod tournaments;
|
||||||
|
pub mod types;
|
||||||
|
|
||||||
|
pub type Clients = Arc<RwLock<HashMap<String, Arc<RwLock<Client>>>>>;
|
||||||
|
pub type Usernames = Arc<RwLock<HashMap<SocketAddr, String>>>;
|
||||||
|
pub type Observers = Arc<RwLock<HashMap<SocketAddr, UnboundedSender<Message>>>>;
|
||||||
|
pub type Matches = Arc<RwLock<HashMap<u32, Arc<RwLock<Match>>>>>;
|
||||||
|
pub type Reservations = Arc<RwLock<Vec<(String, String)>>>;
|
||||||
|
pub type WrappedTournament = Arc<RwLock<Option<Arc<RwLock<dyn Tournament + Send + Sync>>>>>;
|
||||||
|
|
||||||
|
pub const SERVER_PLAYER_USERNAME: &str = "The Server";
|
||||||
|
// pub const SERVER_PLAYER_ADDR: &str = "127.0.0.1:6666";
|
||||||
|
|
||||||
|
pub async fn broadcast_message(observers: &Observers, msg: &str) {
|
||||||
|
let observer_guard = observers.read().await;
|
||||||
|
for tx in observer_guard.values() {
|
||||||
|
let _ = send(tx, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn gen_match_id(matches: &Matches) -> u32 {
|
||||||
|
let matches_guard = matches.read().await;
|
||||||
|
let mut result = rand::rng().random_range(100000..=999999);
|
||||||
|
while matches_guard.get(&result).is_some() {
|
||||||
|
result = rand::rng().random_range(100000..=999999);
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn random_move(board: &[Vec<Color>]) -> usize {
|
||||||
|
let mut random = rand::rng().random_range(0..7);
|
||||||
|
while board[random][5] != Color::None {
|
||||||
|
random = rand::rng().random_range(0..7);
|
||||||
|
}
|
||||||
|
|
||||||
|
random
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send(tx: &UnboundedSender<Message>, text: &str) -> Result<(), SendError<Message>> {
|
||||||
|
tx.send(Message::text(text))
|
||||||
|
}
|
||||||
1145
src/main.rs
1145
src/main.rs
File diff suppressed because it is too large
Load Diff
1447
src/server.rs
Normal file
1447
src/server.rs
Normal file
File diff suppressed because it is too large
Load Diff
431
src/tournaments/knockout_bracket.rs
Normal file
431
src/tournaments/knockout_bracket.rs
Normal file
@@ -0,0 +1,431 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
server::*,
|
||||||
|
tournaments::{RoundRobin, Tournament},
|
||||||
|
*,
|
||||||
|
};
|
||||||
|
|
||||||
|
type Score = u32;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct KnockoutBracket {
|
||||||
|
pub blitz_round_robin: RoundRobin,
|
||||||
|
pub players: Vec<(String, Score, bool)>,
|
||||||
|
pub pairings: Vec<String>,
|
||||||
|
pub current_matches: Vec<u32>,
|
||||||
|
pub previous_wait: u64,
|
||||||
|
pub completed: bool,
|
||||||
|
pub started: bool,
|
||||||
|
pub skip_round_robin: bool,
|
||||||
|
pub clients: Clients,
|
||||||
|
pub matches: Matches,
|
||||||
|
pub observers: Observers,
|
||||||
|
pub usernames: Vec<String>,
|
||||||
|
pub data: Vec<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KnockoutBracket {
|
||||||
|
async fn create_matches(&mut self) {
|
||||||
|
let clients_guard = self.clients.read().await;
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
while i < self.pairings.len() {
|
||||||
|
let player1_username = self.pairings[i].clone();
|
||||||
|
let player2_username = self.pairings.get(i + 1);
|
||||||
|
|
||||||
|
if player2_username.is_none() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let player2_username = player2_username.unwrap().clone();
|
||||||
|
|
||||||
|
let match_id: u32 = gen_match_id(&self.matches).await;
|
||||||
|
self.current_matches.push(match_id);
|
||||||
|
let new_match = Arc::new(RwLock::new(Match::new(
|
||||||
|
match_id,
|
||||||
|
player1_username.clone(),
|
||||||
|
player2_username.clone(),
|
||||||
|
false,
|
||||||
|
)));
|
||||||
|
let match_guard = new_match.read().await;
|
||||||
|
|
||||||
|
let mut player1 = clients_guard.get(&player1_username).unwrap().write().await;
|
||||||
|
player1.current_match = Some(match_id);
|
||||||
|
player1.ready = false;
|
||||||
|
broadcast_message(
|
||||||
|
&self.observers,
|
||||||
|
&format!("READY:{}:{}", player1.username.clone(), false),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if match_guard.player1 == player1_username {
|
||||||
|
player1.color = Color::Red;
|
||||||
|
let _ = send(&player1.connection, "GAME:START:1");
|
||||||
|
} else {
|
||||||
|
player1.color = Color::Yellow;
|
||||||
|
let _ = send(&player1.connection, "GAME:START:0");
|
||||||
|
}
|
||||||
|
drop(player1);
|
||||||
|
|
||||||
|
let mut player2 = clients_guard.get(&player2_username).unwrap().write().await;
|
||||||
|
player2.current_match = Some(match_id);
|
||||||
|
player2.ready = false;
|
||||||
|
broadcast_message(
|
||||||
|
&self.observers,
|
||||||
|
&format!("READY:{}:{}", player2.username.clone(), false),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if match_guard.player1 == player2_username {
|
||||||
|
player2.color = Color::Red;
|
||||||
|
let _ = send(&player2.connection, "GAME:START:1");
|
||||||
|
} else {
|
||||||
|
player2.color = Color::Yellow;
|
||||||
|
let _ = send(&player2.connection, "GAME:START:0");
|
||||||
|
}
|
||||||
|
drop(player2);
|
||||||
|
|
||||||
|
self.matches.write().await.insert(match_id, new_match.clone());
|
||||||
|
broadcast_message(
|
||||||
|
&self.observers,
|
||||||
|
&format!(
|
||||||
|
"GAME:START:{},{},{}",
|
||||||
|
match_id, match_guard.player1, match_guard.player2
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
i += 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Tournament for KnockoutBracket {
|
||||||
|
async fn new(ready_players: &[String], server: &Server) -> KnockoutBracket {
|
||||||
|
let previous_wait = server.waiting_timeout.read().await.clone();
|
||||||
|
let bracket_file = std::fs::read_to_string("bracket_pairings.txt").unwrap_or_default();
|
||||||
|
let bracket_players = bracket_file.split('\n').collect::<Vec<_>>();
|
||||||
|
let mut skip_round_robin =
|
||||||
|
!bracket_players.is_empty() && bracket_players.len() == ready_players.len();
|
||||||
|
|
||||||
|
if skip_round_robin {
|
||||||
|
for player in bracket_players {
|
||||||
|
let mut player_match = false;
|
||||||
|
for ready_player in ready_players {
|
||||||
|
if player == ready_player {
|
||||||
|
player_match = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !player_match {
|
||||||
|
skip_round_robin = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KnockoutBracket {
|
||||||
|
blitz_round_robin: RoundRobin::new(ready_players, server).await,
|
||||||
|
players: Vec::new(),
|
||||||
|
pairings: Vec::new(),
|
||||||
|
current_matches: Vec::new(),
|
||||||
|
previous_wait,
|
||||||
|
completed: false,
|
||||||
|
started: false,
|
||||||
|
skip_round_robin,
|
||||||
|
clients: server.clients.clone(),
|
||||||
|
matches: server.matches.clone(),
|
||||||
|
observers: server.observers.clone(),
|
||||||
|
usernames: ready_players.to_vec(),
|
||||||
|
data: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn next(&mut self, server: &Server) {
|
||||||
|
if self.completed {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.started {
|
||||||
|
self.blitz_round_robin.next(server).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.blitz_round_robin.completed && !self.started {
|
||||||
|
self.started = true;
|
||||||
|
*server.waiting_timeout.write().await = self.previous_wait;
|
||||||
|
|
||||||
|
let mut players = Vec::new();
|
||||||
|
for player in self.blitz_round_robin.players.values() {
|
||||||
|
players.push((player.0.clone(), player.1, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
players.sort_by(|a, b| a.1.cmp(&b.1));
|
||||||
|
self.players = players;
|
||||||
|
|
||||||
|
for player in &self.players {
|
||||||
|
self.pairings.push(player.0.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.data.push(self.pairings.clone());
|
||||||
|
self.create_matches().await;
|
||||||
|
broadcast_message(
|
||||||
|
&self.observers,
|
||||||
|
&format!("GET:TOURNAMENT_DATA:{}", self.get_data().unwrap()),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.started {
|
||||||
|
self.pairings.retain(|p| !p.is_empty());
|
||||||
|
if self.pairings.len() == 1 {
|
||||||
|
self.completed = true;
|
||||||
|
} else {
|
||||||
|
self.data.push(self.pairings.clone());
|
||||||
|
self.create_matches().await;
|
||||||
|
broadcast_message(
|
||||||
|
&self.observers,
|
||||||
|
&format!("GET:TOURNAMENT_DATA:{}", self.get_data().unwrap()),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn start(&mut self, server: &Server) {
|
||||||
|
if self.skip_round_robin {
|
||||||
|
let bracket_file = std::fs::read_to_string("bracket_pairings.txt").unwrap_or_default();
|
||||||
|
self.blitz_round_robin.completed = true;
|
||||||
|
self.started = true;
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
bracket_file.split('\n').into_iter().for_each(|line| {
|
||||||
|
self.players.push((line.to_string(), i, false));
|
||||||
|
self.pairings.push(line.to_string());
|
||||||
|
i += 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
self.data.push(self.pairings.clone());
|
||||||
|
self.create_matches().await;
|
||||||
|
broadcast_message(
|
||||||
|
&self.observers,
|
||||||
|
&format!("GET:TOURNAMENT_DATA:{}", self.get_data().unwrap()),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
} else {
|
||||||
|
*server.waiting_timeout.write().await = 5;
|
||||||
|
self.blitz_round_robin.start(server).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cancel(&mut self, server: &Server) {
|
||||||
|
if !self.started {
|
||||||
|
self.blitz_round_robin.cancel(server).await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for match_id in &self.current_matches {
|
||||||
|
server.terminate_match(*match_id).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let clients_guard = server.clients.read().await;
|
||||||
|
for username in &self.players {
|
||||||
|
let client = clients_guard.get(&username.0).cloned();
|
||||||
|
if client.is_none() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let client = client.unwrap();
|
||||||
|
let client = client.read().await;
|
||||||
|
|
||||||
|
let _ = send(&client.connection, "TOURNAMENT:END");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn inform_winner(
|
||||||
|
&mut self,
|
||||||
|
winner: String,
|
||||||
|
match_id: u32,
|
||||||
|
player1: String,
|
||||||
|
player2: String,
|
||||||
|
) {
|
||||||
|
if !self.started {
|
||||||
|
self.blitz_round_robin.inform_winner(winner, match_id, player1, player2).await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut winner = winner;
|
||||||
|
|
||||||
|
// there's a tie
|
||||||
|
if winner.is_empty() {
|
||||||
|
let mut player1_track = (String::new(), 0, false);
|
||||||
|
let mut player2_track = (String::new(), 0, false);
|
||||||
|
|
||||||
|
for player in self.players.iter_mut() {
|
||||||
|
if player.0 == player1 {
|
||||||
|
player1_track = player.clone();
|
||||||
|
} else if player.0 == player2 {
|
||||||
|
player2_track = player.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
if !player1_track.0.is_empty() && !player2_track.0.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if player1_track.2 || player2_track.2 {
|
||||||
|
if player1_track.1 < player2_track.1 {
|
||||||
|
winner = player2_track.0.clone();
|
||||||
|
} else {
|
||||||
|
winner = player1_track.0.clone();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for player in self.players.iter_mut() {
|
||||||
|
if player.0 == player1 || player.0 == player2 {
|
||||||
|
player.2 = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_match_id: u32 = gen_match_id(&self.matches).await;
|
||||||
|
self.current_matches.push(new_match_id);
|
||||||
|
let new_match = Arc::new(RwLock::new(Match::new_with_order(
|
||||||
|
new_match_id,
|
||||||
|
player2.clone(),
|
||||||
|
player1.clone(),
|
||||||
|
false,
|
||||||
|
)));
|
||||||
|
|
||||||
|
let match_guard = new_match.read().await;
|
||||||
|
let clients_guard = self.clients.read().await;
|
||||||
|
let mut player1 = clients_guard.get(&player1).unwrap().write().await;
|
||||||
|
|
||||||
|
player1.current_match = Some(new_match_id);
|
||||||
|
player1.ready = false;
|
||||||
|
let player1_name = player1.username.clone();
|
||||||
|
|
||||||
|
if match_guard.player1 == player1.username {
|
||||||
|
player1.color = Color::Red;
|
||||||
|
let _ = send(&player1.connection, "GAME:START:1");
|
||||||
|
} else {
|
||||||
|
player1.color = Color::Yellow;
|
||||||
|
let _ = send(&player1.connection, "GAME:START:0");
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(player1);
|
||||||
|
|
||||||
|
let mut player2 = clients_guard.get(&player2).unwrap().write().await;
|
||||||
|
|
||||||
|
player2.current_match = Some(new_match_id);
|
||||||
|
player2.ready = false;
|
||||||
|
let player2_name = player2.username.clone();
|
||||||
|
|
||||||
|
if match_guard.player1 == player2.username {
|
||||||
|
player2.color = Color::Red;
|
||||||
|
let _ = send(&player2.connection, "GAME:START:1");
|
||||||
|
} else {
|
||||||
|
player2.color = Color::Yellow;
|
||||||
|
let _ = send(&player2.connection, "GAME:START:0");
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(player2);
|
||||||
|
|
||||||
|
broadcast_message(
|
||||||
|
&self.observers,
|
||||||
|
&format!("READY:{}:{}", player1_name, false),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
broadcast_message(
|
||||||
|
&self.observers,
|
||||||
|
&format!("READY:{}:{}", player2_name, false),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
self.matches.write().await.insert(new_match_id, new_match.clone());
|
||||||
|
broadcast_message(
|
||||||
|
&self.observers,
|
||||||
|
&format!(
|
||||||
|
"GAME:START:{},{},{}",
|
||||||
|
new_match_id, match_guard.player1, match_guard.player2
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
self.current_matches.retain(|v| *v != match_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut loser = String::new();
|
||||||
|
for i in 0..self.pairings.len() {
|
||||||
|
if self.pairings[i] == winner {
|
||||||
|
if i % 2 == 0 {
|
||||||
|
loser = self.pairings[i + 1].clone();
|
||||||
|
self.pairings[i + 1].clear();
|
||||||
|
} else {
|
||||||
|
loser = self.pairings[i - 1].clone();
|
||||||
|
self.pairings[i - 1].clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset tie tracking
|
||||||
|
for player in self.players.iter_mut() {
|
||||||
|
if player.0 == winner || player.0 == loser {
|
||||||
|
player.2 = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.current_matches.retain(|v| *v != match_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains_player(&self, username: String) -> bool {
|
||||||
|
self.usernames.contains(&username)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_completed(&self) -> bool {
|
||||||
|
self.completed
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_players(&self) -> Vec<String> {
|
||||||
|
self.usernames.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_winner(&self) -> Option<String> {
|
||||||
|
if self.completed {
|
||||||
|
return Some(self.pairings[0].clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_data(&self) -> Option<String> {
|
||||||
|
if !self.started {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut message = String::new();
|
||||||
|
for round in self.data.iter() {
|
||||||
|
for player in round.iter() {
|
||||||
|
message += player;
|
||||||
|
message += ",";
|
||||||
|
}
|
||||||
|
message.pop();
|
||||||
|
message.push('|');
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.data.len() > 0 {
|
||||||
|
message.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_type(&self) -> String {
|
||||||
|
"KnockoutBracket".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/tournaments/mod.rs
Normal file
31
src/tournaments/mod.rs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
use crate::server::Server;
|
||||||
|
|
||||||
|
pub mod round_robin;
|
||||||
|
pub use round_robin::RoundRobin;
|
||||||
|
pub mod knockout_bracket;
|
||||||
|
pub use knockout_bracket::KnockoutBracket;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait Tournament {
|
||||||
|
async fn new(ready_players: &[String], server: &Server) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
async fn next(&mut self, server: &Server);
|
||||||
|
async fn start(&mut self, server: &Server);
|
||||||
|
async fn cancel(&mut self, server: &Server);
|
||||||
|
async fn inform_winner(
|
||||||
|
&mut self,
|
||||||
|
winner: String,
|
||||||
|
match_id: u32,
|
||||||
|
player1: String,
|
||||||
|
player2: String,
|
||||||
|
);
|
||||||
|
fn contains_player(&self, username: String) -> bool;
|
||||||
|
fn is_completed(&self) -> bool;
|
||||||
|
fn get_players(&self) -> Vec<String>;
|
||||||
|
fn get_winner(&self) -> Option<String>;
|
||||||
|
fn get_data(&self) -> Option<String>;
|
||||||
|
fn get_type(&self) -> String;
|
||||||
|
}
|
||||||
249
src/tournaments/round_robin.rs
Normal file
249
src/tournaments/round_robin.rs
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
use crate::{server::Server, *};
|
||||||
|
|
||||||
|
type Score = u32;
|
||||||
|
type ID = u32;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct RoundRobin {
|
||||||
|
pub players: HashMap<ID, (String, Score)>,
|
||||||
|
pub top_half: Vec<ID>,
|
||||||
|
pub bottom_half: Vec<ID>,
|
||||||
|
pub completed: bool,
|
||||||
|
pub total_rounds: usize,
|
||||||
|
pub rounds_played: usize,
|
||||||
|
pub current_matches: Vec<ID>,
|
||||||
|
pub usernames: Vec<String>,
|
||||||
|
pub observers: Observers,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RoundRobin {
|
||||||
|
async fn create_matches(&mut self, clients: &Clients, matches: &Matches) {
|
||||||
|
let clients_guard = clients.read().await;
|
||||||
|
for (i, id) in self.top_half.iter().enumerate() {
|
||||||
|
let Some(player1_username) = self.players.get(id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(player2_id) = self.bottom_half.get(i) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(player2_username) = self.players.get(player2_id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let match_id: u32 = gen_match_id(matches).await;
|
||||||
|
let new_match = Arc::new(RwLock::new(Match::new(
|
||||||
|
match_id,
|
||||||
|
player1_username.0.clone(),
|
||||||
|
player2_username.0.clone(),
|
||||||
|
false,
|
||||||
|
)));
|
||||||
|
|
||||||
|
self.current_matches.push(match_id.clone());
|
||||||
|
let match_guard = new_match.read().await;
|
||||||
|
|
||||||
|
let mut player1 = clients_guard.get(&player1_username.0).unwrap().write().await;
|
||||||
|
player1.current_match = Some(match_id);
|
||||||
|
player1.ready = false;
|
||||||
|
broadcast_message(
|
||||||
|
&self.observers,
|
||||||
|
&format!("READY:{}:{}", player1.username.clone(), false),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if match_guard.player1 == player1_username.0 {
|
||||||
|
player1.color = Color::Red;
|
||||||
|
let _ = send(&player1.connection, "GAME:START:1");
|
||||||
|
} else {
|
||||||
|
player1.color = Color::Yellow;
|
||||||
|
let _ = send(&player1.connection, "GAME:START:0");
|
||||||
|
}
|
||||||
|
drop(player1);
|
||||||
|
|
||||||
|
let mut player2 = clients_guard.get(&player2_username.0).unwrap().write().await;
|
||||||
|
player2.current_match = Some(match_id);
|
||||||
|
player2.ready = false;
|
||||||
|
broadcast_message(
|
||||||
|
&self.observers,
|
||||||
|
&format!("READY:{}:{}", player2.username.clone(), false),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if match_guard.player1 == player2_username.0 {
|
||||||
|
player2.color = Color::Red;
|
||||||
|
let _ = send(&player2.connection, "GAME:START:1");
|
||||||
|
} else {
|
||||||
|
player2.color = Color::Yellow;
|
||||||
|
let _ = send(&player2.connection, "GAME:START:0");
|
||||||
|
}
|
||||||
|
drop(player2);
|
||||||
|
|
||||||
|
matches.write().await.insert(match_id, new_match.clone());
|
||||||
|
broadcast_message(
|
||||||
|
&self.observers,
|
||||||
|
&format!(
|
||||||
|
"GAME:START:{},{},{}",
|
||||||
|
match_id, match_guard.player1, match_guard.player2
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Tournament for RoundRobin {
|
||||||
|
async fn new(ready_players: &[String], server: &Server) -> RoundRobin {
|
||||||
|
let mut result = RoundRobin {
|
||||||
|
players: HashMap::new(),
|
||||||
|
top_half: Vec::new(),
|
||||||
|
bottom_half: Vec::new(),
|
||||||
|
completed: false,
|
||||||
|
total_rounds: 0,
|
||||||
|
rounds_played: 0,
|
||||||
|
current_matches: Vec::new(),
|
||||||
|
usernames: ready_players.to_vec(),
|
||||||
|
observers: server.observers.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let size = ready_players.len();
|
||||||
|
let total_slots = if size % 2 == 0 { size } else { size + 1 };
|
||||||
|
result.total_rounds = if size < 2 { 0 } else { total_slots - 1 };
|
||||||
|
result.completed = result.total_rounds == 0;
|
||||||
|
|
||||||
|
for (id, player) in ready_players.iter().enumerate() {
|
||||||
|
result.players.insert(id as u32, (player.clone(), 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 0..total_slots / 2 {
|
||||||
|
result.top_half.push(i as u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in total_slots / 2..total_slots {
|
||||||
|
result.bottom_half.push(i as u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn inform_winner(&mut self, winner: String, match_id: u32, _: String, _: String) {
|
||||||
|
if winner.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (_, username) in self.players.iter_mut() {
|
||||||
|
if username.0 == winner {
|
||||||
|
username.1 += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.current_matches.retain(|id| !(*id == match_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn next(&mut self, server: &Server) {
|
||||||
|
if self.completed {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let clients_guard = server.clients.read().await;
|
||||||
|
let mut player_scores: Vec<(String, u32)> = Vec::new();
|
||||||
|
for (_, username) in self.players.iter() {
|
||||||
|
let player = clients_guard.get(&username.0).unwrap().read().await;
|
||||||
|
player_scores.push((player.username.clone(), username.1));
|
||||||
|
}
|
||||||
|
drop(clients_guard);
|
||||||
|
|
||||||
|
player_scores.sort_by(|a, b| b.1.cmp(&a.1));
|
||||||
|
|
||||||
|
// Send scores
|
||||||
|
let mut message = "TOURNAMENT:SCORES:".to_string();
|
||||||
|
for (player, score) in player_scores.iter() {
|
||||||
|
message.push_str(&format!("{},{}|", player, score))
|
||||||
|
}
|
||||||
|
message.pop();
|
||||||
|
|
||||||
|
server.broadcast(&message).await;
|
||||||
|
|
||||||
|
if self.rounds_played >= self.total_rounds {
|
||||||
|
self.completed = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let last_from_top = self.top_half.pop().unwrap();
|
||||||
|
let first_from_bottom = self.bottom_half.remove(0);
|
||||||
|
|
||||||
|
self.top_half.insert(1, first_from_bottom);
|
||||||
|
self.bottom_half.push(last_from_top);
|
||||||
|
|
||||||
|
self.rounds_played += 1;
|
||||||
|
self.create_matches(&server.clients, &server.matches).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn start(&mut self, server: &Server) {
|
||||||
|
if self.completed {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.rounds_played = 1;
|
||||||
|
self.create_matches(&server.clients, &server.matches).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cancel(&mut self, server: &Server) {
|
||||||
|
for match_id in &self.current_matches {
|
||||||
|
server.terminate_match(*match_id).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let clients_guard = server.clients.read().await;
|
||||||
|
for (_, (username, _)) in self.players.iter() {
|
||||||
|
let client = clients_guard.get(username);
|
||||||
|
if client.is_none() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let client = client.unwrap().read().await;
|
||||||
|
let _ = send(&client.connection, "TOURNAMENT:END");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains_player(&self, username: String) -> bool {
|
||||||
|
self.usernames.contains(&username)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_completed(&self) -> bool {
|
||||||
|
self.completed
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_players(&self) -> Vec<String> {
|
||||||
|
self.usernames.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_winner(&self) -> Option<String> {
|
||||||
|
if !self.is_completed() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut best_score = 0;
|
||||||
|
let mut winner = None;
|
||||||
|
|
||||||
|
for (_, (username, score)) in self.players.iter() {
|
||||||
|
if *score > best_score {
|
||||||
|
best_score = *score;
|
||||||
|
winner = Some(username.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
winner
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_data(&self) -> Option<String> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_type(&self) -> String {
|
||||||
|
"RoundRobin".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
396
src/types.rs
396
src/types.rs
@@ -1,282 +1,166 @@
|
|||||||
use rand::Rng;
|
use rand::RngExt;
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::Arc;
|
use std::time::Instant;
|
||||||
use std::vec;
|
use std::{ops, vec};
|
||||||
use async_trait::async_trait;
|
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
use tokio::sync::RwLock;
|
|
||||||
use tokio_tungstenite::tungstenite::Message;
|
use tokio_tungstenite::tungstenite::Message;
|
||||||
use crate::{broadcast_message_all_observers, gen_match_id, send, terminate_match};
|
|
||||||
|
|
||||||
pub type Clients = Arc<RwLock<HashMap<SocketAddr, Arc<RwLock<Client>>>>>;
|
#[derive(PartialEq, Clone, Copy)]
|
||||||
pub type Usernames = Arc<RwLock<HashMap<String, SocketAddr>>>;
|
pub enum Color {
|
||||||
pub type Observers = Arc<RwLock<HashMap<SocketAddr, UnboundedSender<Message>>>>;
|
Red,
|
||||||
pub type Matches = Arc<RwLock<HashMap<u32, Arc<RwLock<Match>>>>>;
|
Yellow,
|
||||||
pub type WrappedTournament = Arc<RwLock<Option<Arc<RwLock<dyn Tournament + Send + Sync>>>>>;
|
None,
|
||||||
|
|
||||||
pub struct Server {
|
|
||||||
pub clients: Clients,
|
|
||||||
pub usernames: Usernames,
|
|
||||||
pub observers: Observers,
|
|
||||||
pub matches: Matches,
|
|
||||||
pub admin: Arc<RwLock<Option<SocketAddr>>>,
|
|
||||||
pub admin_password: Arc<String>,
|
|
||||||
pub tournament: WrappedTournament,
|
|
||||||
pub waiting_timeout: Arc<RwLock<u64>>,
|
|
||||||
pub demo_mode: bool,
|
|
||||||
pub tournament_type: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone)]
|
impl ops::Not for Color {
|
||||||
pub enum Color {
|
type Output = Color;
|
||||||
Red,
|
|
||||||
Yellow,
|
fn not(self) -> Color {
|
||||||
None,
|
match self {
|
||||||
|
Color::Red => Color::Yellow,
|
||||||
|
Color::Yellow => Color::Red,
|
||||||
|
Color::None => Color::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Color> for bool {
|
||||||
|
fn from(color: Color) -> bool {
|
||||||
|
match color {
|
||||||
|
Color::Red => true,
|
||||||
|
Color::Yellow => false,
|
||||||
|
Color::None => panic!("Cannot convert Color::None to bool"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub connection: UnboundedSender<Message>,
|
pub connection: UnboundedSender<Message>,
|
||||||
pub ready: bool,
|
pub ready: bool,
|
||||||
pub color: Color,
|
pub color: Color,
|
||||||
pub current_match: Option<u32>,
|
pub current_match: Option<u32>,
|
||||||
pub round_robin_id: u32,
|
pub addr: SocketAddr,
|
||||||
pub score: u32,
|
|
||||||
pub addr: SocketAddr,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
pub fn new(username: String, connection: UnboundedSender<Message>, addr: SocketAddr) -> Client {
|
pub fn new(username: String, connection: UnboundedSender<Message>, addr: SocketAddr) -> Client {
|
||||||
Client {
|
Client {
|
||||||
username,
|
username,
|
||||||
connection,
|
connection,
|
||||||
ready: false,
|
ready: false,
|
||||||
color: Color::None,
|
color: Color::None,
|
||||||
current_match: None,
|
current_match: None,
|
||||||
round_robin_id: 0,
|
addr,
|
||||||
score: 0,
|
|
||||||
addr,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
pub trait Tournament {
|
|
||||||
fn new(ready_players: &[SocketAddr]) -> Self where Self: Sized;
|
|
||||||
async fn next(&mut self, clients: &Clients, matches: &Matches, observers: &Observers);
|
|
||||||
async fn start(&mut self, clients: &Clients, matches: &Matches);
|
|
||||||
async fn cancel(&mut self, clients: &Clients, matches: &Matches, observers: &Observers);
|
|
||||||
fn is_completed(&self) -> bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct RoundRobin {
|
|
||||||
pub players: HashMap<u32, SocketAddr>,
|
|
||||||
pub top_half: Vec<u32>,
|
|
||||||
pub bottom_half: Vec<u32>,
|
|
||||||
pub is_completed: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RoundRobin {
|
|
||||||
async fn create_matches(&self, clients: &Clients, matches: &Matches) {
|
|
||||||
let clients_guard = clients.read().await;
|
|
||||||
for (i, id) in self.top_half.iter().enumerate() {
|
|
||||||
let player1_addr = self.players.get(id).unwrap();
|
|
||||||
let player2_addr = self.players.get(self.bottom_half.get(i).unwrap());
|
|
||||||
|
|
||||||
if player2_addr.is_none() { continue; }
|
|
||||||
let player2_addr = player2_addr.unwrap();
|
|
||||||
|
|
||||||
let match_id: u32 = gen_match_id(matches).await;
|
|
||||||
let new_match = Arc::new(RwLock::new(Match::new(
|
|
||||||
match_id,
|
|
||||||
*player1_addr,
|
|
||||||
*player2_addr,
|
|
||||||
)));
|
|
||||||
|
|
||||||
let match_guard = new_match.read().await;
|
|
||||||
let mut player1 = clients_guard.get(player1_addr).unwrap().write().await;
|
|
||||||
|
|
||||||
player1.current_match = Some(match_id);
|
|
||||||
player1.ready = false;
|
|
||||||
|
|
||||||
if match_guard.player1 == *player1_addr {
|
|
||||||
player1.color = Color::Red;
|
|
||||||
let _ = send(&player1.connection, "GAME:START:1");
|
|
||||||
} else {
|
|
||||||
player1.color = Color::Yellow;
|
|
||||||
let _ = send(&player1.connection, "GAME:START:0");
|
|
||||||
}
|
|
||||||
|
|
||||||
drop(player1);
|
|
||||||
|
|
||||||
let mut player2 = clients_guard.get(player2_addr).unwrap().write().await;
|
|
||||||
|
|
||||||
player2.current_match = Some(match_id);
|
|
||||||
player2.ready = false;
|
|
||||||
|
|
||||||
if match_guard.player1 == *player2_addr {
|
|
||||||
player2.color = Color::Red;
|
|
||||||
let _ = send(&player2.connection, "GAME:START:1");
|
|
||||||
} else {
|
|
||||||
player2.color = Color::Yellow;
|
|
||||||
let _ = send(&player2.connection, "GAME:START:0");
|
|
||||||
}
|
|
||||||
|
|
||||||
drop(player2);
|
|
||||||
|
|
||||||
matches.write().await.insert(match_id, new_match.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl Tournament for RoundRobin {
|
|
||||||
fn new(ready_players: &[SocketAddr]) -> RoundRobin {
|
|
||||||
let mut result = RoundRobin {
|
|
||||||
players: HashMap::new(),
|
|
||||||
top_half: Vec::new(),
|
|
||||||
bottom_half: Vec::new(),
|
|
||||||
is_completed: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let size = ready_players.len();
|
|
||||||
|
|
||||||
for (id, player) in ready_players.iter().enumerate() {
|
|
||||||
result.players.insert(id as u32, *player);
|
|
||||||
}
|
|
||||||
|
|
||||||
for i in 0..size / 2 {
|
|
||||||
result.top_half.push(i as u32);
|
|
||||||
}
|
|
||||||
|
|
||||||
for i in size / 2..size {
|
|
||||||
result.bottom_half.push(i as u32);
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn next(&mut self, clients: &Clients, matches: &Matches, observers: &Observers) {
|
|
||||||
if self.is_completed {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.top_half.len() <= 1 || self.bottom_half.is_empty() {
|
|
||||||
self.is_completed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let last_from_top = self.top_half.pop().unwrap();
|
|
||||||
let first_from_bottom = self.bottom_half.remove(0);
|
|
||||||
|
|
||||||
self.top_half.insert(1, first_from_bottom);
|
|
||||||
self.bottom_half.push(last_from_top);
|
|
||||||
|
|
||||||
let expected_bottom_start = self.top_half.len() as u32;
|
|
||||||
if self.top_half[1] == 1 && self.bottom_half[0] == expected_bottom_start {
|
|
||||||
self.is_completed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.is_completed() {
|
|
||||||
// Send scores
|
|
||||||
let clients_guard = clients.read().await;
|
|
||||||
let mut player_scores: Vec<(String, u32)> = Vec::new();
|
|
||||||
for (_, player_addr) in self.players.iter() {
|
|
||||||
let mut player = clients_guard.get(player_addr).unwrap().write().await;
|
|
||||||
let _ = send(&player.connection.clone(), "TOURNAMENT:END");
|
|
||||||
player_scores.push((player.username.clone(), player.score));
|
|
||||||
player.score = 0;
|
|
||||||
player.round_robin_id = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
player_scores.sort_by(|a, b| b.1.cmp(&a.1));
|
|
||||||
|
|
||||||
let mut message = "TOURNAMENT:END:".to_string();
|
|
||||||
for (player, score) in player_scores.iter() {
|
|
||||||
message.push_str(&format!("{},{}|", player, score))
|
|
||||||
}
|
|
||||||
message.pop();
|
|
||||||
|
|
||||||
broadcast_message_all_observers(observers, &message).await;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Create next matches
|
|
||||||
self.create_matches(clients, matches).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn start(&mut self, clients: &Clients, matches: &Matches) {
|
|
||||||
self.create_matches(clients, matches).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn cancel(&mut self, clients: &Clients, matches: &Matches, observers: &Observers) {
|
|
||||||
for (_, addr) in self.players.iter() {
|
|
||||||
let clients_guard = clients.read().await;
|
|
||||||
|
|
||||||
let client = clients_guard.get(addr);
|
|
||||||
if client.is_none() { continue; }
|
|
||||||
let client = client.unwrap().read().await;
|
|
||||||
let client_connection = client.connection.clone();
|
|
||||||
let client_ready = client.ready;
|
|
||||||
|
|
||||||
let match_id = client.current_match;
|
|
||||||
if match_id.is_none() { continue; }
|
|
||||||
let match_id = match_id.unwrap();
|
|
||||||
|
|
||||||
drop(client);
|
|
||||||
drop(clients_guard);
|
|
||||||
|
|
||||||
terminate_match(match_id, matches, clients, observers, false).await;
|
|
||||||
|
|
||||||
if !client_ready {
|
|
||||||
let _ = send(&client_connection, "TOURNAMENT:END");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_completed(&self) -> bool { self.is_completed }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Match {
|
pub struct Match {
|
||||||
pub id: u32,
|
pub id: u32,
|
||||||
pub board: Vec<Vec<Color>>,
|
pub demo_mode: bool,
|
||||||
pub viewers: Vec<SocketAddr>,
|
pub board: Vec<Vec<Color>>,
|
||||||
pub ledger: Vec<(Color, usize)>,
|
pub ledger: Vec<(Color, usize, Instant)>,
|
||||||
pub move_to_dispatch: (Color, usize),
|
pub wait_thread: Option<tokio::task::JoinHandle<()>>,
|
||||||
pub wait_thread: Option<tokio::task::JoinHandle<()>>,
|
pub timeout_thread: Option<tokio::task::JoinHandle<()>>,
|
||||||
pub player1: SocketAddr,
|
pub player1: String,
|
||||||
pub player2: SocketAddr,
|
pub player2: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Match {
|
impl Match {
|
||||||
pub fn new(id: u32, player1: SocketAddr, player2: SocketAddr) -> Match {
|
pub fn new(id: u32, player1: String, player2: String, demo_mode: bool) -> Match {
|
||||||
let first = if rand::rng().random_range(0..=1) == 0 {
|
let (first_player, second_player) = if rand::rng().random_range(0..=1) == 0 {
|
||||||
player1.to_string().parse().unwrap()
|
(player1, player2)
|
||||||
} else {
|
} else {
|
||||||
player2.to_string().parse().unwrap()
|
(player2, player1)
|
||||||
};
|
};
|
||||||
|
|
||||||
Match {
|
Match {
|
||||||
id,
|
id,
|
||||||
board: vec![vec![Color::None; 6]; 7],
|
demo_mode,
|
||||||
viewers: Vec::new(),
|
board: vec![vec![Color::None; 6]; 7],
|
||||||
ledger: Vec::new(),
|
ledger: Vec::new(),
|
||||||
move_to_dispatch: (Color::None, 0),
|
wait_thread: None,
|
||||||
wait_thread: None,
|
timeout_thread: None,
|
||||||
player1: if player1 == first {player1} else {player2},
|
player1: first_player,
|
||||||
player2: if player1 == first {player2} else {player1},
|
player2: second_player,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_with_order(id: u32, player1: String, player2: String, demo_mode: bool) -> Match {
|
||||||
|
Match {
|
||||||
|
id,
|
||||||
|
demo_mode,
|
||||||
|
board: vec![vec![Color::None; 6]; 7],
|
||||||
|
ledger: Vec::new(),
|
||||||
|
wait_thread: None,
|
||||||
|
timeout_thread: None,
|
||||||
|
player1,
|
||||||
|
player2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn place_token(&mut self, color: Color, column: usize) {
|
||||||
|
for i in 0..6 {
|
||||||
|
if self.board[column][i] == Color::None {
|
||||||
|
self.board[column][i] = color;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn end_game_check(&self) -> (Color, bool) {
|
||||||
|
let mut result = (Color::None, false);
|
||||||
|
|
||||||
|
let mut any_empty = true;
|
||||||
|
for x in 0..7 {
|
||||||
|
for y in 0..6 {
|
||||||
|
let color = self.board[x][y].clone();
|
||||||
|
let mut horizontal_end = true;
|
||||||
|
let mut vertical_end = true;
|
||||||
|
let mut diagonal_end_up = true;
|
||||||
|
let mut diagonal_end_down = true;
|
||||||
|
|
||||||
|
if any_empty && color == Color::None {
|
||||||
|
any_empty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for i in 0..4 {
|
||||||
|
if x + i >= 7 || self.board[x + i][y] != color && horizontal_end {
|
||||||
|
horizontal_end = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if y + i >= 6 || self.board[x][y + i] != color && vertical_end {
|
||||||
|
vertical_end = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if x + i >= 7 || y + i >= 6 || self.board[x + i][y + i] != color && diagonal_end_up {
|
||||||
|
diagonal_end_up = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if x + i >= 7
|
||||||
|
|| (y as i32 - i as i32) < 0
|
||||||
|
|| self.board[x + i][y - i] != color && diagonal_end_down
|
||||||
|
{
|
||||||
|
diagonal_end_down = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if horizontal_end || vertical_end || diagonal_end_up || diagonal_end_down {
|
||||||
|
result = (color.clone(), false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if result.0 != Color::None {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn place_token(&mut self, color: Color, column: usize) {
|
if any_empty && result.0 == Color::None {
|
||||||
for i in 0..6 {
|
result.1 = true;
|
||||||
if self.board[column][i] == Color::None {
|
|
||||||
self.board[column][i] = color;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user