Network logic
This commit is contained in:
Generated
+502
@@ -0,0 +1,502 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "audiopoker_core"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"rand",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "audiopoker_server"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"audiopoker_core",
|
||||||
|
"futures-util",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tokio",
|
||||||
|
"tokio-tungstenite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-buffer"
|
||||||
|
version = "0.10.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpufeatures"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crypto-common"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "data-encoding"
|
||||||
|
version = "2.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.10.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer",
|
||||||
|
"crypto-common",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-core"
|
||||||
|
version = "0.3.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-macro"
|
||||||
|
version = "0.3.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-sink"
|
||||||
|
version = "0.3.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-task"
|
||||||
|
version = "0.3.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-util"
|
||||||
|
version = "0.3.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-macro",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-task",
|
||||||
|
"pin-project-lite",
|
||||||
|
"slab",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "generic-array"
|
||||||
|
version = "0.14.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||||
|
dependencies = [
|
||||||
|
"typenum",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"itoa",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httparse"
|
||||||
|
version = "1.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.189"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
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]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.107"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.47"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||||
|
dependencies = [
|
||||||
|
"serde_core",
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_core"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.151"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"memchr",
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
|
"zmij",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha1"
|
||||||
|
version = "0.10.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "slab"
|
||||||
|
version = "0.4.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||||
|
|
||||||
|
[[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 = "syn"
|
||||||
|
version = "2.0.119"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "3.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio"
|
||||||
|
version = "1.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"pin-project-lite",
|
||||||
|
"socket2",
|
||||||
|
"tokio-macros",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-macros"
|
||||||
|
version = "2.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-tungstenite"
|
||||||
|
version = "0.24.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9"
|
||||||
|
dependencies = [
|
||||||
|
"futures-util",
|
||||||
|
"log",
|
||||||
|
"tokio",
|
||||||
|
"tungstenite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tungstenite"
|
||||||
|
version = "0.24.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"bytes",
|
||||||
|
"data-encoding",
|
||||||
|
"http",
|
||||||
|
"httparse",
|
||||||
|
"log",
|
||||||
|
"rand",
|
||||||
|
"sha1",
|
||||||
|
"thiserror",
|
||||||
|
"utf-8",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.20.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf-8"
|
||||||
|
version = "0.7.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.1+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-link"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.61.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy"
|
||||||
|
version = "0.8.55"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.8.55"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zmij"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||||
@@ -35,8 +35,11 @@ Ein barrierefreies Multiplayer-Pokergame für blinde Spieler, das primär über
|
|||||||
- [x] **Netzwerkprotokoll**: `core::network::{ClientMessage, ServerMessage}` neu entworfen, sodass der Server dieselben Announce-/PlaySfx-"Häppchen" verschickt, die der lokale Client schon per TTS/SFX verarbeitet (`GameManager::take_action` liefert sie direkt). Erhöhungsbeträge werden bewusst serverseitig festgelegt (kein clientseitig frei wählbarer Raise-Betrag).
|
- [x] **Netzwerkprotokoll**: `core::network::{ClientMessage, ServerMessage}` neu entworfen, sodass der Server dieselben Announce-/PlaySfx-"Häppchen" verschickt, die der lokale Client schon per TTS/SFX verarbeitet (`GameManager::take_action` liefert sie direkt). Erhöhungsbeträge werden bewusst serverseitig festgelegt (kein clientseitig frei wählbarer Raise-Betrag).
|
||||||
- [x] **Multiplayer-Lobby/Tischsuche (einfache Variante)**: Tischname im `JoinTable` dient als Suchbegriff; unbekannte Namen legen automatisch einen neuen Tisch an. Kein Auflisten aller offenen Tische (`ListTables`) - falls gewünscht, wäre das die nächste Ausbaustufe.
|
- [x] **Multiplayer-Lobby/Tischsuche (einfache Variante)**: Tischname im `JoinTable` dient als Suchbegriff; unbekannte Namen legen automatisch einen neuen Tisch an. Kein Auflisten aller offenen Tische (`ListTables`) - falls gewünscht, wäre das die nächste Ausbaustufe.
|
||||||
- **Getestet**: `cargo test -p audiopoker_core` (10 Tests) und ein Ende-zu-Ende-Smoke-Test mit zwei echten WebSocket-Clients (Python) gegen den laufenden `audiopoker_server` liefen hier erfolgreich durch (kompletter Hand-Durchlauf Pre-Flop bis Showdown, korrekte Sieger-Ermittlung).
|
- **Getestet**: `cargo test -p audiopoker_core` (10 Tests) und ein Ende-zu-Ende-Smoke-Test mit zwei echten WebSocket-Clients (Python) gegen den laufenden `audiopoker_server` liefen hier erfolgreich durch (kompletter Hand-Durchlauf Pre-Flop bis Showdown, korrekte Sieger-Ermittlung).
|
||||||
- [ ] **Client-Anbindung an den Server fehlt noch**: Der Bevy-Client (`client/`) spielt aktuell weiterhin nur lokal/offline (siehe `game_plugin.rs`, 2 fest verdrahtete Spieler). Die Anbindung per `web_sys::WebSocket` (Feature ist in `client/Cargo.toml` schon vorbereitet) an `audiopoker_server` ist der nächste sinnvolle Schritt.
|
- [x] **Client-Anbindung an den Server**: `client/src/network.rs` verbindet sich per `web_sys::WebSocket` mit `audiopoker_server` (Non-Send-Resource, da die JS-Callbacks nicht `Send` sind). `game_plugin.rs` spielt jetzt nicht mehr lokal/offline, sondern hält nur noch eine schlanke `NetworkGameView` (eigene Hole Cards, sichtbare Community Cards, wer am Zug ist) und schickt Aktionen als `ClientMessage` an den Server; `ServerMessage`s werden zu `Announce`/`PlaySfx` übersetzt (gleicher Audio-Pfad wie zuvor lokal). Server-Bugfix nebenbei gefixt: Aktionen nach `Round::Showdown` wurden bisher nicht abgelehnt - jetzt gibt's dafür einen `Error` plus ein neues `ServerMessage::HandFinished`-Signal, mit dem Clients die Eingabe sperren.
|
||||||
|
- Server-/Tisch-/Spielername sind über Browser-URL-Parameter steuerbar (`?server=...&table=...&name=...`), damit sich mehrere Browser-Tabs bequem als unterschiedliche Spieler an denselben Tisch hängen lassen.
|
||||||
|
- **Wichtiger Verifikationshinweis**: Der eigentliche `web_sys::WebSocket`-Code (`#[cfg(target_arch = "wasm32")]`) konnte hier nicht kompiliert werden (kein wasm32-Std verfügbar). Ein natives `cargo check -p audiopoker_client` prüft nur den `not(wasm32)`-Dummy-Zweig, NICHT den echten WebSocket-Pfad! Bitte zusätzlich `cargo check --target wasm32-unknown-unknown -p audiopoker_client` (Target ggf. per `rustup target add wasm32-unknown-unknown` installieren) ausführen, um das wirklich zu verifizieren. Der plattformunabhängige Teil (`ClientMessage`/`ServerMessage`-Nutzung, Query-Parameter-Parsing) wurde hier isoliert gegen den nativen Dummy-Zweig getestet und funktioniert.
|
||||||
- [ ] Tisch-Übersicht/`ListTables` für echte Tischsuche (aktuell nur "Name kennen und beitreten").
|
- [ ] Tisch-Übersicht/`ListTables` für echte Tischsuche (aktuell nur "Name kennen und beitreten").
|
||||||
|
- [ ] Neue Hand nach Showdown starten (serverseitig; der Client sperrt nach `HandFinished` nur die Eingabe).
|
||||||
|
|
||||||
### Phase 3: Audio Experience & UI
|
### Phase 3: Audio Experience & UI
|
||||||
- [x] **TTS-Logik verfeinert**: `GameManager::take_action` erzeugt kontextuelle Ansagen (Aktion des Spielers, Rundenwechsel inkl. aufgedeckter Community Cards, Showdown-Gewinner mit Hand-Kategorie, "wer ist am Zug"). Karten werden über `Card`/`Suit`-`Display` sprachfreundlich ausgegeben ("König Herz").
|
- [x] **TTS-Logik verfeinert**: `GameManager::take_action` erzeugt kontextuelle Ansagen (Aktion des Spielers, Rundenwechsel inkl. aufgedeckter Community Cards, Showdown-Gewinner mit Hand-Kategorie, "wer ist am Zug"). Karten werden über `Card`/`Suit`-`Display` sprachfreundlich ausgegeben ("König Herz").
|
||||||
@@ -49,3 +52,10 @@ Ein barrierefreies Multiplayer-Pokergame für blinde Spieler, das primär über
|
|||||||
- [ ] Barrierefreiheits-Audit (Tastatur-Flow ohne Maus).
|
- [ ] Barrierefreiheits-Audit (Tastatur-Flow ohne Maus).
|
||||||
- [ ] Latenztests im Multiplayer.
|
- [ ] Latenztests im Multiplayer.
|
||||||
- [ ] WebAssembly Deployment Vorbereitung.
|
- [ ] WebAssembly Deployment Vorbereitung.
|
||||||
|
|
||||||
|
## Lokal zu zweit testen (nach Client-Netzwerk-Anbindung)
|
||||||
|
1. Server starten: `cargo run -p audiopoker_server` (lauscht standardmäßig auf `ws://0.0.0.0:9001`, überschreibbar über die Umgebungsvariable `AUDIOPOKER_ADDR`).
|
||||||
|
2. Client für Wasm bauen/starten (z. B. über `trunk serve` o. ä. - noch nicht Teil dieses Repos, siehe offene Punkte) und zweimal im Browser öffnen:
|
||||||
|
- Tab 1: `?server=ws://127.0.0.1:9001&table=tisch1&name=Anna`
|
||||||
|
- Tab 2: `?server=ws://127.0.0.1:9001&table=tisch1&name=Ben`
|
||||||
|
3. Sobald beide beigetreten sind, startet die Hand automatisch; Steuerung wie gehabt über Tab/Space/Enter.
|
||||||
|
|||||||
+3
-5
@@ -8,12 +8,10 @@ audiopoker_core = { path = "../core" }
|
|||||||
bevy = { version = "0.19", features = ["webgl2"] }
|
bevy = { version = "0.19", features = ["webgl2"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
# wasm-bindgen/web-sys/js-sys/futures: aktuell nur für audio::tts (Web Speech
|
# wasm-bindgen/web-sys/js-sys/futures: für audio::tts (Web Speech API) und
|
||||||
# API) genutzt. Werden zusätzlich gebraucht, sobald der Client per
|
# network (WebSocket-Anbindung an ../server), siehe src/network.rs.
|
||||||
# web_sys::WebSocket an den `server` (siehe ../server) angebunden wird -
|
|
||||||
# das ist noch offen, siehe PLAN.md.
|
|
||||||
wasm-bindgen = "0.2"
|
wasm-bindgen = "0.2"
|
||||||
web-sys = { version = "0.3", features = ["SpeechRecognition", "SpeechSynthesis", "SpeechSynthesisUtterance", "Window", "HtmlInputElement", "console", "WebSocket", "MessageEvent"] }
|
web-sys = { version = "0.3", features = ["SpeechRecognition", "SpeechSynthesis", "SpeechSynthesisUtterance", "Window", "HtmlInputElement", "console", "WebSocket", "MessageEvent", "ErrorEvent", "CloseEvent", "Location", "UrlSearchParams"] }
|
||||||
js-sys = "0.3"
|
js-sys = "0.3"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
|||||||
+137
-60
@@ -1,10 +1,17 @@
|
|||||||
//! Verbindet die (bewusst Bevy-unabhängige) Kernlogik aus `core` und `audio`
|
//! Verbindet die Bevy-`App` mit der Audio-Engine (`audio`) und dem
|
||||||
//! mit einer echten Bevy-`App` (siehe PLAN.md, Phase 1 & 3).
|
//! `network`-Modul, das die WebSocket-Verbindung zu `audiopoker_server`
|
||||||
|
//! hält (siehe PLAN.md, Phase 2/3).
|
||||||
|
//!
|
||||||
|
//! Architektur: Der Server ist die einzige Quelle der Wahrheit für den
|
||||||
|
//! Spielzustand (siehe `core::network`-Doku). Der Client hält deshalb nur
|
||||||
|
//! eine schlanke `NetworkGameView` (eigene Hole Cards, sichtbare Community
|
||||||
|
//! Cards, wer am Zug ist) statt eines vollen `GameState` - das verhindert
|
||||||
|
//! auch, dass ein manipulierter Client sich selbst fremde Hole Cards zeigen
|
||||||
|
//! könnte, da der Server sie ohnehin nur an den jeweiligen Spieler schickt.
|
||||||
//!
|
//!
|
||||||
//! Hinweis zur Bevy-Version: Seit Bevy 0.17 sind gepufferte Events als
|
//! Hinweis zur Bevy-Version: Seit Bevy 0.17 sind gepufferte Events als
|
||||||
//! `Message` (statt `Event`) modelliert und laufen über `MessageWriter` /
|
//! `Message` (statt `Event`) modelliert, siehe `MessageWriter`/`MessageReader`/
|
||||||
//! `MessageReader` / `app.add_message::<T>()`. `Event` bezeichnet seither
|
//! `app.add_message::<T>()`. Dieser Code ist auf Bevy 0.19 ausgelegt.
|
||||||
//! ausschließlich Observer-Events. Dieser Code ist auf Bevy 0.19 ausgelegt.
|
|
||||||
//!
|
//!
|
||||||
//! Steuerung (barrierefrei, tastaturzentriert, siehe PLAN.md):
|
//! Steuerung (barrierefrei, tastaturzentriert, siehe PLAN.md):
|
||||||
//! - `Tab`: wechselt die ausgewählte Aktion (Fold / Call-Check / Raise).
|
//! - `Tab`: wechselt die ausgewählte Aktion (Fold / Call-Check / Raise).
|
||||||
@@ -12,14 +19,11 @@
|
|||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
use crate::audio::AudioEngine;
|
use audiopoker_core::logic::card::Card;
|
||||||
use audiopoker_core::game::{BettingAction, GameManager, GameState, Round, RAISE_INCREMENT};
|
use audiopoker_core::network::{ClientMessage, ServerMessage};
|
||||||
use audiopoker_core::logic::Deck;
|
|
||||||
|
|
||||||
/// Bevy-Resource-Wrapper um den Engine-unabhängigen Spielzustand.
|
use crate::audio::AudioEngine;
|
||||||
/// Bewusst als Newtype, damit `core::game` selbst keine Bevy-Abhängigkeit braucht.
|
use crate::network::{self, ConnectionConfig, NetworkClient};
|
||||||
#[derive(Resource)]
|
|
||||||
pub struct Table(pub GameState);
|
|
||||||
|
|
||||||
/// Bevy-Resource-Wrapper um die Engine-unabhängige Audio-Engine (`audio::AudioEngine`).
|
/// Bevy-Resource-Wrapper um die Engine-unabhängige Audio-Engine (`audio::AudioEngine`).
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
@@ -34,7 +38,19 @@ pub struct ActionCursor {
|
|||||||
|
|
||||||
const ACTION_LABELS: [&str; 3] = ["Fold", "Call/Check", "Raise"];
|
const ACTION_LABELS: [&str; 3] = ["Fold", "Call/Check", "Raise"];
|
||||||
|
|
||||||
/// Message: Ein Text soll per TTS angesagt werden (z. B. "Spieler 1 setzt 50").
|
/// Der clientseitige Blick auf das Spiel: nur das, was der Server diesem
|
||||||
|
/// Client tatsächlich mitgeteilt hat. Kein autoritativer Zustand - der
|
||||||
|
/// liegt beim Server (`audiopoker_server::table::TableActor`).
|
||||||
|
#[derive(Resource, Default)]
|
||||||
|
pub struct NetworkGameView {
|
||||||
|
pub my_player_id: Option<u32>,
|
||||||
|
pub hole_cards: Vec<Card>,
|
||||||
|
pub community_cards: Vec<Card>,
|
||||||
|
pub to_act: Option<u32>,
|
||||||
|
pub hand_finished: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Message: Ein Text soll per TTS angesagt werden.
|
||||||
#[derive(Message, Debug, Clone)]
|
#[derive(Message, Debug, Clone)]
|
||||||
pub struct Announce(pub String);
|
pub struct Announce(pub String);
|
||||||
|
|
||||||
@@ -48,55 +64,119 @@ impl Plugin for AudiopokerPlugin {
|
|||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.insert_resource(AudioRes(AudioEngine::new()))
|
app.insert_resource(AudioRes(AudioEngine::new()))
|
||||||
.init_resource::<ActionCursor>()
|
.init_resource::<ActionCursor>()
|
||||||
|
.init_resource::<NetworkGameView>()
|
||||||
.add_message::<Announce>()
|
.add_message::<Announce>()
|
||||||
.add_message::<PlaySfx>()
|
.add_message::<PlaySfx>()
|
||||||
.add_systems(Startup, setup_game)
|
.add_systems(Startup, setup_network)
|
||||||
.add_systems(Update, (handle_player_input, speak_announcements, play_sfx_cues));
|
.add_systems(
|
||||||
|
Update,
|
||||||
|
(
|
||||||
|
receive_network_messages,
|
||||||
|
handle_player_input,
|
||||||
|
speak_announcements,
|
||||||
|
play_sfx_cues,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Startet eine neue Partie, teilt Karten aus und kündigt beides per
|
/// Baut beim Start die WebSocket-Verbindung auf. Server-/Tisch-/Spielername
|
||||||
/// TTS/SFX-Message an. Läuft einmalig im `Startup`-Schedule.
|
/// lassen sich über die Browser-URL steuern (siehe `network::read_query_param`),
|
||||||
fn setup_game(
|
/// damit mehrere Browser-Tabs bequem als unterschiedliche Spieler denselben
|
||||||
mut commands: Commands,
|
/// Tisch testen können.
|
||||||
|
fn setup_network(world: &mut World) {
|
||||||
|
let config = ConnectionConfig {
|
||||||
|
server_url: network::read_query_param("server", "ws://127.0.0.1:9001"),
|
||||||
|
table_name: network::read_query_param("table", "tisch1"),
|
||||||
|
player_name: network::read_query_param(
|
||||||
|
"name",
|
||||||
|
&format!("Spieler{}", network::random_suffix()),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
let client = NetworkClient::connect(config);
|
||||||
|
world.insert_non_send_resource(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Übersetzt eine Server-`PlaySfx`-Cue-ID (freier String) in eine der lokal
|
||||||
|
/// bekannten, `'static` Cue-IDs. Unbekannte IDs werden ignoriert, statt das
|
||||||
|
/// Spiel abstürzen zu lassen (z. B. falls Server und Client mal auseinanderlaufen).
|
||||||
|
fn map_sfx_id(id: &str) -> Option<&'static str> {
|
||||||
|
match id {
|
||||||
|
"shuffle" => Some("shuffle"),
|
||||||
|
"chip" => Some("chip"),
|
||||||
|
"fold" => Some("fold"),
|
||||||
|
"flip" => Some("flip"),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Leert die eingehende Nachrichten-Queue der Netzwerkverbindung und
|
||||||
|
/// übersetzt jede `ServerMessage` in ein Update von `NetworkGameView`
|
||||||
|
/// und/oder eine `Announce`/`PlaySfx`-Message fürs Audio-Feedback.
|
||||||
|
fn receive_network_messages(
|
||||||
|
net: NonSend<NetworkClient>,
|
||||||
|
mut view: ResMut<NetworkGameView>,
|
||||||
mut announcements: MessageWriter<Announce>,
|
mut announcements: MessageWriter<Announce>,
|
||||||
mut sfx_cues: MessageWriter<PlaySfx>,
|
mut sfx_cues: MessageWriter<PlaySfx>,
|
||||||
) {
|
) {
|
||||||
let mut game = GameManager::start_new_game(vec!["Spieler 1".into(), "Spieler 2".into()]);
|
for message in net.drain_incoming() {
|
||||||
|
match message {
|
||||||
let mut deck = Deck::new();
|
ServerMessage::Welcome { player_id } => {
|
||||||
deck.shuffle();
|
view.my_player_id = Some(player_id);
|
||||||
for player in game.players.iter_mut() {
|
announcements.write(Announce(format!("Verbunden als Spieler {player_id}.")));
|
||||||
player.hole_cards = deck.deal_hand(2);
|
|
||||||
}
|
|
||||||
game.community_cards = deck.deal_hand(5);
|
|
||||||
|
|
||||||
sfx_cues.write(PlaySfx("shuffle"));
|
|
||||||
announcements.write(Announce(format!(
|
|
||||||
"Neues Spiel gestartet mit {} Spielern.",
|
|
||||||
game.players.len()
|
|
||||||
)));
|
|
||||||
announcements.write(Announce(
|
announcements.write(Announce(
|
||||||
"Steuerung: Tab wählt eine Aktion aus, Leertaste oder Eingabetaste bestätigt.".into(),
|
"Steuerung: Tab wählt eine Aktion aus, Leertaste oder Eingabetaste bestätigt.".into(),
|
||||||
));
|
));
|
||||||
announcements.write(Announce(format!("{} ist am Zug.", game.players[game.to_act].name)));
|
}
|
||||||
|
ServerMessage::Announce(text) => announcements.write(Announce(text)),
|
||||||
commands.insert_resource(Table(game));
|
ServerMessage::PlaySfx(id) => {
|
||||||
|
if let Some(cue) = map_sfx_id(&id) {
|
||||||
|
sfx_cues.write(PlaySfx(cue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ServerMessage::HoleCards(cards) => {
|
||||||
|
let spoken = cards
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(" und ");
|
||||||
|
announcements.write(Announce(format!("Deine Karten: {spoken}.")));
|
||||||
|
view.hole_cards = cards;
|
||||||
|
}
|
||||||
|
ServerMessage::CommunityCards(cards) => {
|
||||||
|
view.community_cards = cards;
|
||||||
|
}
|
||||||
|
ServerMessage::TurnToAct { player_id } => {
|
||||||
|
view.to_act = Some(player_id);
|
||||||
|
if view.my_player_id == Some(player_id) {
|
||||||
|
announcements.write(Announce("Du bist am Zug.".into()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ServerMessage::HandFinished => {
|
||||||
|
view.hand_finished = true;
|
||||||
|
}
|
||||||
|
ServerMessage::Error(reason) => {
|
||||||
|
announcements.write(Announce(format!("Fehler: {reason}")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Liest Tab/Space/Enter und wendet die ausgewählte Aktion auf den aktuell
|
/// Liest Tab/Space/Enter und schickt bei Bestätigung die passende
|
||||||
/// am Zug befindlichen Spieler an. Die eigentliche Spiellogik (Straßen-
|
/// `ClientMessage` an den Server. Die eigentliche Spiellogik läuft
|
||||||
/// Fortschritt, Showdown) steckt komplett in `GameManager::take_action`.
|
/// ausschließlich serverseitig (`GameManager::take_action`); der Client
|
||||||
|
/// wartet einfach auf die resultierenden `ServerMessage`s.
|
||||||
fn handle_player_input(
|
fn handle_player_input(
|
||||||
keys: Res<ButtonInput<KeyCode>>,
|
keys: Res<ButtonInput<KeyCode>>,
|
||||||
mut cursor: ResMut<ActionCursor>,
|
mut cursor: ResMut<ActionCursor>,
|
||||||
mut table: ResMut<Table>,
|
view: Res<NetworkGameView>,
|
||||||
|
net: NonSend<NetworkClient>,
|
||||||
mut announcements: MessageWriter<Announce>,
|
mut announcements: MessageWriter<Announce>,
|
||||||
mut sfx_cues: MessageWriter<PlaySfx>,
|
|
||||||
) {
|
) {
|
||||||
if table.0.current_round == Round::Showdown {
|
if view.hand_finished {
|
||||||
// Hand ist beendet. Eine neue Hand starten ist noch nicht implementiert
|
// Hand ist beendet. Eine neue Hand starten ist noch nicht
|
||||||
// (nächster sinnvoller Schritt, z. B. über eine eigene "Neue Hand"-Taste).
|
// implementiert (nächster sinnvoller Schritt, siehe PLAN.md).
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,24 +186,21 @@ fn handle_player_input(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if keys.just_pressed(KeyCode::Space) || keys.just_pressed(KeyCode::Enter) {
|
if keys.just_pressed(KeyCode::Space) || keys.just_pressed(KeyCode::Enter) {
|
||||||
let state = &mut table.0;
|
let Some(my_id) = view.my_player_id else {
|
||||||
let player_idx = state.to_act;
|
announcements.write(Announce("Noch nicht mit dem Tisch verbunden.".into()));
|
||||||
let to_call = GameManager::amount_to_call(state, player_idx);
|
return;
|
||||||
|
|
||||||
let action = match cursor.selected {
|
|
||||||
0 => BettingAction::Fold,
|
|
||||||
1 if to_call == 0 => BettingAction::Check,
|
|
||||||
1 => BettingAction::Call(to_call),
|
|
||||||
_ => BettingAction::Raise(to_call + RAISE_INCREMENT),
|
|
||||||
};
|
};
|
||||||
|
if view.to_act != Some(my_id) {
|
||||||
|
announcements.write(Announce("Du bist gerade nicht am Zug.".into()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let outcome = GameManager::take_action(state, player_idx, action);
|
let message = match cursor.selected {
|
||||||
for line in outcome.announcements {
|
0 => ClientMessage::Fold,
|
||||||
announcements.write(Announce(line));
|
1 => ClientMessage::Check, // Server wertet das bei fälligem Call automatisch als Call.
|
||||||
}
|
_ => ClientMessage::Raise,
|
||||||
for cue in outcome.sfx_cues {
|
};
|
||||||
sfx_cues.write(PlaySfx(cue));
|
net.send(&message);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
mod audio;
|
mod audio;
|
||||||
mod game_plugin;
|
mod game_plugin;
|
||||||
|
mod network;
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use game_plugin::AudiopokerPlugin;
|
use game_plugin::AudiopokerPlugin;
|
||||||
|
|||||||
@@ -0,0 +1,196 @@
|
|||||||
|
//! Client-seitige WebSocket-Anbindung an `audiopoker_server` (siehe PLAN.md, Phase 2).
|
||||||
|
//!
|
||||||
|
//! `web_sys::WebSocket` ist callback-basiert (onopen/onmessage/onerror/onclose)
|
||||||
|
//! und die dafür nötigen `Closure`s sind nicht `Send`. Deshalb wird die
|
||||||
|
//! Verbindung nicht als normale Bevy-`Resource`, sondern als
|
||||||
|
//! Non-Send-Resource gehalten (`app.insert_non_send_resource`, gelesen über
|
||||||
|
//! `NonSend`/`NonSendMut`) - das ist das übliche Bevy-Muster für JS-Interop.
|
||||||
|
//! Eingehende Nachrichten landen zunächst in einer gemeinsamen Queue
|
||||||
|
//! (`Rc<RefCell<VecDeque<...>>>`), die ein normales Bevy-System jeden Frame
|
||||||
|
//! leert und in `Announce`/`PlaySfx`/`NetworkGameView`-Updates übersetzt
|
||||||
|
//! (siehe `game_plugin.rs`).
|
||||||
|
//!
|
||||||
|
//! Wichtiger Hinweis zur Verifikation: Dieser Code nutzt `#[cfg(target_arch
|
||||||
|
//! = "wasm32")]`, weil `web_sys::WebSocket` zur Laufzeit eine echte
|
||||||
|
//! Browser-Umgebung braucht. Ein natives `cargo check`/`cargo build` prüft
|
||||||
|
//! ausschließlich den `not(wasm32)`-Dummy-Zweig unten - der eigentliche
|
||||||
|
//! WebSocket-Code wird dabei NICHT typgeprüft. Zur echten Verifikation bitte
|
||||||
|
//! zusätzlich `cargo check --target wasm32-unknown-unknown -p
|
||||||
|
//! audiopoker_client` ausführen.
|
||||||
|
|
||||||
|
use audiopoker_core::network::{ClientMessage, ServerMessage};
|
||||||
|
|
||||||
|
/// Serverurl/Tisch/Spielername lassen sich über die Browser-URL steuern,
|
||||||
|
/// z. B. `index.html?server=ws://localhost:9001&table=tisch1&name=Anna` -
|
||||||
|
/// praktisch, um mehrere Browser-Tabs als unterschiedliche Spieler an
|
||||||
|
/// denselben Tisch zu hängen.
|
||||||
|
pub struct ConnectionConfig {
|
||||||
|
pub server_url: String,
|
||||||
|
pub table_name: String,
|
||||||
|
pub player_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
mod wasm_impl {
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
|
use web_sys::{CloseEvent, ErrorEvent, MessageEvent, WebSocket};
|
||||||
|
|
||||||
|
use super::{ClientMessage, ConnectionConfig, ServerMessage};
|
||||||
|
|
||||||
|
/// Non-Send-Resource: hält die WebSocket-Verbindung und die Closures,
|
||||||
|
/// die JS beim Aufruf von `set_on...` am Leben halten muss (würden sie
|
||||||
|
/// gedroppt, ruft der Browser sie nicht mehr auf bzw. meldet einen Fehler).
|
||||||
|
pub struct NetworkClient {
|
||||||
|
socket: WebSocket,
|
||||||
|
incoming: Rc<RefCell<VecDeque<ServerMessage>>>,
|
||||||
|
_on_open: Closure<dyn FnMut()>,
|
||||||
|
_on_message: Closure<dyn FnMut(MessageEvent)>,
|
||||||
|
_on_error: Closure<dyn FnMut(ErrorEvent)>,
|
||||||
|
_on_close: Closure<dyn FnMut(CloseEvent)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NetworkClient {
|
||||||
|
pub fn connect(config: ConnectionConfig) -> Self {
|
||||||
|
let socket = WebSocket::new(&config.server_url)
|
||||||
|
.expect("WebSocket-Verbindung konnte nicht aufgebaut werden");
|
||||||
|
let incoming = Rc::new(RefCell::new(VecDeque::new()));
|
||||||
|
|
||||||
|
let on_open = {
|
||||||
|
let socket = socket.clone();
|
||||||
|
let table_name = config.table_name.clone();
|
||||||
|
let player_name = config.player_name.clone();
|
||||||
|
Closure::<dyn FnMut()>::new(move || {
|
||||||
|
let join = ClientMessage::JoinTable {
|
||||||
|
table_name: table_name.clone(),
|
||||||
|
player_name: player_name.clone(),
|
||||||
|
};
|
||||||
|
if let Ok(text) = serde_json::to_string(&join) {
|
||||||
|
let _ = socket.send_with_str(&text);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
socket.set_onopen(Some(on_open.as_ref().unchecked_ref()));
|
||||||
|
|
||||||
|
let on_message = {
|
||||||
|
let incoming = Rc::clone(&incoming);
|
||||||
|
Closure::<dyn FnMut(MessageEvent)>::new(move |event: MessageEvent| {
|
||||||
|
let Some(text) = event.data().as_string() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
match serde_json::from_str::<ServerMessage>(&text) {
|
||||||
|
Ok(message) => incoming.borrow_mut().push_back(message),
|
||||||
|
Err(err) => web_sys::console::warn_1(
|
||||||
|
&format!("Ungültige Server-Nachricht ignoriert: {err}").into(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
socket.set_onmessage(Some(on_message.as_ref().unchecked_ref()));
|
||||||
|
|
||||||
|
let on_error = Closure::<dyn FnMut(ErrorEvent)>::new(move |_event: ErrorEvent| {
|
||||||
|
web_sys::console::error_1(&"WebSocket-Fehler.".into());
|
||||||
|
});
|
||||||
|
socket.set_onerror(Some(on_error.as_ref().unchecked_ref()));
|
||||||
|
|
||||||
|
let on_close = Closure::<dyn FnMut(CloseEvent)>::new(move |event: CloseEvent| {
|
||||||
|
web_sys::console::warn_1(
|
||||||
|
&format!("WebSocket-Verbindung geschlossen ({}).", event.reason()).into(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
socket.set_onclose(Some(on_close.as_ref().unchecked_ref()));
|
||||||
|
|
||||||
|
Self {
|
||||||
|
socket,
|
||||||
|
incoming,
|
||||||
|
_on_open: on_open,
|
||||||
|
_on_message: on_message,
|
||||||
|
_on_error: on_error,
|
||||||
|
_on_close: on_close,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Schickt eine Client-Nachricht, sofern die Verbindung offen ist.
|
||||||
|
/// Verworfene Nachrichten (z. B. weil noch nicht verbunden) landen
|
||||||
|
/// nur als Konsolen-Warnung, nicht als Panik - ein Verbindungsabbruch
|
||||||
|
/// soll das Spiel nicht crashen lassen.
|
||||||
|
pub fn send(&self, message: &ClientMessage) {
|
||||||
|
if self.socket.ready_state() != WebSocket::OPEN {
|
||||||
|
web_sys::console::warn_1(&"Nicht verbunden, Nachricht verworfen.".into());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
match serde_json::to_string(message) {
|
||||||
|
Ok(text) => {
|
||||||
|
let _ = self.socket.send_with_str(&text);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
web_sys::console::error_1(&format!("Konnte Nachricht nicht kodieren: {err}").into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Entnimmt alle bislang eingegangenen Server-Nachrichten (FIFO).
|
||||||
|
pub fn drain_incoming(&self) -> Vec<ServerMessage> {
|
||||||
|
self.incoming.borrow_mut().drain(..).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Liest einen Query-Parameter aus der aktuellen Browser-URL, mit Fallback.
|
||||||
|
pub fn read_query_param(name: &str, default: &str) -> String {
|
||||||
|
web_sys::window()
|
||||||
|
.and_then(|w| w.location().search().ok())
|
||||||
|
.and_then(|search| web_sys::UrlSearchParams::new_with_str(&search).ok())
|
||||||
|
.and_then(|params| params.get(name))
|
||||||
|
.unwrap_or_else(|| default.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Zufallszahl für Default-Spielernamen ("Spieler1234"), ohne auf
|
||||||
|
/// `rand`/OS-Entropie angewiesen zu sein (im Browser über `Math.random`).
|
||||||
|
pub fn random_suffix() -> u32 {
|
||||||
|
(js_sys::Math::random() * 10_000.0) as u32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
mod native_stub {
|
||||||
|
use super::{ClientMessage, ConnectionConfig, ServerMessage};
|
||||||
|
|
||||||
|
/// Dummy für native Builds/Tests: `web_sys::WebSocket` funktioniert zur
|
||||||
|
/// Laufzeit nur im Browser. Diese Variante verbindet sich nicht wirklich,
|
||||||
|
/// sondern loggt nur - so bleiben `cargo check`/`cargo test` auf dem
|
||||||
|
/// nativen Target weiterhin nutzbar (siehe Modul-Kommentar oben).
|
||||||
|
pub struct NetworkClient;
|
||||||
|
|
||||||
|
impl NetworkClient {
|
||||||
|
pub fn connect(config: ConnectionConfig) -> Self {
|
||||||
|
println!(
|
||||||
|
"Networking (Dummy, natives Target): würde zu {} verbinden (Tisch '{}', Name '{}').",
|
||||||
|
config.server_url, config.table_name, config.player_name
|
||||||
|
);
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send(&self, _message: &ClientMessage) {}
|
||||||
|
|
||||||
|
pub fn drain_incoming(&self) -> Vec<ServerMessage> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_query_param(_name: &str, default: &str) -> String {
|
||||||
|
default.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn random_suffix() -> u32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
pub use wasm_impl::*;
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub use native_stub::*;
|
||||||
@@ -43,6 +43,10 @@ pub enum ServerMessage {
|
|||||||
CommunityCards(Vec<Card>),
|
CommunityCards(Vec<Card>),
|
||||||
/// Wer als Nächstes agieren muss.
|
/// Wer als Nächstes agieren muss.
|
||||||
TurnToAct { player_id: u32 },
|
TurnToAct { player_id: u32 },
|
||||||
|
/// Die Hand ist vorbei (Showdown erreicht oder alle bis auf einen
|
||||||
|
/// gefoldet). Der Text dazu kommt separat als `Announce`; dies ist nur
|
||||||
|
/// das strukturierte Signal, damit Clients z. B. die Eingabe sperren können.
|
||||||
|
HandFinished,
|
||||||
/// Aktion war ungültig (z. B. nicht am Zug, Tisch schon voll) oder ein
|
/// Aktion war ungültig (z. B. nicht am Zug, Tisch schon voll) oder ein
|
||||||
/// sonstiger Fehler ist aufgetreten.
|
/// sonstiger Fehler ist aufgetreten.
|
||||||
Error(String),
|
Error(String),
|
||||||
|
|||||||
+8
-1
@@ -147,6 +147,11 @@ impl TableActor {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if state.current_round == Round::Showdown {
|
||||||
|
self.send_to(player_id, ServerMessage::Error("Die Hand ist bereits beendet.".into()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let to_call = GameManager::amount_to_call(state, player_id as usize);
|
let to_call = GameManager::amount_to_call(state, player_id as usize);
|
||||||
let action = match message {
|
let action = match message {
|
||||||
ClientMessage::Fold => BettingAction::Fold,
|
ClientMessage::Fold => BettingAction::Fold,
|
||||||
@@ -183,7 +188,9 @@ impl TableActor {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if state.current_round != Round::Showdown {
|
if state.current_round == Round::Showdown {
|
||||||
|
self.broadcast(ServerMessage::HandFinished);
|
||||||
|
} else {
|
||||||
self.broadcast(ServerMessage::TurnToAct {
|
self.broadcast(ServerMessage::TurnToAct {
|
||||||
player_id: state.players[state.to_act].id,
|
player_id: state.players[state.to_act].id,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user