From ccd7ab749d65c987c950f61cd14db9e576bcd426 Mon Sep 17 00:00:00 2001
From: Artlef <artlef@localhost>
Date: Sun, 3 May 2020 18:31:18 +0200
Subject: [PATCH] WIP: lobby system

---
 src/bin/client.rs | 218 ++++++++++++++++++++++++----------------------
 src/bin/server.rs |  49 ++++++++++-
 src/lib.rs        |  22 +++--
 3 files changed, 178 insertions(+), 111 deletions(-)

diff --git a/src/bin/client.rs b/src/bin/client.rs
index 0f3e99f..416020a 100644
--- a/src/bin/client.rs
+++ b/src/bin/client.rs
@@ -1,9 +1,12 @@
 extern crate ctrlc;
-use clichess::{RecvPositionError, UserRole, EXIT_MSG};
+
+use clichess::Player;
+use clichess::{PlayingStatus, RecvPositionError, UserRole, EXIT_MSG};
 use serde_json::json;
 use shakmaty::fen::Fen;
 use shakmaty::{Chess, Color, Outcome, Position, Setup};
 use std::io;
+use std::iter::Iterator;
 use std::os::unix::net::UnixStream;
 use std::sync::atomic::{AtomicBool, Ordering};
 use std::sync::mpsc::{channel, Receiver, TryRecvError};
