mirror of
https://github.com/szymon-jozef/checkers.git
synced 2026-10-27 19:20:44 +01:00
Merge pull request #1 from szymon-jozef/feat/networking
feat/networking
This commit is contained in:
279
Cargo.lock
generated
279
Cargo.lock
generated
@@ -2,16 +2,6 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "Checkers"
|
|
||||||
version = "0.0.1"
|
|
||||||
dependencies = [
|
|
||||||
"env_logger",
|
|
||||||
"log",
|
|
||||||
"rand",
|
|
||||||
"uuid",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.1.5"
|
version = "1.1.5"
|
||||||
@@ -73,9 +63,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.102"
|
version = "1.0.104"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "atomic-polyfill"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
|
||||||
|
dependencies = [
|
||||||
|
"critical-section",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
@@ -95,6 +94,18 @@ version = "3.20.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytes"
|
||||||
|
version = "1.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
@@ -112,6 +123,28 @@ dependencies = [
|
|||||||
"rand_core",
|
"rand_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "checkers"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"env_logger",
|
||||||
|
"log",
|
||||||
|
"postcard",
|
||||||
|
"rand",
|
||||||
|
"serde",
|
||||||
|
"tokio",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cobs"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorchoice"
|
name = "colorchoice"
|
||||||
version = "1.0.5"
|
version = "1.0.5"
|
||||||
@@ -127,6 +160,12 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "critical-section"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "defmt"
|
name = "defmt"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
@@ -158,6 +197,18 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-io"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-io"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_filter"
|
name = "env_filter"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
@@ -187,6 +238,16 @@ version = "1.0.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno"
|
||||||
|
version = "0.3.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foldhash"
|
name = "foldhash"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
@@ -231,6 +292,15 @@ dependencies = [
|
|||||||
"wasip3",
|
"wasip3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hash32"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.15.5"
|
version = "0.15.5"
|
||||||
@@ -246,6 +316,20 @@ version = "0.17.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heapless"
|
||||||
|
version = "0.7.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
|
||||||
|
dependencies = [
|
||||||
|
"atomic-polyfill",
|
||||||
|
"hash32",
|
||||||
|
"rustc_version",
|
||||||
|
"serde",
|
||||||
|
"spin",
|
||||||
|
"stable_deref_trait",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@@ -341,6 +425,15 @@ version = "0.2.189"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lock_api"
|
||||||
|
version = "0.4.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
||||||
|
dependencies = [
|
||||||
|
"scopeguard",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.33"
|
version = "0.4.33"
|
||||||
@@ -353,6 +446,17 @@ version = "2.8.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "1.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.4"
|
version = "1.21.4"
|
||||||
@@ -365,6 +469,29 @@ version = "1.70.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot"
|
||||||
|
version = "0.12.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
||||||
|
dependencies = [
|
||||||
|
"lock_api",
|
||||||
|
"parking_lot_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot_core"
|
||||||
|
version = "0.9.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"redox_syscall",
|
||||||
|
"smallvec",
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
@@ -386,6 +513,19 @@ dependencies = [
|
|||||||
"portable-atomic",
|
"portable-atomic",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "postcard"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
|
||||||
|
dependencies = [
|
||||||
|
"cobs",
|
||||||
|
"embedded-io 0.4.0",
|
||||||
|
"embedded-io 0.6.1",
|
||||||
|
"heapless",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prettyplease"
|
name = "prettyplease"
|
||||||
version = "0.2.37"
|
version = "0.2.37"
|
||||||
@@ -437,6 +577,15 @@ version = "0.10.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
|
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.5.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.13.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.13.1"
|
version = "1.13.1"
|
||||||
@@ -466,12 +615,27 @@ version = "0.8.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc_version"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
|
||||||
|
dependencies = [
|
||||||
|
"semver",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.23"
|
version = "1.0.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "1.0.28"
|
version = "1.0.28"
|
||||||
@@ -480,31 +644,32 @@ checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.228"
|
version = "1.0.229"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_core",
|
"serde_core",
|
||||||
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_core"
|
name = "serde_core"
|
||||||
version = "1.0.228"
|
version = "1.0.229"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.228"
|
version = "1.0.229"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.119",
|
"syn 3.0.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -520,12 +685,53 @@ dependencies = [
|
|||||||
"zmij",
|
"zmij",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.4.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
|
||||||
|
dependencies = [
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.12"
|
version = "0.4.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.15.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "socket2"
|
||||||
|
version = "0.6.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "spin"
|
||||||
|
version = "0.9.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
|
||||||
|
dependencies = [
|
||||||
|
"lock_api",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stable_deref_trait"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.119"
|
version = "2.0.119"
|
||||||
@@ -568,6 +774,34 @@ dependencies = [
|
|||||||
"syn 3.0.3",
|
"syn 3.0.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio"
|
||||||
|
version = "1.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"parking_lot",
|
||||||
|
"pin-project-lite",
|
||||||
|
"signal-hook-registry",
|
||||||
|
"socket2",
|
||||||
|
"tokio-macros",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-macros"
|
||||||
|
version = "2.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.3",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.24"
|
version = "1.0.24"
|
||||||
@@ -594,9 +828,16 @@ checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
|
"serde_core",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.1+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasip2"
|
name = "wasip2"
|
||||||
version = "1.0.3+wasi-0.2.9"
|
version = "1.0.3+wasi-0.2.9"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "Checkers"
|
name = "checkers"
|
||||||
description = "Game of checkers"
|
description = "Game of checkers"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@@ -12,10 +12,11 @@ edition = "2024"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
env_logger = "0.11.11"
|
env_logger = "0.11.11"
|
||||||
log = "0.4.33"
|
log = "0.4.33"
|
||||||
|
postcard = { version = "1.1.3", features = ["alloc"] }
|
||||||
rand = "0.10.2"
|
rand = "0.10.2"
|
||||||
|
serde = { version = "1.0.229", features = ["derive"] }
|
||||||
|
tokio = { version = "1.53.1", features = ["full"] }
|
||||||
|
|
||||||
[dependencies.uuid]
|
[dependencies.uuid]
|
||||||
version = "1.24.0"
|
version = "1.24.0"
|
||||||
features = [
|
features = ["serde", "v4"]
|
||||||
"v4",
|
|
||||||
]
|
|
||||||
|
|||||||
21
README.md
21
README.md
@@ -1,2 +1,21 @@
|
|||||||
# Game of checkers
|
# Game of checkers online
|
||||||
|
|
||||||
|
Currently WIP
|
||||||
|
|
||||||
|
# Architecture
|
||||||
|
## Game logic
|
||||||
|
Game logic is handled through board and game-master structs. It's self-contained
|
||||||
|
|
||||||
|
## Network
|
||||||
|
This project has network server and client written with tokio.
|
||||||
|
|
||||||
|
## Example clients
|
||||||
|
In `src/bin/client.rs` there is an example client in cli. It works for now, but in the end there will be also a gui client. Anyone can write their own clients, due to the way it's written, tho.
|
||||||
|
|
||||||
|
# Thoughts
|
||||||
|
Server is self-contained, so frontend can be written in:
|
||||||
|
- ratatui?
|
||||||
|
- raylib bindings for rust
|
||||||
|
- egui
|
||||||
|
- macroquad
|
||||||
|
- cli lol (already kinda happening)
|
||||||
|
|||||||
295
src/bin/client.rs
Normal file
295
src/bin/client.rs
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
use std::{
|
||||||
|
fmt::Display,
|
||||||
|
io::{self, Write},
|
||||||
|
};
|
||||||
|
|
||||||
|
use checkers::{
|
||||||
|
logic::board::pawn::{CapturePath, MovePath},
|
||||||
|
network::{
|
||||||
|
client::{Client, ClientData},
|
||||||
|
network_identity::NetworkIdentity,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use log::{debug, error, info, warn};
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
const QUIT_COMMAND: &str = "/quit";
|
||||||
|
const HELP_COMMAND: &str = "/help";
|
||||||
|
const SEND_COMMAND: &str = "/send";
|
||||||
|
const READY_COMMAND: &str = "/ready";
|
||||||
|
|
||||||
|
const CAPTURE_COMMAND: &str = "/capture";
|
||||||
|
const MOVE_COMMAND: &str = "/move";
|
||||||
|
|
||||||
|
enum CliCommands {
|
||||||
|
Quit,
|
||||||
|
Help,
|
||||||
|
Send,
|
||||||
|
Ready,
|
||||||
|
|
||||||
|
Capture,
|
||||||
|
Move,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
struct UnknownCommandError;
|
||||||
|
|
||||||
|
impl Display for UnknownCommandError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "Unknown command!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for CliCommands {
|
||||||
|
type Error = UnknownCommandError;
|
||||||
|
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
QUIT_COMMAND => Ok(CliCommands::Quit),
|
||||||
|
HELP_COMMAND => Ok(CliCommands::Help),
|
||||||
|
SEND_COMMAND => Ok(CliCommands::Send),
|
||||||
|
READY_COMMAND => Ok(CliCommands::Ready),
|
||||||
|
|
||||||
|
CAPTURE_COMMAND => Ok(CliCommands::Capture),
|
||||||
|
MOVE_COMMAND => Ok(CliCommands::Move),
|
||||||
|
_ => Err(UnknownCommandError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ClientContext {
|
||||||
|
pub identity: Option<NetworkIdentity>,
|
||||||
|
pub is_my_turn: bool,
|
||||||
|
|
||||||
|
pub available_captures: Option<Vec<CapturePath>>,
|
||||||
|
pub available_moves: Option<Vec<MovePath>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ClientContext {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
identity: None,
|
||||||
|
is_my_turn: false,
|
||||||
|
|
||||||
|
available_captures: None,
|
||||||
|
available_moves: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
let Some(mut client) = Client::new().await else {
|
||||||
|
println!("Not worky, ending");
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
let (client_sender, mut client_receiver) = mpsc::channel(1024);
|
||||||
|
|
||||||
|
client.set_update_sender(client_sender);
|
||||||
|
client.update();
|
||||||
|
|
||||||
|
let (stdin_sender, mut stdin_receiver) = mpsc::channel(1024);
|
||||||
|
|
||||||
|
let mut context: ClientContext = ClientContext::default();
|
||||||
|
|
||||||
|
tokio::task::spawn_blocking(move || {
|
||||||
|
let stdin = std::io::stdin();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mut buffer = String::new();
|
||||||
|
if stdin.read_line(&mut buffer).is_ok() {
|
||||||
|
if stdin_sender.blocking_send(buffer).is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let help_msg: String = format!(
|
||||||
|
"\n┌────Help────────────────────────────
|
||||||
|
│\t {} - quit
|
||||||
|
│\t {} - show this message
|
||||||
|
│\t {} - message the server you're ready
|
||||||
|
│\t {} - send text message to the server
|
||||||
|
│
|
||||||
|
│\t {} <index>- choose a capture path from the list
|
||||||
|
│\t {} <index> <index>- choose move from the list. First number is pawn you want to move and second is position it should take
|
||||||
|
└───────────────────────────────────────
|
||||||
|
",
|
||||||
|
QUIT_COMMAND, HELP_COMMAND, READY_COMMAND, SEND_COMMAND, CAPTURE_COMMAND, MOVE_COMMAND
|
||||||
|
);
|
||||||
|
|
||||||
|
info!("───────Type /help for help───────");
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
msg = client_receiver.recv() => {
|
||||||
|
match msg {
|
||||||
|
Some(ClientData::GameStart {
|
||||||
|
identity,
|
||||||
|
}) => {
|
||||||
|
info!(
|
||||||
|
"Got new identity: {}",
|
||||||
|
identity
|
||||||
|
);
|
||||||
|
context.identity = Some(identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(ClientData::BoardView(view)) => {
|
||||||
|
info!("New board state:\n{}", view.to_string(&context.identity.as_ref().unwrap().id));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(ClientData::CurrentTurn(current_turn)) => {
|
||||||
|
context.is_my_turn = current_turn == context.identity.as_ref().expect("Server sent us new current turn without telling us who we are!").id;
|
||||||
|
if context.is_my_turn {
|
||||||
|
info!("It's our turn!");
|
||||||
|
} else {
|
||||||
|
info!("It's the turn of our enemy!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(ClientData::AvailableCaptures(capture_paths)) => {
|
||||||
|
info!("Available captures paths: {:?}", capture_paths);
|
||||||
|
context.available_moves = None;
|
||||||
|
context.available_captures = Some(capture_paths);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(ClientData::AvailableMoves(move_paths)) => {
|
||||||
|
info!("Available moves: {:?}", move_paths);
|
||||||
|
context.available_moves = Some(move_paths);
|
||||||
|
context.available_captures = None;
|
||||||
|
},
|
||||||
|
|
||||||
|
Some(ClientData::TextMessage {sender, content}) => {
|
||||||
|
info!("[{}] - {}", sender, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(ClientData::GameEnd(game_result)) => {
|
||||||
|
info!("Game has ended with the result: {:?}", game_result);
|
||||||
|
}
|
||||||
|
|
||||||
|
None => {
|
||||||
|
warn!("Connection broken! Stopping");
|
||||||
|
break Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer_opt = stdin_receiver.recv() => {
|
||||||
|
if let Some(mut buffer) = buffer_opt {
|
||||||
|
buffer.pop(); // remove \n
|
||||||
|
|
||||||
|
if !buffer.is_empty() {
|
||||||
|
let words: Vec<&str> = buffer.split_whitespace().collect();
|
||||||
|
let cmd = words[0];
|
||||||
|
|
||||||
|
match CliCommands::try_from(cmd) {
|
||||||
|
Ok(cmd) => match cmd {
|
||||||
|
CliCommands::Quit => {
|
||||||
|
info!("Bye bye");
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
CliCommands::Help => {
|
||||||
|
info!("{}", help_msg);
|
||||||
|
}
|
||||||
|
CliCommands::Ready => {
|
||||||
|
client.signal_readiness().await;
|
||||||
|
}
|
||||||
|
CliCommands::Send => {
|
||||||
|
let mut args: String = words[1..].join(" ");
|
||||||
|
args = args.trim().to_string();
|
||||||
|
client.send_text_message(args).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
CliCommands::Capture => {
|
||||||
|
if words.len() < 2 {
|
||||||
|
error!("Too little arguments!");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let arg = words[1];
|
||||||
|
|
||||||
|
match arg.parse::<usize>() {
|
||||||
|
Ok(index) => {
|
||||||
|
if let Some(captures) = context.available_captures.as_ref() && context.is_my_turn {
|
||||||
|
if index >= captures.len() {
|
||||||
|
error!("Cannot capture path that doesn't exist!");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !context.is_my_turn {
|
||||||
|
error!("We cannot capture when it's not our turn!");
|
||||||
|
}
|
||||||
|
|
||||||
|
client.send_capture(captures[index].clone()).await;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(_) => {
|
||||||
|
error!("Invalid input! Use: /capture <index>, where index is the number from capture_path");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
CliCommands::Move => {
|
||||||
|
if words.len() < 3 {
|
||||||
|
error!("Too little arguments provided!");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let arg = words[1];
|
||||||
|
let move_arg = words[2];
|
||||||
|
|
||||||
|
|
||||||
|
match arg.parse::<usize>() {
|
||||||
|
Ok(index) => {
|
||||||
|
if let Some(moves) = context.available_moves.as_ref() && context.is_my_turn {
|
||||||
|
if index >= moves.len() {
|
||||||
|
error!("Cannot make a move that doesn't exist!");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !context.is_my_turn {
|
||||||
|
error!("We cannot make a move when it's not our turn!");
|
||||||
|
}
|
||||||
|
|
||||||
|
let move_path = moves[index].clone();
|
||||||
|
|
||||||
|
match move_arg.parse::<usize>() {
|
||||||
|
Ok(move_index) => {
|
||||||
|
if move_index >= move_path.available_steps.len() {
|
||||||
|
error!("Second index out of range!");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.send_move(move_path.from, move_path.available_steps[move_index]).await;
|
||||||
|
},
|
||||||
|
Err(_) => {
|
||||||
|
error!("Invalid second index. Use: /move <index> <index>, where first index is the pawn you want to move and second index is position you want it to take");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(_) => {
|
||||||
|
error!("Invalid input! Use: /move <index> <index>, where first index is the pawn you want to move and second index is position you want it to take");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Err(e) => error!("{}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print!("> ");
|
||||||
|
io::stdout().flush()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/bin/server.rs
Normal file
13
src/bin/server.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
use checkers::network::server::Server;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let mut server: Server = Server::new().await;
|
||||||
|
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
server.start().await;
|
||||||
|
server.update().await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
2
src/lib.rs
Normal file
2
src/lib.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod logic;
|
||||||
|
pub mod network;
|
||||||
@@ -11,6 +11,9 @@ use std::ops::{Index, IndexMut};
|
|||||||
|
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, error, info, trace, warn};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct Board {
|
pub struct Board {
|
||||||
board: Vec<Field>,
|
board: Vec<Field>,
|
||||||
pub size: usize,
|
pub size: usize,
|
||||||
@@ -69,6 +72,12 @@ impl Board {
|
|||||||
Board { board, size }
|
Board { board, size }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the clone of the board inner vec. Meant for `BoardView`. This vec shouldn't be
|
||||||
|
/// manipulated by hand
|
||||||
|
pub fn get_board(&self) -> Vec<Field> {
|
||||||
|
self.board.clone()
|
||||||
|
}
|
||||||
|
|
||||||
/// For testing
|
/// For testing
|
||||||
fn new_empty(player1: &mut Player, player2: &mut Player, size: Option<usize>) -> Self {
|
fn new_empty(player1: &mut Player, player2: &mut Player, size: Option<usize>) -> Self {
|
||||||
debug!("Creating new empty board!");
|
debug!("Creating new empty board!");
|
||||||
@@ -524,8 +533,8 @@ mod tests {
|
|||||||
fn new_board_test() {
|
fn new_board_test() {
|
||||||
init_logger();
|
init_logger();
|
||||||
|
|
||||||
let mut player1: Player = Player::new("Morbius".to_string());
|
let mut player1: Player = Player::new("Morbius");
|
||||||
let mut player2: Player = Player::new("Milo".to_string());
|
let mut player2: Player = Player::new("Milo");
|
||||||
|
|
||||||
let test_board: Board = Board::new(&mut player1, &mut player2, None);
|
let test_board: Board = Board::new(&mut player1, &mut player2, None);
|
||||||
let size: usize = test_board.size;
|
let size: usize = test_board.size;
|
||||||
@@ -572,8 +581,8 @@ mod tests {
|
|||||||
fn test_overloaded_operators() {
|
fn test_overloaded_operators() {
|
||||||
init_logger();
|
init_logger();
|
||||||
|
|
||||||
let mut player1: Player = Player::new("Morbius".to_string());
|
let mut player1: Player = Player::new("Morbius");
|
||||||
let mut player2: Player = Player::new("Milo".to_string());
|
let mut player2: Player = Player::new("Milo");
|
||||||
|
|
||||||
let test_board: Board = Board::new(&mut player1, &mut player2, None);
|
let test_board: Board = Board::new(&mut player1, &mut player2, None);
|
||||||
|
|
||||||
@@ -604,8 +613,8 @@ mod tests {
|
|||||||
fn test_moving_pawn() {
|
fn test_moving_pawn() {
|
||||||
init_logger();
|
init_logger();
|
||||||
|
|
||||||
let mut player1: Player = Player::new("Morbius".to_string());
|
let mut player1: Player = Player::new("Morbius");
|
||||||
let mut player2: Player = Player::new("Milo".to_string());
|
let mut player2: Player = Player::new("Milo");
|
||||||
|
|
||||||
let mut test_board: Board = Board::new(&mut player1, &mut player2, None);
|
let mut test_board: Board = Board::new(&mut player1, &mut player2, None);
|
||||||
|
|
||||||
@@ -639,8 +648,8 @@ mod tests {
|
|||||||
fn test_getting_pawn_moves() {
|
fn test_getting_pawn_moves() {
|
||||||
init_logger();
|
init_logger();
|
||||||
|
|
||||||
let mut p1: Player = Player::new("Morbius".to_string());
|
let mut p1: Player = Player::new("Morbius");
|
||||||
let mut p2: Player = Player::new("Milo".to_string());
|
let mut p2: Player = Player::new("Milo");
|
||||||
|
|
||||||
let mut board: Board = Board::new_empty(&mut p1, &mut p2, None);
|
let mut board: Board = Board::new_empty(&mut p1, &mut p2, None);
|
||||||
|
|
||||||
@@ -673,8 +682,8 @@ mod tests {
|
|||||||
fn test_getting_player_pawns_positions() {
|
fn test_getting_player_pawns_positions() {
|
||||||
init_logger();
|
init_logger();
|
||||||
|
|
||||||
let mut p1: Player = Player::new("Morbius".to_string());
|
let mut p1: Player = Player::new("Morbius");
|
||||||
let mut p2: Player = Player::new("Milo".to_string());
|
let mut p2: Player = Player::new("Milo");
|
||||||
let test_board: Board = Board::new(&mut p1, &mut p2, None);
|
let test_board: Board = Board::new(&mut p1, &mut p2, None);
|
||||||
|
|
||||||
let expected_positions: Vec<Position> = (0..test_board.size)
|
let expected_positions: Vec<Position> = (0..test_board.size)
|
||||||
@@ -704,8 +713,8 @@ mod tests {
|
|||||||
fn test_is_movable() {
|
fn test_is_movable() {
|
||||||
init_logger();
|
init_logger();
|
||||||
|
|
||||||
let mut p1: Player = Player::new("Morbius".to_string());
|
let mut p1: Player = Player::new("Morbius");
|
||||||
let mut p2: Player = Player::new("Milo".to_string());
|
let mut p2: Player = Player::new("Milo");
|
||||||
let test_board: Board = Board::new(&mut p1, &mut p2, None);
|
let test_board: Board = Board::new(&mut p1, &mut p2, None);
|
||||||
|
|
||||||
assert!(test_board.is_pawn_movable(Position { row: 2, column: 1 }, &p1));
|
assert!(test_board.is_pawn_movable(Position { row: 2, column: 1 }, &p1));
|
||||||
@@ -723,8 +732,8 @@ mod tests {
|
|||||||
fn test_getting_captures_path() {
|
fn test_getting_captures_path() {
|
||||||
init_logger();
|
init_logger();
|
||||||
|
|
||||||
let mut p1 = Player::new("Morbius".to_string());
|
let mut p1 = Player::new("Morbius");
|
||||||
let mut p2 = Player::new("Milo".to_string());
|
let mut p2 = Player::new("Milo");
|
||||||
|
|
||||||
let mut board = Board::new_empty(&mut p1, &mut p2, None);
|
let mut board = Board::new_empty(&mut p1, &mut p2, None);
|
||||||
|
|
||||||
@@ -755,8 +764,8 @@ mod tests {
|
|||||||
fn test_capturing() {
|
fn test_capturing() {
|
||||||
init_logger();
|
init_logger();
|
||||||
|
|
||||||
let mut p1 = Player::new("Morbius".to_string());
|
let mut p1 = Player::new("Morbius");
|
||||||
let mut p2 = Player::new("Milo".to_string());
|
let mut p2 = Player::new("Milo");
|
||||||
|
|
||||||
let mut board = Board::new_empty(&mut p1, &mut p2, None);
|
let mut board = Board::new_empty(&mut p1, &mut p2, None);
|
||||||
|
|
||||||
|
|||||||
63
src/logic/board/board_view.rs
Normal file
63
src/logic/board/board_view.rs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
use std::ops::Index;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::logic::board::{board::Board, field::Field, pawn::Pawn};
|
||||||
|
|
||||||
|
/// View of the board, without any methods. Meant for sending over network
|
||||||
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub struct BoardView {
|
||||||
|
pub board: Vec<Field>,
|
||||||
|
pub size: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Board> for BoardView {
|
||||||
|
fn from(value: Board) -> Self {
|
||||||
|
Self {
|
||||||
|
board: value.get_board(),
|
||||||
|
size: value.size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index<usize> for BoardView {
|
||||||
|
type Output = [Field];
|
||||||
|
|
||||||
|
fn index<'a>(&'a self, i: usize) -> &'a [Field] {
|
||||||
|
let start = i * &self.size;
|
||||||
|
let end: usize = start + &self.size;
|
||||||
|
&self.board[start..end]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BoardView {
|
||||||
|
pub fn to_string(&self, owner: &Uuid) -> String {
|
||||||
|
(0..self.size)
|
||||||
|
.map(move |row| {
|
||||||
|
(0..self.size)
|
||||||
|
.map(move |column| match &self[row][column].pawn {
|
||||||
|
Some(pawn) => match pawn.state {
|
||||||
|
super::pawn::PawnState::Man => {
|
||||||
|
if pawn.owner == *owner {
|
||||||
|
'm'
|
||||||
|
} else {
|
||||||
|
'M'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super::pawn::PawnState::Dame => {
|
||||||
|
if pawn.owner == *owner {
|
||||||
|
'd'
|
||||||
|
} else {
|
||||||
|
'D'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => ' ',
|
||||||
|
})
|
||||||
|
.collect::<String>()
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::logic::{board::pawn::Pawn, math::position::Position};
|
use crate::logic::{board::pawn::Pawn, math::position::Position};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
|
||||||
pub struct Field {
|
pub struct Field {
|
||||||
pub position: Position,
|
pub position: Position,
|
||||||
pub pawn: Option<Pawn>,
|
pub pawn: Option<Pawn>,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
pub mod board;
|
pub mod board;
|
||||||
|
pub mod board_view;
|
||||||
mod field;
|
mod field;
|
||||||
pub mod pawn;
|
pub mod pawn;
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::logic::{math::position::Position, player::Player};
|
use crate::logic::{math::position::Position, player::Player};
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug)]
|
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)]
|
||||||
pub enum PawnState {
|
pub enum PawnState {
|
||||||
Man,
|
Man,
|
||||||
Dame,
|
Dame,
|
||||||
Captured,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Basic pawn
|
/// Basic pawn
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
|
||||||
pub struct Pawn {
|
pub struct Pawn {
|
||||||
pub state: PawnState,
|
pub state: PawnState,
|
||||||
pub owner: Uuid,
|
pub owner: Uuid,
|
||||||
@@ -25,6 +25,7 @@ impl Pawn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct CapturePath {
|
pub struct CapturePath {
|
||||||
pub from: Position,
|
pub from: Position,
|
||||||
pub steps: Vec<Position>,
|
pub steps: Vec<Position>,
|
||||||
@@ -39,7 +40,7 @@ impl CapturePath {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq, Deserialize, Serialize, Clone)]
|
||||||
pub struct MovePath {
|
pub struct MovePath {
|
||||||
pub from: Position,
|
pub from: Position,
|
||||||
pub available_steps: Vec<Position>,
|
pub available_steps: Vec<Position>,
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::logic::{
|
use crate::logic::{
|
||||||
board::{
|
board::{
|
||||||
board::Board,
|
board::Board,
|
||||||
|
board_view::BoardView,
|
||||||
pawn::{CapturePath, MovePath},
|
pawn::{CapturePath, MovePath},
|
||||||
},
|
},
|
||||||
math::position::Position,
|
math::position::Position,
|
||||||
player::Player,
|
player::Player,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
|
||||||
pub enum GameResult {
|
pub enum GameResult {
|
||||||
Lost(uuid::Uuid),
|
Lost(uuid::Uuid),
|
||||||
Draw,
|
Draw,
|
||||||
@@ -21,7 +25,7 @@ struct PlayerList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PlayerList {
|
impl PlayerList {
|
||||||
pub fn new(player1_name: String, player2_name: String) -> Self {
|
pub fn new(player1_name: &str, player2_name: &str) -> Self {
|
||||||
PlayerList {
|
PlayerList {
|
||||||
players: [Player::new(player1_name), Player::new(player2_name)],
|
players: [Player::new(player1_name), Player::new(player2_name)],
|
||||||
current_turn: 0,
|
current_turn: 0,
|
||||||
@@ -48,7 +52,7 @@ pub struct GameMaster {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl GameMaster {
|
impl GameMaster {
|
||||||
pub fn new(player1_name: String, player2_name: String) -> Self {
|
pub fn new(player1_name: &str, player2_name: &str) -> Self {
|
||||||
let mut players: PlayerList = PlayerList::new(player1_name, player2_name);
|
let mut players: PlayerList = PlayerList::new(player1_name, player2_name);
|
||||||
let [player1, player2] = &mut players.players;
|
let [player1, player2] = &mut players.players;
|
||||||
|
|
||||||
@@ -57,6 +61,23 @@ impl GameMaster {
|
|||||||
GameMaster { players, board }
|
GameMaster { players, board }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_players_names_and_ids(&self) -> Vec<(String, Uuid)> {
|
||||||
|
let mut result: Vec<(String, Uuid)> = vec![];
|
||||||
|
for player in &self.players.players {
|
||||||
|
result.push((player.name.clone(), player.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_board_view(&self) -> BoardView {
|
||||||
|
BoardView::from(self.board.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_current_turn(&self) -> Uuid {
|
||||||
|
self.players.get_current_turn().id
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_current_player_captures(&self) -> Vec<CapturePath> {
|
pub fn get_current_player_captures(&self) -> Vec<CapturePath> {
|
||||||
self.board
|
self.board
|
||||||
.get_player_pawns_positions(self.players.get_current_turn())
|
.get_player_pawns_positions(self.players.get_current_turn())
|
||||||
@@ -165,7 +186,7 @@ mod tests {
|
|||||||
use rand::seq::IndexedRandom;
|
use rand::seq::IndexedRandom;
|
||||||
|
|
||||||
fn run_game() {
|
fn run_game() {
|
||||||
let mut master: GameMaster = GameMaster::new("Morbius".to_string(), "Milo".to_string());
|
let mut master: GameMaster = GameMaster::new("Morbius", "Milo");
|
||||||
let mut turns = 0;
|
let mut turns = 0;
|
||||||
let max_turns = 2000;
|
let max_turns = 2000;
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ use core::fmt;
|
|||||||
use std::ops::Sub;
|
use std::ops::Sub;
|
||||||
|
|
||||||
use log::{debug, trace};
|
use log::{debug, trace};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::logic::math::vector::Vector2D;
|
use crate::logic::math::vector::Vector2D;
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||||
/// Position on the board. Consists of row and column. Should be treated like a point in space on
|
/// Position on the board. Consists of row and column. Should be treated like a point in space on
|
||||||
/// unsigned grid.
|
/// unsigned grid.
|
||||||
///
|
///
|
||||||
@@ -31,7 +32,9 @@ impl Position {
|
|||||||
trace!("Adding row: {} + {} = {}", self.row, delta.row, new_row);
|
trace!("Adding row: {} + {} = {}", self.row, delta.row, new_row);
|
||||||
trace!(
|
trace!(
|
||||||
"Adding column: {} + {} = {}",
|
"Adding column: {} + {} = {}",
|
||||||
self.column, delta.column, new_column
|
self.column,
|
||||||
|
delta.column,
|
||||||
|
new_column
|
||||||
);
|
);
|
||||||
|
|
||||||
Some(Position {
|
Some(Position {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
mod board;
|
pub mod board;
|
||||||
pub mod game_master;
|
pub mod game_master;
|
||||||
mod math;
|
pub mod math;
|
||||||
mod player;
|
mod player;
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ pub struct Player {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Player {
|
impl Player {
|
||||||
pub fn new(name: String) -> Self {
|
pub fn new(name: &str) -> Self {
|
||||||
Player {
|
Player {
|
||||||
name,
|
name: name.to_string(),
|
||||||
id: Uuid::new_v4(),
|
id: Uuid::new_v4(),
|
||||||
end_row: None,
|
end_row: None,
|
||||||
vertical_direction: None,
|
vertical_direction: None,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
mod logic;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("Hello, world!");
|
println!("Hello, world!");
|
||||||
|
|||||||
292
src/network/client.rs
Normal file
292
src/network/client.rs
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||||
|
|
||||||
|
use log::{debug, error, info, warn};
|
||||||
|
use tokio::sync::mpsc::{self, Receiver, Sender};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
logic::{
|
||||||
|
board::{
|
||||||
|
board_view::BoardView,
|
||||||
|
pawn::{CapturePath, MovePath},
|
||||||
|
},
|
||||||
|
game_master::GameResult,
|
||||||
|
math::position::Position,
|
||||||
|
},
|
||||||
|
network::{
|
||||||
|
connection::{Connection, ConnectionType},
|
||||||
|
message::{
|
||||||
|
ClientMessage::{self, SignalReadiness, TextMessage},
|
||||||
|
Message, ServerMessage,
|
||||||
|
},
|
||||||
|
network_identity::NetworkIdentity,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ClientSettings {
|
||||||
|
server_url: SocketAddr,
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ClientCommands {
|
||||||
|
SendCapture(CapturePath),
|
||||||
|
SendMove { from: Position, to: Position },
|
||||||
|
SendText(String),
|
||||||
|
SendReady,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Data that network client gives to normal client. I have no better idea for this name, hence this
|
||||||
|
/// comment
|
||||||
|
pub enum ClientData {
|
||||||
|
GameStart { identity: NetworkIdentity },
|
||||||
|
GameEnd(GameResult),
|
||||||
|
|
||||||
|
AvailableCaptures(Vec<CapturePath>),
|
||||||
|
AvailableMoves(Vec<MovePath>),
|
||||||
|
|
||||||
|
BoardView(BoardView),
|
||||||
|
CurrentTurn(Uuid),
|
||||||
|
|
||||||
|
TextMessage { sender: String, content: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Client {
|
||||||
|
conn_reciever_incoming: Option<Receiver<(SocketAddr, Message<ServerMessage>)>>,
|
||||||
|
conn_sender_outgoing: Sender<Message<ClientMessage>>,
|
||||||
|
settings: ClientSettings,
|
||||||
|
|
||||||
|
commands_sender: Option<Sender<ClientCommands>>,
|
||||||
|
commands_receiver: Option<Receiver<ClientCommands>>,
|
||||||
|
|
||||||
|
update_sender: Option<Sender<ClientData>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Client {
|
||||||
|
pub async fn new() -> Option<Client> {
|
||||||
|
let settings = ClientSettings {
|
||||||
|
server_url: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 6767),
|
||||||
|
name: "Morbius".into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let (conn_sender, conn_reciever) =
|
||||||
|
mpsc::channel::<(SocketAddr, Message<ServerMessage>)>(1024);
|
||||||
|
let mut conn: Connection<ServerMessage, ClientMessage> =
|
||||||
|
Connection::new(ConnectionType::Server, conn_sender);
|
||||||
|
let conn_sender_outgoing = conn.get_sender();
|
||||||
|
|
||||||
|
let (commands_sender, commands_receiver) = mpsc::channel(1024);
|
||||||
|
|
||||||
|
if !conn.connect_to_server(settings.server_url).await {
|
||||||
|
error!("Could not connect with the server: {}", settings.server_url);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
conn.start_listening().await;
|
||||||
|
});
|
||||||
|
|
||||||
|
Some(Client {
|
||||||
|
conn_reciever_incoming: Some(conn_reciever),
|
||||||
|
conn_sender_outgoing,
|
||||||
|
|
||||||
|
settings,
|
||||||
|
|
||||||
|
commands_receiver: Some(commands_receiver),
|
||||||
|
commands_sender: Some(commands_sender),
|
||||||
|
|
||||||
|
update_sender: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_commands_sender(&mut self) -> Option<Sender<ClientCommands>> {
|
||||||
|
if self.commands_sender.is_none() {
|
||||||
|
warn!("Tried taking commands sender a second time! This shouldn't happen");
|
||||||
|
}
|
||||||
|
self.commands_sender.take()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_update_sender(&mut self, sender: Sender<ClientData>) {
|
||||||
|
if self.update_sender.is_some() {
|
||||||
|
warn!("Cannot set update sender a second time!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.update_sender = Some(sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self) {
|
||||||
|
let Some(mut conn_reciever_incoming) = self.conn_reciever_incoming.take() else {
|
||||||
|
error!("Tried updating, but there's no conn_reciever of incoming messages!");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let conn_sender = self.conn_sender_outgoing.clone();
|
||||||
|
|
||||||
|
let Some(update_sender) = self.update_sender.take() else {
|
||||||
|
error!("Update sender is missing. Cannot update!");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(mut commands_receiver) = self.commands_receiver.take() else {
|
||||||
|
error!("Commands receiver is missing! Cannot update.");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let settings = self.settings.clone();
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
oldest_msg = conn_reciever_incoming.recv() => {
|
||||||
|
if let Some(oldest_msg) = oldest_msg {
|
||||||
|
let (addr, msg) = oldest_msg;
|
||||||
|
debug!("Got message from: {:?}", addr);
|
||||||
|
|
||||||
|
match msg.content {
|
||||||
|
ServerMessage::RequestHandshake => {
|
||||||
|
info!("Server requested handshake!");
|
||||||
|
let msg = Message::new(ClientMessage::AnswerHandshake {
|
||||||
|
player_name: settings.name.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let _ = conn_sender.send(msg.unwrap()).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerMessage::AcceptHandshake => {
|
||||||
|
info!("Server accepted us! Yay :D");
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerMessage::DeclineHandshake { reason } => {
|
||||||
|
error!("Server declined connection: {}", reason);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerMessage::GameStart {
|
||||||
|
identity,
|
||||||
|
} => {
|
||||||
|
debug!("Got GameStart message! Identity: {}", identity);
|
||||||
|
let _ = update_sender.send(ClientData::GameStart { identity }).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerMessage::BroadcastCurrentTurn { active_player } => {
|
||||||
|
let _ = update_sender.send(ClientData::CurrentTurn(active_player)).await;
|
||||||
|
},
|
||||||
|
ServerMessage::BroadcastBoardState { board } => {
|
||||||
|
let _ = update_sender.send(ClientData::BoardView(board)).await;
|
||||||
|
},
|
||||||
|
|
||||||
|
ServerMessage::BroadCastTextMessage {sender, content} => {
|
||||||
|
let _ = update_sender.send(ClientData::TextMessage {sender, content}).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ServerMessage::AvailableCaptures { captures } => {
|
||||||
|
let _ = update_sender.send(ClientData::AvailableCaptures(captures)).await;
|
||||||
|
},
|
||||||
|
|
||||||
|
ServerMessage::AvailableMoves { moves } => {
|
||||||
|
let _ = update_sender.send(ClientData::AvailableMoves(moves)).await;
|
||||||
|
},
|
||||||
|
|
||||||
|
ServerMessage::GameEnd { result } => {
|
||||||
|
let _ = update_sender.send(ClientData::GameEnd(result)).await;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error!("Connection broken!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
oldest_cmd = commands_receiver.recv() => {
|
||||||
|
if let Some(oldest_cmd) = oldest_cmd {
|
||||||
|
match oldest_cmd {
|
||||||
|
ClientCommands::SendCapture(capture_path) => {
|
||||||
|
debug!("Sending capture path: {:?}", capture_path);
|
||||||
|
let msg = Message::new(ClientMessage::RequestCapture { capture_path });
|
||||||
|
send_message(msg, &conn_sender).await;
|
||||||
|
},
|
||||||
|
|
||||||
|
ClientCommands::SendMove { from, to } => {
|
||||||
|
debug!("Sending move from: {} to: {}", from, to);
|
||||||
|
let msg = Message::new(ClientMessage::RequestMove { from, to});
|
||||||
|
send_message(msg, &conn_sender).await;
|
||||||
|
},
|
||||||
|
|
||||||
|
ClientCommands::SendText(content) => {
|
||||||
|
debug!("Seding text message: {}", content);
|
||||||
|
let msg = Message::new(TextMessage(content));
|
||||||
|
send_message(msg, &conn_sender).await;
|
||||||
|
},
|
||||||
|
|
||||||
|
ClientCommands::SendReady => {
|
||||||
|
debug!("Signaling readiness!");
|
||||||
|
let msg = Message::new(SignalReadiness);
|
||||||
|
send_message(msg, &conn_sender).await;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error!("Couldn't decipher the command!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_capture(&mut self, capture: CapturePath) {
|
||||||
|
debug!("Requesting sending capture: {:?}", capture);
|
||||||
|
if let Some(sender) = &self.commands_sender {
|
||||||
|
let _ = sender.send(ClientCommands::SendCapture(capture)).await;
|
||||||
|
} else {
|
||||||
|
error!("Command sender not set!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_move(&mut self, from: Position, to: Position) {
|
||||||
|
debug!("Requested sending move from: {} to: {}", from, to);
|
||||||
|
if let Some(sender) = &self.commands_sender {
|
||||||
|
let _ = sender.send(ClientCommands::SendMove { from, to }).await;
|
||||||
|
} else {
|
||||||
|
error!("Command sender not set!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_text_message(&mut self, content: String) {
|
||||||
|
if let Some(sender) = &self.commands_sender {
|
||||||
|
let _ = sender.send(ClientCommands::SendText(content)).await;
|
||||||
|
} else {
|
||||||
|
error!("Command sender not set!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn signal_readiness(&mut self) {
|
||||||
|
if let Some(sender) = &self.commands_sender {
|
||||||
|
let _ = sender.send(ClientCommands::SendReady).await;
|
||||||
|
} else {
|
||||||
|
error!("Command sender not set!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_message(
|
||||||
|
msg: Result<Message<ClientMessage>, postcard::Error>,
|
||||||
|
sender: &Sender<Message<ClientMessage>>,
|
||||||
|
) {
|
||||||
|
debug!("Sending message to the server!");
|
||||||
|
let _ = match msg {
|
||||||
|
Err(e) => {
|
||||||
|
error!("There was an error while creating the message: {}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Ok(msg) => {
|
||||||
|
if let Err(e) = sender.send(msg).await {
|
||||||
|
error!("Error while sending a message to connection thread: {}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
222
src/network/connection.rs
Normal file
222
src/network/connection.rs
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
use std::io;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
use log::{debug, error, info};
|
||||||
|
use tokio::sync::mpsc::Receiver;
|
||||||
|
use tokio::{net::TcpStream, sync::mpsc::Sender};
|
||||||
|
|
||||||
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
|
||||||
|
use crate::network::message::{Message, MessageLike};
|
||||||
|
|
||||||
|
/// Enum defines __TO WHAT__ connection is connecting
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ConnectionType {
|
||||||
|
Server,
|
||||||
|
Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Connection<Inbound, Outbound>
|
||||||
|
where
|
||||||
|
Inbound: MessageLike,
|
||||||
|
Outbound: MessageLike,
|
||||||
|
{
|
||||||
|
conn_type: ConnectionType,
|
||||||
|
sender: Sender<(SocketAddr, Message<Inbound>)>,
|
||||||
|
reciever: Option<Receiver<Message<Outbound>>>,
|
||||||
|
tcp: Option<TcpStream>,
|
||||||
|
peer: Option<SocketAddr>,
|
||||||
|
outbound_type: PhantomData<Outbound>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Inbound, Outbound> Connection<Inbound, Outbound>
|
||||||
|
where
|
||||||
|
Inbound: MessageLike,
|
||||||
|
Outbound: MessageLike,
|
||||||
|
{
|
||||||
|
pub fn new(
|
||||||
|
conn_type: ConnectionType,
|
||||||
|
sender: Sender<(SocketAddr, Message<Inbound>)>,
|
||||||
|
) -> Connection<Inbound, Outbound> {
|
||||||
|
Connection {
|
||||||
|
conn_type,
|
||||||
|
sender,
|
||||||
|
reciever: None,
|
||||||
|
tcp: None,
|
||||||
|
peer: None,
|
||||||
|
outbound_type: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Meant for clients
|
||||||
|
pub async fn connect_to_server(&mut self, url: SocketAddr) -> bool {
|
||||||
|
match self.conn_type {
|
||||||
|
ConnectionType::Server => {
|
||||||
|
self.tcp = match TcpStream::connect(url).await {
|
||||||
|
Ok(conn) => {
|
||||||
|
self.peer = Some(url);
|
||||||
|
Some(conn)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Error while connecting to the server: {}", e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.tcp.is_some()
|
||||||
|
}
|
||||||
|
ConnectionType::Client => false, // only client can connect to the server
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Meant for server
|
||||||
|
pub fn delegate(&mut self, tcp: TcpStream, client_addr: SocketAddr) -> bool {
|
||||||
|
match self.conn_type {
|
||||||
|
ConnectionType::Server => false, // only server can delegate
|
||||||
|
ConnectionType::Client => {
|
||||||
|
self.peer = Some(client_addr);
|
||||||
|
self.tcp = Some(tcp);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_sender(&mut self) -> Sender<Message<Outbound>> {
|
||||||
|
let (sender, reciever) = tokio::sync::mpsc::channel(1024);
|
||||||
|
self.reciever = Some(reciever);
|
||||||
|
sender
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send(&mut self, msg: Message<Outbound>) {
|
||||||
|
let size = msg.size;
|
||||||
|
let content = msg.content;
|
||||||
|
|
||||||
|
let Some(tcp) = &mut self.tcp else {
|
||||||
|
error!("Tcp connection is not established! Cannot send");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = tcp.write_all(&size.to_be_bytes()).await {
|
||||||
|
error!("Error while sending header to: {:?}\n{}", self.peer, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Ok(msg) = content.to_bits() else {
|
||||||
|
error!("Cannot change message into bytes while sending!");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = tcp.write_all(&msg).await {
|
||||||
|
error!("Error while sending content to: {:?}\n{}", self.peer, e);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn start_listening(&mut self) {
|
||||||
|
let mut receiver = self
|
||||||
|
.reciever
|
||||||
|
.take()
|
||||||
|
.expect("Tried listening, but get_sender wasn't called");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
result = self.read_header() => {
|
||||||
|
if let Err(e) = result {
|
||||||
|
error!(
|
||||||
|
"Connection broken with {:?}: {}. Disconnecting...",
|
||||||
|
self.peer, e
|
||||||
|
);
|
||||||
|
self.tcp = None;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
msg_opt = receiver.recv() => {
|
||||||
|
match msg_opt {
|
||||||
|
Some(msg) => {
|
||||||
|
self.send(msg).await;
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
info!("Remote closed the channel for {:?}. Disconnecting...", self.peer);
|
||||||
|
self.tcp = None;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.reciever = Some(receiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_header(&mut self) -> Result<(), io::Error> {
|
||||||
|
let mut buff = [0u8; 4];
|
||||||
|
|
||||||
|
let tcp = self.tcp.as_mut().ok_or(io::ErrorKind::NotConnected)?;
|
||||||
|
|
||||||
|
tcp.read_exact(&mut buff).await?;
|
||||||
|
|
||||||
|
let size_to_read: u32 = u32::from_be_bytes(buff);
|
||||||
|
|
||||||
|
self.read_content(size_to_read).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_content(&mut self, size_to_read: u32) -> Result<(), io::Error> {
|
||||||
|
let max_size = 1024 * 1024 * 2;
|
||||||
|
|
||||||
|
if size_to_read > max_size {
|
||||||
|
error!(
|
||||||
|
"Message sent exeeds the limit. Sent {}, max: {}",
|
||||||
|
size_to_read, max_size
|
||||||
|
);
|
||||||
|
self.tcp = None;
|
||||||
|
return Err(io::ErrorKind::InvalidData.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buff: Vec<u8> = vec![0; size_to_read as usize];
|
||||||
|
|
||||||
|
let Some(tcp) = &mut self.tcp else {
|
||||||
|
error!(
|
||||||
|
"Tried reading content, but tcp connection is not established! This should __never__ happen"
|
||||||
|
);
|
||||||
|
panic!();
|
||||||
|
};
|
||||||
|
|
||||||
|
let bytes_read = tcp.read_exact(&mut buff).await?;
|
||||||
|
debug!("Read {} bytes during read_content", bytes_read);
|
||||||
|
|
||||||
|
let msg_result: Result<Inbound, postcard::Error> = MessageLike::from_bits(&buff);
|
||||||
|
|
||||||
|
match msg_result {
|
||||||
|
Ok(msg_content) => {
|
||||||
|
let new_msg = Message::new(msg_content);
|
||||||
|
|
||||||
|
match new_msg {
|
||||||
|
Ok(msg) => {
|
||||||
|
if let Err(e) = self
|
||||||
|
.sender
|
||||||
|
.send((self.peer.expect("Couldn't get peer addr"), msg))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
error!("Erro while sending message to main thread: {}", e);
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!(
|
||||||
|
"Error while preparing message in read_content for sender: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Error while getting the message from bits: {}", e);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
140
src/network/message.rs
Normal file
140
src/network/message.rs
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize, de::DeserializeOwned};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
logic::{
|
||||||
|
board::{
|
||||||
|
board::Board,
|
||||||
|
board_view::BoardView,
|
||||||
|
pawn::{CapturePath, MovePath},
|
||||||
|
},
|
||||||
|
game_master::GameResult,
|
||||||
|
math::position::Position,
|
||||||
|
},
|
||||||
|
network::network_identity::NetworkIdentity,
|
||||||
|
};
|
||||||
|
|
||||||
|
use postcard::{from_bytes, to_allocvec};
|
||||||
|
|
||||||
|
pub trait MessageLike {
|
||||||
|
fn to_bits(&self) -> Result<Vec<u8>, postcard::Error>;
|
||||||
|
fn from_bits(bits: &[u8]) -> Result<Self, postcard::Error>
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> MessageLike for T
|
||||||
|
where
|
||||||
|
T: DeserializeOwned + Serialize,
|
||||||
|
{
|
||||||
|
fn to_bits(&self) -> Result<Vec<u8>, postcard::Error> {
|
||||||
|
to_allocvec(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_bits(bits: &[u8]) -> Result<Self, postcard::Error> {
|
||||||
|
from_bytes(bits)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub enum ServerMessage {
|
||||||
|
RequestHandshake,
|
||||||
|
AcceptHandshake,
|
||||||
|
DeclineHandshake { reason: String },
|
||||||
|
|
||||||
|
GameStart { identity: NetworkIdentity },
|
||||||
|
|
||||||
|
AvailableCaptures { captures: Vec<CapturePath> },
|
||||||
|
AvailableMoves { moves: Vec<MovePath> },
|
||||||
|
|
||||||
|
BroadcastBoardState { board: BoardView },
|
||||||
|
BroadcastCurrentTurn { active_player: Uuid },
|
||||||
|
|
||||||
|
BroadCastTextMessage { sender: String, content: String },
|
||||||
|
|
||||||
|
GameEnd { result: GameResult },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub enum ClientMessage {
|
||||||
|
AnswerHandshake { player_name: String },
|
||||||
|
SignalReadiness,
|
||||||
|
|
||||||
|
ConnectionDead { addr: SocketAddr },
|
||||||
|
|
||||||
|
RequestCapture { capture_path: CapturePath },
|
||||||
|
RequestMove { from: Position, to: Position },
|
||||||
|
|
||||||
|
TextMessage(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Message<T>
|
||||||
|
where
|
||||||
|
T: MessageLike,
|
||||||
|
{
|
||||||
|
pub size: u32,
|
||||||
|
pub content: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Message<T>
|
||||||
|
where
|
||||||
|
T: MessageLike,
|
||||||
|
{
|
||||||
|
pub fn new(content: T) -> Result<Message<T>, postcard::Error> {
|
||||||
|
let size: usize = content.to_bits()?.len();
|
||||||
|
|
||||||
|
Ok(Message {
|
||||||
|
size: size as u32,
|
||||||
|
content,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_server_message_serde() {
|
||||||
|
let server_msg_content: ServerMessage = ServerMessage::RequestHandshake;
|
||||||
|
let msg: Message<ServerMessage> =
|
||||||
|
Message::new(server_msg_content.clone()).expect("Couldn't serialize message");
|
||||||
|
|
||||||
|
assert_eq!(1, msg.size);
|
||||||
|
|
||||||
|
let bits = msg
|
||||||
|
.content
|
||||||
|
.to_bits()
|
||||||
|
.expect("Could not convert message content to bits!");
|
||||||
|
|
||||||
|
let recovered_message: ServerMessage =
|
||||||
|
MessageLike::from_bits(&bits).expect("Could not recover the message");
|
||||||
|
|
||||||
|
assert_eq!(recovered_message, server_msg_content);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_client_message_serde() {
|
||||||
|
let from: Position = Position { row: 0, column: 0 };
|
||||||
|
let to: Position = Position { row: 1, column: 1 };
|
||||||
|
|
||||||
|
let client_msg_content: ClientMessage = ClientMessage::RequestMove { from, to };
|
||||||
|
|
||||||
|
let msg = Message::new(client_msg_content.clone()).expect("Could not serialize message");
|
||||||
|
|
||||||
|
assert_eq!(5, msg.size);
|
||||||
|
|
||||||
|
let bits = msg
|
||||||
|
.content
|
||||||
|
.to_bits()
|
||||||
|
.expect("Could not convert the message into bits!");
|
||||||
|
|
||||||
|
let recovered_message: ClientMessage =
|
||||||
|
MessageLike::from_bits(&bits).expect("Could not recover the message!");
|
||||||
|
|
||||||
|
assert_eq!(recovered_message, client_msg_content);
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/network/mod.rs
Normal file
5
src/network/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
pub mod client;
|
||||||
|
pub mod connection;
|
||||||
|
pub mod message;
|
||||||
|
pub mod network_identity;
|
||||||
|
pub mod server;
|
||||||
51
src/network/network_identity.rs
Normal file
51
src/network/network_identity.rs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::sync::mpsc::Sender;
|
||||||
|
|
||||||
|
use crate::network::message::{Message, ServerMessage};
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub struct NetworkIdentity {
|
||||||
|
pub name: String,
|
||||||
|
pub id: uuid::Uuid,
|
||||||
|
pub is_ready: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for NetworkIdentity {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "(Name: {}, id: {})", self.name, self.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for NetworkIdentity {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
name: String::new(),
|
||||||
|
id: uuid::Uuid::nil(),
|
||||||
|
is_ready: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NetworkServerIdentity {
|
||||||
|
// this feels silly but i have no better idea rn (i've been
|
||||||
|
// writing this since 10 a.m. and it's 9 p.m. already)
|
||||||
|
pub identity: NetworkIdentity,
|
||||||
|
pub sender: Sender<Message<ServerMessage>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NetworkServerIdentity {
|
||||||
|
pub fn new(sender: Sender<Message<ServerMessage>>) -> Self {
|
||||||
|
Self {
|
||||||
|
identity: NetworkIdentity::default(),
|
||||||
|
sender,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsMut<NetworkIdentity> for NetworkServerIdentity {
|
||||||
|
fn as_mut(&mut self) -> &mut NetworkIdentity {
|
||||||
|
&mut self.identity
|
||||||
|
}
|
||||||
|
}
|
||||||
490
src/network/server.rs
Normal file
490
src/network/server.rs
Normal file
@@ -0,0 +1,490 @@
|
|||||||
|
use log::{debug, error, info, warn};
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||||
|
};
|
||||||
|
use tokio::{
|
||||||
|
net::TcpListener,
|
||||||
|
sync::mpsc::{Receiver, Sender},
|
||||||
|
};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
logic::{
|
||||||
|
board::{board_view::BoardView, pawn::CapturePath},
|
||||||
|
game_master::{GameMaster, GameResult},
|
||||||
|
math::position::Position,
|
||||||
|
},
|
||||||
|
network::{
|
||||||
|
connection::{Connection, ConnectionType},
|
||||||
|
message::{
|
||||||
|
ClientMessage::{self},
|
||||||
|
Message, ServerMessage,
|
||||||
|
},
|
||||||
|
network_identity::{NetworkIdentity, NetworkServerIdentity},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO! More settings!
|
||||||
|
pub struct ServerSettings {
|
||||||
|
pub addr: SocketAddr,
|
||||||
|
pub max_connections: usize,
|
||||||
|
pub allow_spectators: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
enum ServerStage {
|
||||||
|
Lobby,
|
||||||
|
Game,
|
||||||
|
End,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ServerState {
|
||||||
|
ready_count: usize,
|
||||||
|
stage: ServerStage,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ServerState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
ready_count: 0,
|
||||||
|
stage: ServerStage::Lobby,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Server {
|
||||||
|
listener: Option<TcpListener>,
|
||||||
|
connections: HashMap<SocketAddr, NetworkServerIdentity>,
|
||||||
|
|
||||||
|
reciever: Receiver<(SocketAddr, Message<ClientMessage>)>,
|
||||||
|
sender: Sender<(SocketAddr, Message<ClientMessage>)>,
|
||||||
|
|
||||||
|
welcoming_reciever: Option<Receiver<(SocketAddr, Sender<Message<ServerMessage>>)>>,
|
||||||
|
|
||||||
|
game_master: Option<GameMaster>,
|
||||||
|
settings: ServerSettings,
|
||||||
|
|
||||||
|
state: ServerState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Server {
|
||||||
|
pub async fn new() -> Server {
|
||||||
|
// TODO! Should be read from a file
|
||||||
|
let settings = ServerSettings {
|
||||||
|
addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 6767),
|
||||||
|
max_connections: 2,
|
||||||
|
allow_spectators: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let (sender, reciever) =
|
||||||
|
tokio::sync::mpsc::channel::<(SocketAddr, Message<ClientMessage>)>(1024); // random
|
||||||
|
// number
|
||||||
|
|
||||||
|
Server {
|
||||||
|
listener: Some(
|
||||||
|
TcpListener::bind(settings.addr)
|
||||||
|
.await
|
||||||
|
.expect("Could not start the server"),
|
||||||
|
), // TODO! Handle this nicely, as this will
|
||||||
|
// crash gui app
|
||||||
|
connections: HashMap::with_capacity(2),
|
||||||
|
sender,
|
||||||
|
reciever,
|
||||||
|
welcoming_reciever: None,
|
||||||
|
game_master: None,
|
||||||
|
settings,
|
||||||
|
state: ServerState::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn start(&mut self) {
|
||||||
|
let (welcoming_tx, welcoming_rx) =
|
||||||
|
tokio::sync::mpsc::channel::<(SocketAddr, Sender<Message<ServerMessage>>)>(10);
|
||||||
|
|
||||||
|
self.welcoming_reciever = Some(welcoming_rx);
|
||||||
|
|
||||||
|
let listener = self
|
||||||
|
.listener
|
||||||
|
.take()
|
||||||
|
.expect("Cannot start the server without tcp listener.");
|
||||||
|
|
||||||
|
info!("Starting the server at: {:?}", listener.local_addr());
|
||||||
|
|
||||||
|
let server_sender = self.sender.clone();
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
loop {
|
||||||
|
if let Ok((socket, addr)) = listener.accept().await {
|
||||||
|
info!("New connection from: {:?}", addr);
|
||||||
|
let mut conn: Connection<ClientMessage, ServerMessage> =
|
||||||
|
Connection::new(ConnectionType::Client, server_sender.clone());
|
||||||
|
|
||||||
|
conn.delegate(socket, addr);
|
||||||
|
|
||||||
|
let sender = conn.get_sender();
|
||||||
|
let disconnect_sender = server_sender.clone();
|
||||||
|
let _ = welcoming_tx.send((addr, sender)).await;
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
conn.start_listening().await;
|
||||||
|
|
||||||
|
// TODO! Add waiting for reconnect
|
||||||
|
|
||||||
|
let disconnect_msg = Message::new(ClientMessage::ConnectionDead { addr });
|
||||||
|
|
||||||
|
let _ = disconnect_sender
|
||||||
|
.send((
|
||||||
|
addr,
|
||||||
|
disconnect_msg.expect(
|
||||||
|
"Could not create disconnect msg. this shouldn't happen",
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process incoming messages
|
||||||
|
pub async fn update(&mut self) {
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
new_client_opt = self.welcoming_reciever.as_mut().expect("Welcoming reciever was not set. Was the server started?").recv() => {
|
||||||
|
if let Some((addr, sender)) = new_client_opt {
|
||||||
|
if self.state.stage == ServerStage::Lobby && self.connections.keys().len() < self.settings.max_connections && !self.settings.allow_spectators {
|
||||||
|
let identity = NetworkServerIdentity::new(sender);
|
||||||
|
self.connections.insert(addr, identity);
|
||||||
|
self.request_handshake(addr).await;
|
||||||
|
} else {
|
||||||
|
let reason = "There is currently game going and it doesn't allow spectators".to_string();
|
||||||
|
info!("Rejecting {}, because {}", addr, reason);
|
||||||
|
|
||||||
|
let msg = Message::new(ServerMessage::DeclineHandshake { reason: reason });
|
||||||
|
|
||||||
|
if let Ok(msg) = msg {
|
||||||
|
let _ = sender.send(msg).await; // we can't use send_message because we
|
||||||
|
// don't add addr to connections
|
||||||
|
} else {
|
||||||
|
warn!("Could not send reason of rejection. Server is acting like my ex...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("Welcoming reciever closed!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msg_opt = self.reciever.recv() => {
|
||||||
|
if let Some(oldest_message) = msg_opt {
|
||||||
|
let (addr, msg) = oldest_message;
|
||||||
|
|
||||||
|
match msg.content {
|
||||||
|
ClientMessage::AnswerHandshake { player_name } => {
|
||||||
|
self.process_handshake(addr, player_name).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientMessage::ConnectionDead { addr } => {
|
||||||
|
self.remove_client(addr);
|
||||||
|
},
|
||||||
|
|
||||||
|
ClientMessage::SignalReadiness => {
|
||||||
|
self.handle_readiness(addr).await
|
||||||
|
},
|
||||||
|
|
||||||
|
ClientMessage::RequestCapture { capture_path } => {
|
||||||
|
self.process_capture(capture_path).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientMessage::RequestMove { from, to } => {
|
||||||
|
self.process_move(from, to).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientMessage::TextMessage (content) => {
|
||||||
|
info!("{} sent us a message: {}", addr, content);
|
||||||
|
let sender = self.connections[&addr].identity.name.clone();
|
||||||
|
self.broadcast_text_message(sender, content).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("Couldn't get latest message!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn start_game(&mut self) {
|
||||||
|
info!("Starting the game!");
|
||||||
|
let mut names: Vec<String> = vec![];
|
||||||
|
for networkidentity in self.connections.values() {
|
||||||
|
if networkidentity.identity.is_ready {
|
||||||
|
names.push(networkidentity.identity.name.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if names.len() != self.settings.max_connections {
|
||||||
|
error!("Tried starting the game, but could not gather enough ready player names");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let game_master = GameMaster::new(&names[0], &names[1]);
|
||||||
|
|
||||||
|
let names_and_ids: Vec<(String, Uuid)> = game_master.get_players_names_and_ids();
|
||||||
|
|
||||||
|
let mut msgs: Vec<(SocketAddr, Result<Message<ServerMessage>, postcard::Error>)> = vec![];
|
||||||
|
|
||||||
|
for (name, id) in names_and_ids {
|
||||||
|
for (addr, networkidentity) in self.connections.iter_mut() {
|
||||||
|
if networkidentity.identity.name == name {
|
||||||
|
networkidentity.identity.id = id;
|
||||||
|
|
||||||
|
msgs.push((
|
||||||
|
*addr,
|
||||||
|
Message::new(ServerMessage::GameStart {
|
||||||
|
identity: networkidentity.identity.clone(),
|
||||||
|
}),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (addr, msg) in msgs {
|
||||||
|
self.send_message(addr, msg).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.game_master = Some(game_master);
|
||||||
|
self.on_player_action().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ======= Processing messages :D helper functions ======== */
|
||||||
|
|
||||||
|
async fn process_handshake(&mut self, addr: SocketAddr, player_name: String) {
|
||||||
|
info!("Client: {:?} sent us his name: {}", addr, player_name);
|
||||||
|
self.send_message(addr, Message::new(ServerMessage::AcceptHandshake))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let fixed_name: String;
|
||||||
|
|
||||||
|
if self
|
||||||
|
.connections
|
||||||
|
.values()
|
||||||
|
.any(|v| v.identity.name == player_name)
|
||||||
|
{
|
||||||
|
fixed_name = format!("{}(2)", player_name);
|
||||||
|
warn!(
|
||||||
|
"Player has the same name as the other player so changing it to {}",
|
||||||
|
fixed_name
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
fixed_name = player_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(identity) = self.connections.get_mut(&addr) else {
|
||||||
|
error!("Could not get network identity while processing the handshake!");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
identity.identity.name = fixed_name
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn process_capture(&mut self, capture_path: CapturePath) {
|
||||||
|
if let Some(gm) = self.game_master.as_mut() {
|
||||||
|
if gm.capture(&capture_path) {
|
||||||
|
debug!("Capture valid!");
|
||||||
|
self.on_player_action().await;
|
||||||
|
} else {
|
||||||
|
debug!("Invalid capture!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn process_move(&mut self, from: Position, to: Position) {
|
||||||
|
if let Some(gm) = self.game_master.as_mut() {
|
||||||
|
if gm.move_pawn(from, to) {
|
||||||
|
debug!("Move was valid!");
|
||||||
|
self.on_player_action().await;
|
||||||
|
} else {
|
||||||
|
debug!("Move was invalid!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn request_handshake(&mut self, addr: SocketAddr) {
|
||||||
|
let msg = Message::new(ServerMessage::RequestHandshake);
|
||||||
|
self.send_message(addr, msg).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_readiness(&mut self, addr: SocketAddr) {
|
||||||
|
self.state.ready_count += 1;
|
||||||
|
info!(
|
||||||
|
"{} signaled readiness. Currently ready: {}/{}",
|
||||||
|
addr, self.state.ready_count, self.settings.max_connections
|
||||||
|
);
|
||||||
|
self.connections.get_mut(&addr).expect("Couldn't get the identity of the ready player. I guess this should never happen so i'm panicking like a little bitch").identity.is_ready = true; // TODO! This sometimes happens for some reason (when there is a game going, both player leave and someone tries to join)
|
||||||
|
|
||||||
|
if self.state.ready_count == self.settings.max_connections {
|
||||||
|
info!("All players are ready! Changing state to ServerStage::Game");
|
||||||
|
self.state.stage = ServerStage::Game;
|
||||||
|
self.start_game().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Sending messages === */
|
||||||
|
|
||||||
|
/// Should be used after every players move
|
||||||
|
async fn on_player_action(&mut self) {
|
||||||
|
if self.state.stage == ServerStage::Game {
|
||||||
|
self.broadcast_board_view().await;
|
||||||
|
self.broadcast_current_turn().await;
|
||||||
|
|
||||||
|
let Some(gm) = &self.game_master else {
|
||||||
|
error!("Game master not set while player made an action!");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let captures = gm.get_current_player_captures();
|
||||||
|
let moves = gm.get_current_player_moves();
|
||||||
|
let current_player = gm.get_current_turn();
|
||||||
|
|
||||||
|
if let Some(result) = gm.get_game_result() {
|
||||||
|
info!("Game ended with result: {:?}", result);
|
||||||
|
self.handle_game_end(result).await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !captures.is_empty() {
|
||||||
|
debug!("Current captures: {:?}", captures);
|
||||||
|
self.send_player_captures(current_player, captures).await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !moves.is_empty() {
|
||||||
|
debug!("Current moves: {:?}", moves);
|
||||||
|
self.send_player_moves(current_player, moves).await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
panic!("on_player_action should never get here");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_player_captures(&mut self, current_player: Uuid, captures: Vec<CapturePath>) {
|
||||||
|
let Some(addr) = self.get_client_by_id(current_player) else {
|
||||||
|
error!("Could not get player connection, while sending him his captures!");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let msg = Message::new(ServerMessage::AvailableCaptures { captures });
|
||||||
|
self.send_message(*addr, msg).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_player_moves(
|
||||||
|
&mut self,
|
||||||
|
current_player: Uuid,
|
||||||
|
moves: Vec<crate::logic::board::pawn::MovePath>,
|
||||||
|
) {
|
||||||
|
let Some(addr) = self.get_client_by_id(current_player) else {
|
||||||
|
error!("Could not get player connection, while sending him his moves!");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let msg = Message::new(ServerMessage::AvailableMoves { moves });
|
||||||
|
self.send_message(*addr, msg).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_game_end(&mut self, result: GameResult) {
|
||||||
|
let msg = Message::new(ServerMessage::GameEnd { result });
|
||||||
|
self.broadcast_message(msg).await;
|
||||||
|
self.state.stage = ServerStage::End;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn broadcast_board_view(&mut self) {
|
||||||
|
if let Some(gm) = &self.game_master {
|
||||||
|
let msg = Message::new(ServerMessage::BroadcastBoardState {
|
||||||
|
board: gm.get_board_view(),
|
||||||
|
});
|
||||||
|
self.broadcast_message(msg).await;
|
||||||
|
} else {
|
||||||
|
error!("Cannot broadcast board view as game manager is not set!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn broadcast_current_turn(&mut self) {
|
||||||
|
if let Some(gm) = &self.game_master {
|
||||||
|
let msg = Message::new(ServerMessage::BroadcastCurrentTurn {
|
||||||
|
active_player: gm.get_current_turn(),
|
||||||
|
});
|
||||||
|
self.broadcast_message(msg).await;
|
||||||
|
} else {
|
||||||
|
error!("Cannot broadcast current turn as game master doesn't exists!!!!!!!!!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn broadcast_text_message(&mut self, sender: String, content: String) {
|
||||||
|
let msg = Message::new(ServerMessage::BroadCastTextMessage { sender, content });
|
||||||
|
self.broadcast_message(msg).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Helper methods === */
|
||||||
|
|
||||||
|
async fn send_message(
|
||||||
|
&mut self,
|
||||||
|
addr: SocketAddr,
|
||||||
|
msg: Result<Message<ServerMessage>, postcard::Error>,
|
||||||
|
) {
|
||||||
|
let conn_opt = self.connections.get_mut(&addr);
|
||||||
|
|
||||||
|
let Some(identity) = conn_opt else {
|
||||||
|
error!("Could not get identity from connections list");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = match msg {
|
||||||
|
Err(e) => {
|
||||||
|
error!("There was an error while creating message: {}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Ok(msg) => identity.sender.send(msg).await,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn broadcast_message(&mut self, msg: Result<Message<ServerMessage>, postcard::Error>) {
|
||||||
|
match msg {
|
||||||
|
Ok(msg) => {
|
||||||
|
for identity in self.connections.values_mut() {
|
||||||
|
let _ = identity.sender.send(msg.clone()).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Error while broadcasting the message: {}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_client_by_id(&self, id: Uuid) -> Option<&SocketAddr> {
|
||||||
|
self.connections
|
||||||
|
.iter()
|
||||||
|
.find(|(_, indentity)| indentity.identity.id == id)
|
||||||
|
.map(|(addr, _)| addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_client(&mut self, addr: SocketAddr) {
|
||||||
|
warn!("Removing: {:?} from connection list", addr);
|
||||||
|
let is_ready = &mut self.connections.get_mut(&addr).expect("Couldn't get the identity of disconnected player. I don't think i should panic but i don't care fuck you").identity.is_ready;
|
||||||
|
|
||||||
|
if *is_ready {
|
||||||
|
*is_ready = false;
|
||||||
|
self.state.ready_count -= 1;
|
||||||
|
info!(
|
||||||
|
"Currently {}/{} players are ready",
|
||||||
|
self.state.ready_count, self.settings.max_connections
|
||||||
|
);
|
||||||
|
}
|
||||||
|
self.connections.remove(&addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user