Compare commits

..

18 Commits

Author SHA1 Message Date
11d3b0755d Release 0.4.0 2020-12-06 22:49:16 +01:00
ae3791a24b Reset the board at the end of the game
The two players must press enter to reset. For spectators, the game is
stopped.
2020-12-06 22:47:02 +01:00
8377717cc3 Add binary to fetch fen from game
It can be used to show the game in external programs.
2020-12-06 22:46:02 +01:00
4748d61bca Refactor game loop 2020-11-29 21:50:26 +01:00
d167e1c84f Fix backspace display issue 2020-11-29 20:11:25 +01:00
30aee61cad Display help 2020-11-29 19:56:51 +01:00
55fe2fb538 Limit input size 2020-11-29 15:49:01 +01:00
00ea1759d4 Do not display unsupported key in debug 2020-11-29 15:46:07 +01:00
9c17bcfeb8 Fix prompt resetting when switching to ascii mode 2020-11-29 15:43:04 +01:00
27be72f3be fixup! Add ascii mode: @ print the board using only ascii 2020-11-29 01:51:21 +01:00
cb6ad49e85 Fix error display 2020-11-29 01:47:13 +01:00
69adc99e19 Add ascii mode: @ print the board using only ascii 2020-11-29 01:20:23 +01:00
4c54d9b5e5 Do not take client input when it's not his turn 2020-11-29 00:02:53 +01:00
8d098c1ea4 fixup! Fix display issues 2020-11-22 20:36:11 +01:00
fdb701dbf1 Fix display issues 2020-11-22 16:15:58 +01:00
11e3c7d48e Add termion dependency to manage UI 2020-11-15 21:18:03 +01:00
7f2ec8e957 Refactor messages between server and client 2020-09-19 00:17:35 +02:00
7a889356b3 Show opponent last move 2020-08-06 00:21:27 +02:00
6 changed files with 943 additions and 515 deletions

202
Cargo.lock generated
View File

