clichess/src/bin/client.rs

291 lines
9.4 KiB
Rust

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<AtomicBool>,
input_buffer: Arc<Mutex<String>>,
server_message_recv: Receiver<String>,
}
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(&current_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<Mutex<String>> {
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<AtomicBool> {
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<Receiver<String>> {
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<Chess, RecvPositionError> {
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<UserRole> {
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<UserRole> {
roles_from_str(&fetch_message_from_server(client)).unwrap()
}
fn roles_from_str(s: &str) -> Result<Vec<UserRole>, String> {
s.split(',').map(clichess::parse_to_role).collect()
}