From 7b4fc568a13ba59a5fcf6cdd4b83ee53d8cb2310 Mon Sep 17 00:00:00 2001 From: szymon Date: Fri, 14 Aug 2026 18:21:46 +0200 Subject: [PATCH 01/41] feat: add messages enums --- src/logic/mod.rs | 4 ++-- src/main.rs | 1 + src/network/client.rs | 0 src/network/connection.rs | 0 src/network/message.rs | 31 +++++++++++++++++++++++++++++++ src/network/mod.rs | 4 ++++ src/network/server.rs | 0 7 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 src/network/client.rs create mode 100644 src/network/connection.rs create mode 100644 src/network/message.rs create mode 100644 src/network/mod.rs create mode 100644 src/network/server.rs diff --git a/src/logic/mod.rs b/src/logic/mod.rs index bb9dc31..c09da7c 100644 --- a/src/logic/mod.rs +++ b/src/logic/mod.rs @@ -1,4 +1,4 @@ -mod board; +pub mod board; pub mod game_master; -mod math; +pub mod math; mod player; diff --git a/src/main.rs b/src/main.rs index 59c15eb..d7771a6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ mod logic; +mod network; fn main() { println!("Hello, world!"); diff --git a/src/network/client.rs b/src/network/client.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/network/connection.rs b/src/network/connection.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/network/message.rs b/src/network/message.rs new file mode 100644 index 0000000..37a1020 --- /dev/null +++ b/src/network/message.rs @@ -0,0 +1,31 @@ +use uuid::Uuid; + +use crate::logic::{ + board::{ + board::Board, + pawn::{CapturePath, MovePath}, + }, + game_master::GameResult, + math::position::Position, +}; + +pub enum ServerMessage { + RequestHandshake, + AcceptHandshake { player_id: Uuid }, + DeclineHandshake { reason: String }, + + AvailableCaptures { captures: Vec }, + AvailableMoves { moves: Vec }, + + BroadcastBoardState { board: Board }, + BroadcastCurrentTurn { active_player: Uuid }, + + GameEnd { result: GameResult }, +} + +pub enum ClientMessage { + AnswerHandshake { player_name: String }, + + RequestCapture { capture_path: CapturePath }, + RequestMove { from: Position, to: Position }, +} diff --git a/src/network/mod.rs b/src/network/mod.rs new file mode 100644 index 0000000..17a8c2f --- /dev/null +++ b/src/network/mod.rs @@ -0,0 +1,4 @@ +mod client; +mod connection; +mod message; +mod server; diff --git a/src/network/server.rs b/src/network/server.rs new file mode 100644 index 0000000..e69de29 From 7895919f87be70c6439982903443c8dac10fcabc Mon Sep 17 00:00:00 2001 From: szymon Date: Fri, 14 Aug 2026 18:30:00 +0200 Subject: [PATCH 02/41] feat: add serde for necessary fields --- Cargo.lock | 17 ++++++++++------- Cargo.toml | 5 ++--- src/logic/board/board.rs | 3 +++ src/logic/board/field.rs | 4 +++- src/logic/board/pawn.rs | 5 +++-- src/logic/math/position.rs | 3 ++- 6 files changed, 23 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ac90b9e..88f5475 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,6 +9,7 @@ dependencies = [ "env_logger", "log", "rand", + "serde", "uuid", ] @@ -480,31 +481,32 @@ checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", + "serde_derive", ] [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] @@ -594,6 +596,7 @@ checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" dependencies = [ "getrandom", "js-sys", + "serde_core", "wasm-bindgen", ] diff --git a/Cargo.toml b/Cargo.toml index 632801c..5d05366 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,9 +13,8 @@ edition = "2024" env_logger = "0.11.11" log = "0.4.33" rand = "0.10.2" +serde = { version = "1.0.229", features = ["derive"] } [dependencies.uuid] version = "1.24.0" -features = [ - "v4", -] +features = ["serde", "v4"] diff --git a/src/logic/board/board.rs b/src/logic/board/board.rs index c6c8b65..a7e8675 100644 --- a/src/logic/board/board.rs +++ b/src/logic/board/board.rs @@ -11,6 +11,9 @@ use std::ops::{Index, IndexMut}; use log::{debug, error, info, trace, warn}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] pub struct Board { board: Vec, pub size: usize, diff --git a/src/logic/board/field.rs b/src/logic/board/field.rs index 592bfe4..7b093b8 100644 --- a/src/logic/board/field.rs +++ b/src/logic/board/field.rs @@ -1,8 +1,10 @@ use std::fmt; +use serde::{Deserialize, Serialize}; + use crate::logic::{board::pawn::Pawn, math::position::Position}; -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct Field { pub position: Position, pub pawn: Option, diff --git a/src/logic/board/pawn.rs b/src/logic/board/pawn.rs index 4ff18a8..9ad752d 100644 --- a/src/logic/board/pawn.rs +++ b/src/logic/board/pawn.rs @@ -1,8 +1,9 @@ +use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::logic::{math::position::Position, player::Player}; -#[derive(PartialEq, Eq, Debug)] +#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)] pub enum PawnState { Man, Dame, @@ -10,7 +11,7 @@ pub enum PawnState { } /// Basic pawn -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct Pawn { pub state: PawnState, pub owner: Uuid, diff --git a/src/logic/math/position.rs b/src/logic/math/position.rs index 6188ca9..6dcaf6b 100644 --- a/src/logic/math/position.rs +++ b/src/logic/math/position.rs @@ -2,10 +2,11 @@ use core::fmt; use std::ops::Sub; use log::{debug, trace}; +use serde::{Deserialize, Serialize}; 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 /// unsigned grid. /// From 4293e475c8abd06530b115e563bfa07dc6aac85f Mon Sep 17 00:00:00 2001 From: szymon Date: Sat, 15 Aug 2026 12:30:37 +0200 Subject: [PATCH 03/41] feat: add message serialization --- Cargo.lock | 118 +++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/logic/board/board.rs | 2 +- src/logic/board/field.rs | 2 +- src/logic/board/pawn.rs | 7 ++- src/logic/game_master.rs | 2 + src/network/message.rs | 93 ++++++++++++++++++++++++++++++ 7 files changed, 220 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 88f5475..eed5cec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,7 @@ version = "0.0.1" dependencies = [ "env_logger", "log", + "postcard", "rand", "serde", "uuid", @@ -78,6 +79,15 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -96,6 +106,12 @@ version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "cfg-if" version = "1.0.4" @@ -113,6 +129,15 @@ dependencies = [ "rand_core", ] +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror", +] + [[package]] name = "colorchoice" version = "1.0.5" @@ -128,6 +153,12 @@ dependencies = [ "libc", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "defmt" version = "1.1.1" @@ -159,6 +190,18 @@ dependencies = [ "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]] name = "env_filter" version = "2.0.0" @@ -232,6 +275,15 @@ dependencies = [ "wasip3", ] +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -247,6 +299,20 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "heck" version = "0.5.0" @@ -342,6 +408,15 @@ version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "log" version = "0.4.33" @@ -387,6 +462,19 @@ dependencies = [ "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]] name = "prettyplease" version = "0.2.37" @@ -467,12 +555,27 @@ version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "rustversion" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "semver" version = "1.0.28" @@ -528,6 +631,21 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +[[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]] name = "syn" version = "2.0.119" diff --git a/Cargo.toml b/Cargo.toml index 5d05366..11d7e76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ edition = "2024" [dependencies] env_logger = "0.11.11" log = "0.4.33" +postcard = { version = "1.1.3", features = ["alloc"] } rand = "0.10.2" serde = { version = "1.0.229", features = ["derive"] } diff --git a/src/logic/board/board.rs b/src/logic/board/board.rs index a7e8675..3db6114 100644 --- a/src/logic/board/board.rs +++ b/src/logic/board/board.rs @@ -13,7 +13,7 @@ use log::{debug, error, info, trace, warn}; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct Board { board: Vec, pub size: usize, diff --git a/src/logic/board/field.rs b/src/logic/board/field.rs index 7b093b8..7ee3a18 100644 --- a/src/logic/board/field.rs +++ b/src/logic/board/field.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use crate::logic::{board::pawn::Pawn, math::position::Position}; -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)] pub struct Field { pub position: Position, pub pawn: Option, diff --git a/src/logic/board/pawn.rs b/src/logic/board/pawn.rs index 9ad752d..02c860e 100644 --- a/src/logic/board/pawn.rs +++ b/src/logic/board/pawn.rs @@ -3,7 +3,7 @@ use uuid::Uuid; use crate::logic::{math::position::Position, player::Player}; -#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)] +#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)] pub enum PawnState { Man, Dame, @@ -11,7 +11,7 @@ pub enum PawnState { } /// Basic pawn -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)] pub struct Pawn { pub state: PawnState, pub owner: Uuid, @@ -26,6 +26,7 @@ impl Pawn { } } +#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)] pub struct CapturePath { pub from: Position, pub steps: Vec, @@ -40,7 +41,7 @@ impl CapturePath { } } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Deserialize, Serialize, Clone)] pub struct MovePath { pub from: Position, pub available_steps: Vec, diff --git a/src/logic/game_master.rs b/src/logic/game_master.rs index ba5fb8a..d480c6c 100644 --- a/src/logic/game_master.rs +++ b/src/logic/game_master.rs @@ -1,4 +1,5 @@ use log::{info, warn}; +use serde::{Deserialize, Serialize}; use crate::logic::{ board::{ @@ -9,6 +10,7 @@ use crate::logic::{ player::Player, }; +#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)] pub enum GameResult { Lost(uuid::Uuid), Draw, diff --git a/src/network/message.rs b/src/network/message.rs index 37a1020..7f1f483 100644 --- a/src/network/message.rs +++ b/src/network/message.rs @@ -1,3 +1,4 @@ +use serde::{Deserialize, Serialize, de::DeserializeOwned}; use uuid::Uuid; use crate::logic::{ @@ -9,6 +10,29 @@ use crate::logic::{ math::position::Position, }; +use postcard::{from_bytes, to_allocvec}; + +pub trait MessageLike { + fn to_bits(&self) -> Result, postcard::Error>; + fn from_bits(bits: &[u8]) -> Result + where + Self: Sized; +} + +impl MessageLike for T +where + T: DeserializeOwned + Serialize, +{ + fn to_bits(&self) -> Result, postcard::Error> { + to_allocvec(self) + } + + fn from_bits(bits: &[u8]) -> Result { + from_bytes(bits) + } +} + +#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)] pub enum ServerMessage { RequestHandshake, AcceptHandshake { player_id: Uuid }, @@ -23,9 +47,78 @@ pub enum ServerMessage { GameEnd { result: GameResult }, } +#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)] pub enum ClientMessage { AnswerHandshake { player_name: String }, RequestCapture { capture_path: CapturePath }, RequestMove { from: Position, to: Position }, } + +pub struct Message +where + T: MessageLike, +{ + pub size: u32, + pub content: T, +} + +impl Message +where + T: MessageLike, +{ + pub fn new(content: T) -> Result, 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 = + 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); + } +} From 593e56e264a960f645ed6c60a8da7214c3c9d4c1 Mon Sep 17 00:00:00 2001 From: szymon Date: Sat, 15 Aug 2026 15:35:00 +0200 Subject: [PATCH 04/41] feat: add reading data from network in connection --- Cargo.lock | 120 ++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/network/connection.rs | 141 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 262 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index eed5cec..6b0eea8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,7 @@ dependencies = [ "postcard", "rand", "serde", + "tokio", "uuid", ] @@ -112,6 +113,12 @@ 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" @@ -231,6 +238,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "foldhash" version = "0.1.5" @@ -429,6 +446,17 @@ 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 = "once_cell" version = "1.21.4" @@ -441,6 +469,29 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "pin-project-lite" version = "0.2.17" @@ -526,6 +577,15 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "regex" version = "1.13.1" @@ -625,12 +685,38 @@ dependencies = [ "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]] name = "slab" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" 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" @@ -688,6 +774,34 @@ dependencies = [ "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]] name = "unicode-ident" version = "1.0.24" @@ -718,6 +832,12 @@ dependencies = [ "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]] name = "wasip2" version = "1.0.3+wasi-0.2.9" diff --git a/Cargo.toml b/Cargo.toml index 11d7e76..ba9e96a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ log = "0.4.33" postcard = { version = "1.1.3", features = ["alloc"] } rand = "0.10.2" serde = { version = "1.0.229", features = ["derive"] } +tokio = { version = "1.53.1", features = ["full"] } [dependencies.uuid] version = "1.24.0" diff --git a/src/network/connection.rs b/src/network/connection.rs index e69de29..a127b46 100644 --- a/src/network/connection.rs +++ b/src/network/connection.rs @@ -0,0 +1,141 @@ +use std::marker::PhantomData; +use std::net::SocketAddr; + +use log::{debug, error, warn}; +use postcard::from_bytes; +use tokio::{net::TcpStream, sync::mpsc::Sender}; + +use tokio::io::AsyncReadExt; + +use crate::network::message::{Message, MessageLike}; + +/// Enum defines __TO WHAT__ connection is connecting +pub enum ConnectionType { + Server, + Client, +} + +pub struct Connection +where + Inbound: MessageLike, +{ + conn_type: ConnectionType, + sender: Sender>, + tcp: Option, + outbound_type: PhantomData, +} + +impl Connection +where + Inbound: MessageLike, + Outbound: MessageLike, +{ + pub fn new( + conn_type: ConnectionType, + sender: Sender>, + ) -> Connection { + Connection { + conn_type, + sender, + tcp: None, + outbound_type: PhantomData, + } + } + + 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) => 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 + } + } + + pub async fn send(&mut self, msg: Message) { + todo!(); + } + + pub async fn start_listening(&self) { + todo!() + } + + async fn read_header(&mut self) { + let mut buff = [0u8; 4]; + let Some(tcp) = &mut self.tcp else { + error!("Tried reading header, but tcp connection is not established!"); + return; + }; + + let result = tcp.read_exact(&mut buff).await; + + if let Err(e) = result { + error!("Error while reading header: {}", e); + return; + } + + 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) { + let mut buff: Vec = 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; + + match bytes_read { + Ok(bytes_size) => { + debug!("Read {} bytes during read_content", bytes_size); + } + Err(e) => { + error!( + "Error while recieving bytes from network during read_content: {}", + e + ); + return; + } + } + + let msg_result: Result = 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(msg).await { + error!("Erro while sending message to main thread: {}", e); + } + } + Err(e) => { + error!( + "Error while preparing message in read_content for sender: {}", + e + ); + return; + } + } + } + Err(e) => { + error!("Error while getting the message from bits: {}", e); + return; + } + } + } +} From 51103dc42ce52e61070719828c37bc6457cd7fd6 Mon Sep 17 00:00:00 2001 From: szymon Date: Sat, 15 Aug 2026 15:51:49 +0200 Subject: [PATCH 05/41] feat: add start_listening to connection --- src/network/connection.rs | 54 +++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/src/network/connection.rs b/src/network/connection.rs index a127b46..8d7500c 100644 --- a/src/network/connection.rs +++ b/src/network/connection.rs @@ -1,3 +1,4 @@ +use std::io; use std::marker::PhantomData; use std::net::SocketAddr; @@ -63,30 +64,34 @@ where todo!(); } - pub async fn start_listening(&self) { - todo!() + pub async fn start_listening(&mut self) { + loop { + let result = self.read_header().await; + + if let Err(e) = result { + error!( + "Error while reading data from connection with: {} {}. Connection is broken. Disconnecting...", + self.tcp.as_ref().unwrap().peer_addr().unwrap(), + e + ); + return; + } + } } - async fn read_header(&mut self) { + async fn read_header(&mut self) -> Result<(), io::Error> { let mut buff = [0u8; 4]; - let Some(tcp) = &mut self.tcp else { - error!("Tried reading header, but tcp connection is not established!"); - return; - }; - let result = tcp.read_exact(&mut buff).await; + let tcp = self.tcp.as_mut().ok_or(io::ErrorKind::NotConnected)?; - if let Err(e) = result { - error!("Error while reading header: {}", e); - return; - } + tcp.read_exact(&mut buff).await?; let size_to_read: u32 = u32::from_be_bytes(buff); - self.read_content(size_to_read).await; + self.read_content(size_to_read).await } - async fn read_content(&mut self, size_to_read: u32) { + async fn read_content(&mut self, size_to_read: u32) -> Result<(), io::Error> { let mut buff: Vec = vec![0; size_to_read as usize]; let Some(tcp) = &mut self.tcp else { @@ -96,20 +101,8 @@ where panic!(); }; - let bytes_read = tcp.read_exact(&mut buff).await; - - match bytes_read { - Ok(bytes_size) => { - debug!("Read {} bytes during read_content", bytes_size); - } - Err(e) => { - error!( - "Error while recieving bytes from network during read_content: {}", - e - ); - return; - } - } + let bytes_read = tcp.read_exact(&mut buff).await?; + debug!("Read {} bytes during read_content", bytes_read); let msg_result: Result = MessageLike::from_bits(&buff); @@ -122,19 +115,20 @@ where if let Err(e) = self.sender.send(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; + return Ok(()); } } } Err(e) => { error!("Error while getting the message from bits: {}", e); - return; + return Ok(()); } } } From 843903400d2a70ef07b7df477716df164fd181aa Mon Sep 17 00:00:00 2001 From: szymon Date: Sat, 15 Aug 2026 16:09:08 +0200 Subject: [PATCH 06/41] feat: add sending to connection --- src/network/connection.rs | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/src/network/connection.rs b/src/network/connection.rs index 8d7500c..8f0e820 100644 --- a/src/network/connection.rs +++ b/src/network/connection.rs @@ -6,7 +6,7 @@ use log::{debug, error, warn}; use postcard::from_bytes; use tokio::{net::TcpStream, sync::mpsc::Sender}; -use tokio::io::AsyncReadExt; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; use crate::network::message::{Message, MessageLike}; @@ -23,6 +23,7 @@ where conn_type: ConnectionType, sender: Sender>, tcp: Option, + peer: Option, outbound_type: PhantomData, } @@ -39,6 +40,7 @@ where conn_type, sender, tcp: None, + peer: None, outbound_type: PhantomData, } } @@ -47,7 +49,10 @@ where match self.conn_type { ConnectionType::Server => { self.tcp = match TcpStream::connect(url).await { - Ok(conn) => Some(conn), + Ok(conn) => { + self.peer = Some(url); + Some(conn) + } Err(e) => { error!("Error while connecting to the server: {}", e); None @@ -61,7 +66,28 @@ where } pub async fn send(&mut self, msg: Message) { - todo!(); + 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) { @@ -70,9 +96,8 @@ where if let Err(e) = result { error!( - "Error while reading data from connection with: {} {}. Connection is broken. Disconnecting...", - self.tcp.as_ref().unwrap().peer_addr().unwrap(), - e + "Error while reading data from connection with: {:?} {}. Connection is broken. Disconnecting...", + self.peer, e ); return; } From 813a28402a3aa3f1fbff22bc53a9bc9cb55561d6 Mon Sep 17 00:00:00 2001 From: szymon Date: Sat, 15 Aug 2026 16:39:17 +0200 Subject: [PATCH 07/41] feat: add delegate() to connection --- src/network/connection.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/network/connection.rs b/src/network/connection.rs index 8f0e820..3a587e2 100644 --- a/src/network/connection.rs +++ b/src/network/connection.rs @@ -4,6 +4,7 @@ use std::net::SocketAddr; use log::{debug, error, warn}; use postcard::from_bytes; +use tokio::net::TcpSocket; use tokio::{net::TcpStream, sync::mpsc::Sender}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; @@ -45,6 +46,7 @@ where } } + /// Meant for clients pub async fn connect_to_server(&mut self, url: SocketAddr) -> bool { match self.conn_type { ConnectionType::Server => { @@ -65,6 +67,18 @@ where } } + /// 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 async fn send(&mut self, msg: Message) { let size = msg.size; let content = msg.content; From 4f8a43dc4dd4a1404e7c5894047ab09396e36a94 Mon Sep 17 00:00:00 2001 From: szymon Date: Sat, 15 Aug 2026 17:48:26 +0200 Subject: [PATCH 08/41] feat: add server start method --- src/network/connection.rs | 6 +-- src/network/server.rs | 92 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 3 deletions(-) diff --git a/src/network/connection.rs b/src/network/connection.rs index 3a587e2..2e2e62f 100644 --- a/src/network/connection.rs +++ b/src/network/connection.rs @@ -2,9 +2,7 @@ use std::io; use std::marker::PhantomData; use std::net::SocketAddr; -use log::{debug, error, warn}; -use postcard::from_bytes; -use tokio::net::TcpSocket; +use log::{debug, error}; use tokio::{net::TcpStream, sync::mpsc::Sender}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; @@ -12,11 +10,13 @@ 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 where Inbound: MessageLike, diff --git a/src/network/server.rs b/src/network/server.rs index e69de29..41ad2f5 100644 --- a/src/network/server.rs +++ b/src/network/server.rs @@ -0,0 +1,92 @@ +use log::{error, info}; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use tokio::{ + net::{TcpListener, TcpStream}, + sync::mpsc::{Receiver, Sender}, +}; + +use crate::{ + logic::game_master::GameMaster, + network::{ + connection::{Connection, ConnectionType}, + message::{ClientMessage, Message, ServerMessage}, + }, +}; + +// TODO! More settings! +pub struct ServerSettings { + pub addr: SocketAddr, + pub max_connections: usize, + pub allow_spectators: bool, +} + +pub struct Server { + listener: TcpListener, + connections: Vec>, + reciever: Receiver>, + sender: Sender>, + game_master: Option, + settings: ServerSettings, +} + +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, mut reciever) = tokio::sync::mpsc::channel::>(1024); // random + // number + + Server { + listener: TcpListener::bind(settings.addr) + .await + .expect("Could not start the server"), // TODO! Handle this nicely, as this will + // crash gui app + connections: vec![], + sender, + reciever, + game_master: None, + settings, + } + } + + pub async fn start(&mut self) { + self.start_listening().await; + } + + async fn start_listening(&mut self) { + // TODO! Maybe allow connection and then gently refuse with reason? + while self.connections.len() < self.settings.max_connections + && !self.settings.allow_spectators + { + match self.listener.accept().await { + Ok((socket, addr)) => { + self.handle_new_client(socket, addr); + } + Err(e) => { + error!("Error while accepting a new connection: {}", e); + } + } + } + } + + fn handle_new_client(&mut self, socket: TcpStream, addr: SocketAddr) { + info!("New connection from: {:?}", addr); + let mut conn: Connection = + Connection::new(ConnectionType::Client, self.sender.clone()); + + conn.delegate(socket, addr); + self.connections.push(conn); + } + + fn request_handshake(&self) { + let conn = self.connections.last().unwrap(); + info!("Requesting handshake from: {:?}", conn); + + todo!(); + } +} From 68e7365432c251396b50078626838401a1129e0d Mon Sep 17 00:00:00 2001 From: szymon Date: Sun, 16 Aug 2026 12:53:15 +0200 Subject: [PATCH 09/41] feat: add server message loop --- src/network/connection.rs | 61 +++++++++++++++++++------ src/network/server.rs | 93 ++++++++++++++++++++++++++++++++++----- 2 files changed, 130 insertions(+), 24 deletions(-) diff --git a/src/network/connection.rs b/src/network/connection.rs index 2e2e62f..1d4cf5b 100644 --- a/src/network/connection.rs +++ b/src/network/connection.rs @@ -2,7 +2,8 @@ use std::io; use std::marker::PhantomData; use std::net::SocketAddr; -use log::{debug, error}; +use log::{debug, error, info}; +use tokio::sync::mpsc::Receiver; use tokio::{net::TcpStream, sync::mpsc::Sender}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; @@ -20,9 +21,11 @@ pub enum ConnectionType { pub struct Connection where Inbound: MessageLike, + Outbound: MessageLike, { conn_type: ConnectionType, - sender: Sender>, + sender: Sender<(SocketAddr, Message)>, + reciever: Option>>, tcp: Option, peer: Option, outbound_type: PhantomData, @@ -35,11 +38,12 @@ where { pub fn new( conn_type: ConnectionType, - sender: Sender>, + sender: Sender<(SocketAddr, Message)>, ) -> Connection { Connection { conn_type, sender, + reciever: None, tcp: None, peer: None, outbound_type: PhantomData, @@ -79,7 +83,13 @@ where } } - pub async fn send(&mut self, msg: Message) { + pub fn get_sender(&mut self) -> Sender> { + let (sender, reciever) = tokio::sync::mpsc::channel(1024); + self.reciever = Some(reciever); + sender + } + + async fn send(&mut self, msg: Message) { let size = msg.size; let content = msg.content; @@ -105,17 +115,38 @@ where } pub async fn start_listening(&mut self) { - loop { - let result = self.read_header().await; + let mut receiver = self + .reciever + .take() + .expect("Tried listening, but get_sender wasn't called"); - if let Err(e) = result { - error!( - "Error while reading data from connection with: {:?} {}. Connection is broken. Disconnecting...", - self.peer, e - ); - return; + loop { + tokio::select! { + result = self.read_header() => { + if let Err(e) = result { + error!( + "Connection broken with {:?}: {}. Disconnecting...", + self.peer, e + ); + break; + } + }, + + msg_opt = receiver.recv() => { + match msg_opt { + Some(msg) => { + self.send(msg).await; + }, + None => { + info!("Server closed the channel for {:?}. Disconnecting...", self.peer); + break; + } + } + } } } + + self.reciever = Some(receiver); } async fn read_header(&mut self) -> Result<(), io::Error> { @@ -151,7 +182,11 @@ where match new_msg { Ok(msg) => { - if let Err(e) = self.sender.send(msg).await { + 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(()); diff --git a/src/network/server.rs b/src/network/server.rs index 41ad2f5..0f0718c 100644 --- a/src/network/server.rs +++ b/src/network/server.rs @@ -1,5 +1,8 @@ use log::{error, info}; -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::{ + collections::HashMap, + net::{IpAddr, Ipv4Addr, SocketAddr}, +}; use tokio::{ net::{TcpListener, TcpStream}, sync::mpsc::{Receiver, Sender}, @@ -9,7 +12,10 @@ use crate::{ logic::game_master::GameMaster, network::{ connection::{Connection, ConnectionType}, - message::{ClientMessage, Message, ServerMessage}, + message::{ + ClientMessage::{self, AnswerHandshake, RequestCapture, RequestMove}, + Message, ServerMessage, + }, }, }; @@ -22,9 +28,9 @@ pub struct ServerSettings { pub struct Server { listener: TcpListener, - connections: Vec>, - reciever: Receiver>, - sender: Sender>, + connections: HashMap>>, + reciever: Receiver<(SocketAddr, Message)>, + sender: Sender<(SocketAddr, Message)>, game_master: Option, settings: ServerSettings, } @@ -38,7 +44,8 @@ impl Server { allow_spectators: false, }; - let (sender, mut reciever) = tokio::sync::mpsc::channel::>(1024); // random + let (sender, mut reciever) = + tokio::sync::mpsc::channel::<(SocketAddr, Message)>(1024); // random // number Server { @@ -46,7 +53,7 @@ impl Server { .await .expect("Could not start the server"), // TODO! Handle this nicely, as this will // crash gui app - connections: vec![], + connections: HashMap::new(), sender, reciever, game_master: None, @@ -80,13 +87,77 @@ impl Server { Connection::new(ConnectionType::Client, self.sender.clone()); conn.delegate(socket, addr); - self.connections.push(conn); + let sender = conn.get_sender(); + + self.connections.insert(addr, sender); + + self.listen_client(conn); } - fn request_handshake(&self) { - let conn = self.connections.last().unwrap(); - info!("Requesting handshake from: {:?}", conn); + fn listen_client(&mut self, mut conn: Connection) { + tokio::spawn(async move { + conn.start_listening().await; + }); + } + /// Process incoming messages + pub async fn update(&mut self) { + let Some(oldest_message) = self.reciever.recv().await else { + return; + }; + + let (addr, msg) = oldest_message; + + match msg.content { + ClientMessage::AnswerHandshake { player_name } => { + self.process_handshake(); + } + ClientMessage::RequestCapture { capture_path } => { + self.process_capture(); + } + ClientMessage::RequestMove { from, to } => { + self.process_move(); + } + } + } + + /* ======= Processing messages :D helper functions ======== */ + + fn process_handshake(&self) { todo!(); } + + fn process_capture(&self) { + todo!(); + } + + fn process_move(&self) { + todo!(); + } + + async fn request_handshake(&mut self, addr: SocketAddr) { + let msg = Message::new(ServerMessage::RequestHandshake); + self.send_message(addr, msg).await; + } + + async fn send_message( + &mut self, + addr: SocketAddr, + msg: Result, postcard::Error>, + ) { + let conn_opt = self.connections.get_mut(&addr); + + let Some(conn) = conn_opt else { + error!("Could not get connection from connections list"); + return; + }; + + let _ = match msg { + Err(e) => { + error!("There was an error while creating message: {}", e); + return; + } + Ok(msg) => conn.send(msg).await, + }; + } } From 7d0452f451a30aad4d360a7211701b389c5ad696 Mon Sep 17 00:00:00 2001 From: szymon Date: Sun, 16 Aug 2026 14:28:01 +0200 Subject: [PATCH 10/41] feat: add server update loop --- src/logic/math/position.rs | 4 +- src/network/client.rs | 1 + src/network/message.rs | 2 +- src/network/server.rs | 115 +++++++++++++++++++++---------------- 4 files changed, 69 insertions(+), 53 deletions(-) diff --git a/src/logic/math/position.rs b/src/logic/math/position.rs index 6dcaf6b..5b12c47 100644 --- a/src/logic/math/position.rs +++ b/src/logic/math/position.rs @@ -32,7 +32,9 @@ impl Position { trace!("Adding row: {} + {} = {}", self.row, delta.row, new_row); trace!( "Adding column: {} + {} = {}", - self.column, delta.column, new_column + self.column, + delta.column, + new_column ); Some(Position { diff --git a/src/network/client.rs b/src/network/client.rs index e69de29..8b13789 100644 --- a/src/network/client.rs +++ b/src/network/client.rs @@ -0,0 +1 @@ + diff --git a/src/network/message.rs b/src/network/message.rs index 7f1f483..cbdfad1 100644 --- a/src/network/message.rs +++ b/src/network/message.rs @@ -1,4 +1,4 @@ -use serde::{Deserialize, Serialize, de::DeserializeOwned}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use uuid::Uuid; use crate::logic::{ diff --git a/src/network/server.rs b/src/network/server.rs index 0f0718c..5bee189 100644 --- a/src/network/server.rs +++ b/src/network/server.rs @@ -4,7 +4,7 @@ use std::{ net::{IpAddr, Ipv4Addr, SocketAddr}, }; use tokio::{ - net::{TcpListener, TcpStream}, + net::TcpListener, sync::mpsc::{Receiver, Sender}, }; @@ -13,7 +13,7 @@ use crate::{ network::{ connection::{Connection, ConnectionType}, message::{ - ClientMessage::{self, AnswerHandshake, RequestCapture, RequestMove}, + ClientMessage::{self}, Message, ServerMessage, }, }, @@ -27,10 +27,14 @@ pub struct ServerSettings { } pub struct Server { - listener: TcpListener, + listener: Option, connections: HashMap>>, + reciever: Receiver<(SocketAddr, Message)>, sender: Sender<(SocketAddr, Message)>, + + welcoming_reciever: Option>)>>, + game_master: Option, settings: ServerSettings, } @@ -49,74 +53,83 @@ impl Server { // number Server { - listener: TcpListener::bind(settings.addr) - .await - .expect("Could not start the server"), // TODO! Handle this nicely, as this will + 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::new(), sender, reciever, + welcoming_reciever: None, game_master: None, settings, } } pub async fn start(&mut self) { - self.start_listening().await; - } + let (welcoming_tx, mut welcoming_rx) = + tokio::sync::mpsc::channel::<(SocketAddr, Sender>)>(10); - async fn start_listening(&mut self) { - // TODO! Maybe allow connection and then gently refuse with reason? - while self.connections.len() < self.settings.max_connections - && !self.settings.allow_spectators - { - match self.listener.accept().await { - Ok((socket, addr)) => { - self.handle_new_client(socket, addr); - } - Err(e) => { - error!("Error while accepting a new connection: {}", e); + self.welcoming_reciever = Some(welcoming_rx); + + let listener = self + .listener + .take() + .expect("Cannot start the server without tcp listener."); + + let 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 = + Connection::new(ConnectionType::Client, sender.clone()); + + conn.delegate(socket, addr); + + let sender = conn.get_sender(); + let _ = welcoming_tx.send((addr, sender)).await; + + tokio::spawn(async move { + conn.start_listening().await; + }); } } - } - } - - fn handle_new_client(&mut self, socket: TcpStream, addr: SocketAddr) { - info!("New connection from: {:?}", addr); - let mut conn: Connection = - Connection::new(ConnectionType::Client, self.sender.clone()); - - conn.delegate(socket, addr); - let sender = conn.get_sender(); - - self.connections.insert(addr, sender); - - self.listen_client(conn); - } - - fn listen_client(&mut self, mut conn: Connection) { - tokio::spawn(async move { - conn.start_listening().await; }); } /// Process incoming messages pub async fn update(&mut self) { - let Some(oldest_message) = self.reciever.recv().await else { - return; - }; + 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 { + // TODO! Check size options etc + self.connections.insert(addr, sender); + self.request_handshake(addr).await; + } + } - let (addr, msg) = oldest_message; + 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(); - } - ClientMessage::RequestCapture { capture_path } => { - self.process_capture(); - } - ClientMessage::RequestMove { from, to } => { - self.process_move(); + match msg.content { + ClientMessage::AnswerHandshake { player_name: _ } => { + self.process_handshake(); + } + ClientMessage::RequestCapture { capture_path: _ } => { + self.process_capture(); + } + ClientMessage::RequestMove { from: _, to: _ } => { + self.process_move(); + } + } + } + } } } } From 3d8219c65a6dc3c142a8334855b9c544a163f38a Mon Sep 17 00:00:00 2001 From: szymon Date: Sun, 16 Aug 2026 14:31:04 +0200 Subject: [PATCH 11/41] fix: close the update loop when theres no reciever --- src/network/server.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/network/server.rs b/src/network/server.rs index 5bee189..4be134c 100644 --- a/src/network/server.rs +++ b/src/network/server.rs @@ -1,4 +1,4 @@ -use log::{error, info}; +use log::{error, info, warn}; use std::{ collections::HashMap, net::{IpAddr, Ipv4Addr, SocketAddr}, @@ -110,6 +110,9 @@ impl Server { // TODO! Check size options etc self.connections.insert(addr, sender); self.request_handshake(addr).await; + } else { + warn!("Welcoming reciever closed!"); + break; } } @@ -128,6 +131,9 @@ impl Server { self.process_move(); } } + } else { + warn!("Couldn't get latest message!"); + break; } } } From 562640cb1680f48c74969ce1abc136767959216c Mon Sep 17 00:00:00 2001 From: szymon Date: Sun, 16 Aug 2026 14:52:25 +0200 Subject: [PATCH 12/41] feat: add server binary --- Cargo.lock | 26 +++++++++++++------------- Cargo.toml | 2 +- src/bin/server.rs | 13 +++++++++++++ src/lib.rs | 2 ++ src/main.rs | 2 -- src/network/mod.rs | 8 ++++---- src/network/server.rs | 2 ++ 7 files changed, 35 insertions(+), 20 deletions(-) create mode 100644 src/bin/server.rs create mode 100644 src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 6b0eea8..3279ac2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,19 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "Checkers" -version = "0.0.1" -dependencies = [ - "env_logger", - "log", - "postcard", - "rand", - "serde", - "tokio", - "uuid", -] - [[package]] name = "aho-corasick" version = "1.1.5" @@ -136,6 +123,19 @@ dependencies = [ "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" diff --git a/Cargo.toml b/Cargo.toml index ba9e96a..765094a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "Checkers" +name = "checkers" description = "Game of checkers" version = "0.0.1" readme = "README.md" diff --git a/src/bin/server.rs b/src/bin/server.rs new file mode 100644 index 0000000..2c1ab1d --- /dev/null +++ b/src/bin/server.rs @@ -0,0 +1,13 @@ +use checkers::network::server::Server; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let mut server: Server = Server::new().await; + + env_logger::init(); + + server.start().await; + server.update().await; + + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..8b83056 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,2 @@ +pub mod logic; +pub mod network; diff --git a/src/main.rs b/src/main.rs index d7771a6..7da3a48 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,3 @@ -mod logic; -mod network; fn main() { println!("Hello, world!"); diff --git a/src/network/mod.rs b/src/network/mod.rs index 17a8c2f..101e02a 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -1,4 +1,4 @@ -mod client; -mod connection; -mod message; -mod server; +pub mod client; +pub mod connection; +pub mod message; +pub mod server; diff --git a/src/network/server.rs b/src/network/server.rs index 4be134c..d527491 100644 --- a/src/network/server.rs +++ b/src/network/server.rs @@ -79,6 +79,8 @@ impl Server { .take() .expect("Cannot start the server without tcp listener."); + info!("Starting the server at: {:?}", listener.local_addr()); + let sender = self.sender.clone(); tokio::spawn(async move { From febb0adc4336a9e99d42ec6fae9f9773697f84fa Mon Sep 17 00:00:00 2001 From: szymon Date: Sun, 16 Aug 2026 15:46:01 +0200 Subject: [PATCH 13/41] feat: add client::{start, update} --- src/bin/client.rs | 11 +++++ src/network/client.rs | 91 +++++++++++++++++++++++++++++++++++++++ src/network/connection.rs | 2 +- src/network/server.rs | 4 +- 4 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 src/bin/client.rs diff --git a/src/bin/client.rs b/src/bin/client.rs new file mode 100644 index 0000000..daa1ff1 --- /dev/null +++ b/src/bin/client.rs @@ -0,0 +1,11 @@ +use checkers::network::client::Client; + +#[tokio::main] +async fn main() -> Result<(), Box> { + env_logger::init(); + + let mut client: Client = Client::new().await; + client.update().await; + + return Ok(()); +} diff --git a/src/network/client.rs b/src/network/client.rs index 8b13789..572f34e 100644 --- a/src/network/client.rs +++ b/src/network/client.rs @@ -1 +1,92 @@ +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use log::{debug, error, info}; +use tokio::sync::mpsc::{self, Receiver, Sender}; + +use crate::network::{ + connection::{Connection, ConnectionType}, + message::{ClientMessage, Message, ServerMessage}, +}; + +pub struct ClientSettings { + server_url: SocketAddr, + name: String, +} + +pub struct Client { + conn_reciever_incoming: Receiver<(SocketAddr, Message)>, + conn_sender_outgoing: Sender>, + settings: ClientSettings, +} + +impl Client { + pub async fn new() -> Client { + let settings = ClientSettings { + server_url: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 6767), + name: "Szymon".into(), + }; + + let (conn_sender, conn_reciever) = + mpsc::channel::<(SocketAddr, Message)>(1024); + + let mut conn: Connection = + Connection::new(ConnectionType::Server, conn_sender); + + let conn_sender_outgoing = conn.get_sender(); + + conn.connect_to_server(settings.server_url).await; + + tokio::spawn(async move { + conn.start_listening().await; + }); + + Client { + conn_reciever_incoming: conn_reciever, + conn_sender_outgoing, + settings, + } + } + + pub async fn update(&mut self) { + loop { + if let Some(oldest_msg) = self.conn_reciever_incoming.recv().await { + let (addr, msg) = oldest_msg; + info!("Got message from: {:?}", addr); + + match msg.content { + ServerMessage::RequestHandshake => { + info!("Server requested handshake!"); + let msg = Message::new(ClientMessage::AnswerHandshake { + player_name: self.settings.name.clone(), + }); + + self.send_message(msg).await; + } + ServerMessage::AcceptHandshake { player_id } => todo!(), + ServerMessage::DeclineHandshake { reason } => todo!(), + ServerMessage::AvailableCaptures { captures } => todo!(), + ServerMessage::AvailableMoves { moves } => todo!(), + ServerMessage::BroadcastBoardState { board } => todo!(), + ServerMessage::BroadcastCurrentTurn { active_player } => todo!(), + ServerMessage::GameEnd { result } => todo!(), + } + } + } + } + + async fn send_message(&mut self, msg: Result, postcard::Error>) { + 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) = self.conn_sender_outgoing.send(msg).await { + error!("Error while sending a message to connection thread: {}", e); + return; + } + } + }; + } +} diff --git a/src/network/connection.rs b/src/network/connection.rs index 1d4cf5b..9f01af8 100644 --- a/src/network/connection.rs +++ b/src/network/connection.rs @@ -138,7 +138,7 @@ where self.send(msg).await; }, None => { - info!("Server closed the channel for {:?}. Disconnecting...", self.peer); + info!("Remote closed the channel for {:?}. Disconnecting...", self.peer); break; } } diff --git a/src/network/server.rs b/src/network/server.rs index d527491..ac5bb40 100644 --- a/src/network/server.rs +++ b/src/network/server.rs @@ -48,7 +48,7 @@ impl Server { allow_spectators: false, }; - let (sender, mut reciever) = + let (sender, reciever) = tokio::sync::mpsc::channel::<(SocketAddr, Message)>(1024); // random // number @@ -69,7 +69,7 @@ impl Server { } pub async fn start(&mut self) { - let (welcoming_tx, mut welcoming_rx) = + let (welcoming_tx, welcoming_rx) = tokio::sync::mpsc::channel::<(SocketAddr, Sender>)>(10); self.welcoming_reciever = Some(welcoming_rx); From 3fb10c7c69453408a227563e68e13ca391b0f5c6 Mon Sep 17 00:00:00 2001 From: szymon Date: Sun, 16 Aug 2026 16:13:27 +0200 Subject: [PATCH 14/41] feat: add sending text messages --- src/bin/client.rs | 17 ++++++++++- src/network/client.rs | 66 +++++++++++++++++++++++++++--------------- src/network/message.rs | 4 ++- src/network/server.rs | 14 +++++---- 4 files changed, 71 insertions(+), 30 deletions(-) diff --git a/src/bin/client.rs b/src/bin/client.rs index daa1ff1..59d0e27 100644 --- a/src/bin/client.rs +++ b/src/bin/client.rs @@ -1,3 +1,5 @@ +use std::io; + use checkers::network::client::Client; #[tokio::main] @@ -5,7 +7,20 @@ async fn main() -> Result<(), Box> { env_logger::init(); let mut client: Client = Client::new().await; - client.update().await; + client.update(); + + println!("=== Write to stding to send text messages ==="); + println!("Write /close to leave"); + let mut buffer = String::new(); + loop { + io::stdin().read_line(&mut buffer)?; + if buffer == "/close\n" { + break; + } + + client.send_text_message(buffer.clone()).await; + buffer.clear(); + } return Ok(()); } diff --git a/src/network/client.rs b/src/network/client.rs index 572f34e..3e8238b 100644 --- a/src/network/client.rs +++ b/src/network/client.rs @@ -5,16 +5,20 @@ use tokio::sync::mpsc::{self, Receiver, Sender}; use crate::network::{ connection::{Connection, ConnectionType}, - message::{ClientMessage, Message, ServerMessage}, + message::{ + ClientMessage::{self, TextMessage}, + Message, ServerMessage, + }, }; +#[derive(Clone)] pub struct ClientSettings { server_url: SocketAddr, name: String, } pub struct Client { - conn_reciever_incoming: Receiver<(SocketAddr, Message)>, + conn_reciever_incoming: Option)>>, conn_sender_outgoing: Sender>, settings: ClientSettings, } @@ -41,37 +45,53 @@ impl Client { }); Client { - conn_reciever_incoming: conn_reciever, + conn_reciever_incoming: Some(conn_reciever), conn_sender_outgoing, settings, } } - pub async fn update(&mut self) { - loop { - if let Some(oldest_msg) = self.conn_reciever_incoming.recv().await { - let (addr, msg) = oldest_msg; - info!("Got message from: {:?}", addr); + 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; + }; - match msg.content { - ServerMessage::RequestHandshake => { - info!("Server requested handshake!"); - let msg = Message::new(ClientMessage::AnswerHandshake { - player_name: self.settings.name.clone(), - }); + let sender = self.conn_sender_outgoing.clone(); - self.send_message(msg).await; + let settings = self.settings.clone(); + + tokio::spawn(async move { + loop { + if let Some(oldest_msg) = conn_reciever_incoming.recv().await { + let (addr, msg) = oldest_msg; + info!("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 _ = sender.send(msg.unwrap()).await; + } + ServerMessage::AcceptHandshake { player_id } => todo!(), + ServerMessage::DeclineHandshake { reason } => todo!(), + ServerMessage::AvailableCaptures { captures } => todo!(), + ServerMessage::AvailableMoves { moves } => todo!(), + ServerMessage::BroadcastBoardState { board } => todo!(), + ServerMessage::BroadcastCurrentTurn { active_player } => todo!(), + ServerMessage::GameEnd { result } => todo!(), } - ServerMessage::AcceptHandshake { player_id } => todo!(), - ServerMessage::DeclineHandshake { reason } => todo!(), - ServerMessage::AvailableCaptures { captures } => todo!(), - ServerMessage::AvailableMoves { moves } => todo!(), - ServerMessage::BroadcastBoardState { board } => todo!(), - ServerMessage::BroadcastCurrentTurn { active_player } => todo!(), - ServerMessage::GameEnd { result } => todo!(), } } - } + }); + } + + pub async fn send_text_message(&mut self, content: String) { + let msg = Message::new(TextMessage(content)); + self.send_message(msg).await; } async fn send_message(&mut self, msg: Result, postcard::Error>) { diff --git a/src/network/message.rs b/src/network/message.rs index cbdfad1..08a7076 100644 --- a/src/network/message.rs +++ b/src/network/message.rs @@ -1,4 +1,4 @@ -use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde::{Deserialize, Serialize, de::DeserializeOwned}; use uuid::Uuid; use crate::logic::{ @@ -53,6 +53,8 @@ pub enum ClientMessage { RequestCapture { capture_path: CapturePath }, RequestMove { from: Position, to: Position }, + + TextMessage(String), } pub struct Message diff --git a/src/network/server.rs b/src/network/server.rs index ac5bb40..b315957 100644 --- a/src/network/server.rs +++ b/src/network/server.rs @@ -120,11 +120,11 @@ impl Server { msg_opt = self.reciever.recv() => { if let Some(oldest_message) = msg_opt { - let (_addr, msg) = oldest_message; + let (addr, msg) = oldest_message; match msg.content { - ClientMessage::AnswerHandshake { player_name: _ } => { - self.process_handshake(); + ClientMessage::AnswerHandshake { player_name } => { + self.process_handshake(addr, player_name); } ClientMessage::RequestCapture { capture_path: _ } => { self.process_capture(); @@ -132,6 +132,9 @@ impl Server { ClientMessage::RequestMove { from: _, to: _ } => { self.process_move(); } + ClientMessage::TextMessage (content) => { + info!("{} sent us a message: {}", addr, content); + } } } else { warn!("Couldn't get latest message!"); @@ -144,8 +147,9 @@ impl Server { /* ======= Processing messages :D helper functions ======== */ - fn process_handshake(&self) { - todo!(); + fn process_handshake(&self, addr: SocketAddr, player_name: String) { + info!("Client: {:?} sent us his name: {}", addr, player_name); + // TODO! Do something with this name i guess } fn process_capture(&self) { From 59884919a6016d384fa5461b3e299d1dbd521b62 Mon Sep 17 00:00:00 2001 From: szymon Date: Sun, 16 Aug 2026 16:16:43 +0200 Subject: [PATCH 15/41] fix: dont busy wait when server is not available --- src/bin/client.rs | 6 +++++- src/network/client.rs | 11 +++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/bin/client.rs b/src/bin/client.rs index 59d0e27..8e7604d 100644 --- a/src/bin/client.rs +++ b/src/bin/client.rs @@ -6,7 +6,11 @@ use checkers::network::client::Client; async fn main() -> Result<(), Box> { env_logger::init(); - let mut client: Client = Client::new().await; + let Some(mut client) = Client::new().await else { + println!("Not worky, ending"); + return Ok(()); + }; + client.update(); println!("=== Write to stding to send text messages ==="); diff --git a/src/network/client.rs b/src/network/client.rs index 3e8238b..f33d1f8 100644 --- a/src/network/client.rs +++ b/src/network/client.rs @@ -24,7 +24,7 @@ pub struct Client { } impl Client { - pub async fn new() -> Client { + pub async fn new() -> Option { let settings = ClientSettings { server_url: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 6767), name: "Szymon".into(), @@ -38,17 +38,20 @@ impl Client { let conn_sender_outgoing = conn.get_sender(); - conn.connect_to_server(settings.server_url).await; + 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; }); - Client { + Some(Client { conn_reciever_incoming: Some(conn_reciever), conn_sender_outgoing, settings, - } + }) } pub fn update(&mut self) { From 71105d978346560e38873ed45ec862e0f154fb1a Mon Sep 17 00:00:00 2001 From: szymon Date: Sun, 16 Aug 2026 17:53:42 +0200 Subject: [PATCH 16/41] fix: dont freak out when client loses connection --- src/bin/client.rs | 4 ++++ src/network/client.rs | 3 +++ src/network/connection.rs | 11 +++++++++++ 3 files changed, 18 insertions(+) diff --git a/src/bin/client.rs b/src/bin/client.rs index 8e7604d..7a58ae6 100644 --- a/src/bin/client.rs +++ b/src/bin/client.rs @@ -22,6 +22,10 @@ async fn main() -> Result<(), Box> { break; } + if buffer == "/test\n" { + buffer = String::from("A").repeat(1024 * 1024 * 2); + } + client.send_text_message(buffer.clone()).await; buffer.clear(); } diff --git a/src/network/client.rs b/src/network/client.rs index f33d1f8..481b1dd 100644 --- a/src/network/client.rs +++ b/src/network/client.rs @@ -87,6 +87,9 @@ impl Client { ServerMessage::BroadcastCurrentTurn { active_player } => todo!(), ServerMessage::GameEnd { result } => todo!(), } + } else { + error!("Connection broken"); + break; } } }); diff --git a/src/network/connection.rs b/src/network/connection.rs index 9f01af8..c6f56f3 100644 --- a/src/network/connection.rs +++ b/src/network/connection.rs @@ -162,6 +162,17 @@ where } 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 = vec![0; size_to_read as usize]; let Some(tcp) = &mut self.tcp else { From 44051b0ed8299e4b82c9623746701006aedb28c5 Mon Sep 17 00:00:00 2001 From: szymon Date: Sun, 16 Aug 2026 18:26:37 +0200 Subject: [PATCH 17/41] feat: add server state and signaling readiness --- src/bin/client.rs | 6 ++++++ src/network/client.rs | 14 +++++++++++-- src/network/message.rs | 1 + src/network/server.rs | 46 +++++++++++++++++++++++++++++++++++++++--- 4 files changed, 62 insertions(+), 5 deletions(-) diff --git a/src/bin/client.rs b/src/bin/client.rs index 7a58ae6..7e6f446 100644 --- a/src/bin/client.rs +++ b/src/bin/client.rs @@ -26,6 +26,12 @@ async fn main() -> Result<(), Box> { buffer = String::from("A").repeat(1024 * 1024 * 2); } + if buffer == "/ready\n" { + buffer.clear(); + client.signal_readiness().await; + continue; + } + client.send_text_message(buffer.clone()).await; buffer.clear(); } diff --git a/src/network/client.rs b/src/network/client.rs index 481b1dd..4b50f72 100644 --- a/src/network/client.rs +++ b/src/network/client.rs @@ -6,7 +6,7 @@ use tokio::sync::mpsc::{self, Receiver, Sender}; use crate::network::{ connection::{Connection, ConnectionType}, message::{ - ClientMessage::{self, TextMessage}, + ClientMessage::{self, SignalReadiness, TextMessage}, Message, ServerMessage, }, }; @@ -80,7 +80,10 @@ impl Client { let _ = sender.send(msg.unwrap()).await; } ServerMessage::AcceptHandshake { player_id } => todo!(), - ServerMessage::DeclineHandshake { reason } => todo!(), + ServerMessage::DeclineHandshake { reason } => { + error!("Server declined connection: {}", reason); + break; + } ServerMessage::AvailableCaptures { captures } => todo!(), ServerMessage::AvailableMoves { moves } => todo!(), ServerMessage::BroadcastBoardState { board } => todo!(), @@ -96,10 +99,17 @@ impl Client { } pub async fn send_text_message(&mut self, content: String) { + debug!("Seding text message: {}", content); let msg = Message::new(TextMessage(content)); self.send_message(msg).await; } + pub async fn signal_readiness(&mut self) { + debug!("Signaling readiness!"); + let msg = Message::new(SignalReadiness); + self.send_message(msg).await; + } + async fn send_message(&mut self, msg: Result, postcard::Error>) { debug!("Sending message to the server!"); let _ = match msg { diff --git a/src/network/message.rs b/src/network/message.rs index 08a7076..9d175eb 100644 --- a/src/network/message.rs +++ b/src/network/message.rs @@ -50,6 +50,7 @@ pub enum ServerMessage { #[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)] pub enum ClientMessage { AnswerHandshake { player_name: String }, + SignalReadiness, RequestCapture { capture_path: CapturePath }, RequestMove { from: Position, to: Position }, diff --git a/src/network/server.rs b/src/network/server.rs index b315957..f44ce54 100644 --- a/src/network/server.rs +++ b/src/network/server.rs @@ -26,6 +26,27 @@ pub struct ServerSettings { 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, connections: HashMap>>, @@ -37,6 +58,8 @@ pub struct Server { game_master: Option, settings: ServerSettings, + + state: ServerState, } impl Server { @@ -65,6 +88,7 @@ impl Server { welcoming_reciever: None, game_master: None, settings, + state: ServerState::default(), } } @@ -109,9 +133,13 @@ impl Server { 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 { - // TODO! Check size options etc - self.connections.insert(addr, sender); - self.request_handshake(addr).await; + if self.state.stage == ServerStage::Lobby && self.connections.keys().len() < self.settings.max_connections && !self.settings.allow_spectators { + self.connections.insert(addr, sender); + self.request_handshake(addr).await; + } else { + let msg = Message::new(ServerMessage::DeclineHandshake { reason: "There is currently game going and it doesn't allow spectators".to_string() }); + self.send_message(addr, msg).await; + } } else { warn!("Welcoming reciever closed!"); break; @@ -126,12 +154,24 @@ impl Server { ClientMessage::AnswerHandshake { player_name } => { self.process_handshake(addr, player_name); } + + ClientMessage::SignalReadiness => { + self.state.ready_count += 1; + info!("{} signaled readiness. Currently ready: {}/{}", addr, self.state.ready_count, self.settings.max_connections); + if self.state.ready_count == self.settings.max_connections { + info!("All players are ready! Changing state to ServerStage::Game"); + self.state.stage = ServerStage::Game; + } + }, + ClientMessage::RequestCapture { capture_path: _ } => { self.process_capture(); } + ClientMessage::RequestMove { from: _, to: _ } => { self.process_move(); } + ClientMessage::TextMessage (content) => { info!("{} sent us a message: {}", addr, content); } From 429ab216700a1946c4a8fa043c5366c4c08cfff1 Mon Sep 17 00:00:00 2001 From: szymon Date: Sun, 16 Aug 2026 18:32:36 +0200 Subject: [PATCH 18/41] fix: not sending rejection message --- src/network/server.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/network/server.rs b/src/network/server.rs index f44ce54..00328fd 100644 --- a/src/network/server.rs +++ b/src/network/server.rs @@ -137,8 +137,19 @@ impl Server { self.connections.insert(addr, sender); self.request_handshake(addr).await; } else { - let msg = Message::new(ServerMessage::DeclineHandshake { reason: "There is currently game going and it doesn't allow spectators".to_string() }); - self.send_message(addr, msg).await; + 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!"); From 72e62d4bd762fe197a29d4dbc67e6287b9affa73 Mon Sep 17 00:00:00 2001 From: szymon Date: Sun, 16 Aug 2026 19:39:38 +0200 Subject: [PATCH 19/41] feat: add removing from connection list on connection broke --- src/network/connection.rs | 2 ++ src/network/message.rs | 4 ++++ src/network/server.rs | 30 +++++++++++++++++++++++++++--- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/network/connection.rs b/src/network/connection.rs index c6f56f3..7d58775 100644 --- a/src/network/connection.rs +++ b/src/network/connection.rs @@ -128,6 +128,7 @@ where "Connection broken with {:?}: {}. Disconnecting...", self.peer, e ); + self.tcp = None; break; } }, @@ -139,6 +140,7 @@ where }, None => { info!("Remote closed the channel for {:?}. Disconnecting...", self.peer); + self.tcp = None; break; } } diff --git a/src/network/message.rs b/src/network/message.rs index 9d175eb..460f6e6 100644 --- a/src/network/message.rs +++ b/src/network/message.rs @@ -1,3 +1,5 @@ +use std::net::SocketAddr; + use serde::{Deserialize, Serialize, de::DeserializeOwned}; use uuid::Uuid; @@ -52,6 +54,8 @@ pub enum ClientMessage { AnswerHandshake { player_name: String }, SignalReadiness, + ConnectionDead { addr: SocketAddr }, + RequestCapture { capture_path: CapturePath }, RequestMove { from: Position, to: Position }, diff --git a/src/network/server.rs b/src/network/server.rs index 00328fd..974ae24 100644 --- a/src/network/server.rs +++ b/src/network/server.rs @@ -105,22 +105,35 @@ impl Server { info!("Starting the server at: {:?}", listener.local_addr()); - let sender = self.sender.clone(); + 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 = - Connection::new(ConnectionType::Client, sender.clone()); + 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; }); } } @@ -142,7 +155,6 @@ impl Server { 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 @@ -166,6 +178,14 @@ impl Server { self.process_handshake(addr, player_name); } + ClientMessage::ConnectionDead { addr } => { + warn!("Removing: {:?} from connection list", addr); + // TODO! Should decrement ready_counter if player was ready. But + // how to check that? Maybe create new struct that will hold + // sender and ready and maybe some other data. + self.connections.remove(&addr); + }, + ClientMessage::SignalReadiness => { self.state.ready_count += 1; info!("{} signaled readiness. Currently ready: {}/{}", addr, self.state.ready_count, self.settings.max_connections); @@ -236,4 +256,8 @@ impl Server { Ok(msg) => conn.send(msg).await, }; } + + fn remove_client(&mut self, addr: SocketAddr) { + self.connections.remove(&addr); + } } From bef5368aecd6d466d04003c6c0fc48cae270f7f1 Mon Sep 17 00:00:00 2001 From: szymon Date: Sun, 16 Aug 2026 20:13:18 +0200 Subject: [PATCH 20/41] feat: add args parsing to cli client --- src/bin/client.rs | 88 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 72 insertions(+), 16 deletions(-) diff --git a/src/bin/client.rs b/src/bin/client.rs index 7e6f446..fea79ff 100644 --- a/src/bin/client.rs +++ b/src/bin/client.rs @@ -1,6 +1,42 @@ -use std::io; +use std::{fmt::Display, io}; use checkers::network::client::Client; +use log::{error, info}; + +const QUIT_COMMAND: &str = "/quit"; +const HELP_COMMAND: &str = "/help"; +const SEND_COMMAND: &str = "/send"; +const READY_COMMAND: &str = "/ready"; + +enum CliCommands { + Quit, + Help, + Send, + Ready, +} + +#[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 { + match value { + QUIT_COMMAND => Ok(CliCommands::Quit), + HELP_COMMAND => Ok(CliCommands::Help), + SEND_COMMAND => Ok(CliCommands::Send), + READY_COMMAND => Ok(CliCommands::Ready), + _ => Err(UnknownCommandError), + } + } +} #[tokio::main] async fn main() -> Result<(), Box> { @@ -11,28 +47,48 @@ async fn main() -> Result<(), Box> { return Ok(()); }; + 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 + ", + QUIT_COMMAND, HELP_COMMAND, READY_COMMAND, SEND_COMMAND, + ); + client.update(); - println!("=== Write to stding to send text messages ==="); - println!("Write /close to leave"); + info!("====== Type /help for help ========"); let mut buffer = String::new(); loop { io::stdin().read_line(&mut buffer)?; - if buffer == "/close\n" { - break; - } + let words: Vec<&str> = buffer.split_whitespace().collect(); + let cmd = words[0]; + let mut args: String = words[1..].iter().map(|s| s.to_string() + " ").collect(); + args = args.trim().to_string(); - if buffer == "/test\n" { - buffer = String::from("A").repeat(1024 * 1024 * 2); + match CliCommands::try_from(cmd) { + Ok(cmd) => match cmd { + CliCommands::Quit => { + info!("Bye bye"); + break; + } + CliCommands::Help => { + info!("{}", help_msg); + buffer.clear(); + } + CliCommands::Ready => { + client.signal_readiness().await; + buffer.clear(); + } + CliCommands::Send => { + client.send_text_message(args).await; + buffer.clear(); + } + }, + Err(e) => error!("{}", e), } - - if buffer == "/ready\n" { - buffer.clear(); - client.signal_readiness().await; - continue; - } - - client.send_text_message(buffer.clone()).await; buffer.clear(); } From 302c5064857bd0460a19a0f39261575a269fa49f Mon Sep 17 00:00:00 2001 From: szymon Date: Sun, 16 Aug 2026 20:16:34 +0200 Subject: [PATCH 21/41] fix: cli client crashing on no input --- src/bin/client.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/bin/client.rs b/src/bin/client.rs index fea79ff..69d0d48 100644 --- a/src/bin/client.rs +++ b/src/bin/client.rs @@ -63,9 +63,16 @@ async fn main() -> Result<(), Box> { let mut buffer = String::new(); loop { io::stdin().read_line(&mut buffer)?; + buffer.pop(); // remove \n + + if buffer.is_empty() { + buffer.clear(); + continue; + } + let words: Vec<&str> = buffer.split_whitespace().collect(); let cmd = words[0]; - let mut args: String = words[1..].iter().map(|s| s.to_string() + " ").collect(); + let mut args: String = words[1..].join(" "); args = args.trim().to_string(); match CliCommands::try_from(cmd) { From ff52b8e1d7ff3af34ba12af14b7da96f6e86957f Mon Sep 17 00:00:00 2001 From: szymon Date: Sun, 16 Aug 2026 20:24:45 +0200 Subject: [PATCH 22/41] feat: add moving and capturing to server --- src/network/server.rs | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/network/server.rs b/src/network/server.rs index 974ae24..6d14ad1 100644 --- a/src/network/server.rs +++ b/src/network/server.rs @@ -1,4 +1,4 @@ -use log::{error, info, warn}; +use log::{debug, error, info, warn}; use std::{ collections::HashMap, net::{IpAddr, Ipv4Addr, SocketAddr}, @@ -9,7 +9,7 @@ use tokio::{ }; use crate::{ - logic::game_master::GameMaster, + logic::{board::pawn::CapturePath, game_master::GameMaster, math::position::Position}, network::{ connection::{Connection, ConnectionType}, message::{ @@ -195,12 +195,12 @@ impl Server { } }, - ClientMessage::RequestCapture { capture_path: _ } => { - self.process_capture(); + ClientMessage::RequestCapture { capture_path } => { + self.process_capture(capture_path); } - ClientMessage::RequestMove { from: _, to: _ } => { - self.process_move(); + ClientMessage::RequestMove { from, to } => { + self.process_move(from, to); } ClientMessage::TextMessage (content) => { @@ -223,12 +223,24 @@ impl Server { // TODO! Do something with this name i guess } - fn process_capture(&self) { - todo!(); + 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!"); + } else { + debug!("Invalid capture!"); + } + } } - fn process_move(&self) { - todo!(); + 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!"); + } else { + debug!("Move was invalid!"); + } + } } async fn request_handshake(&mut self, addr: SocketAddr) { From 3bbf85de960cc750d9a7ab2844a4824c6fa4330b Mon Sep 17 00:00:00 2001 From: szymon Date: Sun, 16 Aug 2026 20:39:56 +0200 Subject: [PATCH 23/41] feat: add handshake answer --- src/network/client.rs | 6 ++++-- src/network/message.rs | 2 +- src/network/server.rs | 16 ++++++++++++---- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/network/client.rs b/src/network/client.rs index 4b50f72..3667570 100644 --- a/src/network/client.rs +++ b/src/network/client.rs @@ -68,7 +68,7 @@ impl Client { loop { if let Some(oldest_msg) = conn_reciever_incoming.recv().await { let (addr, msg) = oldest_msg; - info!("Got message from: {:?}", addr); + debug!("Got message from: {:?}", addr); match msg.content { ServerMessage::RequestHandshake => { @@ -79,7 +79,9 @@ impl Client { let _ = sender.send(msg.unwrap()).await; } - ServerMessage::AcceptHandshake { player_id } => todo!(), + ServerMessage::AcceptHandshake => { + info!("Server accepted us! Yay :D"); + } ServerMessage::DeclineHandshake { reason } => { error!("Server declined connection: {}", reason); break; diff --git a/src/network/message.rs b/src/network/message.rs index 460f6e6..9d23ae1 100644 --- a/src/network/message.rs +++ b/src/network/message.rs @@ -37,7 +37,7 @@ where #[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)] pub enum ServerMessage { RequestHandshake, - AcceptHandshake { player_id: Uuid }, + AcceptHandshake, DeclineHandshake { reason: String }, AvailableCaptures { captures: Vec }, diff --git a/src/network/server.rs b/src/network/server.rs index 6d14ad1..b9940e4 100644 --- a/src/network/server.rs +++ b/src/network/server.rs @@ -175,7 +175,7 @@ impl Server { match msg.content { ClientMessage::AnswerHandshake { player_name } => { - self.process_handshake(addr, player_name); + self.process_handshake(addr, player_name).await; } ClientMessage::ConnectionDead { addr } => { @@ -183,7 +183,7 @@ impl Server { // TODO! Should decrement ready_counter if player was ready. But // how to check that? Maybe create new struct that will hold // sender and ready and maybe some other data. - self.connections.remove(&addr); + self.remove_client(addr); }, ClientMessage::SignalReadiness => { @@ -192,6 +192,8 @@ impl Server { 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(); } }, @@ -216,11 +218,17 @@ impl Server { } } + fn start_game(&self) { + todo!(); + } + /* ======= Processing messages :D helper functions ======== */ - fn process_handshake(&self, addr: SocketAddr, player_name: String) { + async fn process_handshake(&mut self, addr: SocketAddr, player_name: String) { info!("Client: {:?} sent us his name: {}", addr, player_name); - // TODO! Do something with this name i guess + self.send_message(addr, Message::new(ServerMessage::AcceptHandshake)) + .await; + // TODO! Validate if the name isn't already in use and append something to it if it is } fn process_capture(&mut self, capture_path: CapturePath) { From e36ff7b0485b11bedfc1889909fa55bfa83bbd7b Mon Sep 17 00:00:00 2001 From: szymon Date: Sun, 16 Aug 2026 20:49:42 +0200 Subject: [PATCH 24/41] feat: add prompt to cli client --- src/bin/client.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/bin/client.rs b/src/bin/client.rs index 69d0d48..5174288 100644 --- a/src/bin/client.rs +++ b/src/bin/client.rs @@ -1,6 +1,11 @@ -use std::{fmt::Display, io}; +use std::{ + fmt::Display, + io::{self, Write}, + time::Duration, +}; use checkers::network::client::Client; +use env_logger::TimestampPrecision::Seconds; use log::{error, info}; const QUIT_COMMAND: &str = "/quit"; @@ -62,6 +67,9 @@ async fn main() -> Result<(), Box> { info!("====== Type /help for help ========"); let mut buffer = String::new(); loop { + print!("> "); + io::stdout().flush()?; + io::stdin().read_line(&mut buffer)?; buffer.pop(); // remove \n From d4fc5dff2eff501d13dc0a2178b8b5afe2dfdb3e Mon Sep 17 00:00:00 2001 From: szymon Date: Sun, 16 Aug 2026 21:33:36 +0200 Subject: [PATCH 25/41] feat: add network identity --- src/bin/client.rs | 2 -- src/network/mod.rs | 1 + src/network/network_identity.rs | 43 ++++++++++++++++++++++++++++++++ src/network/server.rs | 44 ++++++++++++++++++++++++++++----- 4 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 src/network/network_identity.rs diff --git a/src/bin/client.rs b/src/bin/client.rs index 5174288..8540db4 100644 --- a/src/bin/client.rs +++ b/src/bin/client.rs @@ -1,11 +1,9 @@ use std::{ fmt::Display, io::{self, Write}, - time::Duration, }; use checkers::network::client::Client; -use env_logger::TimestampPrecision::Seconds; use log::{error, info}; const QUIT_COMMAND: &str = "/quit"; diff --git a/src/network/mod.rs b/src/network/mod.rs index 101e02a..3c74f01 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -1,4 +1,5 @@ pub mod client; pub mod connection; pub mod message; +mod network_identity; pub mod server; diff --git a/src/network/network_identity.rs b/src/network/network_identity.rs new file mode 100644 index 0000000..4753e00 --- /dev/null +++ b/src/network/network_identity.rs @@ -0,0 +1,43 @@ +use serde::{Deserialize, Serialize}; +use tokio::sync::mpsc::Sender; + +use crate::network::message::{Message, ServerMessage}; + +#[derive(Deserialize, Serialize)] +pub struct NetworkIdentity { + pub name: String, + pub id: uuid::Uuid, + pub is_ready: bool, +} + +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>, +} + +impl NetworkServerIdentity { + pub fn new(sender: Sender>) -> Self { + Self { + identity: NetworkIdentity::default(), + sender, + } + } +} + +impl AsMut for NetworkServerIdentity { + fn as_mut(&mut self) -> &mut NetworkIdentity { + &mut self.identity + } +} diff --git a/src/network/server.rs b/src/network/server.rs index b9940e4..e3e8648 100644 --- a/src/network/server.rs +++ b/src/network/server.rs @@ -16,6 +16,7 @@ use crate::{ ClientMessage::{self}, Message, ServerMessage, }, + network_identity::{NetworkIdentity, NetworkServerIdentity}, }, }; @@ -49,7 +50,7 @@ impl Default for ServerState { pub struct Server { listener: Option, - connections: HashMap>>, + connections: HashMap, reciever: Receiver<(SocketAddr, Message)>, sender: Sender<(SocketAddr, Message)>, @@ -126,6 +127,7 @@ impl Server { // TODO! Add waiting for reconnect let disconnect_msg = Message::new(ClientMessage::ConnectionDead { addr }); + let _ = disconnect_sender .send(( addr, @@ -147,7 +149,8 @@ impl Server { 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 { - self.connections.insert(addr, sender); + 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(); @@ -183,16 +186,25 @@ impl Server { // TODO! Should decrement ready_counter if player was ready. But // how to check that? Maybe create new struct that will hold // sender and ready and maybe some other data. + + 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.remove_client(addr); }, ClientMessage::SignalReadiness => { 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; + 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(); } }, @@ -228,6 +240,26 @@ impl Server { 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) + } 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 + // TODO! Validate if the name isn't already in use and append something to it if it is } @@ -263,8 +295,8 @@ impl Server { ) { let conn_opt = self.connections.get_mut(&addr); - let Some(conn) = conn_opt else { - error!("Could not get connection from connections list"); + let Some(identity) = conn_opt else { + error!("Could not get identity from connections list"); return; }; @@ -273,7 +305,7 @@ impl Server { error!("There was an error while creating message: {}", e); return; } - Ok(msg) => conn.send(msg).await, + Ok(msg) => identity.sender.send(msg).await, }; } From 5cb99cd45a50ee69977b3a6b9420c29fe1d40997 Mon Sep 17 00:00:00 2001 From: szymon Date: Sun, 16 Aug 2026 21:36:17 +0200 Subject: [PATCH 26/41] chore: add logging doubled names --- src/network/server.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/network/server.rs b/src/network/server.rs index e3e8648..8aac15f 100644 --- a/src/network/server.rs +++ b/src/network/server.rs @@ -248,9 +248,13 @@ impl Server { .values() .any(|v| v.identity.name == player_name) { - fixed_name = format!("{}(2)", 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 + fixed_name = player_name; } let Some(identity) = self.connections.get_mut(&addr) else { From ffcab4f58b332b5d6024813eedbeb2aba21585fb Mon Sep 17 00:00:00 2001 From: szymon Date: Sun, 16 Aug 2026 22:38:51 +0200 Subject: [PATCH 27/41] docs: add something to readme so it's not that ugly --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4cc69c8..7c2be72 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,11 @@ -# Game of checkers +# Game of checkers online +Currently WIP + +# Thoughts +Server is self-contained, so frontend can be written in: +- ratatui? +- raylib bindings for rust +- egui +- macroquad +- cli lol (already kinda happening) From a12f1fce4d12e794963d38c89769500f3cb86b42 Mon Sep 17 00:00:00 2001 From: szymon Date: Mon, 17 Aug 2026 14:10:28 +0200 Subject: [PATCH 28/41] refactor: server message handling into its own methods --- src/network/server.rs | 48 ++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/src/network/server.rs b/src/network/server.rs index 8aac15f..cf106b0 100644 --- a/src/network/server.rs +++ b/src/network/server.rs @@ -182,31 +182,11 @@ impl Server { } ClientMessage::ConnectionDead { addr } => { - warn!("Removing: {:?} from connection list", addr); - // TODO! Should decrement ready_counter if player was ready. But - // how to check that? Maybe create new struct that will hold - // sender and ready and maybe some other data. - - 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.remove_client(addr); }, ClientMessage::SignalReadiness => { - 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; - - 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(); - } + self.handle_readiness(addr) }, ClientMessage::RequestCapture { capture_path } => { @@ -313,7 +293,33 @@ impl Server { }; } + 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; + + 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(); + } + } + 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); } } From 4f024cae34493cdc6a70b8b3c7e412d2e12b7189 Mon Sep 17 00:00:00 2001 From: szymon Date: Mon, 17 Aug 2026 15:50:52 +0200 Subject: [PATCH 29/41] feat: add starting the game --- src/logic/board/board.rs | 38 ++++++++++++--------- src/logic/board/board_view.rs | 19 +++++++++++ src/logic/board/mod.rs | 1 + src/logic/game_master.rs | 21 ++++++++++-- src/logic/player.rs | 4 +-- src/network/client.rs | 39 +++++++++++++++++++--- src/network/message.rs | 45 ++++++++++++++++++------- src/network/network_identity.rs | 10 +++++- src/network/server.rs | 59 +++++++++++++++++++++++++++++---- 9 files changed, 190 insertions(+), 46 deletions(-) create mode 100644 src/logic/board/board_view.rs diff --git a/src/logic/board/board.rs b/src/logic/board/board.rs index 3db6114..b5eda57 100644 --- a/src/logic/board/board.rs +++ b/src/logic/board/board.rs @@ -72,6 +72,12 @@ impl Board { 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 { + self.board.clone() + } + /// For testing fn new_empty(player1: &mut Player, player2: &mut Player, size: Option) -> Self { debug!("Creating new empty board!"); @@ -527,8 +533,8 @@ mod tests { fn new_board_test() { init_logger(); - let mut player1: Player = Player::new("Morbius".to_string()); - let mut player2: Player = Player::new("Milo".to_string()); + let mut player1: Player = Player::new("Morbius"); + let mut player2: Player = Player::new("Milo"); let test_board: Board = Board::new(&mut player1, &mut player2, None); let size: usize = test_board.size; @@ -575,8 +581,8 @@ mod tests { fn test_overloaded_operators() { init_logger(); - let mut player1: Player = Player::new("Morbius".to_string()); - let mut player2: Player = Player::new("Milo".to_string()); + let mut player1: Player = Player::new("Morbius"); + let mut player2: Player = Player::new("Milo"); let test_board: Board = Board::new(&mut player1, &mut player2, None); @@ -607,8 +613,8 @@ mod tests { fn test_moving_pawn() { init_logger(); - let mut player1: Player = Player::new("Morbius".to_string()); - let mut player2: Player = Player::new("Milo".to_string()); + let mut player1: Player = Player::new("Morbius"); + let mut player2: Player = Player::new("Milo"); let mut test_board: Board = Board::new(&mut player1, &mut player2, None); @@ -642,8 +648,8 @@ mod tests { fn test_getting_pawn_moves() { init_logger(); - let mut p1: Player = Player::new("Morbius".to_string()); - let mut p2: Player = Player::new("Milo".to_string()); + let mut p1: Player = Player::new("Morbius"); + let mut p2: Player = Player::new("Milo"); let mut board: Board = Board::new_empty(&mut p1, &mut p2, None); @@ -676,8 +682,8 @@ mod tests { fn test_getting_player_pawns_positions() { init_logger(); - let mut p1: Player = Player::new("Morbius".to_string()); - let mut p2: Player = Player::new("Milo".to_string()); + let mut p1: Player = Player::new("Morbius"); + let mut p2: Player = Player::new("Milo"); let test_board: Board = Board::new(&mut p1, &mut p2, None); let expected_positions: Vec = (0..test_board.size) @@ -707,8 +713,8 @@ mod tests { fn test_is_movable() { init_logger(); - let mut p1: Player = Player::new("Morbius".to_string()); - let mut p2: Player = Player::new("Milo".to_string()); + let mut p1: Player = Player::new("Morbius"); + let mut p2: Player = Player::new("Milo"); let test_board: Board = Board::new(&mut p1, &mut p2, None); assert!(test_board.is_pawn_movable(Position { row: 2, column: 1 }, &p1)); @@ -726,8 +732,8 @@ mod tests { fn test_getting_captures_path() { init_logger(); - let mut p1 = Player::new("Morbius".to_string()); - let mut p2 = Player::new("Milo".to_string()); + let mut p1 = Player::new("Morbius"); + let mut p2 = Player::new("Milo"); let mut board = Board::new_empty(&mut p1, &mut p2, None); @@ -758,8 +764,8 @@ mod tests { fn test_capturing() { init_logger(); - let mut p1 = Player::new("Morbius".to_string()); - let mut p2 = Player::new("Milo".to_string()); + let mut p1 = Player::new("Morbius"); + let mut p2 = Player::new("Milo"); let mut board = Board::new_empty(&mut p1, &mut p2, None); diff --git a/src/logic/board/board_view.rs b/src/logic/board/board_view.rs new file mode 100644 index 0000000..6b61ea6 --- /dev/null +++ b/src/logic/board/board_view.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; + +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, + pub size: usize, +} + +impl From for BoardView { + fn from(value: Board) -> Self { + Self { + board: value.get_board(), + size: value.size, + } + } +} diff --git a/src/logic/board/mod.rs b/src/logic/board/mod.rs index 079d7f1..81d9a95 100644 --- a/src/logic/board/mod.rs +++ b/src/logic/board/mod.rs @@ -1,3 +1,4 @@ pub mod board; +pub mod board_view; mod field; pub mod pawn; diff --git a/src/logic/game_master.rs b/src/logic/game_master.rs index d480c6c..e2c07b1 100644 --- a/src/logic/game_master.rs +++ b/src/logic/game_master.rs @@ -1,9 +1,11 @@ use log::{info, warn}; use serde::{Deserialize, Serialize}; +use uuid::Uuid; use crate::logic::{ board::{ board::Board, + board_view::BoardView, pawn::{CapturePath, MovePath}, }, math::position::Position, @@ -23,7 +25,7 @@ struct PlayerList { } impl PlayerList { - pub fn new(player1_name: String, player2_name: String) -> Self { + pub fn new(player1_name: &str, player2_name: &str) -> Self { PlayerList { players: [Player::new(player1_name), Player::new(player2_name)], current_turn: 0, @@ -50,7 +52,7 @@ pub struct 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 [player1, player2] = &mut players.players; @@ -59,6 +61,19 @@ impl GameMaster { 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_player_captures(&self) -> Vec { self.board .get_player_pawns_positions(self.players.get_current_turn()) @@ -167,7 +182,7 @@ mod tests { use rand::seq::IndexedRandom; 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 max_turns = 2000; diff --git a/src/logic/player.rs b/src/logic/player.rs index 6036a0f..307ef54 100644 --- a/src/logic/player.rs +++ b/src/logic/player.rs @@ -12,9 +12,9 @@ pub struct Player { } impl Player { - pub fn new(name: String) -> Self { + pub fn new(name: &str) -> Self { Player { - name, + name: name.to_string(), id: Uuid::new_v4(), end_row: None, vertical_direction: None, diff --git a/src/network/client.rs b/src/network/client.rs index 3667570..c40c580 100644 --- a/src/network/client.rs +++ b/src/network/client.rs @@ -3,11 +3,15 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use log::{debug, error, info}; use tokio::sync::mpsc::{self, Receiver, Sender}; -use crate::network::{ - connection::{Connection, ConnectionType}, - message::{ - ClientMessage::{self, SignalReadiness, TextMessage}, - Message, ServerMessage, +use crate::{ + logic::board::board_view::BoardView, + network::{ + connection::{Connection, ConnectionType}, + message::{ + ClientMessage::{self, SignalReadiness, TextMessage}, + Message, ServerMessage, + }, + network_identity::NetworkIdentity, }, }; @@ -21,6 +25,10 @@ pub struct Client { conn_reciever_incoming: Option)>>, conn_sender_outgoing: Sender>, settings: ClientSettings, + + identity: Option, + board_view: Option, + update_reciever: Option>>, } impl Client { @@ -51,6 +59,9 @@ impl Client { conn_reciever_incoming: Some(conn_reciever), conn_sender_outgoing, settings, + identity: None, + board_view: None, + update_reciever: None, }) } @@ -64,6 +75,9 @@ impl Client { let settings = self.settings.clone(); + let (update_sender, update_reciever) = mpsc::channel::>(1024); + self.update_reciever = Some(update_reciever); + tokio::spawn(async move { loop { if let Some(oldest_msg) = conn_reciever_incoming.recv().await { @@ -79,13 +93,28 @@ impl Client { let _ = 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, + board_view, + } => { + info!("Server started the game!"); + info!("Our identity is: {}", identity); + /* + self.board_view = Some(board_view); + self.identity = Some(identity); + */ + } + ServerMessage::AvailableCaptures { captures } => todo!(), ServerMessage::AvailableMoves { moves } => todo!(), ServerMessage::BroadcastBoardState { board } => todo!(), diff --git a/src/network/message.rs b/src/network/message.rs index 9d23ae1..19cfe19 100644 --- a/src/network/message.rs +++ b/src/network/message.rs @@ -3,13 +3,17 @@ use std::net::SocketAddr; use serde::{Deserialize, Serialize, de::DeserializeOwned}; use uuid::Uuid; -use crate::logic::{ - board::{ - board::Board, - pawn::{CapturePath, MovePath}, +use crate::{ + logic::{ + board::{ + board::Board, + board_view::BoardView, + pawn::{CapturePath, MovePath}, + }, + game_master::GameResult, + math::position::Position, }, - game_master::GameResult, - math::position::Position, + network::network_identity::NetworkIdentity, }; use postcard::{from_bytes, to_allocvec}; @@ -38,15 +42,32 @@ where pub enum ServerMessage { RequestHandshake, AcceptHandshake, - DeclineHandshake { reason: String }, + DeclineHandshake { + reason: String, + }, - AvailableCaptures { captures: Vec }, - AvailableMoves { moves: Vec }, + GameStart { + identity: NetworkIdentity, + board_view: BoardView, + }, - BroadcastBoardState { board: Board }, - BroadcastCurrentTurn { active_player: Uuid }, + AvailableCaptures { + captures: Vec, + }, + AvailableMoves { + moves: Vec, + }, - GameEnd { result: GameResult }, + BroadcastBoardState { + board: Board, + }, + BroadcastCurrentTurn { + active_player: Uuid, + }, + + GameEnd { + result: GameResult, + }, } #[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)] diff --git a/src/network/network_identity.rs b/src/network/network_identity.rs index 4753e00..02ace06 100644 --- a/src/network/network_identity.rs +++ b/src/network/network_identity.rs @@ -1,15 +1,23 @@ +use std::fmt::Display; + use serde::{Deserialize, Serialize}; use tokio::sync::mpsc::Sender; use crate::network::message::{Message, ServerMessage}; -#[derive(Deserialize, Serialize)] +#[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 { diff --git a/src/network/server.rs b/src/network/server.rs index cf106b0..cb5d942 100644 --- a/src/network/server.rs +++ b/src/network/server.rs @@ -7,9 +7,14 @@ use tokio::{ net::TcpListener, sync::mpsc::{Receiver, Sender}, }; +use uuid::Uuid; use crate::{ - logic::{board::pawn::CapturePath, game_master::GameMaster, math::position::Position}, + logic::{ + board::{board_view::BoardView, pawn::CapturePath}, + game_master::GameMaster, + math::position::Position, + }, network::{ connection::{Connection, ConnectionType}, message::{ @@ -83,7 +88,7 @@ impl Server { .expect("Could not start the server"), ), // TODO! Handle this nicely, as this will // crash gui app - connections: HashMap::new(), + connections: HashMap::with_capacity(2), sender, reciever, welcoming_reciever: None, @@ -186,7 +191,7 @@ impl Server { }, ClientMessage::SignalReadiness => { - self.handle_readiness(addr) + self.handle_readiness(addr).await }, ClientMessage::RequestCapture { capture_path } => { @@ -210,8 +215,48 @@ impl Server { } } - fn start_game(&self) { - todo!(); + async fn start_game(&mut self) { + info!("Starting the game!"); + let mut names: Vec = 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 board_view: BoardView = game_master.get_board_view(); + + let mut msgs: Vec<(SocketAddr, Result, 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(), + board_view: board_view.clone(), + }), + )); + } + } + } + + for (addr, msg) in msgs { + self.send_message(addr, msg).await; + } + + self.game_master = Some(game_master); } /* ======= Processing messages :D helper functions ======== */ @@ -293,7 +338,7 @@ impl Server { }; } - fn handle_readiness(&mut self, addr: SocketAddr) { + async fn handle_readiness(&mut self, addr: SocketAddr) { self.state.ready_count += 1; info!( "{} signaled readiness. Currently ready: {}/{}", @@ -304,7 +349,7 @@ impl Server { 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(); + self.start_game().await; } } From 10a874da3248fd5638f93caa619d680bf2025cbf Mon Sep 17 00:00:00 2001 From: szymon Date: Mon, 17 Aug 2026 17:25:37 +0200 Subject: [PATCH 30/41] feat: add communication between newtwork client update loop and local client --- src/bin/client.rs | 39 ++++++- src/network/client.rs | 233 +++++++++++++++++++++++++++++------------- 2 files changed, 196 insertions(+), 76 deletions(-) diff --git a/src/bin/client.rs b/src/bin/client.rs index 8540db4..3a25e06 100644 --- a/src/bin/client.rs +++ b/src/bin/client.rs @@ -4,7 +4,8 @@ use std::{ }; use checkers::network::client::Client; -use log::{error, info}; +use log::{debug, error, info}; +use tokio::sync::mpsc; const QUIT_COMMAND: &str = "/quit"; const HELP_COMMAND: &str = "/help"; @@ -50,6 +51,12 @@ async fn main() -> Result<(), Box> { return Ok(()); }; + let (client_sender, mut client_receiver) = mpsc::channel(1024); + + client.set_update_sender(client_sender); + + client.update(); + let help_msg: String = format!( "\n┌────Help──── │\t {} - quit @@ -60,8 +67,6 @@ async fn main() -> Result<(), Box> { QUIT_COMMAND, HELP_COMMAND, READY_COMMAND, SEND_COMMAND, ); - client.update(); - info!("====== Type /help for help ========"); let mut buffer = String::new(); loop { @@ -71,6 +76,34 @@ async fn main() -> Result<(), Box> { io::stdin().read_line(&mut buffer)?; buffer.pop(); // remove \n + match client_receiver.try_recv() { + Ok(checkers::network::client::ClientData::GameStart { + identity, + board_view, + }) => { + info!( + "Got new identity: {} and board_view. Game is about to start!", + identity + ); + } + + Ok(checkers::network::client::ClientData::AvailableCaptures(capture_paths)) => { + todo!() + } + + Ok(checkers::network::client::ClientData::AvailableMoves(move_paths)) => todo!(), + + Ok(checkers::network::client::ClientData::TextMessage(content)) => { + info!("Got message from the server: {}", content); + } + + Ok(checkers::network::client::ClientData::GameEnd(game_result)) => { + info!("Game has ended with the result: {:?}", game_result); + } + + _ => {} + } + if buffer.is_empty() { buffer.clear(); continue; diff --git a/src/network/client.rs b/src/network/client.rs index c40c580..b674a8c 100644 --- a/src/network/client.rs +++ b/src/network/client.rs @@ -1,10 +1,17 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; -use log::{debug, error, info}; +use log::{debug, error, info, warn}; use tokio::sync::mpsc::{self, Receiver, Sender}; use crate::{ - logic::board::board_view::BoardView, + logic::{ + board::{ + board_view::BoardView, + pawn::{CapturePath, MovePath}, + }, + game_master::GameResult, + math::position::Position, + }, network::{ connection::{Connection, ConnectionType}, message::{ @@ -21,14 +28,35 @@ pub struct ClientSettings { 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, + board_view: BoardView, + }, + AvailableCaptures(Vec), + AvailableMoves(Vec), + TextMessage(String), + GameEnd(GameResult), +} + pub struct Client { conn_reciever_incoming: Option)>>, conn_sender_outgoing: Sender>, settings: ClientSettings, - identity: Option, - board_view: Option, - update_reciever: Option>>, + commands_sender: Option>, + commands_receiver: Option>, + + update_sender: Option>, } impl Client { @@ -40,12 +68,12 @@ impl Client { let (conn_sender, conn_reciever) = mpsc::channel::<(SocketAddr, Message)>(1024); - let mut conn: Connection = 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; @@ -58,102 +86,161 @@ impl Client { Some(Client { conn_reciever_incoming: Some(conn_reciever), conn_sender_outgoing, + settings, - identity: None, - board_view: None, - update_reciever: None, + + commands_receiver: Some(commands_receiver), + commands_sender: Some(commands_sender), + + update_sender: None, }) } + pub fn get_commands_sender(&mut self) -> Option> { + 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) { + 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 sender = self.conn_sender_outgoing.clone(); + 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(); - let (update_sender, update_reciever) = mpsc::channel::>(1024); - self.update_reciever = Some(update_reciever); - tokio::spawn(async move { loop { - if let Some(oldest_msg) = conn_reciever_incoming.recv().await { - let (addr, msg) = oldest_msg; - debug!("Got message from: {:?}", addr); + 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(), - }); + match msg.content { + ServerMessage::RequestHandshake => { + info!("Server requested handshake!"); + let msg = Message::new(ClientMessage::AnswerHandshake { + player_name: settings.name.clone(), + }); - let _ = sender.send(msg.unwrap()).await; - } + let _ = conn_sender.send(msg.unwrap()).await; + } - ServerMessage::AcceptHandshake => { - info!("Server accepted us! Yay :D"); - } + ServerMessage::AcceptHandshake => { + info!("Server accepted us! Yay :D"); + } - ServerMessage::DeclineHandshake { reason } => { - error!("Server declined connection: {}", reason); + ServerMessage::DeclineHandshake { reason } => { + error!("Server declined connection: {}", reason); + break; + } + + ServerMessage::GameStart { + identity, + board_view, + } => { + debug!("Got GameStart message! Identity: {}", identity); + let _ = update_sender.send(ClientData::GameStart { identity, board_view }).await; + } + + ServerMessage::AvailableCaptures { captures } => todo!(), + ServerMessage::AvailableMoves { moves } => todo!(), + ServerMessage::BroadcastBoardState { board } => todo!(), + ServerMessage::BroadcastCurrentTurn { active_player } => todo!(), + ServerMessage::GameEnd { result } => todo!(), + } + } else { + error!("Connection broken!"); break; } - - ServerMessage::GameStart { - identity, - board_view, - } => { - info!("Server started the game!"); - info!("Our identity is: {}", identity); - /* - self.board_view = Some(board_view); - self.identity = Some(identity); - */ - } - - ServerMessage::AvailableCaptures { captures } => todo!(), - ServerMessage::AvailableMoves { moves } => todo!(), - ServerMessage::BroadcastBoardState { board } => todo!(), - ServerMessage::BroadcastCurrentTurn { active_player } => todo!(), - ServerMessage::GameEnd { result } => todo!(), } - } else { - error!("Connection broken"); - break; + + oldest_cmd = commands_receiver.recv() => { + if let Some(oldest_cmd) = oldest_cmd { + match oldest_cmd { + ClientCommands::SendCapture(capture_path) => todo!(), + + ClientCommands::SendMove { from, to } => todo!(), + + 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_text_message(&mut self, content: String) { - debug!("Seding text message: {}", content); - let msg = Message::new(TextMessage(content)); - self.send_message(msg).await; + 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) { - debug!("Signaling readiness!"); - let msg = Message::new(SignalReadiness); - self.send_message(msg).await; - } - - async fn send_message(&mut self, msg: Result, postcard::Error>) { - 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) = self.conn_sender_outgoing.send(msg).await { - error!("Error while sending a message to connection thread: {}", e); - return; - } - } - }; + 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, postcard::Error>, + sender: &Sender>, +) { + 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; + } + } + }; +} From ed6fc3583a20b1886c5ee2cde30e82d47ccc02f8 Mon Sep 17 00:00:00 2001 From: szymon Date: Mon, 17 Aug 2026 17:57:28 +0200 Subject: [PATCH 31/41] feat: add displaying board_view as string --- src/bin/client.rs | 1 + src/logic/board/board_view.rs | 44 +++++++++++++++++++++++++++++++++++ src/logic/board/pawn.rs | 1 - 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/bin/client.rs b/src/bin/client.rs index 3a25e06..243c240 100644 --- a/src/bin/client.rs +++ b/src/bin/client.rs @@ -85,6 +85,7 @@ async fn main() -> Result<(), Box> { "Got new identity: {} and board_view. Game is about to start!", identity ); + println!("{}", board_view.to_string(identity.id)); } Ok(checkers::network::client::ClientData::AvailableCaptures(capture_paths)) => { diff --git a/src/logic/board/board_view.rs b/src/logic/board/board_view.rs index 6b61ea6..86a092f 100644 --- a/src/logic/board/board_view.rs +++ b/src/logic/board/board_view.rs @@ -1,4 +1,7 @@ +use std::ops::Index; + use serde::{Deserialize, Serialize}; +use uuid::Uuid; use crate::logic::board::{board::Board, field::Field, pawn::Pawn}; @@ -17,3 +20,44 @@ impl From for BoardView { } } } + +impl Index 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::() + }) + .collect::>() + .join("\n") + } +} diff --git a/src/logic/board/pawn.rs b/src/logic/board/pawn.rs index 02c860e..548bd78 100644 --- a/src/logic/board/pawn.rs +++ b/src/logic/board/pawn.rs @@ -7,7 +7,6 @@ use crate::logic::{math::position::Position, player::Player}; pub enum PawnState { Man, Dame, - Captured, } /// Basic pawn From 1be6ece6652d93494242718ad83cdb28230d946e Mon Sep 17 00:00:00 2001 From: szymon Date: Mon, 17 Aug 2026 18:38:30 +0200 Subject: [PATCH 32/41] feat: make cli client take stdin async --- src/bin/client.rs | 152 +++++++++++++++++++++++++--------------------- 1 file changed, 83 insertions(+), 69 deletions(-) diff --git a/src/bin/client.rs b/src/bin/client.rs index 243c240..1ebdc0b 100644 --- a/src/bin/client.rs +++ b/src/bin/client.rs @@ -4,7 +4,7 @@ use std::{ }; use checkers::network::client::Client; -use log::{debug, error, info}; +use log::{debug, error, info, warn}; use tokio::sync::mpsc; const QUIT_COMMAND: &str = "/quit"; @@ -54,90 +54,104 @@ async fn main() -> Result<(), Box> { 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); + + 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 + "\n┌────Help──────────────────────────── +│\t {} - quit +│\t {} - show this message │\t {} - message the server you're ready │\t {} - send text message to the server +└─────────────────────────────────────── ", QUIT_COMMAND, HELP_COMMAND, READY_COMMAND, SEND_COMMAND, ); - info!("====== Type /help for help ========"); - let mut buffer = String::new(); + info!("───────Type /help for help───────"); loop { - print!("> "); - io::stdout().flush()?; + tokio::select! { + msg = client_receiver.recv() => { + match msg { + Some(checkers::network::client::ClientData::GameStart { + identity, + board_view, + }) => { + info!( + "Got new identity: {} and board_view. Game is about to start!", + identity + ); + println!("{}", board_view.to_string(identity.id)); + } - io::stdin().read_line(&mut buffer)?; - buffer.pop(); // remove \n + Some(checkers::network::client::ClientData::AvailableCaptures(capture_paths)) => { + todo!() + } - match client_receiver.try_recv() { - Ok(checkers::network::client::ClientData::GameStart { - identity, - board_view, - }) => { - info!( - "Got new identity: {} and board_view. Game is about to start!", - identity - ); - println!("{}", board_view.to_string(identity.id)); + Some(checkers::network::client::ClientData::AvailableMoves(move_paths)) => todo!(), + + Some(checkers::network::client::ClientData::TextMessage(content)) => { + info!("Got message from the server: {}", content); + } + + Some(checkers::network::client::ClientData::GameEnd(game_result)) => { + info!("Game has ended with the result: {:?}", game_result); + } + + None => { + warn!("Connection broken! Stopping"); + break Ok(()); + } + } } - Ok(checkers::network::client::ClientData::AvailableCaptures(capture_paths)) => { - todo!() + 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]; + let mut args: String = words[1..].join(" "); + args = args.trim().to_string(); + + 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 => { + client.send_text_message(args).await; + } + }, + Err(e) => error!("{}", e), + } + } + print!("> "); + io::stdout().flush()?; } - - Ok(checkers::network::client::ClientData::AvailableMoves(move_paths)) => todo!(), - - Ok(checkers::network::client::ClientData::TextMessage(content)) => { - info!("Got message from the server: {}", content); - } - - Ok(checkers::network::client::ClientData::GameEnd(game_result)) => { - info!("Game has ended with the result: {:?}", game_result); - } - - _ => {} } - - if buffer.is_empty() { - buffer.clear(); - continue; } - - let words: Vec<&str> = buffer.split_whitespace().collect(); - let cmd = words[0]; - let mut args: String = words[1..].join(" "); - args = args.trim().to_string(); - - match CliCommands::try_from(cmd) { - Ok(cmd) => match cmd { - CliCommands::Quit => { - info!("Bye bye"); - break; - } - CliCommands::Help => { - info!("{}", help_msg); - buffer.clear(); - } - CliCommands::Ready => { - client.signal_readiness().await; - buffer.clear(); - } - CliCommands::Send => { - client.send_text_message(args).await; - buffer.clear(); - } - }, - Err(e) => error!("{}", e), - } - buffer.clear(); } - - return Ok(()); } From a096c990a98b43b99acde0946eee6fe0d3f926ae Mon Sep 17 00:00:00 2001 From: szymon Date: Mon, 17 Aug 2026 19:11:26 +0200 Subject: [PATCH 33/41] feat: add broadcasting to the server --- src/network/message.rs | 3 +++ src/network/server.rs | 42 ++++++++++++++++++++++++++++-------------- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/src/network/message.rs b/src/network/message.rs index 19cfe19..4a76441 100644 --- a/src/network/message.rs +++ b/src/network/message.rs @@ -51,6 +51,8 @@ pub enum ServerMessage { board_view: BoardView, }, + BoardState(BoardView), + AvailableCaptures { captures: Vec, }, @@ -83,6 +85,7 @@ pub enum ClientMessage { TextMessage(String), } +#[derive(Clone)] pub struct Message where T: MessageLike, diff --git a/src/network/server.rs b/src/network/server.rs index cb5d942..38151e4 100644 --- a/src/network/server.rs +++ b/src/network/server.rs @@ -288,8 +288,6 @@ impl Server { }; identity.identity.name = fixed_name - - // TODO! Validate if the name isn't already in use and append something to it if it is } fn process_capture(&mut self, capture_path: CapturePath) { @@ -317,6 +315,23 @@ impl Server { 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; + + 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; + } + } + + /* === Helper methods === */ + async fn send_message( &mut self, addr: SocketAddr, @@ -338,18 +353,17 @@ impl Server { }; } - 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; - - 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; + async fn broadcast_message(&mut self, msg: Result, postcard::Error>) { + match msg { + Ok(msg) => { + for identity in self.connections.values_mut() { + identity.sender.send(msg.clone()).await; + } + } + Err(e) => { + error!("Error while broadcasting the message: {}", e); + return; + } } } From a8a6e11350bd8f63cf5abfe7c3e1e1b8f0284d54 Mon Sep 17 00:00:00 2001 From: szymon Date: Mon, 17 Aug 2026 19:39:46 +0200 Subject: [PATCH 34/41] feat: add broadcasting current turn and board view --- src/bin/client.rs | 36 ++++++++++++++++++++++++++------- src/logic/board/board_view.rs | 6 +++--- src/logic/game_master.rs | 4 ++++ src/network/client.rs | 16 ++++++++++++--- src/network/message.rs | 5 ++--- src/network/mod.rs | 2 +- src/network/server.rs | 38 +++++++++++++++++++++++++++++++++-- 7 files changed, 88 insertions(+), 19 deletions(-) diff --git a/src/bin/client.rs b/src/bin/client.rs index 1ebdc0b..448fb69 100644 --- a/src/bin/client.rs +++ b/src/bin/client.rs @@ -3,9 +3,13 @@ use std::{ io::{self, Write}, }; -use checkers::network::client::Client; +use checkers::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"; @@ -58,6 +62,9 @@ async fn main() -> Result<(), Box> { let (stdin_sender, mut stdin_receiver) = mpsc::channel(1024); + let mut indentity: Option = None; + let mut is_my_turn: bool = false; + tokio::task::spawn_blocking(move || { let stdin = std::io::stdin(); @@ -87,7 +94,7 @@ async fn main() -> Result<(), Box> { tokio::select! { msg = client_receiver.recv() => { match msg { - Some(checkers::network::client::ClientData::GameStart { + Some(ClientData::GameStart { identity, board_view, }) => { @@ -95,20 +102,35 @@ async fn main() -> Result<(), Box> { "Got new identity: {} and board_view. Game is about to start!", identity ); - println!("{}", board_view.to_string(identity.id)); + println!("{}", board_view.to_string(&identity.id)); + indentity = Some(identity); } - Some(checkers::network::client::ClientData::AvailableCaptures(capture_paths)) => { + Some(ClientData::BoardView(view)) => { + info!("New board state:\n{}", view.to_string(&indentity.as_ref().unwrap().id)); + + } + + Some(ClientData::CurrentTurn(current_turn)) => { + is_my_turn = current_turn == indentity.as_ref().expect("Server sent us new current turn without telling us who we are!").id; + if is_my_turn { + info!("It's our turn!"); + } else { + info!("It's the turn of our enemy!"); + } + } + + Some(ClientData::AvailableCaptures(capture_paths)) => { todo!() } - Some(checkers::network::client::ClientData::AvailableMoves(move_paths)) => todo!(), + Some(ClientData::AvailableMoves(move_paths)) => todo!(), - Some(checkers::network::client::ClientData::TextMessage(content)) => { + Some(ClientData::TextMessage(content)) => { info!("Got message from the server: {}", content); } - Some(checkers::network::client::ClientData::GameEnd(game_result)) => { + Some(ClientData::GameEnd(game_result)) => { info!("Game has ended with the result: {:?}", game_result); } diff --git a/src/logic/board/board_view.rs b/src/logic/board/board_view.rs index 86a092f..8e0e14b 100644 --- a/src/logic/board/board_view.rs +++ b/src/logic/board/board_view.rs @@ -32,21 +32,21 @@ impl Index for BoardView { } impl BoardView { - pub fn to_string(&self, owner: Uuid) -> String { + 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 { + if pawn.owner == *owner { 'm' } else { 'M' } } super::pawn::PawnState::Dame => { - if pawn.owner == owner { + if pawn.owner == *owner { 'd' } else { 'D' diff --git a/src/logic/game_master.rs b/src/logic/game_master.rs index e2c07b1..bbcd997 100644 --- a/src/logic/game_master.rs +++ b/src/logic/game_master.rs @@ -74,6 +74,10 @@ impl GameMaster { 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 { self.board .get_player_pawns_positions(self.players.get_current_turn()) diff --git a/src/network/client.rs b/src/network/client.rs index b674a8c..361d597 100644 --- a/src/network/client.rs +++ b/src/network/client.rs @@ -2,6 +2,7 @@ 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::{ @@ -42,10 +43,15 @@ pub enum ClientData { identity: NetworkIdentity, board_view: BoardView, }, + GameEnd(GameResult), + AvailableCaptures(Vec), AvailableMoves(Vec), + + BoardView(BoardView), + CurrentTurn(Uuid), + TextMessage(String), - GameEnd(GameResult), } pub struct Client { @@ -169,8 +175,12 @@ impl Client { ServerMessage::AvailableCaptures { captures } => todo!(), ServerMessage::AvailableMoves { moves } => todo!(), - ServerMessage::BroadcastBoardState { board } => todo!(), - ServerMessage::BroadcastCurrentTurn { active_player } => todo!(), + ServerMessage::BroadcastBoardState { board } => { + let _ = update_sender.send(ClientData::BoardView(board)).await; + }, + ServerMessage::BroadcastCurrentTurn { active_player } => { + let _ = update_sender.send(ClientData::CurrentTurn(active_player)).await; + }, ServerMessage::GameEnd { result } => todo!(), } } else { diff --git a/src/network/message.rs b/src/network/message.rs index 4a76441..ff9dbf9 100644 --- a/src/network/message.rs +++ b/src/network/message.rs @@ -51,8 +51,6 @@ pub enum ServerMessage { board_view: BoardView, }, - BoardState(BoardView), - AvailableCaptures { captures: Vec, }, @@ -61,8 +59,9 @@ pub enum ServerMessage { }, BroadcastBoardState { - board: Board, + board: BoardView, }, + BroadcastCurrentTurn { active_player: Uuid, }, diff --git a/src/network/mod.rs b/src/network/mod.rs index 3c74f01..47fa7b1 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -1,5 +1,5 @@ pub mod client; pub mod connection; pub mod message; -mod network_identity; +pub mod network_identity; pub mod server; diff --git a/src/network/server.rs b/src/network/server.rs index 38151e4..4c31a09 100644 --- a/src/network/server.rs +++ b/src/network/server.rs @@ -257,6 +257,7 @@ impl Server { } self.game_master = Some(game_master); + self.on_player_action().await; } /* ======= Processing messages :D helper functions ======== */ @@ -290,20 +291,22 @@ impl Server { identity.identity.name = fixed_name } - fn process_capture(&mut self, capture_path: CapturePath) { + 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!"); } } } - fn process_move(&mut self, from: Position, to: Position) { + 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!"); } @@ -330,6 +333,37 @@ impl Server { } } + /* === Sending messages === */ + + /// Wrapper around broadcast_current_turn and broadcast_board_view. Should be used after every + /// players move + async fn on_player_action(&mut self) { + self.broadcast_board_view().await; + self.broadcast_current_turn().await; + } + + 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!!!!!!!!!"); + } + } + /* === Helper methods === */ async fn send_message( From 8ee7242726d050a5cf430c5dd21efbed4f47ee18 Mon Sep 17 00:00:00 2001 From: szymon Date: Mon, 17 Aug 2026 19:44:09 +0200 Subject: [PATCH 35/41] fix: remove double sending board state --- src/bin/client.rs | 4 +--- src/network/client.rs | 8 ++------ src/network/message.rs | 29 +++++++---------------------- src/network/server.rs | 8 +++----- 4 files changed, 13 insertions(+), 36 deletions(-) diff --git a/src/bin/client.rs b/src/bin/client.rs index 448fb69..d223c01 100644 --- a/src/bin/client.rs +++ b/src/bin/client.rs @@ -96,13 +96,11 @@ async fn main() -> Result<(), Box> { match msg { Some(ClientData::GameStart { identity, - board_view, }) => { info!( - "Got new identity: {} and board_view. Game is about to start!", + "Got new identity: {}", identity ); - println!("{}", board_view.to_string(&identity.id)); indentity = Some(identity); } diff --git a/src/network/client.rs b/src/network/client.rs index 361d597..1abc5f4 100644 --- a/src/network/client.rs +++ b/src/network/client.rs @@ -39,10 +39,7 @@ pub enum ClientCommands { /// 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, - board_view: BoardView, - }, + GameStart { identity: NetworkIdentity }, GameEnd(GameResult), AvailableCaptures(Vec), @@ -167,10 +164,9 @@ impl Client { ServerMessage::GameStart { identity, - board_view, } => { debug!("Got GameStart message! Identity: {}", identity); - let _ = update_sender.send(ClientData::GameStart { identity, board_view }).await; + let _ = update_sender.send(ClientData::GameStart { identity }).await; } ServerMessage::AvailableCaptures { captures } => todo!(), diff --git a/src/network/message.rs b/src/network/message.rs index ff9dbf9..bd581d0 100644 --- a/src/network/message.rs +++ b/src/network/message.rs @@ -42,33 +42,18 @@ where pub enum ServerMessage { RequestHandshake, AcceptHandshake, - DeclineHandshake { - reason: String, - }, + DeclineHandshake { reason: String }, - GameStart { - identity: NetworkIdentity, - board_view: BoardView, - }, + GameStart { identity: NetworkIdentity }, - AvailableCaptures { - captures: Vec, - }, - AvailableMoves { - moves: Vec, - }, + AvailableCaptures { captures: Vec }, + AvailableMoves { moves: Vec }, - BroadcastBoardState { - board: BoardView, - }, + BroadcastBoardState { board: BoardView }, - BroadcastCurrentTurn { - active_player: Uuid, - }, + BroadcastCurrentTurn { active_player: Uuid }, - GameEnd { - result: GameResult, - }, + GameEnd { result: GameResult }, } #[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)] diff --git a/src/network/server.rs b/src/network/server.rs index 4c31a09..49d620d 100644 --- a/src/network/server.rs +++ b/src/network/server.rs @@ -195,11 +195,11 @@ impl Server { }, ClientMessage::RequestCapture { capture_path } => { - self.process_capture(capture_path); + self.process_capture(capture_path).await; } ClientMessage::RequestMove { from, to } => { - self.process_move(from, to); + self.process_move(from, to).await; } ClientMessage::TextMessage (content) => { @@ -232,7 +232,6 @@ impl Server { let game_master = GameMaster::new(&names[0], &names[1]); let names_and_ids: Vec<(String, Uuid)> = game_master.get_players_names_and_ids(); - let board_view: BoardView = game_master.get_board_view(); let mut msgs: Vec<(SocketAddr, Result, postcard::Error>)> = vec![]; @@ -245,7 +244,6 @@ impl Server { *addr, Message::new(ServerMessage::GameStart { identity: networkidentity.identity.clone(), - board_view: board_view.clone(), }), )); } @@ -324,7 +322,7 @@ impl Server { "{} 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; + 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"); From 9b86e95701a56f602f1e0df3adcc5b078c5bb951 Mon Sep 17 00:00:00 2001 From: szymon Date: Mon, 17 Aug 2026 19:53:15 +0200 Subject: [PATCH 36/41] feat: add routing text messages --- src/bin/client.rs | 4 ++-- src/network/client.rs | 20 +++++++++++++------- src/network/message.rs | 3 ++- src/network/server.rs | 7 +++++++ 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/bin/client.rs b/src/bin/client.rs index d223c01..346b660 100644 --- a/src/bin/client.rs +++ b/src/bin/client.rs @@ -124,8 +124,8 @@ async fn main() -> Result<(), Box> { Some(ClientData::AvailableMoves(move_paths)) => todo!(), - Some(ClientData::TextMessage(content)) => { - info!("Got message from the server: {}", content); + Some(ClientData::TextMessage {sender, content}) => { + info!("[{}] - {}", sender, content); } Some(ClientData::GameEnd(game_result)) => { diff --git a/src/network/client.rs b/src/network/client.rs index 1abc5f4..0e434bb 100644 --- a/src/network/client.rs +++ b/src/network/client.rs @@ -48,7 +48,7 @@ pub enum ClientData { BoardView(BoardView), CurrentTurn(Uuid), - TextMessage(String), + TextMessage { sender: String, content: String }, } pub struct Client { @@ -66,7 +66,7 @@ impl Client { pub async fn new() -> Option { let settings = ClientSettings { server_url: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 6767), - name: "Szymon".into(), + name: "Morbius".into(), }; let (conn_sender, conn_reciever) = @@ -169,14 +169,20 @@ impl Client { let _ = update_sender.send(ClientData::GameStart { identity }).await; } - ServerMessage::AvailableCaptures { captures } => todo!(), - ServerMessage::AvailableMoves { moves } => todo!(), - ServerMessage::BroadcastBoardState { board } => { - let _ = update_sender.send(ClientData::BoardView(board)).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 } => todo!(), + ServerMessage::AvailableMoves { moves } => todo!(), ServerMessage::GameEnd { result } => todo!(), } } else { diff --git a/src/network/message.rs b/src/network/message.rs index bd581d0..61e6f12 100644 --- a/src/network/message.rs +++ b/src/network/message.rs @@ -50,9 +50,10 @@ pub enum ServerMessage { AvailableMoves { moves: Vec }, BroadcastBoardState { board: BoardView }, - BroadcastCurrentTurn { active_player: Uuid }, + BroadCastTextMessage { sender: String, content: String }, + GameEnd { result: GameResult }, } diff --git a/src/network/server.rs b/src/network/server.rs index 49d620d..8370699 100644 --- a/src/network/server.rs +++ b/src/network/server.rs @@ -204,6 +204,8 @@ impl Server { 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 { @@ -362,6 +364,11 @@ impl Server { } } + 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( From bb2599a3cabb1072cdf8465a5a7e2c60ece45a81 Mon Sep 17 00:00:00 2001 From: szymon Date: Mon, 17 Aug 2026 21:47:03 +0200 Subject: [PATCH 37/41] feat: add sending and receiving moves and captures --- src/bin/client.rs | 6 +++- src/network/client.rs | 26 +++++++++++++--- src/network/server.rs | 72 ++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 94 insertions(+), 10 deletions(-) diff --git a/src/bin/client.rs b/src/bin/client.rs index 346b660..83bed8c 100644 --- a/src/bin/client.rs +++ b/src/bin/client.rs @@ -119,10 +119,14 @@ async fn main() -> Result<(), Box> { } Some(ClientData::AvailableCaptures(capture_paths)) => { + info!("Available captures paths: {:?}", capture_paths); todo!() } - Some(ClientData::AvailableMoves(move_paths)) => todo!(), + Some(ClientData::AvailableMoves(move_paths)) => { + info!("Available moves: {:?}", move_paths); + todo!(); + }, Some(ClientData::TextMessage {sender, content}) => { info!("[{}] - {}", sender, content); diff --git a/src/network/client.rs b/src/network/client.rs index 0e434bb..92a0473 100644 --- a/src/network/client.rs +++ b/src/network/client.rs @@ -181,9 +181,17 @@ impl Client { } - ServerMessage::AvailableCaptures { captures } => todo!(), - ServerMessage::AvailableMoves { moves } => todo!(), - ServerMessage::GameEnd { result } => todo!(), + 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!"); @@ -194,9 +202,17 @@ impl Client { oldest_cmd = commands_receiver.recv() => { if let Some(oldest_cmd) = oldest_cmd { match oldest_cmd { - ClientCommands::SendCapture(capture_path) => todo!(), + 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 } => todo!(), + 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); diff --git a/src/network/server.rs b/src/network/server.rs index 8370699..81dea81 100644 --- a/src/network/server.rs +++ b/src/network/server.rs @@ -12,7 +12,7 @@ use uuid::Uuid; use crate::{ logic::{ board::{board_view::BoardView, pawn::CapturePath}, - game_master::GameMaster, + game_master::{GameMaster, GameResult}, math::position::Position, }, network::{ @@ -335,11 +335,68 @@ impl Server { /* === Sending messages === */ - /// Wrapper around broadcast_current_turn and broadcast_board_view. Should be used after every - /// players move + /// Should be used after every players move async fn on_player_action(&mut self) { 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; + } + + 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) { + 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, + ) { + 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) { @@ -396,7 +453,7 @@ impl Server { match msg { Ok(msg) => { for identity in self.connections.values_mut() { - identity.sender.send(msg.clone()).await; + let _ = identity.sender.send(msg.clone()).await; } } Err(e) => { @@ -406,6 +463,13 @@ impl Server { } } + 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; From e2a37d8a91764f6bef8597d9324e45142fa23102 Mon Sep 17 00:00:00 2001 From: szymon Date: Mon, 17 Aug 2026 23:23:45 +0200 Subject: [PATCH 38/41] feat: add game loop sending and receiving moves from server, client implementation of this in a simple cli --- src/bin/client.rs | 142 +++++++++++++++++++++++++++++++++++++----- src/network/client.rs | 18 ++++++ 2 files changed, 146 insertions(+), 14 deletions(-) diff --git a/src/bin/client.rs b/src/bin/client.rs index 83bed8c..90d9eec 100644 --- a/src/bin/client.rs +++ b/src/bin/client.rs @@ -3,9 +3,12 @@ use std::{ io::{self, Write}, }; -use checkers::network::{ - client::{Client, ClientData}, - network_identity::NetworkIdentity, +use checkers::{ + logic::board::pawn::{CapturePath, MovePath}, + network::{ + client::{Client, ClientData}, + network_identity::NetworkIdentity, + }, }; use log::{debug, error, info, warn}; use tokio::sync::mpsc; @@ -16,11 +19,17 @@ 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)] @@ -41,11 +50,34 @@ impl TryFrom<&str> for CliCommands { 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, + pub is_my_turn: bool, + + pub available_captures: Option>, + pub available_moves: Option>, +} + +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> { env_logger::init(); @@ -62,8 +94,7 @@ async fn main() -> Result<(), Box> { let (stdin_sender, mut stdin_receiver) = mpsc::channel(1024); - let mut indentity: Option = None; - let mut is_my_turn: bool = false; + let mut context: ClientContext = ClientContext::default(); tokio::task::spawn_blocking(move || { let stdin = std::io::stdin(); @@ -84,9 +115,12 @@ async fn main() -> Result<(), Box> { │\t {} - show this message │\t {} - message the server you're ready │\t {} - send text message to the server +│ +│\t {} - choose a capture path from the list +│\t {} - 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, + QUIT_COMMAND, HELP_COMMAND, READY_COMMAND, SEND_COMMAND, CAPTURE_COMMAND, MOVE_COMMAND ); info!("───────Type /help for help───────"); @@ -101,17 +135,17 @@ async fn main() -> Result<(), Box> { "Got new identity: {}", identity ); - indentity = Some(identity); + context.identity = Some(identity); } Some(ClientData::BoardView(view)) => { - info!("New board state:\n{}", view.to_string(&indentity.as_ref().unwrap().id)); + info!("New board state:\n{}", view.to_string(&context.identity.as_ref().unwrap().id)); } Some(ClientData::CurrentTurn(current_turn)) => { - is_my_turn = current_turn == indentity.as_ref().expect("Server sent us new current turn without telling us who we are!").id; - if is_my_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!"); @@ -120,12 +154,14 @@ async fn main() -> Result<(), Box> { Some(ClientData::AvailableCaptures(capture_paths)) => { info!("Available captures paths: {:?}", capture_paths); - todo!() + context.available_moves = None; + context.available_captures = Some(capture_paths); } Some(ClientData::AvailableMoves(move_paths)) => { info!("Available moves: {:?}", move_paths); - todo!(); + context.available_moves = Some(move_paths); + context.available_captures = None; }, Some(ClientData::TextMessage {sender, content}) => { @@ -150,8 +186,6 @@ async fn main() -> Result<(), Box> { if !buffer.is_empty() { let words: Vec<&str> = buffer.split_whitespace().collect(); let cmd = words[0]; - let mut args: String = words[1..].join(" "); - args = args.trim().to_string(); match CliCommands::try_from(cmd) { Ok(cmd) => match cmd { @@ -166,8 +200,88 @@ async fn main() -> Result<(), Box> { 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::() { + 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 , 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::() { + 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::() { + 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 , 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 , 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), } diff --git a/src/network/client.rs b/src/network/client.rs index 92a0473..0ef12c5 100644 --- a/src/network/client.rs +++ b/src/network/client.rs @@ -237,6 +237,24 @@ impl Client { }); } + 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; From f37d91cf8fb183be750a8e1605787c34bd47cbe9 Mon Sep 17 00:00:00 2001 From: szymon Date: Mon, 17 Aug 2026 23:34:54 +0200 Subject: [PATCH 39/41] fix: server crashing on game end --- src/network/server.rs | 55 +++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/src/network/server.rs b/src/network/server.rs index 81dea81..d9e8137 100644 --- a/src/network/server.rs +++ b/src/network/server.rs @@ -337,36 +337,39 @@ impl Server { /// Should be used after every players move async fn on_player_action(&mut self) { - self.broadcast_board_view().await; - self.broadcast_current_turn().await; + 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 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(); + 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; + 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"); } - - 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) { From 2aa15b2bcc68d73c6eb9cadf8dc1dbea8060c1c9 Mon Sep 17 00:00:00 2001 From: szymon Date: Mon, 17 Aug 2026 23:46:10 +0200 Subject: [PATCH 40/41] chore: update anyhow version --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3279ac2..9be20c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,9 +63,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "atomic-polyfill" From 6bab636f918b28eae2bd917f03e2cf24a1a59f29 Mon Sep 17 00:00:00 2001 From: szymon Date: Mon, 17 Aug 2026 23:50:35 +0200 Subject: [PATCH 41/41] docs: update readme with some barebones network info --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 7c2be72..0f64eb4 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,16 @@ 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?