Start to create WS client for cli_player
This commit is contained in:
		
							
								
								
									
										4
									
								
								rust/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								rust/Cargo.lock
									
									
									
										generated
									
									
									
								
							@@ -465,14 +465,18 @@ dependencies = [
 | 
			
		||||
 "clap",
 | 
			
		||||
 "crossterm",
 | 
			
		||||
 "env_logger",
 | 
			
		||||
 "futures",
 | 
			
		||||
 "lazy_static",
 | 
			
		||||
 "log",
 | 
			
		||||
 "num",
 | 
			
		||||
 "num-derive",
 | 
			
		||||
 "num-traits",
 | 
			
		||||
 "sea_battle_backend",
 | 
			
		||||
 "serde_json",
 | 
			
		||||
 "serde_urlencoded",
 | 
			
		||||
 "textwrap",
 | 
			
		||||
 "tokio",
 | 
			
		||||
 "tokio-tungstenite",
 | 
			
		||||
 "tui",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -17,4 +17,8 @@ tokio = "1.21.2"
 | 
			
		||||
num = "0.4.0"
 | 
			
		||||
num-traits = "0.2.15"
 | 
			
		||||
num-derive = "0.3.3"
 | 
			
		||||
textwrap = "0.15.1"
 | 
			
		||||
textwrap = "0.15.1"
 | 
			
		||||
tokio-tungstenite = "0.17.2"
 | 
			
		||||
serde_urlencoded = "0.7.1"
 | 
			
		||||
futures = "0.3.23"
 | 
			
		||||
serde_json = "1.0.85"
 | 
			
		||||
@@ -41,6 +41,11 @@ impl CliArgs {
 | 
			
		||||
            .parse::<u16>()
 | 
			
		||||
            .expect("Failed to parse listen port!")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get local server address
 | 
			
		||||
    pub fn local_server_address(&self) -> String {
 | 
			
		||||
        format!("http://localhost:{}", self.listen_port())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
lazy_static::lazy_static! {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										95
									
								
								rust/cli_player/src/client.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								rust/cli_player/src/client.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,95 @@
 | 
			
		||||
use crate::cli_args::cli_args;
 | 
			
		||||
use crate::server;
 | 
			
		||||
use futures::{SinkExt, StreamExt};
 | 
			
		||||
use sea_battle_backend::data::GameRules;
 | 
			
		||||
use sea_battle_backend::human_player_ws::{ClientMessage, ServerMessage};
 | 
			
		||||
use sea_battle_backend::server::BotPlayQuery;
 | 
			
		||||
use sea_battle_backend::utils::{boxed_error, Res};
 | 
			
		||||
use tokio::net::TcpStream;
 | 
			
		||||
use tokio_tungstenite::tungstenite::Message;
 | 
			
		||||
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
 | 
			
		||||
 | 
			
		||||
/// Connection client
 | 
			
		||||
///
 | 
			
		||||
/// This structure acts as a wrapper around websocket connection that handles automatically parsing
 | 
			
		||||
/// of incoming messages and encoding of outgoing messages
 | 
			
		||||
pub struct Client {
 | 
			
		||||
    socket: WebSocketStream<MaybeTlsStream<TcpStream>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Client {
 | 
			
		||||
    /// Start to play against a bot
 | 
			
		||||
    ///
 | 
			
		||||
    /// When playing against a bot, local server is always used
 | 
			
		||||
    pub async fn start_bot_play(rules: &GameRules) -> Res<Self> {
 | 
			
		||||
        server::start_server_if_missing().await;
 | 
			
		||||
 | 
			
		||||
        Self::connect_url(
 | 
			
		||||
            &cli_args().local_server_address(),
 | 
			
		||||
            &format!(
 | 
			
		||||
                "/play/bot?{}",
 | 
			
		||||
                serde_urlencoded::to_string(&BotPlayQuery {
 | 
			
		||||
                    rules: rules.clone(),
 | 
			
		||||
                    player_name: "Human".to_string()
 | 
			
		||||
                })
 | 
			
		||||
                .unwrap()
 | 
			
		||||
            ),
 | 
			
		||||
        )
 | 
			
		||||
        .await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Do connect to a server, returning
 | 
			
		||||
    async fn connect_url(server: &str, uri: &str) -> Res<Self> {
 | 
			
		||||
        let mut url = server.replace("http", "ws");
 | 
			
		||||
        url.push_str(uri);
 | 
			
		||||
        log::debug!("Connecting to {}", url);
 | 
			
		||||
 | 
			
		||||
        let (socket, _) = tokio_tungstenite::connect_async(url).await?;
 | 
			
		||||
 | 
			
		||||
        Ok(Self { socket })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Receive next message from stream
 | 
			
		||||
    async fn recv_next_msg(&mut self) -> Res<ServerMessage> {
 | 
			
		||||
        loop {
 | 
			
		||||
            let chunk = match self.socket.next().await {
 | 
			
		||||
                None => return Err(boxed_error("No more message in queue!")),
 | 
			
		||||
                Some(d) => d,
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            match chunk? {
 | 
			
		||||
                Message::Text(t) => {
 | 
			
		||||
                    log::debug!("TEXT Got a text message from server!");
 | 
			
		||||
                    let msg: ServerMessage = serde_json::from_str(&t)?;
 | 
			
		||||
                    return Ok(msg);
 | 
			
		||||
                }
 | 
			
		||||
                Message::Binary(_) => {
 | 
			
		||||
                    log::debug!("BINARY Got an unexpected binary message");
 | 
			
		||||
                    return Err(boxed_error("Received an unexpected binary message!"));
 | 
			
		||||
                }
 | 
			
		||||
                Message::Ping(_) => {
 | 
			
		||||
                    log::debug!("PING Got a ping message from server");
 | 
			
		||||
                }
 | 
			
		||||
                Message::Pong(_) => {
 | 
			
		||||
                    log::debug!("PONG Got a pong message");
 | 
			
		||||
                }
 | 
			
		||||
                Message::Close(_) => {
 | 
			
		||||
                    log::debug!("CLOSE Got a close websocket message");
 | 
			
		||||
                    return Err(boxed_error("Server requested to close connection!"));
 | 
			
		||||
                }
 | 
			
		||||
                Message::Frame(_) => {
 | 
			
		||||
                    log::debug!("FRAME Got an unexpected frame from server!");
 | 
			
		||||
                    return Err(boxed_error("Got an unexpected frame!"));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Send a message through the stream
 | 
			
		||||
    pub async fn send_message(&mut self, msg: &ClientMessage) -> Res {
 | 
			
		||||
        self.socket
 | 
			
		||||
            .send(Message::Text(serde_json::to_string(&msg)?))
 | 
			
		||||
            .await?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
pub mod cli_args;
 | 
			
		||||
pub mod client;
 | 
			
		||||
pub mod constants;
 | 
			
		||||
pub mod server;
 | 
			
		||||
pub mod ui_screens;
 | 
			
		||||
 
 | 
			
		||||
@@ -39,6 +39,8 @@ impl Player for BotPlayer {
 | 
			
		||||
        true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn opponent_connected(&self) {}
 | 
			
		||||
 | 
			
		||||
    fn set_other_player_name(&self, _name: &str) {}
 | 
			
		||||
 | 
			
		||||
    fn query_boats_layout(&self, rules: &GameRules) {
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,8 @@ pub trait Player {
 | 
			
		||||
 | 
			
		||||
    fn is_bot(&self) -> bool;
 | 
			
		||||
 | 
			
		||||
    fn opponent_connected(&self);
 | 
			
		||||
 | 
			
		||||
    fn set_other_player_name(&self, name: &str);
 | 
			
		||||
 | 
			
		||||
    fn query_boats_layout(&self, rules: &GameRules);
 | 
			
		||||
@@ -240,6 +242,9 @@ where
 | 
			
		||||
        self.players.push(msg.0);
 | 
			
		||||
 | 
			
		||||
        if self.players.len() == 2 {
 | 
			
		||||
            self.players[0].opponent_connected();
 | 
			
		||||
            self.players[1].opponent_connected();
 | 
			
		||||
 | 
			
		||||
            self.query_boats_disposition();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,10 @@ impl Player for HumanPlayer {
 | 
			
		||||
        false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn opponent_connected(&self) {
 | 
			
		||||
        self.player.do_send(ServerMessage::OpponentConnected);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn set_other_player_name(&self, name: &str) {
 | 
			
		||||
        self.player.do_send(ServerMessage::SetOpponentName {
 | 
			
		||||
            name: name.to_string(),
 | 
			
		||||
 
 | 
			
		||||
@@ -47,6 +47,7 @@ pub enum ServerMessage {
 | 
			
		||||
    },
 | 
			
		||||
    InvalidInviteCode,
 | 
			
		||||
    WaitingForAnotherPlayer,
 | 
			
		||||
    OpponentConnected,
 | 
			
		||||
    SetOpponentName {
 | 
			
		||||
        name: String,
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
@@ -196,6 +196,9 @@ impl BotClient {
 | 
			
		||||
                    log::debug!("Got invalid invite code!");
 | 
			
		||||
                    return Ok(ClientEndResult::InvalidInviteCode);
 | 
			
		||||
                }
 | 
			
		||||
                ServerMessage::OpponentConnected => {
 | 
			
		||||
                    log::debug!("Opponent connected");
 | 
			
		||||
                }
 | 
			
		||||
                ServerMessage::QueryBoatsLayout { rules } => {
 | 
			
		||||
                    assert_eq!(&rules, &self.requested_rules);
 | 
			
		||||
                    log::debug!("Server requested boats layout");
 | 
			
		||||
 
 | 
			
		||||
@@ -1,2 +1,12 @@
 | 
			
		||||
use std::error::Error;
 | 
			
		||||
use std::fmt::Display;
 | 
			
		||||
use std::io::ErrorKind;
 | 
			
		||||
 | 
			
		||||
pub mod network_utils;
 | 
			
		||||
pub mod string_utils;
 | 
			
		||||
 | 
			
		||||
pub type Res<E = ()> = Result<E, Box<dyn Error>>;
 | 
			
		||||
 | 
			
		||||
pub fn boxed_error<D: Display>(msg: D) -> Box<dyn Error> {
 | 
			
		||||
    Box::new(std::io::Error::new(ErrorKind::Other, msg.to_string()))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user