@ -4,232 +4,172 @@
name = "arrayvec" name = "arrayvec"
version = "0.5.1" version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
[[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)",
]
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "0.1.7" version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.2.1" version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]] [[package]]
name = "btoi" name = "btoi"
version = "0.4.1" version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4ced8205e70d9e553d008d53ded735808fa6133597318d48f74fc2bf9861471"
dependencies = [ dependencies = [
"num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits",
] ]
[[package]]
name = "cc"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "clichess" name = "clichess"
version = "0.2.0" version = "0.3.0"
dependencies = [ dependencies = [
"colored 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde",
"ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json",
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "shakmaty",
"serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)", "termion",
"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)",
]
[[package]]
name = "ctrlc"
version = "3.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "0.4.4" version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.66" version = "0.2.66"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
[[package]]
name = "nix"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.10" version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4"
dependencies = [ 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]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.6" version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27"
dependencies = [ dependencies = [
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.2" version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
dependencies = [ 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]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.2" version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.104" version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449"
dependencies = [ dependencies = [
"serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.104" version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64"
dependencies = [ dependencies = [
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "quote",
"syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", "syn",
] ]
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.44" version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7"
dependencies = [ dependencies = [
"itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "itoa",
"ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "ryu",
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "serde",
] ]
[[package]] [[package]]
name = "shakmaty" name = "shakmaty"
version = "0.16.2" version = "0.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f8f7fd48325e0b530cd257df0aa735efeefc923c70133d412f05d69de69108"
dependencies = [ dependencies = [
"arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "arrayvec",
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags",
"btoi 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "btoi",
] ]
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.11" version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff0acdb207ae2fe6d5976617f887eb1e35a2ba52c13c7234c790960cdad9238"
dependencies = [ dependencies = [
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "quote",
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "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]] [[package]]
name = "unicode-xid" name = "unicode-xid"
version = "0.2.0" version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
[[package]]
name = "void"
version = "1.0.2"
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 cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd"
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
"checksum colored 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "433e7ac7d511768127ed85b0c4947f47a254131e37864b2dc13f52aa32cd37e5"
"checksum ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7dfd2d8b4c82121dfdff120f818e09fc4380b0b7e17a742081a89b94853e87f"
"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 nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce"
"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 void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
"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"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "clichess" name = "clichess"
version = "0.2.0" version = "0.4.0"
authors = ["Artlef <artlef@localhost>"] authors = ["Artlef <artlef@localhost>"]
edition = "2018" edition = "2018"
@ -8,7 +8,6 @@ edition = "2018"
[dependencies] [dependencies]
shakmaty = "0.16.2" shakmaty = "0.16.2"
colored = "1.9"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
ctrlc = "3.1.3" termion = "1.5.5"

View File

@ -1,21 +1,28 @@
extern crate ctrlc; extern crate termion;
use clichess::{GameInfo, RecvPositionError, UserRole, EXIT_MSG};
use serde_json::json; use clichess::{ClientRequest, GameInfo, RecvPositionError, UserRole, EXIT_MSG};
use shakmaty::fen::Fen; use shakmaty::{fen, Chess, Color, Outcome, Position, Setup};
use shakmaty::{Chess, Color, Outcome, Position, Setup};
use std::io; use std::io;
use std::os::unix::net::UnixStream; use std::os::unix::net::UnixStream;
use std::sync::mpsc::{channel, Receiver, TryRecvError}; use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::thread; use std::thread;
use std::time; use std::time;
use termion::event::{parse_event, Event, Key};
use std::io::{stdin, stdout, Read, Write};
use termion::raw::{IntoRawMode, RawTerminal};
struct Client { struct Client {
player: clichess::Player, player: clichess::Player,
side: Color, side: Color,
input_buffer: Arc<Mutex<String>>, display_sender: Sender<DisplayMessage>,
waiting_server_msg_receiver: Receiver<WaitingServerMsg>,
keyboard_input_recv: Receiver<String>,
server_message_recv: Receiver<String>, server_message_recv: Receiver<String>,
opponent_name: Option<String>, opponent_name: Option<String>,
last_move: Option<String>,
is_player_turn: Arc<Mutex<bool>>,
} }
fn main() { fn main() {
@ -31,14 +38,19 @@ fn main() {
return; return;
} }
}; };
let username_pubkey_json = json!({
"username": username, let is_player_turn = Arc::new(Mutex::new(true));
"pubkey": public_key, let (display_sender, display_receiver) = channel();
}); let (waiting_server_msg_sender, waiting_server_msg_receiver) = channel();
clichess::write_to_stream(&mut stream, username_pubkey_json.to_string()).unwrap(); let keyboard_input_recv = start_keyboard_input_thread(
let input_buffer = setup_input_buffer(); display_sender.clone(),
waiting_server_msg_sender.clone(),
is_player_turn.clone(),
);
start_display_thread(display_receiver);
//start a thread to listen to server messages //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 { let mut client = Client {
player: clichess::Player { player: clichess::Player {
@ -47,97 +59,441 @@ fn main() {
public_key, public_key,
}, },
side: Color::White, side: Color::White,
input_buffer: input_buffer.clone(), display_sender: display_sender.clone(),
waiting_server_msg_receiver,
keyboard_input_recv,
server_message_recv, server_message_recv,
opponent_name: Option::None, opponent_name: Option::None,
last_move: Option::None,
is_player_turn,
}; };
match prompt_user_for_role(&client, &mut stream) { let login_result = login(&mut client, &mut stream);
Some(role) => client.player.role = role.clone(), if login_result == LoginResult::UserExited {
None => return, send_request(&client, &mut stream, ClientRequest::Exit);
}; client.display_sender.send(DisplayMessage::Exit).unwrap();
return;
}
client.display_sender.send(DisplayMessage::Clear).unwrap();
if client.player.role == UserRole::Black { if client.player.role == UserRole::Black {
client.side = Color::Black; client.side = Color::Black;
} }
if client.player.role == UserRole::Spectator { if client.player.role == UserRole::Spectator {
println!( client
.display_sender
.send(DisplayMessage::Information(format!(
"Hello, {} !\n\r You're spectating !", "Hello, {} !\n\r You're spectating !",
client.player.username client.player.username
) )))
.unwrap();
} else { } else {
println!( client
.display_sender
.send(DisplayMessage::Information(format!(
"Hello, {} !\n\r You're playing with the {} pieces", "Hello, {} !\n\r You're playing with the {} pieces",
client.player.username, client.player.username,
client.player.role.to_string() client.player.role.to_string()
)))
.unwrap();
}
loop {
client.display_sender.send(DisplayMessage::Help).unwrap();
if !game_loop(&mut client, &mut stream) {
break;
}
client.display_sender.send(DisplayMessage::Clear).unwrap();
}
client.display_sender.send(DisplayMessage::Clear).unwrap();
send_request(&client, &mut stream, ClientRequest::Exit);
client.display_sender.send(DisplayMessage::Exit).unwrap();
}
#[derive(PartialEq)]
enum LoginResult {
Success,
UserExited,
}
fn login(client: &mut Client, stream: &mut UnixStream) -> LoginResult {
let mut response = String::from("KO");
while &response != "OK" {
match prompt_user_for_role(client, stream) {
PromptedRoleResponse::Role(role) => {
client.player.role = role.clone();
response = send_request(
client,
stream,
ClientRequest::Login {
username: client.player.username.clone(),
pubkey: client.player.public_key.clone(),
role,
},
); );
} }
let mut current_position = fetch_initial_chess_position(&client); PromptedRoleResponse::Exit => return LoginResult::UserExited,
PromptedRoleResponse::Retry => continue,
};
}
LoginResult::Success
}
fn game_loop(client: &mut Client, stream: &mut UnixStream) -> bool {
let mut replay = false;
let mut current_position = fetch_initial_chess_position(client, stream);
loop { loop {
let is_player_turn_loop = clichess::is_player_turn(&client.player, current_position.turn());
{
let mut is_player_turn = client.is_player_turn.lock().unwrap();
*is_player_turn = is_player_turn_loop;
}
if is_player_turn_loop || client.player.role == UserRole::Spectator {
let mut message = String::from("");
let opponent_name = client.opponent_name.clone();
if opponent_name.is_some() {
message.push_str(&format!("{} played", opponent_name.expect("is some")));
client client
.opponent_name .last_move
.clone() .clone()
.map(|name| println!("{} played", &name[1..(name.len() - 1)])); .map(|chessmove| message.push_str(&format!(" {}", chessmove)));
println!( }
"{}", if message.len() > 0 {
clichess::board_representation(&current_position, client.side) client
); .display_sender
.send(DisplayMessage::Information(message))
.unwrap();
}
}
client
.display_sender
.send(DisplayMessage::Chessboard {
fen: fen::fen(&current_position),
side: client.side.clone(),
})
.unwrap();
//check if game is over. //check if game is over.
if current_position.is_game_over() { if current_position.is_game_over() {
break; break;
} }
if clichess::is_player_turn(&client.player, current_position.turn()) { if is_player_turn_loop {
//it's the user turn, taking user input //it's the user turn, taking user input
let input = read_user_input(&client); let mut input = client.keyboard_input_recv.recv().unwrap();
clichess::write_to_stream(&mut stream, String::from(input.trim())).unwrap(); input = String::from(input.trim());
if input.trim() == EXIT_MSG { if input == EXIT_MSG {
client.waiting_server_msg_receiver.recv().unwrap();
break; break;
} }
let response = send_request(client, stream, ClientRequest::Play(input.clone()));
if response == String::from("KO") {
client
.display_sender
.send(DisplayMessage::Message(format!("Invalid move: {}", input)))
.unwrap();
//go back to taking user input
continue;
} }
//update position after playing. client.last_move = Some(input);
match get_current_position(&mut client) { match get_current_position(client, stream) {
Ok(position) => current_position = position, Ok(position) => current_position = position,
Err(_) => { Err(_) => break,
clichess::write_to_stream(&mut stream, String::from(EXIT_MSG)).unwrap(); };
break; //clear message
} client
.display_sender
.send(DisplayMessage::Message(String::default()))
.unwrap();
} else {
match wait_for_next_move(client, stream) {
Ok(position) => current_position = position,
Err(_) => break,
}; };
} }
match current_position.outcome() { }
None => println!("Bye"), let end_message = match current_position.outcome() {
Some(Outcome::Draw) => println!("Draw game."), None => "",
Some(Outcome::Draw) => "Draw game.",
Some(Outcome::Decisive { winner }) => { Some(Outcome::Decisive { winner }) => {
if winner == Color::White { if winner == Color::White {
println!("White has won the game.") "White has won the game."
} else { } else {
println!("Black has won the game.") "Black has won the game."
} }
} }
} }
.to_string();
if !end_message.is_empty() {
//accept input
if client.player.role != UserRole::Spectator {
client
.display_sender
.send(DisplayMessage::Message(format!(
"{}\n\rPress enter to rematch.",
end_message
)))
.unwrap();
let mut is_player_turn = client.is_player_turn.lock().unwrap();
*is_player_turn = true;
} else {
client
.display_sender
.send(DisplayMessage::Message(format!("{}", end_message)))
.unwrap();
thread::sleep(time::Duration::from_secs(5));
return false;
}
let buffer = client.keyboard_input_recv.recv().unwrap();
if buffer == EXIT_MSG {
client.waiting_server_msg_receiver.recv().unwrap();
} else {
replay = true;
client
.display_sender
.send(DisplayMessage::Message(format!(
"{}\n\rWaiting for your opponent to restart...",
end_message
)))
.unwrap();
send_request(client, stream, ClientRequest::Reset);
}
}
replay
} }
fn setup_input_buffer() -> Arc<Mutex<String>> { fn send_request(client: &Client, stream: &mut UnixStream, request: ClientRequest) -> String {
let buf = Arc::new(Mutex::new(String::new())); let response: String;
let buf2 = buf.clone(); match serde_json::to_string(&request) {
Ok(request_str) => {
clichess::write_to_stream(stream, request_str).unwrap();
response = fetch_message_from_server(client);
}
Err(e) => {
client
.display_sender
.send(DisplayMessage::Message(format!(
"Error when parsing client request: {}",
e
)))
.unwrap();
response = String::from("KO");
}
};
response
}
#[derive(PartialEq, Clone)]
pub enum DisplayMessage {
Chessboard { fen: String, side: Color },
Message(String),
Information(String),
Debug(String),
Help,
Input(Key),
RemoveLastInput,
SwitchAsciiMode,
Enter,
Clear,
Exit,
}
fn start_display_thread(request_recv: Receiver<DisplayMessage>) {
thread::spawn(move || { 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();
let mut ascii_mode = false;
let mut last_fen_position = String::default();
let mut last_side = Color::White;
loop { loop {
let msg = request_recv.recv().unwrap();
match msg {
DisplayMessage::Chessboard { fen, side } => {
let current_position = clichess::parse_position(&fen);
last_fen_position = fen.clone();
last_side = side.clone();
clichess::print_board_representation(
&current_position,
side,
&mut stdout,
ascii_mode,
);
}
DisplayMessage::SwitchAsciiMode => {
ascii_mode = !ascii_mode;
write!(stdout, "{}", termion::cursor::Save,).unwrap();
clichess::print_board_representation(
&clichess::parse_position(&last_fen_position),
last_side,
&mut stdout,
ascii_mode,
);
write!(stdout, "{}", termion::cursor::Restore).unwrap();
}
DisplayMessage::Message(s) => write!(
stdout,
"{}{}{}{}{}",
termion::cursor::Save,
termion::cursor::Goto(3, 18),
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::RemoveLastInput => write!(
stdout,
"{}{}",
termion::cursor::Left(1),
termion::clear::UntilNewline
)
.unwrap(),
DisplayMessage::Help => print_help(&mut stdout),
DisplayMessage::Debug(s) => write!(
stdout,
"{}{}{}{}{}",
termion::cursor::Save,
termion::cursor::Goto(3, 30),
termion::clear::CurrentLine,
s,
termion::cursor::Restore
)
.unwrap(),
DisplayMessage::Exit => break,
}
stdout.flush().unwrap();
}
write!(stdout, "{}", termion::clear::All,).unwrap();
stdout.flush().unwrap();
});
}
fn print_help(stdout: &mut RawTerminal<io::StdoutLock>) {
write!(stdout, "{}", termion::cursor::Save).unwrap();
let help = [
"move are parsed using SAN (Nc3) or UCI (b1c3)",
"@ to toggle ascii mode",
"Ctrl-C to quit",
];
for (i, h) in help.iter().enumerate() {
write!(stdout, "{}{}", termion::cursor::Goto(3, 22 + (i as u16)), h).unwrap();
}
write!(stdout, "{}", termion::cursor::Restore).unwrap();
}
fn start_keyboard_input_thread(
display_sender: Sender<DisplayMessage>,
waiting_server_msg_sender: Sender<WaitingServerMsg>,
is_player_turn: Arc<Mutex<bool>>,
) -> Receiver<String> {
let (sender, receiver) = channel();
thread::spawn(move || {
let mut buffer = String::new(); let mut buffer = String::new();
//wait for user input let stdin = stdin();
io::stdin().read_line(&mut buffer).unwrap(); let stdin = stdin.lock();
let mut bytes = stdin.bytes();
loop {
let b = bytes.next().unwrap().unwrap();
let e = parse_event(b, &mut bytes).unwrap();
let is_player_turn = is_player_turn.lock().unwrap();
if !*is_player_turn
&& e != Event::Key(Key::Ctrl('c'))
&& e != Event::Key(Key::Char('@'))
{ {
let mut user_input = buf2.lock().unwrap(); //ignore input when it's not the client turn
if user_input.is_empty() { continue;
*user_input = buffer; }
} else { match e {
println!("It's not your turn !"); Event::Key(Key::Ctrl('c')) => {
sender.send(String::from(EXIT_MSG)).unwrap();
waiting_server_msg_sender
.send(WaitingServerMsg::UserCanceled)
.unwrap();
break;
}
Event::Key(Key::Char('\n')) => {
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();
}
Event::Key(Key::Char('@')) => {
display_sender
.send(DisplayMessage::SwitchAsciiMode)
.unwrap();
}
Event::Key(Key::Char(c)) => {
if buffer.len() < 10 {
buffer.push_str(&c.to_string());
display_sender
.send(DisplayMessage::Input(Key::Char(c)))
.unwrap();
} }
} }
Event::Key(Key::Backspace) => {
if buffer.len() > 0 {
buffer.pop();
display_sender
.send(DisplayMessage::RemoveLastInput)
.unwrap();
}
}
Event::Key(_) => continue,
Event::Mouse(_) => continue,
Event::Unsupported(_) => continue,
};
} }
}); });
buf receiver
} }
fn setup_server_message_recv(stream: &UnixStream) -> io::Result<Receiver<String>> { fn display_key(stdout: &mut RawTerminal<io::StdoutLock>, 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<WaitingServerMsg>,
) -> io::Result<Receiver<String>> {
let (sender, receiver) = channel(); let (sender, receiver) = channel();
let thread_stream = stream.try_clone()?; let thread_stream = stream.try_clone()?;
@ -147,80 +503,77 @@ fn setup_server_message_recv(stream: &UnixStream) -> io::Result<Receiver<String>
let buffer = let buffer =
clichess::read_line_from_stream(&thread_stream).expect("Error message from server"); clichess::read_line_from_stream(&thread_stream).expect("Error message from server");
sender.send(buffer).unwrap(); sender.send(buffer).unwrap();
waiting_server_msg_sender
.send(WaitingServerMsg::MsgReceived)
.unwrap();
} }
}); });
Ok(receiver) Ok(receiver)
} }
fn read_user_input(client: &Client) -> String { //get current position from server
//clear input before waiting for a new one fn get_current_position(
{ client: &mut Client,
let mut user_input = client.input_buffer.lock().unwrap(); stream: &mut UnixStream,
user_input.clear(); ) -> Result<Chess, RecvPositionError> {
} let response = send_request(client, stream, ClientRequest::GetGameInfo);
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() { if response.is_empty() {
Err(RecvPositionError::UserCanceledError) Err(RecvPositionError::UserCanceledError)
} else { } else {
Ok(parse_position(&game_info.game_fen)) let game_info: GameInfo = serde_json::from_str(&response).unwrap();
update_client(&game_info, client);
Ok(clichess::parse_position(&game_info.game_fen))
}
}
fn wait_for_next_move(
client: &mut Client,
stream: &mut UnixStream,
) -> Result<Chess, RecvPositionError> {
client
.display_sender
.send(DisplayMessage::Information(String::from(
"Waiting for opponent move...",
)))
.unwrap();
send_request(client, stream, ClientRequest::WaitForNextMove);
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();
update_client(&game_info, client);
Ok(clichess::parse_position(&game_info.game_fen))
}
}
fn update_client(game_info: &GameInfo, client: &mut Client) {
if game_info.opponent_name != "" {
client.opponent_name = Some(game_info.opponent_name.clone());
}
if game_info.last_move != "" {
client.last_move = Some(game_info.last_move.clone());
} }
} }
fn fetch_message_from_server(client: &Client) -> String { fn fetch_message_from_server(client: &Client) -> String {
let response; match client.waiting_server_msg_receiver.recv().unwrap() {
loop { WaitingServerMsg::MsgReceived => client.server_message_recv.recv().unwrap(),
thread::sleep(time::Duration::from_millis(10)); WaitingServerMsg::UserCanceled => client.keyboard_input_recv.recv().unwrap(),
{
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 { enum PromptedRoleResponse {
let setup: Fen = string.trim().parse().expect("Invalid message from server."); Role(UserRole),
let position: Chess = setup.position().expect("Invalid message from server."); Exit,
position Retry,
} }
fn prompt_user_for_role(client: &Client, stream: &mut UnixStream) -> Option<UserRole> { fn prompt_user_for_role(client: &Client, stream: &mut UnixStream) -> PromptedRoleResponse {
let mut role = None; let response = send_request(client, stream, ClientRequest::FetchAvailableRoles);
loop { let available_roles = roles_from_str(&response).unwrap();
let available_roles = fetch_available_roles(client);
let mut prompt = String::new(); let mut prompt = String::new();
if !available_roles.contains(&UserRole::White) if !available_roles.contains(&UserRole::White) && !available_roles.contains(&UserRole::Black) {
&& !available_roles.contains(&UserRole::Black)
{
prompt.push_str("You can only spectate this game. Press enter to start spectating."); prompt.push_str("You can only spectate this game. Press enter to start spectating.");
} else if available_roles.contains(&UserRole::White) { } else if available_roles.contains(&UserRole::White) {
prompt = String::from("Do you want to play as White (W)"); prompt = String::from("Do you want to play as White (W)");
@ -231,54 +584,48 @@ fn prompt_user_for_role(client: &Client, stream: &mut UnixStream) -> Option<User
} else { } else {
prompt = String::from("Do you want to play as Black (B) or Spectate (S)?"); prompt = String::from("Do you want to play as Black (B) or Spectate (S)?");
} }
println!("{}", prompt); client
//wait for user to give a correct answer. .display_sender
let mut input = String::from(read_user_input(client).trim()); .send(DisplayMessage::Information(prompt))
if input.trim() == EXIT_MSG { .unwrap();
clichess::write_to_stream(stream, String::from(EXIT_MSG)).unwrap(); let mut input = client.keyboard_input_recv.recv().unwrap();
break; input = String::from(input.trim());
if input == EXIT_MSG {
client.waiting_server_msg_receiver.recv().unwrap();
return PromptedRoleResponse::Exit;
} }
if !available_roles.contains(&UserRole::White) if !available_roles.contains(&UserRole::White) && !available_roles.contains(&UserRole::Black) {
&& !available_roles.contains(&UserRole::Black)
{
//we can only spectate //we can only spectate
input = String::from("S"); input = String::from("S");
} }
match clichess::parse_to_role(&input) { match clichess::parse_to_role(&input) {
Ok(r) => role = Some(r), Ok(r) => {
if !available_roles.contains(&r) {
client
.display_sender
.send(DisplayMessage::Message(String::from(
"Sorry, this side is not available.",
)))
.unwrap();
return PromptedRoleResponse::Retry;
}
return PromptedRoleResponse::Role(r);
}
Err(e) => { Err(e) => {
println!("{}", e); client
continue; .display_sender
.send(DisplayMessage::Message(format!("{}", e)))
.unwrap();
return PromptedRoleResponse::Retry;
} }
}; };
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 { fn fetch_initial_chess_position(client: &Client, stream: &mut UnixStream) -> Chess {
parse_position(&fetch_message_from_server(client)) let response = send_request(client, stream, ClientRequest::GetGameInfo);
} let game_info: GameInfo = serde_json::from_str(&response).unwrap();
clichess::parse_position(&game_info.game_fen)
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> { fn roles_from_str(s: &str) -> Result<Vec<UserRole>, String> {

View File

@ -0,0 +1,20 @@
use clichess::ClientRequest;
use std::os::unix::net::UnixStream;
fn main() {
let version = env!("CARGO_PKG_VERSION");
eprintln!("Running clichess fetch_game_info version {}", version);
let mut stream = match UnixStream::connect("/tmp/clichess.socket") {
Ok(sock) => sock,
Err(_) => {
eprintln!("clichess daemon is not running.");
return;
}
};
clichess::write_to_stream(&mut stream, serde_json::to_string(&ClientRequest::GetGameInfo).unwrap()).unwrap();
let response = clichess::read_line_from_stream(&stream).expect("Error message from server");
clichess::write_to_stream(&mut stream, serde_json::to_string(&ClientRequest::Exit).unwrap()).unwrap();
clichess::read_line_from_stream(&stream).expect("Error message from server");
println!("{}", response);
}

View File

@ -1,7 +1,6 @@
use clichess; use clichess;
use clichess::{GameInfo, Player, RecvPositionError, UserRole, EXIT_MSG}; use clichess::{ClientRequest, GameInfo, Player, RecvPositionError, UserRole};
use serde_json::Value; use shakmaty::{fen, Chess, Setup};
use shakmaty::{fen, Chess, Color, Setup};
use std::collections::HashMap; use std::collections::HashMap;
use std::fs; use std::fs;
use std::io; use std::io;
@ -18,7 +17,9 @@ struct Server {
others_serv_msg_recv: Receiver<GameInfo>, others_serv_msg_recv: Receiver<GameInfo>,
client_message_recv: Receiver<String>, client_message_recv: Receiver<String>,
cvar: Arc<(Mutex<bool>, Condvar)>, cvar: Arc<(Mutex<bool>, Condvar)>,
opponent_name: Option<String>, opponent_name: Arc<Mutex<Option<String>>>,
last_move: Arc<Mutex<Option<String>>>,
confirm_reset: Arc<(Mutex<bool>, Condvar)>,
} }
fn main() { fn main() {
@ -31,6 +32,9 @@ fn main() {
let listener = UnixListener::bind("/tmp/clichess.socket").unwrap(); let listener = UnixListener::bind("/tmp/clichess.socket").unwrap();
//boolean shared amongst all server instances
let confirm_reset = Arc::new((Mutex::new(false), Condvar::new()));
// accept connections and process them, spawning a new thread for each one // accept connections and process them, spawning a new thread for each one
for stream in listener.incoming() { for stream in listener.incoming() {
match stream { match stream {
@ -46,7 +50,9 @@ fn main() {
others_serv_msg_recv, others_serv_msg_recv,
client_message_recv, client_message_recv,
cvar: condvar_pair, cvar: condvar_pair,
opponent_name: Option::None, opponent_name: Arc::new(Mutex::new(Option::None)),
last_move: Arc::new(Mutex::new(Option::None)),
confirm_reset: confirm_reset.clone(),
}; };
/* connection succeeded */ /* connection succeeded */
thread::spawn(move || handle_player(stream, &mut server, others_serv_msg_sender)); thread::spawn(move || handle_player(stream, &mut server, others_serv_msg_sender));
@ -65,105 +71,125 @@ fn handle_player(
server: &mut Server, server: &mut Server,
others_serv_msg_sender: Sender<GameInfo>, others_serv_msg_sender: Sender<GameInfo>,
) { ) {
match initialize_client(&mut stream, &server, others_serv_msg_sender) { loop {
Ok((player, player_turn)) => main_loop(&mut stream, server, player, player_turn), //wait for input
Err(e) => println!("User id {} could not be initialized: {}", server.id, e), let input = server.client_message_recv.recv().unwrap();
}; println!("got input: {}", input);
//route to right function
match serde_json::from_str(&input) {
Ok(ClientRequest::FetchAvailableRoles) => fetch_available_roles(&mut stream, server),
Ok(ClientRequest::Login {
username,
pubkey,
role,
}) => login(
&mut stream,
server,
username,
pubkey,
role,
others_serv_msg_sender.clone(),
),
Ok(ClientRequest::GetGameInfo) => send_game_info(&mut stream, server),
Ok(ClientRequest::WaitForNextMove) => {
//return true if user exited.
if wait_for_next_move(&mut stream, server) {
send_ok(&mut stream);
break;
}
}
Ok(ClientRequest::Play(movestr)) => play(&mut stream, server, movestr),
Ok(ClientRequest::Reset) => reset(&mut stream, server),
Ok(ClientRequest::Exit) => {
send_ok(&mut stream);
break;
}
Err(e) => {
println!("Bad request: {}", e);
break;
}
}
}
player_disconnected(&server); player_disconnected(&server);
} }
fn initialize_client( fn login(
stream: &mut UnixStream, stream: &mut UnixStream,
server: &Server, server: &Server,
username: String,
pubkey: String,
role: UserRole,
others_serv_msg_sender: Sender<GameInfo>, others_serv_msg_sender: Sender<GameInfo>,
) -> Result<(Player, Color), RecvPositionError> { ) {
//create player println!("server {}, player {} is logging in..", server.id, username);
let player = create_player(server, stream, others_serv_msg_sender)?; //check_role
let player_turn: Color; let mut available_roles_after_choice =
//send current position to the player vec![UserRole::White, UserRole::Black, UserRole::Spectator];
println!(
"server {}, send current position to the player...",
server.id
);
{ {
let chess = server.chess_position.lock().unwrap(); let players = server.players.lock().unwrap();
player_turn = chess.turn(); for (_, (player, _, _)) in players.iter() {
clichess::write_to_stream(stream, fen::fen(&*chess)).unwrap(); match available_roles_after_choice
.iter()
.position(|r| *r == player.role)
{
Some(index) => available_roles_after_choice.remove(index),
None => continue,
};
}
}
if available_roles_after_choice.contains(&role) {
create_player(server, username, pubkey, role, others_serv_msg_sender);
send_ok(stream);
} else {
send_ko(stream);
} }
println!("server {}, current position to the player sent", server.id);
Ok((player, player_turn))
} }
fn main_loop(stream: &mut UnixStream, server: &mut Server, player: Player, mut player_turn: Color) { fn fetch_available_roles(stream: &mut UnixStream, server: &Server) {
loop { println!(
if clichess::is_player_turn(&player, player_turn) { "server {}, sending available role to the player...",
//let go of the lock while waiting for user input. server.id
println!("server {}, waiting for player move..", server.id); );
let input; let available_roles = compute_available_roles_to_str(server);
match server.client_message_recv.recv() { println!("Computed available_roles as str: {}", available_roles);
Ok(i) => input = i, clichess::write_to_stream(stream, available_roles.clone()).unwrap();
Err(e) => {
println!("Error while getting user input: {}", e);
break;
} }
};
if input == EXIT_MSG { fn send_game_info(stream: &mut UnixStream, server: &Server) {
break; println!("server {}, sending game info...", server.id);
}
{
let mut chess = server.chess_position.lock().unwrap();
let players = server.players.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 game_info_to_send = GameInfo {
game_fen: fen::fen(&*chess),
opponent_name: players
.get(&server.id)
.expect("current server is in the server list")
.0
.username
.clone(),
};
for (id, (_, others_serv_msg_sender, cvar_pair)) in players.iter() {
if server.id != *id {
others_serv_msg_sender
.send(game_info_to_send.clone())
.unwrap();
let (lock, cvar) = &**cvar_pair;
let mut message_sent = lock.lock().unwrap();
*message_sent = true;
cvar.notify_one();
}
}
if clichess::write_to_stream(
stream,
serde_json::to_string(&GameInfo {
game_fen: fen::fen(&*chess),
opponent_name: server.opponent_name.clone().unwrap_or_default(),
})
.unwrap(),
)
.is_err()
{
break;
}
player_turn = chess.turn();
}
} else {
let game_info_result = wait_for_opponent_move(server);
if game_info_result.is_err() {
break;
}
let game_info = game_info_result.unwrap();
clichess::write_to_stream(stream, serde_json::to_string(&game_info).unwrap()).unwrap();
server.opponent_name = Some(game_info.opponent_name);
let chess = server.chess_position.lock().unwrap(); let chess = server.chess_position.lock().unwrap();
player_turn = chess.turn(); let opponent_name = server
.opponent_name
.lock()
.unwrap()
.clone()
.unwrap_or_default();
let last_move = server.last_move.lock().unwrap().clone().unwrap_or_default();
let game_info = GameInfo {
game_fen: fen::fen(&*chess),
opponent_name,
last_move,
};
clichess::write_to_stream(stream, serde_json::to_string(&game_info).unwrap()).unwrap();
}
fn wait_for_next_move(stream: &mut UnixStream, server: &mut Server) -> bool {
println!("server {}, waiting for opponent move...", server.id);
//acknowledge the request to the client
send_ok(stream);
//wait either for the other server, or the client exit.
match wait_for_opponent_move(server) {
Ok(game_info) => {
update_from_game_info(server, &game_info);
let game_info_serialized = serde_json::to_string(&game_info).unwrap();
//send game_info to client
clichess::write_to_stream(stream, game_info_serialized).unwrap();
}
Err(_) => {
return true;
} }
} }
false
} }
fn wait_for_opponent_move(server: &Server) -> Result<GameInfo, RecvPositionError> { fn wait_for_opponent_move(server: &Server) -> Result<GameInfo, RecvPositionError> {
@ -188,38 +214,128 @@ fn wait_for_opponent_move(server: &Server) -> Result<GameInfo, RecvPositionError
Err(TryRecvError::Empty) => { /*nothing to do*/ } Err(TryRecvError::Empty) => { /*nothing to do*/ }
} }
match server.client_message_recv.try_recv() { match server.client_message_recv.try_recv() {
Ok(msg) => { Ok(_) => {
if msg == EXIT_MSG { println!("User exited.");
returned_result = Err(RecvPositionError::UserCanceledError); returned_result = Err(RecvPositionError::UserCanceledError);
} else {
println!("Client sent message while it's not its turn, this is an error.");
println!("Here is the message: {}", msg);
returned_result = Err(RecvPositionError::UserCanceledError);
}
} }
Err(TryRecvError::Disconnected) => println!("Error: client disconnected."), Err(TryRecvError::Disconnected) => println!("Error: client disconnected."),
Err(TryRecvError::Empty) => {} Err(TryRecvError::Empty) => { /*nothing to do*/ }
} }
returned_result returned_result
} }
fn update_from_game_info(server: &mut Server, game_info: &GameInfo) {
let mut chess_position = server.chess_position.lock().unwrap();
let mut opponent_name = server.opponent_name.lock().unwrap();
let mut last_move = server.last_move.lock().unwrap();
*chess_position = clichess::parse_position(&game_info.game_fen);
if game_info.opponent_name == "" {
*opponent_name = None;
} else {
*opponent_name = Some(game_info.opponent_name.clone());
}
if game_info.last_move == "" {
*last_move = None;
} else {
*last_move = Some(game_info.last_move.clone());
}
}
fn play(stream: &mut UnixStream, server: &Server, movestr: String) {
println!("server {}, user wants to play...", server.id);
let mut chess = server.chess_position.lock().unwrap();
let players = server.players.lock().unwrap();
let player = players.get(&server.id);
if player.is_none() || !clichess::is_player_turn(&player.unwrap().0, chess.turn()) {
send_ko(stream);
return;
}
println!("User tried to play {}", movestr.clone());
match clichess::try_to_play_move(&chess, movestr.clone()) {
Ok(played_chess) => *chess = played_chess,
Err(e) => {
println!("Error: {}", e);
send_ko(stream);
return;
}
};
let player_name = player
.expect("current server is in the server list")
.0
.username
.clone();
//send play to other servers
let game_info_to_send = GameInfo {
game_fen: fen::fen(&*chess),
opponent_name: player_name,
last_move: movestr.clone(),
};
for (id, (_, others_serv_msg_sender, cvar_pair)) in players.iter() {
if server.id != *id {
others_serv_msg_sender
.send(game_info_to_send.clone())
.unwrap();
let (lock, cvar) = &**cvar_pair;
let mut message_sent = lock.lock().unwrap();
*message_sent = true;
cvar.notify_one();
}
}
let opponent_name = server.opponent_name.lock().unwrap();
let last_move = server.last_move.lock().unwrap();
//send updated info to client
clichess::write_to_stream(
stream,
serde_json::to_string(&GameInfo {
game_fen: fen::fen(&*chess),
opponent_name: opponent_name.clone().unwrap_or_default(),
last_move: last_move.clone().unwrap_or_default(),
})
.unwrap(),
)
.unwrap();
}
fn reset(stream: &mut UnixStream, server: &Server) {
//wait for the other server.
println!("client {} wants to restart game.", server.id);
let (lock, cvar) = &*server.confirm_reset;
let mut reset_confirmed = lock.lock().unwrap();
if *reset_confirmed {
println!("restarting game.");
{
let mut chess = server.chess_position.lock().unwrap();
*chess = Chess::default();
}
*reset_confirmed = false;
cvar.notify_one();
} else {
//wait for other server
println!("client {} is waiting.", server.id);
*reset_confirmed = true;
while *reset_confirmed {
reset_confirmed = cvar.wait(reset_confirmed).unwrap();
}
}
send_ok(stream);
}
fn send_ok(stream: &mut UnixStream) {
clichess::write_to_stream(stream, String::from("OK")).unwrap();
}
fn send_ko(stream: &mut UnixStream) {
clichess::write_to_stream(stream, String::from("KO")).unwrap();
}
fn create_player( fn create_player(
server: &Server, server: &Server,
stream: &mut UnixStream, username: String,
public_key: String,
role: UserRole,
others_serv_msg_sender: Sender<GameInfo>, others_serv_msg_sender: Sender<GameInfo>,
) -> Result<Player, RecvPositionError> { ) {
println!("Creating player {}...", server.id);
//get player name and pubkey
let username_pubkey_json = server
.client_message_recv
.recv()
.expect("Player closed connection.");
let username_pubkey_value: Value = serde_json::from_str(&username_pubkey_json).unwrap();
let username = username_pubkey_value["username"].to_string();
let public_key = username_pubkey_value["pubkey"].to_string();
println!("got username: {}", username);
println!("got pubkey: {}", public_key);
let role = receive_user_role(server, stream)?;
let mut players = server.players.lock().unwrap(); let mut players = server.players.lock().unwrap();
let player = Player { let player = Player {
role, role,
@ -231,68 +347,6 @@ fn create_player(
(player.clone(), others_serv_msg_sender, server.cvar.clone()), (player.clone(), others_serv_msg_sender, server.cvar.clone()),
); );
println!("Created player {}", server.id); println!("Created player {}", server.id);
Ok(player)
}
fn receive_user_role(
server: &Server,
stream: &mut UnixStream,
) -> Result<UserRole, RecvPositionError> {
let mut chosen_role: UserRole;
loop {
//send available roles
let available_roles = compute_available_roles_to_str(server);
println!("Computed available_roles as str: {}", available_roles);
clichess::write_to_stream(stream, available_roles.clone()).unwrap();
//receive chosen role
let chosen_role_str = server
.client_message_recv
.recv()
.expect("Player closed connection.");
if chosen_role_str.trim() == EXIT_MSG {
return Err(RecvPositionError::UserCanceledError);
}
println!("Client id {} has chosen {}", server.id, chosen_role_str);
let chosen_role_parse = clichess::parse_to_role(&chosen_role_str);
if chosen_role_parse.is_err() {
return Err(RecvPositionError::ParsePositionError);
}
chosen_role = chosen_role_parse.unwrap();
//check if role is still available after the choice
{
let mut available_roles_after_choice =
vec![UserRole::White, UserRole::Black, UserRole::Spectator];
let players = server.players.lock().unwrap();
for (_, (player, _, _)) in players.iter() {
match available_roles_after_choice
.iter()
.position(|r| *r == player.role)
{
Some(index) => available_roles_after_choice.remove(index),
None => continue,
};
}
if available_roles_after_choice.contains(&chosen_role) {
println!("OK");
clichess::write_to_stream(stream, String::from("OK")).unwrap();
server
.client_message_recv
.recv()
.expect("Player closed connection.");
break;
} else {
println!("KO");
clichess::write_to_stream(stream, String::from("KO")).unwrap();
server
.client_message_recv
.recv()
.expect("Player closed connection.");
continue;
}
}
}
Ok(chosen_role)
} }
fn compute_available_roles_to_str(server: &Server) -> String { fn compute_available_roles_to_str(server: &Server) -> String {

View File

@ -1,5 +1,5 @@
use colored::Colorize;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use shakmaty::fen::Fen;
use shakmaty::san::ParseSanError; use shakmaty::san::ParseSanError;
use shakmaty::san::San; use shakmaty::san::San;
use shakmaty::san::SanError; use shakmaty::san::SanError;
@ -10,6 +10,7 @@ use std::io;
use std::io::prelude::*; use std::io::prelude::*;
use std::io::{BufReader, Write}; use std::io::{BufReader, Write};
use std::os::unix::net::UnixStream; use std::os::unix::net::UnixStream;
use termion::raw::RawTerminal;
//message to send to the server to signal we disconnected. //message to send to the server to signal we disconnected.
pub const EXIT_MSG: &str = "exit"; pub const EXIT_MSG: &str = "exit";
@ -25,15 +26,31 @@ pub struct Player {
pub struct GameInfo { pub struct GameInfo {
pub game_fen: String, pub game_fen: String,
pub opponent_name: String, pub opponent_name: String,
pub last_move: String,
} }
#[derive(PartialEq, Copy, Clone)] #[derive(Serialize, Deserialize, PartialEq, Copy, Clone)]
pub enum UserRole { pub enum UserRole {
White, White,
Black, Black,
Spectator, Spectator,
} }
#[derive(Serialize, Deserialize, PartialEq, Clone)]
pub enum ClientRequest {
FetchAvailableRoles,
Login {
username: String,
pubkey: String,
role: UserRole,
},
GetGameInfo,
WaitForNextMove,
Play(String),
Reset,
Exit,
}
impl fmt::Display for UserRole { impl fmt::Display for UserRole {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!( write!(
@ -113,25 +130,27 @@ impl fmt::Display for MoveInputError {
} }
} }
pub fn board_representation(chess: &Chess, side: Color) -> String { pub fn print_board_representation(
let mut s = String::from(""); chess: &Chess,
side: Color,
stdout: &mut RawTerminal<io::StdoutLock>,
ascii_mode: bool,
) {
let mut rank_numbers; let mut rank_numbers;
if side == Color::White { if side == Color::White {
rank_numbers = 8; rank_numbers = 8;
} else { } else {
rank_numbers = 1; rank_numbers = 1;
} }
for v in board_string_representation(chess, side) { let mut linenb = 6;
for v in board_string_representation(chess, side, ascii_mode) {
write!(stdout, "{}", termion::cursor::Goto(10, linenb)).unwrap();
for square_to_print in v { for square_to_print in v {
s.push_str(&format!( print_square(&square_to_print, stdout, ascii_mode);
"{}",
square_to_print
.square_representation
.color(square_to_print.color)
.on_color(square_to_print.background_color)
));
} }
s.push_str(&format!("{}\n\r", rank_numbers)); linenb += 1;
resetcolors(stdout);
write!(stdout, "{}", rank_numbers).unwrap();
if side == Color::White { if side == Color::White {
rank_numbers -= 1; rank_numbers -= 1;
} else { } else {
@ -142,14 +161,50 @@ pub fn board_representation(chess: &Chess, side: Color) -> String {
if side == Color::Black { if side == Color::Black {
files.reverse(); files.reverse();
} }
resetcolors(stdout);
write!(stdout, "{}", termion::cursor::Goto(10, linenb)).unwrap();
for file in files { for file in files {
s.push_str(&format!("{} ", file)); write!(stdout, "{} ", file).unwrap();
} }
s.push_str("\n\r"); write!(stdout, "{}", termion::cursor::Goto(5, 15)).unwrap();
s stdout.flush().unwrap();
} }
fn board_string_representation(chess: &Chess, side: Color) -> Vec<Vec<SquareToPrint>> { fn print_square(
square_to_print: &SquareToPrint,
stdout: &mut RawTerminal<io::StdoutLock>,
ascii_mode: bool,
) {
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();
if ascii_mode {
write!(stdout, "{}", termion::color::Fg(termion::color::Reset)).unwrap();
write!(stdout, "{}", termion::color::Bg(termion::color::Reset)).unwrap();
}
write!(stdout, "{}", square_to_print.square_representation).unwrap();
}
fn resetcolors(stdout: &mut RawTerminal<io::StdoutLock>) {
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,
ascii_mode: bool,
) -> Vec<Vec<SquareToPrint>> {
let mut full_board_to_print = Vec::new(); let mut full_board_to_print = Vec::new();
for _ in 0..8 { for _ in 0..8 {
full_board_to_print.push(Vec::new()) full_board_to_print.push(Vec::new())
@ -164,7 +219,7 @@ fn board_string_representation(chess: &Chess, side: Color) -> Vec<Vec<SquareToPr
} else { } else {
"green".to_string() "green".to_string()
}, },
square_representation: get_square_representation(&square, chess), square_representation: get_square_representation(&square, chess, ascii_mode),
}; };
full_board_to_print[j as usize].push(square_to_print); full_board_to_print[j as usize].push(square_to_print);
} }
@ -188,11 +243,18 @@ pub fn try_to_play_move(chess: &Chess, movestr: String) -> Result<Chess, MoveInp
Ok((*chess).clone().play(&mv)?) Ok((*chess).clone().play(&mv)?)
} }
fn get_square_representation(square: &Square, chess: &Chess) -> String { fn get_square_representation(square: &Square, chess: &Chess, ascii_mode: bool) -> String {
let board = (*chess).board(); let board = (*chess).board();
match board.piece_at(*square) { match board.piece_at(*square) {
Some(piece) => format!("{} ", piece_char_to_utf8(piece.char())), Some(piece) => format!(
None => " ".to_string(), "{} ",
if ascii_mode {
piece.char()
} else {
piece_char_to_utf8(piece.char())
}
),
None => if ascii_mode { ". " } else { " " }.to_string(),
} }
} }
@ -256,3 +318,9 @@ pub fn is_player_turn(player: &Player, playing_side: Color) -> bool {
(playing_side == Color::White && player.role == UserRole::White) (playing_side == Color::White && player.role == UserRole::White)
|| (playing_side == Color::Black && player.role == UserRole::Black) || (playing_side == Color::Black && player.role == UserRole::Black)
} }
pub 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
}