extern crate ctrlc; use clichess::RecvPositionError; use clichess::UserRole; use serde_json::json; use shakmaty::fen::Fen; use shakmaty::{Chess, Color, Outcome, Position, Setup}; use std::io; use std::os::unix::net::UnixStream; use std::sync::atomic::{AtomicBool, Ordering}; 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, running: Arc, input_buffer: Arc>, server_message_recv: Receiver, } 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"); //send username and public key to server let mut stream = match UnixStream::connect("/tmp/clichess.socket") { Ok(sock) => sock, Err(_) => { println!("clichess daemon is not running."); 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(); //start a thread to listen to server messages let server_message_recv = setup_server_message_recv(&stream).unwrap(); let mut client = Client { player: clichess::Player { role: UserRole::Spectator, username: username, public_key: public_key, }, side: Color::White, running: running.clone(), input_buffer: input_buffer.clone(), server_message_recv, }; match prompt_user_for_role(&client, &mut stream) { Some(role) => client.player.role = role.clone(), None => return, }; if client.player.role == UserRole::Spectator { println!( "Hello, {} !\n\r You're spectating !", client.player.username ) } else { println!( "Hello, {} !\n\r You're playing with the {} pieces", client.player.username, client.player.role.to_string() ); } let mut current_position = fetch_initial_chess_position(&client); loop { println!( "{}", 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.player, current_position.turn()) { //it's the user turn, taking user input 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(&client) { Ok(position) => current_position = position, Err(RecvPositionError::UserCanceledError) => break, Err(RecvPositionError::CommunicationError) => break, Err(RecvPositionError::ParsePositionError) => break, }; } match current_position.outcome() { None => println!("Bye"), Some(Outcome::Draw) => println!("Draw game."), Some(Outcome::Decisive { winner }) => { if winner == Color::White { println!("White has won the game.") } else { println!("Black has won the game.") } } } } fn setup_input_buffer() -> Arc> { let buf = Arc::new(Mutex::new(String::new())); let buf2 = buf.clone(); 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 !"); } } } }); buf } fn setupctrlc() -> Arc { let running = Arc::new(AtomicBool::new(true)); let r = running.clone(); ctrlc::set_handler(move || { r.store(false, Ordering::SeqCst); }) .expect("Error setting Ctrl-C handler"); running } fn setup_server_message_recv(stream: &UnixStream) -> io::Result> { let buf = Arc::new(Mutex::new(String::new())); let (sender, receiver) = channel(); let thread_stream = stream.try_clone()?; thread::spawn(move || { loop { //wait for server message let buffer = clichess::read_line_from_stream(&thread_stream).expect("Error message from server"); sender.send(buffer).unwrap(); } }); 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(); } let mut input = String::new(); while client.running.load(Ordering::SeqCst) { 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; } } } if client.running.load(Ordering::SeqCst) { input } else { String::from("exit") } } //wait for next position from server, then return the current board. 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 client.running.load(Ordering::SeqCst) { 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, } } } 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 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::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 mut input = String::from(read_user_input(client).trim()); if input.trim() == "exit" { clichess::write_to_stream(stream, String::from("exit")).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; } 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() }