From 0cff0e13da463a54601dfbe3806a38ff904b5260 Mon Sep 17 00:00:00 2001 From: Artlef Date: Fri, 3 Jan 2020 22:54:52 +0100 Subject: [PATCH] 2 clients can play and see each other moves. --- Cargo.lock | 80 +++++++++++++++++++++++ Cargo.toml | 2 + src/bin/client.rs | 64 +++++++++++++++++-- src/bin/server.rs | 110 ++++++++++++++++++++++++------- src/lib.rs | 160 ++++++++++++++++++++-------------------------- 5 files changed, 297 insertions(+), 119 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2a47d1c..aaaa27e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,6 +37,8 @@ name = "clichess" version = "0.1.0" dependencies = [ "colored 1.9.0 (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.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -50,6 +52,11 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "itoa" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "lazy_static" version = "1.4.0" @@ -68,6 +75,55 @@ dependencies = [ "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "proc-macro2" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ryu" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_derive" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_json" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "shakmaty" version = "0.16.0" @@ -78,6 +134,21 @@ dependencies = [ "btoi 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "syn" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "winapi" version = "0.3.8" @@ -104,10 +175,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "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 colored 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "433e7ac7d511768127ed85b0c4947f47a254131e37864b2dc13f52aa32cd37e5" +"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 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" +"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" +"checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" +"checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" +"checksum serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)" = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7" "checksum shakmaty 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "995611c42b955b8661f1eaf19e83beec36e72ec3ac51ed41a9018a19323c8022" +"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 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 1329081..7c236a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,5 @@ edition = "2018" [dependencies] shakmaty = "0.16.0" colored = "1.9" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" diff --git a/src/bin/client.rs b/src/bin/client.rs index 2c0364e..3965a4b 100644 --- a/src/bin/client.rs +++ b/src/bin/client.rs @@ -1,4 +1,8 @@ +use clichess::UserRole; +use shakmaty::fen::Fen; +use shakmaty::{Chess, Color, Setup}; use std::io; +use std::io::{BufRead, BufReader}; use std::os::unix::net::UnixStream; fn main() { @@ -10,10 +14,58 @@ fn main() { } }; let mut buffer = String::new(); - let input = clichess::read_from_stream(&stream); - println!("{}", input); - io::stdin().read_line(&mut buffer).unwrap(); - clichess::write_to_stream(&mut stream, String::from(buffer.trim())); - let response = clichess::read_from_stream(&stream); - println!("{}", response); + let (client, chess) = get_connection_info_from_stream(&stream); + //First prompt when connecting to the server + println!( + "Hello, anonymous !\n\r You're playing with the {} pieces", + client.role.to_string() + ); + //then we get the initial role of the connected client. + let mut current_position = chess; + loop { + println!( + "{}", + clichess::board_representation(¤t_position, clichess::to_color(client.side)) + ); + if clichess::is_player_turn(client, current_position.turn()) { + //it's the user turn, taking user input + io::stdin().read_line(&mut buffer).unwrap(); + println!("trying to play {}", buffer); + clichess::write_to_stream(&mut stream, String::from(buffer.trim())); + buffer.clear(); + } + //update position after playing. + current_position = get_current_position(&stream); + + //come back at the beginning of the loop to wait for incoming moves. + } +} + +//wait for next position from server, then return the current board. +fn get_current_position(stream: &UnixStream) -> Chess { + let response = clichess::read_line_from_stream(&stream); + parse_position(&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 +} + +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) } diff --git a/src/bin/server.rs b/src/bin/server.rs index 2733af8..b90fdf4 100644 --- a/src/bin/server.rs +++ b/src/bin/server.rs @@ -1,19 +1,25 @@ use clichess; -use shakmaty::{Chess, Color}; +use clichess::Client; +use clichess::UserRole; +use shakmaty::{fen, Chess, Color, Setup}; +use std::collections::HashMap; use std::fs; -use std::io::prelude::*; use std::os::unix::net::{UnixListener, UnixStream}; -use std::str; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Condvar, Mutex}; + use std::thread; +#[derive(Clone)] struct Server { id: usize, chess_position: Arc>, + clients: 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 mut counter = 0; fs::remove_file("/tmp/clichess.socket"); @@ -23,12 +29,13 @@ fn main() { for stream in listener.incoming() { match stream { Ok(stream) => { - let chess = Arc::clone(&chess); - /* connection succeeded */ let server = Server { id: counter, - chess_position: chess, + chess_position: chess.clone(), + clients: clients.clone(), + receiving_buffer: Arc::new((Mutex::new(String::new()), Condvar::new())), }; + /* connection succeeded */ thread::spawn(|| handle_client(stream, server)); counter += 1; } @@ -41,24 +48,81 @@ fn main() { } fn handle_client(mut stream: UnixStream, server: Server) { + //create client + let client = create_client(&server); + //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); + let mut player_turn: Color; + //send current position to the client + println!( + "server {}, send current position to the client...", + server.id + ); { let chess = server.chess_position.lock().unwrap(); - clichess::write_to_stream( - &mut stream, - clichess::board_representation(&chess, Color::White), - ); + player_turn = chess.turn(); + clichess::write_to_stream(&mut stream, fen::fen(&*chess)); } - //let go of the lock while waiting for user input. - let input = clichess::read_from_stream(&stream); - { - let mut chess = server.chess_position.lock().unwrap(); - match clichess::try_to_play_move(&chess, input) { - Ok(played_chess) => *chess = played_chess, - Err(e) => println!("Error: {}", e), - }; - clichess::write_to_stream( - &mut stream, - clichess::board_representation(&chess, Color::White), - ); + println!("server {}, current position to the client sent", server.id); + loop { + if clichess::is_player_turn(client, player_turn) { + //let go of the lock while waiting for user input. + println!("server {}, waiting for client move..", server.id); + let input = clichess::read_line_from_stream(&stream); + { + let mut chess = server.chess_position.lock().unwrap(); + let clients = server.clients.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() { + if server.id != *id { + cvar_buffer.0.lock().unwrap().push_str(&chessfen); + cvar_buffer.1.notify_one(); + } + } + clichess::write_to_stream(&mut stream, chessfen); + player_turn = chess.turn(); + } + } else { + { + let (buffer_mutex, cvar) = &*server.receiving_buffer; + let mut buffer = buffer_mutex.lock().unwrap(); + println!("server id: {}, waiting for move to send...", server.id); + while buffer.is_empty() { + buffer = cvar.wait(buffer).unwrap(); + } + println!("server id: {}, sending {}", server.id, buffer); + clichess::write_to_stream(&mut stream, buffer.clone()); + buffer.clear(); + let chess = server.chess_position.lock().unwrap(); + player_turn = chess.turn(); + } + } } } + +fn create_client(server: &Server) -> Client { + println!("Creating client {}...", server.id); + let role = match server.id { + 0 => UserRole::White, + 1 => UserRole::Black, + _ => UserRole::Spectator, + }; + let mut clients = server.clients.lock().unwrap(); + let client = Client { + role, + side: clichess::from_color(clichess::get_default_side(role)), + }; + clients.insert(server.id, (client, server.receiving_buffer.clone())); + println!("Created client {}", server.id); + client +} diff --git a/src/lib.rs b/src/lib.rs index 5a04ec1..ff30bb9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,18 +1,48 @@ use colored::Colorize; -use shakmaty::fen; -use shakmaty::fen::Fen; -use shakmaty::fen::ParseFenError; +use serde::{Deserialize, Serialize}; use shakmaty::san::ParseSanError; use shakmaty::san::San; use shakmaty::san::SanError; -use shakmaty::{Chess, Color, IllegalMoveError, Position, PositionError, Setup, Square}; +use shakmaty::{Chess, Color, IllegalMoveError, Position, Setup, Square}; use std::fmt; -use std::fs::File; use std::io::prelude::*; -use std::io::{self, BufReader, ErrorKind, Read, Write}; +use std::io::{BufReader, Write}; use std::os::unix::net::UnixStream; use std::str; +#[derive(Copy, Clone, Serialize, Deserialize)] +pub struct Client { + pub role: UserRole, + pub side: BoardSide, +} + +#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)] +pub enum BoardSide { + White, + Black, +} + +#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)] +pub enum UserRole { + White, + Black, + Spectator, +} + +impl fmt::Display for UserRole { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + match self { + UserRole::White => "white", + UserRole::Black => "black", + UserRole::Spectator => "spectator", + } + ) + } +} + struct SquareToPrint { color: String, background_color: String, @@ -56,76 +86,6 @@ impl fmt::Display for MoveInputError { } } -#[derive(Debug)] -pub enum ReadingFileError { - ReadParseFenError(ParseFenError), - ReadFileError(io::Error), - ReadPositionError(PositionError), -} - -impl From for ReadingFileError { - fn from(error: ParseFenError) -> Self { - ReadingFileError::ReadParseFenError(error) - } -} - -impl From for ReadingFileError { - fn from(error: io::Error) -> Self { - ReadingFileError::ReadFileError(error) - } -} - -impl From for ReadingFileError { - fn from(error: PositionError) -> Self { - ReadingFileError::ReadPositionError(error) - } -} - -impl fmt::Display for ReadingFileError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - ReadingFileError::ReadParseFenError(error) => error.fmt(f), - ReadingFileError::ReadFileError(error) => error.fmt(f), - ReadingFileError::ReadPositionError(error) => error.fmt(f), - } - } -} - -pub fn retrieve_chess_from_file_or_create(filename: &str) -> Result { - match retrieve_chess_from_file(filename) { - Ok(chess) => Ok(chess), - Err(ReadingFileError::ReadFileError(error)) => { - if error.kind() == ErrorKind::NotFound { - create_default_fen_in_file(filename) - } else { - Err(ReadingFileError::ReadFileError(error)) - } - } - other_error => other_error, - } -} - -pub fn save_chess_position(chess: &Chess, filename: &str) -> std::io::Result<()> { - let mut file = File::create(filename)?; - file.write_all(fen::fen(chess).as_bytes())?; - Ok(()) -} - -fn retrieve_chess_from_file(filename: &str) -> Result { - let mut file = File::open(filename)?; - let mut contents = String::new(); - file.read_to_string(&mut contents)?; - let setup: Fen = contents.trim().parse()?; - let position: Chess = setup.position()?; - Ok(position) -} - -fn create_default_fen_in_file(filename: &str) -> Result { - let chess = Chess::default(); - save_chess_position(&Chess::default(), filename)?; - Ok(chess) -} - pub fn board_representation(chess: &Chess, side: Color) -> String { let mut s = String::from(""); let mut rank_numbers; @@ -224,23 +184,43 @@ fn piece_char_to_utf8(piece: char) -> char { } } -const DELIMITER_CHAR: u8 = 4; - -pub fn read_from_stream(stream: &UnixStream) -> String { - let mut result = Vec::new(); +pub fn read_line_from_stream(stream: &UnixStream) -> String { + let mut result = String::new(); let mut reader = BufReader::new(stream); - reader.read_until(DELIMITER_CHAR, &mut result).unwrap(); - if let Some((_, msg)) = result.split_last() { - String::from(str::from_utf8(msg).unwrap()) - } else { - String::new() - } + reader.read_line(&mut result).unwrap(); + String::from(result.trim_end()) } pub fn write_to_stream(stream: &mut UnixStream, msg: String) { - let mut to_send = Vec::new(); - to_send.append(&mut msg.into_bytes()); - to_send.push(DELIMITER_CHAR); - stream.write_all(&to_send).unwrap(); + stream.write_all(&(msg + "\n").as_bytes()).unwrap(); +} + +pub fn get_default_side(role: UserRole) -> Color { + if role == UserRole::Black { + Color::Black + } else { + Color::White + } +} + +pub fn to_color(board_side: BoardSide) -> Color { + if board_side == BoardSide::White { + Color::White + } else { + Color::Black + } +} + +pub fn from_color(color: Color) -> BoardSide { + if color == Color::White { + BoardSide::White + } else { + BoardSide::Black + } +} + +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) }