From 11e3c7d48e8b3b5ef490a2576fa0ab5c605611d5 Mon Sep 17 00:00:00 2001 From: Artlef Date: Sun, 15 Nov 2020 21:14:18 +0100 Subject: [PATCH] Add termion dependency to manage UI --- Cargo.lock | 158 ++++++++++------------- Cargo.toml | 2 +- src/bin/client.rs | 317 ++++++++++++++++++++++++++++++++++++---------- src/bin/server.rs | 6 +- src/lib.rs | 53 ++++++-- 5 files changed, 363 insertions(+), 173 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2a62228..41294b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,190 +4,172 @@ name = "arrayvec" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "atty" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" [[package]] name = "autocfg" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" [[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "btoi" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ced8205e70d9e553d008d53ded735808fa6133597318d48f74fc2bf9861471" dependencies = [ - "num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits", ] [[package]] name = "clichess" version = "0.3.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.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "colored" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde", + "serde_json", + "shakmaty", + "termion", ] [[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" -source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" [[package]] name = "libc" version = "0.2.66" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" [[package]] name = "num-traits" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4" dependencies = [ - "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", ] +[[package]] +name = "numtoa" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" + [[package]] name = "proc-macro2" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27" dependencies = [ - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid", ] [[package]] name = "quote" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" dependencies = [ - "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + +[[package]] +name = "redox_termios" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" +dependencies = [ + "redox_syscall", ] [[package]] name = "ryu" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" [[package]] name = "serde" version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" dependencies = [ - "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" 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)", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "serde_json" version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7" 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)", + "itoa", + "ryu", + "serde", ] [[package]] name = "shakmaty" version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f8f7fd48325e0b530cd257df0aa735efeefc923c70133d412f05d69de69108" dependencies = [ - "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "btoi 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "arrayvec", + "bitflags", + "btoi", ] [[package]] name = "syn" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff0acdb207ae2fe6d5976617f887eb1e35a2ba52c13c7234c790960cdad9238" 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)", + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "termion" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c22cec9d8978d906be5ac94bceb5a010d885c626c4c8855721a4dbd20e3ac905" +dependencies = [ + "libc", + "numtoa", + "redox_syscall", + "redox_termios", ] [[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[metadata] -"checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" -"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" -"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 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.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 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" +checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" diff --git a/Cargo.toml b/Cargo.toml index ee2c995..2741388 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,6 @@ edition = "2018" [dependencies] shakmaty = "0.16.2" -colored = "1.9" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +termion = "1.5.5" diff --git a/src/bin/client.rs b/src/bin/client.rs index b803f59..d0461f2 100644 --- a/src/bin/client.rs +++ b/src/bin/client.rs @@ -1,14 +1,22 @@ +extern crate termion; + use clichess::{ClientRequest, GameInfo, RecvPositionError, UserRole, EXIT_MSG}; -use shakmaty::{Chess, Color, Outcome, Position, Setup}; +use shakmaty::{fen, Chess, Color, Outcome, Position, Setup}; use std::io; use std::os::unix::net::UnixStream; -use std::sync::mpsc::{channel, Receiver, TryRecvError}; +use std::sync::mpsc::{channel, Receiver, Sender}; use std::thread; use std::time; +use termion::event::Key; + +use std::io::{stdin, stdout, Read, Write}; +use termion::raw::{IntoRawMode, RawTerminal}; struct Client { player: clichess::Player, side: Color, + display_sender: Sender, + waiting_server_msg_receiver: Receiver, keyboard_input_recv: Receiver, server_message_recv: Receiver, opponent_name: Option, @@ -28,9 +36,15 @@ fn main() { return; } }; - let keyboard_input_recv = setup_keyboard_input_recv(); + + let (display_sender, display_receiver) = channel(); + let (waiting_server_msg_sender, waiting_server_msg_receiver) = channel(); + let keyboard_input_recv = + start_keyboard_input_thread(display_sender.clone(), waiting_server_msg_sender.clone()); + start_display_thread(display_receiver); //start a thread to listen to server messages - let server_message_recv = setup_server_message_recv(&stream).unwrap(); + let server_message_recv = + setup_server_message_recv(&stream, waiting_server_msg_sender.clone()).unwrap(); let mut client = Client { player: clichess::Player { @@ -39,6 +53,8 @@ fn main() { public_key, }, side: Color::White, + display_sender: display_sender.clone(), + waiting_server_msg_receiver, keyboard_input_recv, server_message_recv, opponent_name: Option::None, @@ -54,34 +70,50 @@ fn main() { client.side = Color::Black; } if client.player.role == UserRole::Spectator { - println!( - "Hello, {} !\n\r You're spectating !", - client.player.username - ) + client + .display_sender + .send(DisplayMessage::Information(format!( + "Hello, {} !\n\r You're spectating !", + client.player.username + ))) + .unwrap(); } else { - println!( - "Hello, {} !\n\r You're playing with the {} pieces", - client.player.username, - client.player.role.to_string() - ); + client + .display_sender + .send(DisplayMessage::Information(format!( + "Hello, {} !\n\r You're playing with the {} pieces", + client.player.username, + client.player.role.to_string() + ))) + .unwrap(); } + let mut current_position = fetch_initial_chess_position(&client, &mut stream); + loop { let is_player_turn = clichess::is_player_turn(&client.player, current_position.turn()); if is_player_turn || client.player.role == UserRole::Spectator { + let mut message = String::from(""); client .opponent_name .clone() - .map(|name| print!("{} played", name)); + .map(|name| message.push_str(&format!("{} played", name))); client .last_move .clone() - .map(|chessmove| println!(" {}", chessmove)); + .map(|chessmove| message.push_str(&format!(" {}", chessmove))); + if message.len() > 0 { + display_sender + .send(DisplayMessage::Information(message)) + .unwrap(); + } } - println!( - "{}", - clichess::board_representation(¤t_position, client.side) - ); + display_sender + .send(DisplayMessage::Chessboard { + fen: fen::fen(¤t_position), + side: client.side.clone(), + }) + .unwrap(); //check if game is over. if current_position.is_game_over() { break; @@ -91,11 +123,14 @@ fn main() { let mut input = client.keyboard_input_recv.recv().unwrap(); input = String::from(input.trim()); if input == EXIT_MSG { + client.waiting_server_msg_receiver.recv().unwrap(); break; } let response = send_request(&client, &mut stream, ClientRequest::Play(input.clone())); if response == String::from("KO") { - println!("Invalid move: {}", input); + display_sender + .send(DisplayMessage::Message(format!("Invalid move: {}", input))) + .unwrap(); //go back to taking user input continue; } @@ -111,18 +146,38 @@ fn main() { }; } } - match current_position.outcome() { - None => println!("Bye"), - Some(Outcome::Draw) => println!("Draw game."), + client.display_sender.send(DisplayMessage::Clear).unwrap(); + let end_message = match current_position.outcome() { + None => "Bye", + Some(Outcome::Draw) => "Draw game.", Some(Outcome::Decisive { winner }) => { if winner == Color::White { - println!("White has won the game.") + "White has won the game." } else { - println!("Black has won the game.") + "Black has won the game." } } } + .to_string(); + client + .display_sender + .send(DisplayMessage::Message(end_message)) + .unwrap(); + thread::sleep(time::Duration::from_secs(1)); + client + .display_sender + .send(DisplayMessage::Debug( + "sending request to server to exit...".to_string(), + )) + .unwrap(); send_request(&client, &mut stream, ClientRequest::Exit); + client + .display_sender + .send(DisplayMessage::Debug( + "request to server to exit sent.".to_string(), + )) + .unwrap(); + client.display_sender.send(DisplayMessage::Exit).unwrap(); } #[derive(PartialEq)] @@ -163,27 +218,157 @@ fn send_request(client: &Client, stream: &mut UnixStream, request: ClientRequest } Err(e) => { - println!("Error when parsing client request: {}", e); + client + .display_sender + .send(DisplayMessage::Message(format!( + "Error when parsing client request: {}", + e + ))) + .unwrap(); response = String::from("KO"); } }; response } -fn setup_keyboard_input_recv() -> Receiver { +#[derive(PartialEq, Clone)] +pub enum DisplayMessage { + Chessboard { fen: String, side: Color }, + Message(String), + Information(String), + Debug(String), + Input(Key), + Enter, + Clear, + Exit, +} + +fn start_display_thread(request_recv: Receiver) { + thread::spawn(move || { + let stdout = stdout(); + let mut stdout = stdout.lock().into_raw_mode().unwrap(); + write!( + stdout, + "{}{}", + termion::clear::All, + termion::cursor::Goto(3, 17) + ) + .unwrap(); + stdout.flush().unwrap(); + loop { + let msg = request_recv.recv().unwrap(); + match msg { + DisplayMessage::Chessboard { fen, side } => { + let current_position = clichess::parse_position(&fen); + clichess::print_board_representation(¤t_position, side, &mut stdout); + } + DisplayMessage::Message(s) => write!( + stdout, + "{}{}{}{}{}", + termion::cursor::Save, + termion::cursor::Goto(3, 20), + termion::clear::CurrentLine, + s, + termion::cursor::Restore + ) + .unwrap(), + DisplayMessage::Information(s) => write!( + stdout, + "{}{}{}{}{}", + termion::cursor::Save, + termion::cursor::Goto(3, 3), + termion::clear::CurrentLine, + s, + termion::cursor::Restore + ) + .unwrap(), + DisplayMessage::Clear => write!(stdout, "{}", termion::clear::All).unwrap(), + DisplayMessage::Enter => write!( + stdout, + "{}{}", + termion::clear::CurrentLine, + termion::cursor::Goto(3, 17) + ) + .unwrap(), + DisplayMessage::Input(k) => display_key(&mut stdout, k), + DisplayMessage::Debug(s) => write!( + stdout, + "{}{}{}{}{}", + termion::cursor::Save, + termion::cursor::Goto(3, 25), + termion::clear::CurrentLine, + s, + termion::cursor::Restore + ) + .unwrap(), + DisplayMessage::Exit => break, + } + stdout.flush().unwrap(); + } + write!(stdout, "{}", termion::clear::All,).unwrap(); + stdout.flush().unwrap(); + }); +} + +fn start_keyboard_input_thread( + display_sender: Sender, + waiting_server_msg_sender: Sender, +) -> Receiver { let (sender, receiver) = channel(); thread::spawn(move || { + let mut buffer = String::new(); + let stdin = stdin(); + let stdin = stdin.lock(); + let mut bytes = stdin.bytes(); loop { - let mut buffer = String::new(); - //wait for user input - io::stdin().read_line(&mut buffer).unwrap(); - sender.send(buffer).unwrap(); + let b = bytes.next().unwrap().unwrap(); + match b { + b'q' => { + sender.send(String::from(EXIT_MSG)).unwrap(); + waiting_server_msg_sender + .send(WaitingServerMsg::UserCanceled) + .unwrap(); + break; + } + b'\r' => { + display_sender.send(DisplayMessage::Enter).unwrap(); + sender.send(buffer.clone()).unwrap(); + if buffer == EXIT_MSG { + waiting_server_msg_sender + .send(WaitingServerMsg::UserCanceled) + .unwrap(); + } + buffer.clear(); + } + b => { + let c = b as char; + buffer.push_str(&c.to_string()); + display_sender + .send(DisplayMessage::Input(Key::Char(c))) + .unwrap(); + } + } } }); receiver } -fn setup_server_message_recv(stream: &UnixStream) -> io::Result> { +fn display_key(stdout: &mut RawTerminal, key: Key) { + match key { + Key::Char(c) => write!(stdout, "{}", c).unwrap(), + _ => {} + }; +} + +enum WaitingServerMsg { + MsgReceived, + UserCanceled, +} + +fn setup_server_message_recv( + stream: &UnixStream, + waiting_server_msg_sender: Sender, +) -> io::Result> { let (sender, receiver) = channel(); let thread_stream = stream.try_clone()?; @@ -193,6 +378,9 @@ fn setup_server_message_recv(stream: &UnixStream) -> io::Result let buffer = clichess::read_line_from_stream(&thread_stream).expect("Error message from server"); sender.send(buffer).unwrap(); + waiting_server_msg_sender + .send(WaitingServerMsg::MsgReceived) + .unwrap(); } }); Ok(receiver) @@ -217,24 +405,15 @@ fn wait_for_next_move( client: &mut Client, stream: &mut UnixStream, ) -> Result { - println!("Waiting for opponent move..."); + client + .display_sender + .send(DisplayMessage::Information(String::from( + "Waiting for opponent move...", + ))) + .unwrap(); send_request(client, stream, ClientRequest::WaitForNextMove); - let response; - loop { - thread::sleep(time::Duration::from_millis(10)); - { - 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, - } - } - } - if response.is_empty() { + let response = fetch_message_from_server(client); + if response == EXIT_MSG { Err(RecvPositionError::UserCanceledError) } else { let game_info: GameInfo = serde_json::from_str(&response).unwrap(); @@ -253,22 +432,10 @@ fn update_client(game_info: &GameInfo, client: &mut Client) { } fn fetch_message_from_server(client: &Client) -> String { - let response; - loop { - thread::sleep(time::Duration::from_millis(10)); - { - 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, - } - } + match client.waiting_server_msg_receiver.recv().unwrap() { + WaitingServerMsg::MsgReceived => client.server_message_recv.recv().unwrap(), + WaitingServerMsg::UserCanceled => client.keyboard_input_recv.recv().unwrap(), } - response } enum PromptedRoleResponse { @@ -292,9 +459,13 @@ fn prompt_user_for_role(client: &Client, stream: &mut UnixStream) -> PromptedRol } else { prompt = String::from("Do you want to play as Black (B) or Spectate (S)?"); } - println!("{}", prompt); - let mut input = String::from(client.keyboard_input_recv.recv().unwrap().trim()); - if input.trim() == EXIT_MSG { + client + .display_sender + .send(DisplayMessage::Information(prompt)) + .unwrap(); + let mut input = client.keyboard_input_recv.recv().unwrap(); + input = String::from(input.trim()); + if input == EXIT_MSG { return PromptedRoleResponse::Exit; } @@ -305,13 +476,21 @@ fn prompt_user_for_role(client: &Client, stream: &mut UnixStream) -> PromptedRol match clichess::parse_to_role(&input) { Ok(r) => { if !available_roles.contains(&r) { - println!("Sorry, this side is not available."); + client + .display_sender + .send(DisplayMessage::Message(String::from( + "Sorry, this side is not available.", + ))) + .unwrap(); return PromptedRoleResponse::Retry; } return PromptedRoleResponse::Role(r); } Err(e) => { - println!("{}", e); + client + .display_sender + .send(DisplayMessage::Message(format!("{}", e))) + .unwrap(); return PromptedRoleResponse::Retry; } }; diff --git a/src/bin/server.rs b/src/bin/server.rs index 7c81d07..4bf418d 100644 --- a/src/bin/server.rs +++ b/src/bin/server.rs @@ -89,6 +89,7 @@ fn handle_player( Ok(ClientRequest::WaitForNextMove) => { //return true if user exited. if wait_for_next_move(&mut stream, server) { + send_ok(&mut stream); break; } } @@ -207,7 +208,10 @@ fn wait_for_opponent_move(server: &Server) -> Result { /*nothing to do*/ } } match server.client_message_recv.try_recv() { - Ok(_) => returned_result = Err(RecvPositionError::UserCanceledError), + Ok(_) => { + println!("User exited."); + returned_result = Err(RecvPositionError::UserCanceledError); + } Err(TryRecvError::Disconnected) => println!("Error: client disconnected."), Err(TryRecvError::Empty) => { /*nothing to do*/ } } diff --git a/src/lib.rs b/src/lib.rs index 1908650..0a0800c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,3 @@ -use colored::Colorize; use serde::{Deserialize, Serialize}; use shakmaty::fen::Fen; use shakmaty::san::ParseSanError; @@ -11,6 +10,7 @@ use std::io; use std::io::prelude::*; use std::io::{BufReader, Write}; use std::os::unix::net::UnixStream; +use termion::raw::RawTerminal; //message to send to the server to signal we disconnected. pub const EXIT_MSG: &str = "exit"; @@ -129,25 +129,26 @@ impl fmt::Display for MoveInputError { } } -pub fn board_representation(chess: &Chess, side: Color) -> String { - let mut s = String::from(""); +pub fn print_board_representation( + chess: &Chess, + side: Color, + stdout: &mut RawTerminal, +) { let mut rank_numbers; if side == Color::White { rank_numbers = 8; } else { rank_numbers = 1; } + let mut linenb = 6; for v in board_string_representation(chess, side) { + write!(stdout, "{}", termion::cursor::Goto(10, linenb)).unwrap(); for square_to_print in v { - s.push_str(&format!( - "{}", - square_to_print - .square_representation - .color(square_to_print.color) - .on_color(square_to_print.background_color) - )); + print_square(&square_to_print, stdout); } - s.push_str(&format!("{}\n\r", rank_numbers)); + linenb += 1; + resetcolors(stdout); + write!(stdout, "{}", rank_numbers).unwrap(); if side == Color::White { rank_numbers -= 1; } else { @@ -158,11 +159,35 @@ pub fn board_representation(chess: &Chess, side: Color) -> String { if side == Color::Black { files.reverse(); } + resetcolors(stdout); + + write!(stdout, "{}", termion::cursor::Goto(10, linenb)).unwrap(); for file in files { - s.push_str(&format!("{} ", file)); + write!(stdout, "{} ", file).unwrap(); } - s.push_str("\n\r"); - s + write!(stdout, "{}", termion::cursor::Goto(5, 15)).unwrap(); + stdout.flush().unwrap(); +} + +fn print_square(square_to_print: &SquareToPrint, stdout: &mut RawTerminal) { + match square_to_print.color.as_ref() { + "black" => write!(stdout, "{}", termion::color::Fg(termion::color::Black)), + _ => write!(stdout, "{}", termion::color::Fg(termion::color::Reset)), + } + .unwrap(); + + match square_to_print.background_color.as_ref() { + "white" => write!(stdout, "{}", termion::color::Bg(termion::color::White)), + "green" => write!(stdout, "{}", termion::color::Bg(termion::color::Green)), + _ => write!(stdout, "{}", termion::color::Bg(termion::color::Reset)), + } + .unwrap(); + write!(stdout, "{}", square_to_print.square_representation).unwrap(); +} + +fn resetcolors(stdout: &mut RawTerminal) { + write!(stdout, "{}", termion::color::Fg(termion::color::Reset)).unwrap(); + write!(stdout, "{}", termion::color::Bg(termion::color::Reset)).unwrap(); } fn board_string_representation(chess: &Chess, side: Color) -> Vec> {