From 175e8fafe6eb3c311497268cbafa9d88f31e4289 Mon Sep 17 00:00:00 2001 From: Artlef Date: Wed, 19 Feb 2020 12:22:36 +0100 Subject: [PATCH] Ask for role on connection --- src/bin/client.rs | 170 +++++++++++++++++++++++++++++++++------------- src/bin/server.rs | 144 +++++++++++++++++++++++++-------------- src/lib.rs | 45 ++++++------ 3 files changed, 234 insertions(+), 125 deletions(-) diff --git a/src/bin/client.rs b/src/bin/client.rs index 408d2a6..725063d 100644 --- a/src/bin/client.rs +++ b/src/bin/client.rs @@ -2,12 +2,20 @@ extern crate ctrlc; use shakmaty::fen::Fen; use shakmaty::{Chess, Color, Outcome, Position, Setup}; use std::io; -use std::io::{BufRead, BufReader}; use std::os::unix::net::UnixStream; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use std::thread; use std::time; +use serde_json::json; + +struct Client { + player: clichess::Player, + side: Color, + running: Arc, + input_buffer: Arc>, + server_message: Arc>, +} enum RecvPositionError { UserCanceledError, @@ -19,7 +27,6 @@ fn main() { let running = setupctrlc(); let username = std::env::args().nth(1).expect("no name given"); let public_key = std::env::args().nth(2).expect("no public key given"); - println!("Name: {}, Public key: {}", username, public_key); //send username and public key to server let mut stream = match UnixStream::connect("/tmp/clichess.socket") { Ok(sock) => sock, @@ -28,39 +35,55 @@ fn main() { return; } }; - clichess::write_to_stream(&mut stream, username).unwrap(); - clichess::write_to_stream(&mut stream, public_key).unwrap(); - let (client, chess) = get_connection_info_from_stream(&stream); - //First prompt when connecting to the server - println!( - "Hello, {} !\n\r You're playing with the {} pieces", - client.username, - client.role.to_string() - ); - //then we get the initial role of the connected client. - let mut current_position = chess; + 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(); //start a thread to listen to server messages let server_message = setup_server_message_recv(&stream).unwrap(); + + let mut client = Client { + player: clichess::Player { + role: clichess::UserRole::Spectator, + username: username, + public_key: public_key, + }, + side: Color::White, + running: running.clone(), + input_buffer: input_buffer.clone(), + server_message: server_message.clone(), + }; + + let role = prompt_user_for_role(&client, &mut stream); + client.player.role = role.clone(); + println!( + "Hello, {} !\n\r You're playing with the {} pieces", + client.player.username, + client.player.role.to_string() + ); + println!("Fetching initial chess position..."); + let mut current_position = fetch_initial_chess_position(&client); loop { println!( "{}", - clichess::board_representation(¤t_position, clichess::to_color(client.side)) + clichess::board_representation(¤t_position, client.side) ); //check if game is over. if current_position.is_game_over() { break; } - if clichess::is_player_turn(&client, current_position.turn()) { + if clichess::is_player_turn(&client.player, current_position.turn()) { //it's the user turn, taking user input - let input = read_user_input(running.clone(), input_buffer.clone()); + let input = read_user_input(&client); clichess::write_to_stream(&mut stream, String::from(input.trim())).unwrap(); if input.trim() == "exit" { break; } } //update position after playing. - match get_current_position(running.clone(), server_message.clone()) { + match get_current_position(&client) { Ok(position) => current_position = position, Err(RecvPositionError::UserCanceledError) => break, Err(RecvPositionError::CommunicationError) => break, @@ -135,17 +158,17 @@ fn setup_server_message_recv(stream: &UnixStream) -> io::Result, input_buffer: Arc>) -> String { +fn read_user_input(client: &Client) -> String { //clear input before waiting for a new one { - let mut user_input = input_buffer.lock().unwrap(); + let mut user_input = client.input_buffer.lock().unwrap(); user_input.clear(); } let mut input = String::new(); - while running.load(Ordering::SeqCst) { + while client.running.load(Ordering::SeqCst) { thread::sleep(time::Duration::from_millis(10)); { - let user_input = input_buffer.lock().unwrap(); + let user_input = client.input_buffer.lock().unwrap(); if user_input.is_empty() { continue; } else { @@ -154,7 +177,7 @@ fn read_user_input(running: Arc, input_buffer: Arc>) - } } } - if running.load(Ordering::SeqCst) { + if client.running.load(Ordering::SeqCst) { input } else { String::from("exit") @@ -162,15 +185,21 @@ fn read_user_input(running: Arc, input_buffer: Arc>) - } //wait for next position from server, then return the current board. -fn get_current_position( - running: Arc, - server_message: Arc>, -) -> Result { +fn get_current_position(client: &Client) -> Result { + let response = fetch_message_from_server(client); + if response.is_empty() { + Err(RecvPositionError::UserCanceledError) + } else { + Ok(parse_position(&response)) + } +} + +fn fetch_message_from_server(client: &Client) -> String { let mut response = String::new(); - while running.load(Ordering::SeqCst) { + while client.running.load(Ordering::SeqCst) { thread::sleep(time::Duration::from_millis(10)); { - let mut server_response = server_message.lock().unwrap(); + let mut server_response = client.server_message.lock().unwrap(); if server_response.is_empty() { continue; } else { @@ -180,11 +209,7 @@ fn get_current_position( } } } - if response.is_empty() { - Err(RecvPositionError::UserCanceledError) - } else { - Ok(parse_position(&response)) - } + response } fn parse_position(string: &str) -> Chess { @@ -193,19 +218,68 @@ fn parse_position(string: &str) -> Chess { position } -fn get_connection_info_from_stream(stream: &UnixStream) -> (clichess::Client, Chess) { - println!("Read lines from stream..."); - let mut buf = String::new(); - let mut reader = BufReader::new(stream); - //first, client as json. - reader - .read_line(&mut buf) - .expect("Server closed connection."); - let client = serde_json::from_str(&(buf.trim())).unwrap(); - buf.clear(); - reader - .read_line(&mut buf) - .expect("Server closed connection."); - let chess = parse_position(buf.trim()); - (client, chess) +fn prompt_user_for_role(client: &Client, stream: &mut UnixStream) -> clichess::UserRole { + let mut role: clichess::UserRole; + loop { + println!("fetching roles from server..."); + let available_roles = fetch_available_roles(client); + println!("available roles fetched."); + let mut prompt = String::new(); + if !available_roles.contains(&clichess::UserRole::White) + && !available_roles.contains(&clichess::UserRole::Black) + { + prompt.push_str("You can only spectate this game."); + } else if available_roles.contains(&clichess::UserRole::White) { + prompt = String::from("Do you want to play as White (W)"); + if available_roles.contains(&clichess::UserRole::White) { + 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)?"); + } + println!("{}", prompt); + //wait for user to give a correct answer. + let input = String::from(read_user_input(client).trim()); + match clichess::parse_to_role(&input) { + Ok(r) => role = r, + Err(e) => { + println!("{}", e); + continue; + } + }; + if !available_roles.contains(&role) { + println!("Sorry, this side is not available."); + continue; + } + //send info to server + println!("Sending choice {} to server", input); + clichess::write_to_stream(stream, String::from(input)).unwrap(); + println!("Sent."); + //get confirmation from server + println!("Get response 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?" + ); + continue; + } + println!("Ok!"); + clichess::write_to_stream(stream, String::from("OK")).unwrap(); + break; + } + role +} + +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 roles_from_str(s: &str) -> Result, String> { + s.split(',').map(clichess::parse_to_role).collect() } diff --git a/src/bin/server.rs b/src/bin/server.rs index 92258fa..a95b3bd 100644 --- a/src/bin/server.rs +++ b/src/bin/server.rs @@ -1,10 +1,10 @@ use clichess; -use clichess::Client; +use clichess::Player; use clichess::UserRole; +use serde_json::Value; use shakmaty::{fen, Chess, Color, Setup}; use std::collections::HashMap; use std::fs; -use std::io::{BufRead, BufReader}; use std::os::unix::net::{UnixListener, UnixStream}; use std::sync::{Arc, Condvar, Mutex}; @@ -14,13 +14,13 @@ use std::thread; struct Server { id: usize, chess_position: Arc>, - clients: Arc, Condvar)>)>>>, + players: Arc, Condvar)>)>>>, receiving_buffer: Arc<(Mutex, Condvar)>, } fn main() { let chess = Arc::new(Mutex::new(Chess::default())); - let clients = Arc::new(Mutex::new(HashMap::new())); + let players = Arc::new(Mutex::new(HashMap::new())); let mut counter = 0; fs::remove_file("/tmp/clichess.socket"); @@ -33,11 +33,11 @@ fn main() { let server = Server { id: counter, chess_position: chess.clone(), - clients: clients.clone(), + players: players.clone(), receiving_buffer: Arc::new((Mutex::new(String::new()), Condvar::new())), }; /* connection succeeded */ - thread::spawn(|| handle_client(stream, server)); + thread::spawn(move || handle_player(stream, server)); counter += 1; } Err(err) => { @@ -48,20 +48,13 @@ fn main() { } } -fn handle_client(mut stream: UnixStream, server: Server) { - //create client - let client = create_client(&server, &stream); - //send its information to the client - println!( - "server {}, send first information to the client...", - server.id - ); - clichess::write_to_stream(&mut stream, serde_json::to_string(&client).unwrap()); - println!("server {}, information to the client sent", server.id); +fn handle_player(mut stream: UnixStream, server: Server) { + //create player + let player = create_player(&server, &mut stream); let mut player_turn: Color; - //send current position to the client + //send current position to the player println!( - "server {}, send current position to the client...", + "server {}, send current position to the player...", server.id ); { @@ -69,11 +62,11 @@ fn handle_client(mut stream: UnixStream, server: Server) { player_turn = chess.turn(); clichess::write_to_stream(&mut stream, fen::fen(&*chess)).unwrap(); } - println!("server {}, current position to the client sent", server.id); + println!("server {}, current position to the player sent", server.id); loop { - if clichess::is_player_turn(&client, player_turn) { + if clichess::is_player_turn(&player, player_turn) { //let go of the lock while waiting for user input. - println!("server {}, waiting for client move..", server.id); + println!("server {}, waiting for player move..", server.id); let input; match clichess::read_line_from_stream(&stream) { Ok(i) => input = i, @@ -87,14 +80,14 @@ fn handle_client(mut stream: UnixStream, server: Server) { } { let mut chess = server.chess_position.lock().unwrap(); - let clients = server.clients.lock().unwrap(); + let players = server.players.lock().unwrap(); println!("User tried to play {}", input); match clichess::try_to_play_move(&chess, input) { Ok(played_chess) => *chess = played_chess, Err(e) => println!("Error: {}", e), }; let chessfen = fen::fen(&*chess); - for (id, (_, cvar_buffer)) in clients.iter() { + for (id, (_, cvar_buffer)) in players.iter() { if server.id != *id { cvar_buffer.0.lock().unwrap().push_str(&chessfen); cvar_buffer.1.notify_one(); @@ -124,38 +117,87 @@ fn handle_client(mut stream: UnixStream, server: Server) { } } } - println!("Client disconnected.") + println!("Player disconnected.") } -fn create_client(server: &Server, stream: &UnixStream) -> Client { - println!("Creating client {}...", server.id); - //get client name and pubkey - println!("Read lines from stream..."); - let mut buf = String::new(); - let mut reader = BufReader::new(stream); - //first, username. - reader - .read_line(&mut buf) - .expect("Client closed connection."); - let username = String::from(buf.trim()); - buf.clear(); - reader - .read_line(&mut buf) - .expect("Client closed connection."); - let public_key = String::from(buf.trim()); - let role = match server.id { - 0 => UserRole::White, - 1 => UserRole::Black, - _ => UserRole::Spectator, - }; - let mut clients = server.clients.lock().unwrap(); - let client = Client { +fn create_player(server: &Server, stream: &mut UnixStream) -> Player { + println!("Creating player {}...", server.id); + //get player name and pubkey + let username_pubkey_json = + clichess::read_line_from_stream(stream).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, - side: clichess::from_color(clichess::get_default_side(role)), username, public_key, }; - clients.insert(server.id, (client.clone(), server.receiving_buffer.clone())); - println!("Created client {}", server.id); - client + players.insert(server.id, (player.clone(), server.receiving_buffer.clone())); + println!("Created player {}", server.id); + player +} + +fn receive_user_role(server: &Server, stream: &mut UnixStream) -> UserRole { + 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 = + clichess::read_line_from_stream(stream).expect("Player closed connection."); + println!("Client id {} has chosen {}", server.id, chosen_role_str); + chosen_role = + clichess::parse_to_role(&chosen_role_str).expect("Client did not send parsable role."); + //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(); + + let get_confirmation = + clichess::read_line_from_stream(stream).expect("Player closed connection."); + break; + } else { + println!("KO"); + clichess::write_to_stream(stream, String::from("KO")).unwrap(); + continue; + } + } + } + chosen_role +} + +fn compute_available_roles_to_str(server: &Server) -> String { + let mut available_roles = vec![UserRole::White, UserRole::Black, UserRole::Spectator]; + let players = server.players.lock().unwrap(); + for (_, (player, _)) in players.iter() { + match available_roles.iter().position(|r| *r == player.role) { + Some(index) => available_roles.remove(index), + None => continue, + }; + } + let available_roles_str: Vec = available_roles + .iter() + .map(|r| clichess::role_to_str(r)) + .collect(); + available_roles_str.join(",") } diff --git a/src/lib.rs b/src/lib.rs index 8e30b5d..0031ae2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,4 @@ use colored::Colorize; -use serde::{Deserialize, Serialize}; use shakmaty::san::ParseSanError; use shakmaty::san::San; use shakmaty::san::SanError; @@ -9,23 +8,16 @@ use std::io; use std::io::prelude::*; use std::io::{BufReader, Write}; use std::os::unix::net::UnixStream; -use std::str; +use std::ops::Add; -#[derive(Clone, Serialize, Deserialize)] -pub struct Client { +#[derive(Clone)] +pub struct Player { pub role: UserRole, - pub side: BoardSide, pub username: String, pub public_key: String, } -#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)] -pub enum BoardSide { - White, - Black, -} - -#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)] +#[derive(PartialEq, Copy, Clone)] pub enum UserRole { White, Black, @@ -208,23 +200,24 @@ pub fn get_default_side(role: UserRole) -> Color { } } -pub fn to_color(board_side: BoardSide) -> Color { - if board_side == BoardSide::White { - Color::White - } else { - Color::Black +pub fn parse_to_role(s: &str) -> Result { + match &*s.to_ascii_lowercase() { + "w" => Ok(UserRole::White), + "b" => Ok(UserRole::Black), + "s" => Ok(UserRole::Spectator), + _ => Err(String::from("Please enter a valid answer.")) } } -pub fn from_color(color: Color) -> BoardSide { - if color == Color::White { - BoardSide::White - } else { - BoardSide::Black - } +pub fn role_to_str(r: &UserRole) -> String { + String::from(match *r { + UserRole::White => "w", + UserRole::Black => "b", + UserRole::Spectator => "s" + }) } -pub fn is_player_turn(client: &Client, playing_side: Color) -> bool { - (playing_side == Color::White && client.role == UserRole::White) - || (playing_side == Color::Black && client.role == UserRole::Black) +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) }