@@ -12,11 +15,21 @@ use std::thread;
 use std::time;
 
 struct Client {
-    player: clichess::Player,
+    player: Player,
     side: Color,
     running: Arc<AtomicBool>,
     input_buffer: Arc<Mutex<String>>,
     server_message_recv: Receiver<String>,
+    playing: bool,
+}
+
+enum LobbyLoopOutcome {
+    ClientLeft,
+}
+
+enum PlayingLoopOutcome {
+    PlayerLeft,
+    Outcome(Outcome),
 }
 
 fn main() {
@@ -41,33 +54,111 @@ fn main() {
     let server_message_recv = setup_server_message_recv(&stream).unwrap();
 
     let mut client = Client {
-        player: clichess::Player {
-            role: UserRole::Spectator,
+        player: Player {
+            id: 0,
             username: username,
             public_key: public_key,
+            playing_status: PlayingStatus::WaitingInLobby,
+            role: UserRole::Spectator,
         },
         side: Color::White,
         running: running.clone(),
         input_buffer: input_buffer.clone(),
         server_message_recv,
+        playing: false,
     };
 
-    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()
-        );
+    //get id from server
+    let player_id = fetch_message_from_server(&client).parse::<usize>().unwrap();
+    println!("got id {} from server", player_id);
+    client.player.id = player_id;
+
+    loop {
+        lobby_loop(&client, &mut stream);
+        match playing_loop(&client, &mut stream) {
+            PlayingLoopOutcome::PlayerLeft => {
+                println!("Bye");
+                break;
+            }
+            PlayingLoopOutcome::Outcome(Outcome::Draw) => println!("Draw game."),
+            PlayingLoopOutcome::Outcome(Outcome::Decisive { winner }) => {
+                if winner == Color::White {
+                    println!("White has won the game.")
+                } else {
+                    println!("Black has won the game.")
+                }
+            }
+        };
     }
+}
+
+fn lobby_loop(client: &Client, stream: &mut UnixStream) {
+    println!("Sending first refresh to server...");
+    clichess::write_to_stream(stream, String::from("refresh")).unwrap();
+    println!("Getting list of players from server...");
+    let players = fetch_players_from_server(client);
+    print_players(client, &players);
+    while client.running.load(Ordering::SeqCst) {
+        let mut user_input = client.input_buffer.lock().unwrap();
+        if user_input.is_empty() {
+            continue;
+        }
+        let mut sent_to_server = String::from(user_input.trim());
+        if sent_to_server == "refresh" || sent_to_server == "r" {
+            sent_to_server = String::from("refresh");
+        }
+        println!("Sending {} to server...", sent_to_server);
+        clichess::write_to_stream(stream, sent_to_server.clone()).unwrap();
+        user_input.clear();
+        if sent_to_server == "refresh" {
+            let players = fetch_players_from_server(client);
+            print_players(client, &players);
+        } else {
+            let response = fetch_message_from_server(client);
+            println!("response from server: {}", response);
+        }
+    }
+}
+
+fn fetch_players_from_server(client: &Client) -> Vec<Player> {
+    let data = fetch_message_from_server(client);
+    let v: Vec<Player> = serde_json::from_str(&data).unwrap();
+    v
+}
+
+fn print_players(client: &Client, players: &Vec<Player>) {
+    let other_players: Vec<&Player> = players.iter().filter(|p| p.id != client.player.id).collect();
+    if other_players.is_empty() {
+        println!("No one is connected.");
+    }
+    for player in other_players {
+        let mut message_to_add: String;
+        match player.playing_status {
+            PlayingStatus::WaitingInLobby => message_to_add = String::default(),
+            PlayingStatus::Challenging(id) => {
+                message_to_add = String::from(if id == client.player.id {
+                    " is challenging you"
+                } else {
+                    ""
+                })
+            }
+            PlayingStatus::PlayingAgainst(id) => {
+                message_to_add = " is playing against ".to_owned();
+                message_to_add.push_str(
+                    &players
+                        .iter()
+                        .filter(|p| p.id == id)
+                        .last()
+                        .expect("id is from the list")
+                        .username,
+                );
+            }
+        };
+        println!("{}{}", player.username, message_to_add);
+    }
+}
+
+fn playing_loop(client: &Client, stream: &mut UnixStream) -> PlayingLoopOutcome {
     let mut current_position = fetch_initial_chess_position(&client);
     loop {
         println!(
@@ -80,31 +171,24 @@ fn main() {
         }
         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();
+            let input = read_user_input(client);
+            clichess::write_to_stream(stream, String::from(input.trim())).unwrap();
             if input.trim() == EXIT_MSG {
                 break;
             }
         }
         //update position after playing.
-        match get_current_position(&client) {
+        match get_current_position(client) {
             Ok(position) => current_position = position,
             Err(_) => {
-                clichess::write_to_stream(&mut stream, String::from(EXIT_MSG)).unwrap();
+                clichess::write_to_stream(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.")
-            }
-        }
+        None => PlayingLoopOutcome::PlayerLeft,
+        Some(outcome) => PlayingLoopOutcome::Outcome(outcome),
     }
 }
 
@@ -121,8 +205,6 @@ fn setup_input_buffer() -> Arc<Mutex<String>> {
                 let mut user_input = buf2.lock().unwrap();
                 if user_input.is_empty() {
                     *user_input = buffer;
-                } else {
-                    println!("It's not your turn !");
                 }
             }
         }
@@ -216,74 +298,6 @@ fn parse_position(string: &str) -> Chess {
     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()
-}
diff --git a/src/bin/server.rs b/src/bin/server.rs
index 0de9acf..d68c9d1 100644
--- a/src/bin/server.rs
+++ b/src/bin/server.rs
@@ -1,5 +1,5 @@
 use clichess;
-use clichess::{Player, RecvPositionError, UserRole, EXIT_MSG};
+use clichess::{Player, RecvPositionError, UserRole, EXIT_MSG, PlayingStatus};
 use serde_json::Value;
 use shakmaty::{fen, Chess, Color, Setup};
 use std::collections::HashMap;
@@ -67,6 +67,15 @@ fn initialize_client(
     //create player
     let player = create_player(server, stream)?;
     let player_turn: Color;
+    //send its id to the player
+    println!(
+        "server {}, send current id: {} to the player...",
+        server.id,
+        player.id
+    );
+    clichess::write_to_stream(stream, player.id.to_string()).unwrap();
+    println!("entering lobby...");
+    lobby_loop(stream, server, &player);
     //send current position to the player
     println!(
         "server {}, send current position to the player...",
@@ -81,6 +90,38 @@ fn initialize_client(
     Ok((player, player_turn))
 }
 
+fn lobby_loop(stream: &mut UnixStream, server: &Server, player: &Player) {
+    loop {
+        let input;
+        match server.client_message_recv.recv() {
+            Ok(i) => input = i,
+            Err(e) => {
+                println!("Error while getting user input: {}", e);
+                break;
+            }
+        };
+        println!("got user input: {}", input);
+        if input == EXIT_MSG {
+            break;
+        } else if input == "refresh" {
+            send_players_to_client(stream, server);
+        } else {
+            clichess::write_to_stream(stream, format!("You challenged user {}", input));
+        }
+    }
+}
+
+fn send_players_to_client(stream: &mut UnixStream, server: &Server) {
+    println!("Sending players to client...");
+    let players = server.players.lock().unwrap();
+    let mut players_vec = Vec::new();
+    for (_, (player, _)) in players.iter() {
+        players_vec.push(player);
+    }
+    let json_msg = serde_json::to_string(&players_vec).unwrap();
+    clichess::write_to_stream(stream, json_msg).unwrap();
+}
+
 fn main_loop(stream: &mut UnixStream, server: &Server, player: Player, mut player_turn: Color) {
     loop {
         if clichess::is_player_turn(&player, player_turn) {
@@ -176,12 +217,14 @@ fn create_player(server: &Server, stream: &mut UnixStream) -> Result<Player, Rec
     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 role = UserRole::Spectator;
     let mut players = server.players.lock().unwrap();
     let player = Player {
-        role,
+        id: server.id,
         username,
         public_key,
+        playing_status: PlayingStatus::WaitingInLobby,
+        role
     };
     players.insert(
         server.id,
diff --git a/src/lib.rs b/src/lib.rs
index ce27fc6..99d3a3b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -9,18 +9,28 @@ use std::io;
 use std::io::prelude::*;
 use std::io::{BufReader, Write};
 use std::os::unix::net::UnixStream;
+use serde::{Deserialize, Serialize};
 
 //message to send to the server to signal we disconnected.
 pub const EXIT_MSG: &str = "exit";
 
-#[derive(Clone)]
-pub struct Player {
-    pub role: UserRole,
-    pub username: String,
-    pub public_key: String,
+#[derive(Clone, Serialize, Deserialize)]
+pub enum PlayingStatus {
+    WaitingInLobby,
+    Challenging(usize),
+    PlayingAgainst(usize)
 }
 
-#[derive(PartialEq, Copy, Clone)]
+#[derive(Clone, Serialize, Deserialize)]
+pub struct Player {
+    pub id: usize,
+    pub username: String,
+    pub public_key: String,
+    pub playing_status: PlayingStatus,
+    pub role: UserRole
+}
+
+#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)]
 pub enum UserRole {
     White,
     Black,