285 lines
9.2 KiB
Rust
285 lines
9.2 KiB
Rust
extern crate ctrlc;
|
|
use clichess::{GameInfo, RecvPositionError, UserRole, EXIT_MSG};
|
|
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::mpsc::{channel, Receiver, TryRecvError};
|
|
use std::sync::{Arc, Mutex};
|
|
use std::thread;
|
|
use std::time;
|
|
|
|
struct Client {
|
|
player: clichess::Player,
|
|
side: Color,
|
|
input_buffer: Arc<Mutex<String>>,
|
|
server_message_recv: Receiver<String>,
|
|
opponent_name: Option<String>,
|
|
}
|
|
|
|
fn main() {
|
|
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,
|
|
input_buffer: input_buffer.clone(),
|
|
server_message_recv,
|
|
opponent_name: Option::None,
|
|
};
|
|
|
|
match prompt_user_for_role(&client, &mut stream) {
|
|
Some(role) => client.player.role = role.clone(),
|
|
None => return,
|
|
};
|
|
if client.player.role == UserRole::Black {
|
|
client.side = Color::Black;
|
|
}
|
|
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 {
|
|
client
|
|
.opponent_name
|
|
.clone()
|
|
.map(|name| println!("{} played", &name[1..(name.len() - 1)]));
|
|
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_MSG {
|
|
break;
|
|
}
|
|
}
|
|
//update position after playing.
|
|
match get_current_position(&mut client) {
|
|
Ok(position) => current_position = position,
|
|
Err(_) => {
|
|
clichess::write_to_stream(&mut stream, String::from(EXIT_MSG)).unwrap();
|
|
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 setup_server_message_recv(stream: &UnixStream) -> io::Result<Receiver<String>> {
|
|
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 input;
|
|
loop {
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
input
|
|
}
|
|
|
|
//wait for next position from server, then return the current board.
|
|
fn get_current_position(client: &mut Client) -> Result<Chess, RecvPositionError> {
|
|
let response = fetch_message_from_server(client);
|
|
let game_info: GameInfo = serde_json::from_str(&response).unwrap();
|
|
if game_info.opponent_name != "" {
|
|
client.opponent_name = Some(game_info.opponent_name);
|
|
}
|
|
if response.is_empty() {
|
|
Err(RecvPositionError::UserCanceledError)
|
|
} else {
|
|
Ok(parse_position(&game_info.game_fen))
|
|
}
|
|
}
|
|
|
|
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,
|
|
}
|
|
}
|
|
}
|
|
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::Black) {
|
|
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_MSG {
|
|
clichess::write_to_stream(stream, String::from(EXIT_MSG)).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()
|
|
}
|