From e599722e45b3d36fb935a4748066985f6fae736c Mon Sep 17 00:00:00 2001 From: htom Date: Sat, 15 Nov 2025 14:44:44 +0100 Subject: [PATCH] added client for testing connection and events --- server/Cargo.toml | 4 + server/src/bin/client.rs | 208 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 server/src/bin/client.rs diff --git a/server/Cargo.toml b/server/Cargo.toml index c631dd0..c0086f4 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -14,3 +14,7 @@ url = "2.5.7" uuid = {version = "1.18.1", features = ["v4", "serde"] } anyhow = "1.0.100" rand = "0.9.2" + +[[bin]] +name = "client" +path = "src/bin/client.rs" diff --git a/server/src/bin/client.rs b/server/src/bin/client.rs new file mode 100644 index 0000000..45e3c56 --- /dev/null +++ b/server/src/bin/client.rs @@ -0,0 +1,208 @@ +use futures_util::{SinkExt, StreamExt}; +use serde::{Deserialize, Serialize}; +use std::io::{self, Write}; +use tokio_tungstenite::{connect_async, tungstenite::Message}; +use url::Url; + +#[derive(Serialize, Deserialize, Debug)] +#[serde(tag = "type")] +enum ClientMessage { + Join { username: String }, + FindMatch, + Move { from: String, to: String }, + Resign, + Chat { text: String }, +} + +#[derive(Serialize, Deserialize, Debug)] +struct ServerMessage { + #[serde(rename = "type")] + message_type: String, + player_id: Option, + match_id: Option, + opponent: Option, + color: Option, + reason: Option, +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + println!("Knightly Chess Client"); + println!("========================"); + + // Get server address from user + print!("Enter server address [ws://127.0.0.1:9001]: "); + io::stdout().flush()?; + let mut server_addr = String::new(); + io::stdin().read_line(&mut server_addr)?; + let server_addr = server_addr.trim(); + let server_addr = if server_addr.is_empty() { + "ws://127.0.0.1:9001".to_string() + } else { + server_addr.to_string() + }; + + // Connect to server + println!("Connecting to {}...", server_addr); + let url = Url::parse(&server_addr)?; + let (ws_stream, _) = connect_async(url).await?; + println!("Connected to server!"); + + let (mut write, mut read) = ws_stream.split(); + + // Spawn a task to handle incoming messages + let read_handle = tokio::spawn(async move { + while let Some(message) = read.next().await { + match message { + Ok(msg) => { + if msg.is_text() { + let text = msg.to_text().unwrap(); + println!("\nServer: {}", text); + + // Try to parse as structured message + if let Ok(parsed) = serde_json::from_str::(text) { + match parsed.message_type.as_str() { + "welcome" => { + if let Some(player_id) = parsed.player_id { + println!("Welcome! Your player ID: {}", player_id); + } + } + "match_found" => { + if let (Some(opponent), Some(color), Some(match_id)) = + (parsed.opponent, parsed.color, parsed.match_id) + { + println!( + "Match found! Opponent: {}, Color: {}, Match ID: {}", + opponent, color, match_id + ); + } + } + "error" => { + if let Some(reason) = parsed.reason { + println!("Error: {}", reason); + } + } + _ => {} + } + } + } + } + Err(e) => { + eprintln!("Error receiving message: {}", e); + break; + } + } + } + }); + + // Main loop for sending messages + println!("\nAvailable commands:"); + println!(" join - Join the server"); + println!(" findmatch - Find a match"); + println!(" move - Make a move (e.g., move e2 e4)"); + println!(" chat - Send chat message"); + println!(" resign - Resign from current game"); + println!(" quit - Exit client"); + println!(); + + loop { + print!("āž”ļø Enter command: "); + io::stdout().flush()?; + + let mut input = String::new(); + io::stdin().read_line(&mut input)?; + let input = input.trim(); + + if input.is_empty() { + continue; + } + + let parts: Vec<&str> = input.split_whitespace().collect(); + let command = parts[0].to_lowercase(); + + match command.as_str() { + "quit" | "exit" => { + println!("šŸ‘‹ Goodbye!"); + break; + } + "join" => { + if parts.len() >= 2 { + let username = parts[1..].join(" "); + let message = ClientMessage::Join { username }; + send_message(&mut write, &message).await?; + } else { + println!("Usage: join "); + } + } + "findmatch" | "find" => { + let message = ClientMessage::FindMatch; + send_message(&mut write, &message).await?; + println!("šŸ” Searching for a match..."); + } + "move" => { + if parts.len() >= 3 { + let from = parts[1].to_string(); + let to = parts[2].to_string(); + let message = ClientMessage::Move { from, to }; + send_message(&mut write, &message).await?; + println!("ā™Ÿļø Sent move: {} -> {}", parts[1], parts[2]); + } else { + println!("Usage: move (e.g., move e2 e4)"); + } + } + "chat" => { + if parts.len() >= 2 { + let text = parts[1..].join(" "); + let message = ClientMessage::Chat { text }; + send_message(&mut write, &message).await?; + } else { + println!("Usage: chat "); + } + } + "resign" => { + let message = ClientMessage::Resign; + send_message(&mut write, &message).await?; + println!("Resigned from current game"); + } + "help" => { + print_help(); + } + _ => { + println!( + "Unknown command: {}. Type 'help' for available commands.", + command + ); + } + } + } + + // Cleanup + read_handle.abort(); + Ok(()) +} + +async fn send_message( + write: &mut futures_util::stream::SplitSink< + tokio_tungstenite::WebSocketStream< + tokio_tungstenite::MaybeTlsStream, + >, + Message, + >, + message: &ClientMessage, +) -> Result<(), Box> { + let json = serde_json::to_string(message)?; + write.send(Message::Text(json)).await?; + Ok(()) +} + +fn print_help() { + println!("\nšŸ“– Available Commands:"); + println!(" join - Register with a username"); + println!(" findmatch - Enter matchmaking queue"); + println!(" move - Make a chess move"); + println!(" chat - Send chat to opponent"); + println!(" resign - Resign from current game"); + println!(" help - Show this help"); + println!(" quit - Exit the client"); + println!(); +}