From 7f2ec8e957d549a23f96ad52b6807e312215e2b3 Mon Sep 17 00:00:00 2001 From: Artlef Date: Sat, 19 Sep 2020 00:17:35 +0200 Subject: [PATCH] Refactor messages between server and client --- Cargo.lock | 44 +----- Cargo.toml | 3 +- src/bin/client.rs | 305 ++++++++++++++++++++----------------- src/bin/server.rs | 381 ++++++++++++++++++++++++---------------------- src/lib.rs | 25 ++- 5 files changed, 387 insertions(+), 371 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index daf159a..2a62228 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,22 +32,11 @@ dependencies = [ "num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "cc" -version = "1.0.50" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "clichess" -version = "0.2.0" +version = "0.3.0" dependencies = [ "colored 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", "shakmaty 0.16.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -63,15 +52,6 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "ctrlc" -version = "3.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "itoa" version = "0.4.4" @@ -87,18 +67,6 @@ name = "libc" version = "0.2.66" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "nix" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "num-traits" version = "0.2.10" @@ -181,11 +149,6 @@ name = "unicode-xid" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "void" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "winapi" version = "0.3.8" @@ -211,14 +174,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" "checksum btoi 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e4ced8205e70d9e553d008d53ded735808fa6133597318d48f74fc2bf9861471" -"checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" -"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" "checksum colored 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "433e7ac7d511768127ed85b0c4947f47a254131e37864b2dc13f52aa32cd37e5" -"checksum ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7dfd2d8b4c82121dfdff120f818e09fc4380b0b7e17a742081a89b94853e87f" "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" -"checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce" "checksum num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4" "checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27" "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" @@ -229,7 +188,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum shakmaty 0.16.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a8f8f7fd48325e0b530cd257df0aa735efeefc923c70133d412f05d69de69108" "checksum syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "dff0acdb207ae2fe6d5976617f887eb1e35a2ba52c13c7234c790960cdad9238" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" -"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 59f3189..ee2c995 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clichess" -version = "0.2.0" +version = "0.3.0" authors = ["Artlef "] edition = "2018" @@ -11,4 +11,3 @@ shakmaty = "0.16.2" colored = "1.9" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -ctrlc = "3.1.3" diff --git a/src/bin/client.rs b/src/bin/client.rs index 0b2a43c..b803f59 100644 --- a/src/bin/client.rs +++ b/src/bin/client.rs @@ -1,19 +1,15 @@ -extern crate ctrlc; -use clichess::{GameInfo, RecvPositionError, UserRole, EXIT_MSG}; -use serde_json::json; -use shakmaty::fen::Fen; -use shakmaty::{Chess, Color, Move, Outcome, Position, Setup}; +use clichess::{ClientRequest, GameInfo, RecvPositionError, UserRole, EXIT_MSG}; +use shakmaty::{Chess, Color, Outcome, Position, Setup}; use std::io; use std::os::unix::net::UnixStream; use std::sync::mpsc::{channel, Receiver, TryRecvError}; -use std::sync::{Arc, Mutex}; use std::thread; use std::time; struct Client { player: clichess::Player, side: Color, - input_buffer: Arc>, + keyboard_input_recv: Receiver, server_message_recv: Receiver, opponent_name: Option, last_move: Option, @@ -32,12 +28,7 @@ fn main() { return; } }; - let username_pubkey_json = json!({ - "username": username, - "pubkey": public_key, - }); - clichess::write_to_stream(&mut stream, username_pubkey_json.to_string()).unwrap(); - let input_buffer = setup_input_buffer(); + let keyboard_input_recv = setup_keyboard_input_recv(); //start a thread to listen to server messages let server_message_recv = setup_server_message_recv(&stream).unwrap(); @@ -48,16 +39,17 @@ fn main() { public_key, }, side: Color::White, - input_buffer: input_buffer.clone(), + keyboard_input_recv, server_message_recv, opponent_name: Option::None, last_move: Option::None, }; - match prompt_user_for_role(&client, &mut stream) { - Some(role) => client.player.role = role.clone(), - None => return, - }; + let login_result = login(&mut client, &mut stream); + if login_result == LoginResult::UserExited { + send_request(&client, &mut stream, ClientRequest::Exit); + return; + } if client.player.role == UserRole::Black { client.side = Color::Black; } @@ -73,15 +65,14 @@ fn main() { client.player.role.to_string() ); } - let mut current_position = fetch_initial_chess_position(&client); + let mut current_position = fetch_initial_chess_position(&client, &mut stream); loop { - if clichess::is_player_turn(&client.player, current_position.turn()) - || client.player.role == UserRole::Spectator - { + let is_player_turn = clichess::is_player_turn(&client.player, current_position.turn()); + if is_player_turn || client.player.role == UserRole::Spectator { client .opponent_name .clone() - .map(|name| print!("{} played", &name[1..(name.len() - 1)])); + .map(|name| print!("{} played", name)); client .last_move .clone() @@ -95,24 +86,30 @@ fn main() { if current_position.is_game_over() { break; } - if clichess::is_player_turn(&client.player, current_position.turn()) { + if is_player_turn { //it's the user turn, taking user input - let mut input = read_user_input(&client); + let mut input = client.keyboard_input_recv.recv().unwrap(); input = String::from(input.trim()); - clichess::write_to_stream(&mut stream, input.clone()).unwrap(); if input == EXIT_MSG { break; } - client.last_move = Some(input); - } - //update position after playing. - match get_current_position(&mut client) { - Ok(position) => current_position = position, - Err(_) => { - clichess::write_to_stream(&mut stream, String::from(EXIT_MSG)).unwrap(); - break; + let response = send_request(&client, &mut stream, ClientRequest::Play(input.clone())); + if response == String::from("KO") { + println!("Invalid move: {}", input); + //go back to taking user input + continue; } - }; + client.last_move = Some(input); + match get_current_position(&mut client, &mut stream) { + Ok(position) => current_position = position, + Err(_) => break, + }; + } else { + match wait_for_next_move(&mut client, &mut stream) { + Ok(position) => current_position = position, + Err(_) => break, + }; + } } match current_position.outcome() { None => println!("Bye"), @@ -125,28 +122,65 @@ fn main() { } } } + send_request(&client, &mut stream, ClientRequest::Exit); } -fn setup_input_buffer() -> Arc> { - let buf = Arc::new(Mutex::new(String::new())); - let buf2 = buf.clone(); +#[derive(PartialEq)] +enum LoginResult { + Success, + UserExited, +} +fn login(client: &mut Client, stream: &mut UnixStream) -> LoginResult { + let mut response = String::from("KO"); + while &response != "OK" { + match prompt_user_for_role(client, stream) { + PromptedRoleResponse::Role(role) => { + client.player.role = role.clone(); + response = send_request( + client, + stream, + ClientRequest::Login { + username: client.player.username.clone(), + pubkey: client.player.public_key.clone(), + role, + }, + ); + } + PromptedRoleResponse::Exit => return LoginResult::UserExited, + PromptedRoleResponse::Retry => continue, + }; + } + LoginResult::Success +} + +fn send_request(client: &Client, stream: &mut UnixStream, request: ClientRequest) -> String { + let response: String; + match serde_json::to_string(&request) { + Ok(request_str) => { + clichess::write_to_stream(stream, request_str).unwrap(); + response = fetch_message_from_server(client); + } + + Err(e) => { + println!("Error when parsing client request: {}", e); + response = String::from("KO"); + } + }; + response +} + +fn setup_keyboard_input_recv() -> Receiver { + let (sender, receiver) = channel(); thread::spawn(move || { loop { let mut buffer = String::new(); //wait for user input io::stdin().read_line(&mut buffer).unwrap(); - { - let mut user_input = buf2.lock().unwrap(); - if user_input.is_empty() { - *user_input = buffer; - } else { - println!("It's not your turn !"); - } - } + sender.send(buffer).unwrap(); } }); - buf + receiver } fn setup_server_message_recv(stream: &UnixStream) -> io::Result> { @@ -164,42 +198,57 @@ fn setup_server_message_recv(stream: &UnixStream) -> io::Result Ok(receiver) } -fn read_user_input(client: &Client) -> String { - //clear input before waiting for a new one - { - let mut user_input = client.input_buffer.lock().unwrap(); - user_input.clear(); +//get current position from server +fn get_current_position( + client: &mut Client, + stream: &mut UnixStream, +) -> Result { + let response = send_request(client, stream, ClientRequest::GetGameInfo); + if response.is_empty() { + Err(RecvPositionError::UserCanceledError) + } else { + let game_info: GameInfo = serde_json::from_str(&response).unwrap(); + update_client(&game_info, client); + Ok(clichess::parse_position(&game_info.game_fen)) } - let input; +} + +fn wait_for_next_move( + client: &mut Client, + stream: &mut UnixStream, +) -> Result { + println!("Waiting for opponent move..."); + send_request(client, stream, ClientRequest::WaitForNextMove); + let response; loop { thread::sleep(time::Duration::from_millis(10)); { - let user_input = client.input_buffer.lock().unwrap(); - if user_input.is_empty() { - continue; - } else { - input = user_input.clone(); - break; + let server_response_recv = &client.server_message_recv; + match server_response_recv.try_recv() { + Ok(msg) => { + response = msg; + break; + } + Err(TryRecvError::Disconnected) => println!("Error: server disconnected."), + Err(TryRecvError::Empty) => continue, } } } - input -} - -//wait for next position from server, then return the current board. -fn get_current_position(client: &mut Client) -> Result { - let response = fetch_message_from_server(client); - let game_info: GameInfo = serde_json::from_str(&response).unwrap(); - if game_info.opponent_name != "" { - client.opponent_name = Some(game_info.opponent_name); - } - if game_info.last_move != "" { - client.last_move = Some(game_info.last_move); - } if response.is_empty() { Err(RecvPositionError::UserCanceledError) } else { - Ok(parse_position(&game_info.game_fen)) + let game_info: GameInfo = serde_json::from_str(&response).unwrap(); + update_client(&game_info, client); + Ok(clichess::parse_position(&game_info.game_fen)) + } +} + +fn update_client(game_info: &GameInfo, client: &mut Client) { + if game_info.opponent_name != "" { + client.opponent_name = Some(game_info.opponent_name.clone()); + } + if game_info.last_move != "" { + client.last_move = Some(game_info.last_move.clone()); } } @@ -222,78 +271,56 @@ fn fetch_message_from_server(client: &Client) -> String { response } -fn parse_position(string: &str) -> Chess { - let setup: Fen = string.trim().parse().expect("Invalid message from server."); - let position: Chess = setup.position().expect("Invalid message from server."); - position +enum PromptedRoleResponse { + Role(UserRole), + Exit, + Retry, } -fn prompt_user_for_role(client: &Client, stream: &mut UnixStream) -> Option { - let mut role = None; - loop { - let available_roles = fetch_available_roles(client); - let mut prompt = String::new(); - if !available_roles.contains(&UserRole::White) - && !available_roles.contains(&UserRole::Black) - { - prompt.push_str("You can only spectate this game. Press enter to start spectating."); - } else if available_roles.contains(&UserRole::White) { - prompt = String::from("Do you want to play as White (W)"); - if available_roles.contains(&UserRole::Black) { - prompt.push_str(", Black (B)"); - } - prompt.push_str(" or Spectate (S)?"); - } else { - prompt = String::from("Do you want to play as Black (B) or Spectate (S)?"); +fn prompt_user_for_role(client: &Client, stream: &mut UnixStream) -> PromptedRoleResponse { + let response = send_request(client, stream, ClientRequest::FetchAvailableRoles); + let available_roles = roles_from_str(&response).unwrap(); + let mut prompt = String::new(); + if !available_roles.contains(&UserRole::White) && !available_roles.contains(&UserRole::Black) { + prompt.push_str("You can only spectate this game. Press enter to start spectating."); + } else if available_roles.contains(&UserRole::White) { + prompt = String::from("Do you want to play as White (W)"); + if available_roles.contains(&UserRole::Black) { + prompt.push_str(", Black (B)"); } - println!("{}", prompt); - //wait for user to give a correct answer. - let mut input = String::from(read_user_input(client).trim()); - if input.trim() == EXIT_MSG { - clichess::write_to_stream(stream, String::from(EXIT_MSG)).unwrap(); - break; - } - - if !available_roles.contains(&UserRole::White) - && !available_roles.contains(&UserRole::Black) - { - //we can only spectate - input = String::from("S"); - } - match clichess::parse_to_role(&input) { - Ok(r) => role = Some(r), - Err(e) => { - println!("{}", e); - continue; - } - }; - if role.is_some() && !available_roles.contains(&role.expect("role is some")) { - println!("Sorry, this side is not available."); - continue; - } - //send info to server - clichess::write_to_stream(stream, String::from(input)).unwrap(); - //get confirmation from server - let response = fetch_message_from_server(&client); - if response != "OK" { - println!( - "There was an issue with the server. Maybe your choice is not available anymore?" - ); - clichess::write_to_stream(stream, String::from("ACK")).unwrap(); - continue; - } - clichess::write_to_stream(stream, String::from("OK")).unwrap(); - break; + prompt.push_str(" or Spectate (S)?"); + } else { + prompt = String::from("Do you want to play as Black (B) or Spectate (S)?"); } - role + println!("{}", prompt); + let mut input = String::from(client.keyboard_input_recv.recv().unwrap().trim()); + if input.trim() == EXIT_MSG { + return PromptedRoleResponse::Exit; + } + + if !available_roles.contains(&UserRole::White) && !available_roles.contains(&UserRole::Black) { + //we can only spectate + input = String::from("S"); + } + match clichess::parse_to_role(&input) { + Ok(r) => { + if !available_roles.contains(&r) { + println!("Sorry, this side is not available."); + return PromptedRoleResponse::Retry; + } + return PromptedRoleResponse::Role(r); + } + Err(e) => { + println!("{}", e); + return PromptedRoleResponse::Retry; + } + }; } -fn fetch_initial_chess_position(client: &Client) -> Chess { - parse_position(&fetch_message_from_server(client)) -} - -fn fetch_available_roles(client: &Client) -> Vec { - roles_from_str(&fetch_message_from_server(client)).unwrap() +fn fetch_initial_chess_position(client: &Client, stream: &mut UnixStream) -> Chess { + let response = send_request(client, stream, ClientRequest::GetGameInfo); + let game_info: GameInfo = serde_json::from_str(&response).unwrap(); + clichess::parse_position(&game_info.game_fen) } fn roles_from_str(s: &str) -> Result, String> { diff --git a/src/bin/server.rs b/src/bin/server.rs index 4dd0f45..7c81d07 100644 --- a/src/bin/server.rs +++ b/src/bin/server.rs @@ -1,7 +1,6 @@ use clichess; -use clichess::{GameInfo, Player, RecvPositionError, UserRole, EXIT_MSG}; -use serde_json::Value; -use shakmaty::{fen, Chess, Color, Setup}; +use clichess::{ClientRequest, GameInfo, Player, RecvPositionError, UserRole}; +use shakmaty::{fen, Chess, Setup}; use std::collections::HashMap; use std::fs; use std::io; @@ -18,8 +17,8 @@ struct Server { others_serv_msg_recv: Receiver, client_message_recv: Receiver, cvar: Arc<(Mutex, Condvar)>, - opponent_name: Option, - last_move: Option + opponent_name: Arc>>, + last_move: Arc>>, } fn main() { @@ -47,8 +46,8 @@ fn main() { others_serv_msg_recv, client_message_recv, cvar: condvar_pair, - opponent_name: Option::None, - last_move: Option::None + opponent_name: Arc::new(Mutex::new(Option::None)), + last_move: Arc::new(Mutex::new(Option::None)), }; /* connection succeeded */ thread::spawn(move || handle_player(stream, &mut server, others_serv_msg_sender)); @@ -67,108 +66,123 @@ fn handle_player( server: &mut Server, others_serv_msg_sender: Sender, ) { - match initialize_client(&mut stream, &server, others_serv_msg_sender) { - Ok((player, player_turn)) => main_loop(&mut stream, server, player, player_turn), - Err(e) => println!("User id {} could not be initialized: {}", server.id, e), - }; + loop { + //wait for input + let input = server.client_message_recv.recv().unwrap(); + println!("got input: {}", input); + //route to right function + match serde_json::from_str(&input) { + Ok(ClientRequest::FetchAvailableRoles) => fetch_available_roles(&mut stream, server), + Ok(ClientRequest::Login { + username, + pubkey, + role, + }) => login( + &mut stream, + server, + username, + pubkey, + role, + others_serv_msg_sender.clone(), + ), + Ok(ClientRequest::GetGameInfo) => send_game_info(&mut stream, server), + Ok(ClientRequest::WaitForNextMove) => { + //return true if user exited. + if wait_for_next_move(&mut stream, server) { + break; + } + } + Ok(ClientRequest::Play(movestr)) => play(&mut stream, server, movestr), + Ok(ClientRequest::Exit) => { + send_ok(&mut stream); + break; + } + Err(e) => { + println!("Bad request: {}", e); + break; + } + } + } player_disconnected(&server); } -fn initialize_client( +fn login( stream: &mut UnixStream, server: &Server, + username: String, + pubkey: String, + role: UserRole, others_serv_msg_sender: Sender, -) -> Result<(Player, Color), RecvPositionError> { - //create player - let player = create_player(server, stream, others_serv_msg_sender)?; - let player_turn: Color; - //send current position to the player - println!( - "server {}, send current position to the player...", - server.id - ); +) { + println!("server {}, player {} is logging in..", server.id, username); + //check_role + let mut available_roles_after_choice = + vec![UserRole::White, UserRole::Black, UserRole::Spectator]; { - let chess = server.chess_position.lock().unwrap(); - player_turn = chess.turn(); - clichess::write_to_stream(stream, fen::fen(&*chess)).unwrap(); - } - println!("server {}, current position to the player sent", server.id); - Ok((player, player_turn)) -} - -fn main_loop(stream: &mut UnixStream, server: &mut Server, player: Player, mut player_turn: Color) { - loop { - if clichess::is_player_turn(&player, player_turn) { - //let go of the lock while waiting for user input. - println!("server {}, waiting for player move..", server.id); - let input; - match server.client_message_recv.recv() { - Ok(i) => input = i, - Err(e) => { - println!("Error while getting user input: {}", e); - break; - } - }; - if input == EXIT_MSG { - break; - } + let players = server.players.lock().unwrap(); + for (_, (player, _, _)) in players.iter() { + match available_roles_after_choice + .iter() + .position(|r| *r == player.role) { - let mut chess = server.chess_position.lock().unwrap(); - let players = server.players.lock().unwrap(); - println!("User tried to play {}", input); - match clichess::try_to_play_move(&chess, input.clone()) { - Ok(played_chess) => *chess = played_chess, - Err(e) => println!("Error: {}", e), - }; - let game_info_to_send = GameInfo { - game_fen: fen::fen(&*chess), - opponent_name: players - .get(&server.id) - .expect("current server is in the server list") - .0 - .username - .clone(), - last_move: input.clone() - }; - for (id, (_, others_serv_msg_sender, cvar_pair)) in players.iter() { - if server.id != *id { - others_serv_msg_sender - .send(game_info_to_send.clone()) - .unwrap(); - let (lock, cvar) = &**cvar_pair; - let mut message_sent = lock.lock().unwrap(); - *message_sent = true; - cvar.notify_one(); - } - } - if clichess::write_to_stream( - stream, - serde_json::to_string(&GameInfo { - game_fen: fen::fen(&*chess), - opponent_name: server.opponent_name.clone().unwrap_or_default(), - last_move: server.last_move.clone().unwrap_or_default(), - }) - .unwrap(), - ) - .is_err() - { - break; - } - player_turn = chess.turn(); - } - } else { - let game_info_result = wait_for_opponent_move(server); - if game_info_result.is_err() { - break; - } - let game_info = game_info_result.unwrap(); - clichess::write_to_stream(stream, serde_json::to_string(&game_info).unwrap()).unwrap(); - server.opponent_name = Some(game_info.opponent_name); - server.last_move = Some(game_info.last_move); - let chess = server.chess_position.lock().unwrap(); - player_turn = chess.turn(); + Some(index) => available_roles_after_choice.remove(index), + None => continue, + }; } } + if available_roles_after_choice.contains(&role) { + create_player(server, username, pubkey, role, others_serv_msg_sender); + send_ok(stream); + } else { + send_ko(stream); + } +} + +fn fetch_available_roles(stream: &mut UnixStream, server: &Server) { + println!( + "server {}, sending available role to the player...", + server.id + ); + let available_roles = compute_available_roles_to_str(server); + println!("Computed available_roles as str: {}", available_roles); + clichess::write_to_stream(stream, available_roles.clone()).unwrap(); +} + +fn send_game_info(stream: &mut UnixStream, server: &Server) { + println!("server {}, sending game info...", server.id); + let chess = server.chess_position.lock().unwrap(); + let opponent_name = server + .opponent_name + .lock() + .unwrap() + .clone() + .unwrap_or_default(); + let last_move = server.last_move.lock().unwrap().clone().unwrap_or_default(); + let game_info = GameInfo { + game_fen: fen::fen(&*chess), + opponent_name, + last_move, + }; + clichess::write_to_stream(stream, serde_json::to_string(&game_info).unwrap()).unwrap(); +} + +fn wait_for_next_move(stream: &mut UnixStream, server: &mut Server) -> bool { + println!("server {}, waiting for opponent move...", server.id); + //acknowledge the request to the client + send_ok(stream); + //wait either for the other server, or the client exit. + match wait_for_opponent_move(server) { + Ok(game_info) => { + update_from_game_info(server, &game_info); + let game_info_serialized = serde_json::to_string(&game_info).unwrap(); + //send game_info to client + clichess::write_to_stream(stream, game_info_serialized).unwrap(); + } + Err(_) => { + return true; + } + } + false } fn wait_for_opponent_move(server: &Server) -> Result { @@ -193,38 +207,97 @@ fn wait_for_opponent_move(server: &Server) -> Result { /*nothing to do*/ } } match server.client_message_recv.try_recv() { - Ok(msg) => { - if msg == EXIT_MSG { - returned_result = Err(RecvPositionError::UserCanceledError); - } else { - println!("Client sent message while it's not its turn, this is an error."); - println!("Here is the message: {}", msg); - returned_result = Err(RecvPositionError::UserCanceledError); - } - } + Ok(_) => returned_result = Err(RecvPositionError::UserCanceledError), Err(TryRecvError::Disconnected) => println!("Error: client disconnected."), - Err(TryRecvError::Empty) => {} + Err(TryRecvError::Empty) => { /*nothing to do*/ } } returned_result } +fn update_from_game_info(server: &mut Server, game_info: &GameInfo) { + let mut chess_position = server.chess_position.lock().unwrap(); + let mut opponent_name = server.opponent_name.lock().unwrap(); + let mut last_move = server.last_move.lock().unwrap(); + *chess_position = clichess::parse_position(&game_info.game_fen); + if game_info.opponent_name == "" { + *opponent_name = None; + } else { + *opponent_name = Some(game_info.opponent_name.clone()); + } + if game_info.last_move == "" { + *last_move = None; + } else { + *last_move = Some(game_info.last_move.clone()); + } +} + +fn play(stream: &mut UnixStream, server: &Server, movestr: String) { + println!("server {}, user wants to play...", server.id); + let mut chess = server.chess_position.lock().unwrap(); + let players = server.players.lock().unwrap(); + let player = players.get(&server.id); + if player.is_none() || !clichess::is_player_turn(&player.unwrap().0, chess.turn()) { + send_ko(stream); + return; + } + println!("User tried to play {}", movestr.clone()); + match clichess::try_to_play_move(&chess, movestr.clone()) { + Ok(played_chess) => *chess = played_chess, + Err(e) => println!("Error: {}", e), + }; + let player_name = player + .expect("current server is in the server list") + .0 + .username + .clone(); + + //send play to other servers + let game_info_to_send = GameInfo { + game_fen: fen::fen(&*chess), + opponent_name: player_name, + last_move: movestr.clone(), + }; + for (id, (_, others_serv_msg_sender, cvar_pair)) in players.iter() { + if server.id != *id { + others_serv_msg_sender + .send(game_info_to_send.clone()) + .unwrap(); + let (lock, cvar) = &**cvar_pair; + let mut message_sent = lock.lock().unwrap(); + *message_sent = true; + cvar.notify_one(); + } + } + let opponent_name = server.opponent_name.lock().unwrap(); + let last_move = server.last_move.lock().unwrap(); + //send updated info to client + clichess::write_to_stream( + stream, + serde_json::to_string(&GameInfo { + game_fen: fen::fen(&*chess), + opponent_name: opponent_name.clone().unwrap_or_default(), + last_move: last_move.clone().unwrap_or_default(), + }) + .unwrap(), + ) + .unwrap(); +} + +fn send_ok(stream: &mut UnixStream) { + clichess::write_to_stream(stream, String::from("OK")).unwrap(); +} + +fn send_ko(stream: &mut UnixStream) { + clichess::write_to_stream(stream, String::from("KO")).unwrap(); +} + fn create_player( server: &Server, - stream: &mut UnixStream, + username: String, + public_key: String, + role: UserRole, others_serv_msg_sender: Sender, -) -> Result { - println!("Creating player {}...", server.id); - //get player name and pubkey - let username_pubkey_json = server - .client_message_recv - .recv() - .expect("Player closed connection."); - let username_pubkey_value: Value = serde_json::from_str(&username_pubkey_json).unwrap(); - let username = username_pubkey_value["username"].to_string(); - let public_key = username_pubkey_value["pubkey"].to_string(); - println!("got username: {}", username); - println!("got pubkey: {}", public_key); - let role = receive_user_role(server, stream)?; +) { let mut players = server.players.lock().unwrap(); let player = Player { role, @@ -236,68 +309,6 @@ fn create_player( (player.clone(), others_serv_msg_sender, server.cvar.clone()), ); println!("Created player {}", server.id); - Ok(player) -} - -fn receive_user_role( - server: &Server, - stream: &mut UnixStream, -) -> Result { - let mut chosen_role: UserRole; - loop { - //send available roles - let available_roles = compute_available_roles_to_str(server); - println!("Computed available_roles as str: {}", available_roles); - clichess::write_to_stream(stream, available_roles.clone()).unwrap(); - //receive chosen role - let chosen_role_str = server - .client_message_recv - .recv() - .expect("Player closed connection."); - if chosen_role_str.trim() == EXIT_MSG { - return Err(RecvPositionError::UserCanceledError); - } - println!("Client id {} has chosen {}", server.id, chosen_role_str); - let chosen_role_parse = clichess::parse_to_role(&chosen_role_str); - - if chosen_role_parse.is_err() { - return Err(RecvPositionError::ParsePositionError); - } - chosen_role = chosen_role_parse.unwrap(); - //check if role is still available after the choice - { - let mut available_roles_after_choice = - vec![UserRole::White, UserRole::Black, UserRole::Spectator]; - let players = server.players.lock().unwrap(); - for (_, (player, _, _)) in players.iter() { - match available_roles_after_choice - .iter() - .position(|r| *r == player.role) - { - Some(index) => available_roles_after_choice.remove(index), - None => continue, - }; - } - if available_roles_after_choice.contains(&chosen_role) { - println!("OK"); - clichess::write_to_stream(stream, String::from("OK")).unwrap(); - server - .client_message_recv - .recv() - .expect("Player closed connection."); - break; - } else { - println!("KO"); - clichess::write_to_stream(stream, String::from("KO")).unwrap(); - server - .client_message_recv - .recv() - .expect("Player closed connection."); - continue; - } - } - } - Ok(chosen_role) } fn compute_available_roles_to_str(server: &Server) -> String { diff --git a/src/lib.rs b/src/lib.rs index 90b21fd..1908650 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ use colored::Colorize; use serde::{Deserialize, Serialize}; +use shakmaty::fen::Fen; use shakmaty::san::ParseSanError; use shakmaty::san::San; use shakmaty::san::SanError; @@ -25,16 +26,30 @@ pub struct Player { pub struct GameInfo { pub game_fen: String, pub opponent_name: String, - pub last_move: String + pub last_move: String, } -#[derive(PartialEq, Copy, Clone)] +#[derive(Serialize, Deserialize, PartialEq, Copy, Clone)] pub enum UserRole { White, Black, Spectator, } +#[derive(Serialize, Deserialize, PartialEq, Clone)] +pub enum ClientRequest { + FetchAvailableRoles, + Login { + username: String, + pubkey: String, + role: UserRole, + }, + GetGameInfo, + WaitForNextMove, + Play(String), + Exit, +} + impl fmt::Display for UserRole { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( @@ -257,3 +272,9 @@ pub fn is_player_turn(player: &Player, playing_side: Color) -> bool { (playing_side == Color::White && player.role == UserRole::White) || (playing_side == Color::Black && player.role == UserRole::Black) } + +pub fn parse_position(string: &str) -> Chess { + let setup: Fen = string.trim().parse().expect("Invalid message from server."); + let position: Chess = setup.position().expect("Invalid message from server."); + position +}