Start to create WS client for cli_player
This commit is contained in:
parent
d90560d330
commit
4af2585a8b
4
rust/Cargo.lock
generated
4
rust/Cargo.lock
generated
@ -465,14 +465,18 @@ dependencies = [
|
|||||||
"clap",
|
"clap",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
"futures",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
"num",
|
"num",
|
||||||
"num-derive",
|
"num-derive",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"sea_battle_backend",
|
"sea_battle_backend",
|
||||||
|
"serde_json",
|
||||||
|
"serde_urlencoded",
|
||||||
"textwrap",
|
"textwrap",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-tungstenite",
|
||||||
"tui",
|
"tui",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -18,3 +18,7 @@ num = "0.4.0"
|
|||||||
num-traits = "0.2.15"
|
num-traits = "0.2.15"
|
||||||
num-derive = "0.3.3"
|
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>()
|
.parse::<u16>()
|
||||||
.expect("Failed to parse listen port!")
|
.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! {
|
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 cli_args;
|
||||||
|
pub mod client;
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
pub mod ui_screens;
|
pub mod ui_screens;
|
||||||
|
@ -39,6 +39,8 @@ impl Player for BotPlayer {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn opponent_connected(&self) {}
|
||||||
|
|
||||||
fn set_other_player_name(&self, _name: &str) {}
|
fn set_other_player_name(&self, _name: &str) {}
|
||||||
|
|
||||||
fn query_boats_layout(&self, rules: &GameRules) {
|
fn query_boats_layout(&self, rules: &GameRules) {
|
||||||
|
@ -14,6 +14,8 @@ pub trait Player {
|
|||||||
|
|
||||||
fn is_bot(&self) -> bool;
|
fn is_bot(&self) -> bool;
|
||||||
|
|
||||||
|
fn opponent_connected(&self);
|
||||||
|
|
||||||
fn set_other_player_name(&self, name: &str);
|
fn set_other_player_name(&self, name: &str);
|
||||||
|
|
||||||
fn query_boats_layout(&self, rules: &GameRules);
|
fn query_boats_layout(&self, rules: &GameRules);
|
||||||
@ -240,6 +242,9 @@ where
|
|||||||
self.players.push(msg.0);
|
self.players.push(msg.0);
|
||||||
|
|
||||||
if self.players.len() == 2 {
|
if self.players.len() == 2 {
|
||||||
|
self.players[0].opponent_connected();
|
||||||
|
self.players[1].opponent_connected();
|
||||||
|
|
||||||
self.query_boats_disposition();
|
self.query_boats_disposition();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,10 @@ impl Player for HumanPlayer {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn opponent_connected(&self) {
|
||||||
|
self.player.do_send(ServerMessage::OpponentConnected);
|
||||||
|
}
|
||||||
|
|
||||||
fn set_other_player_name(&self, name: &str) {
|
fn set_other_player_name(&self, name: &str) {
|
||||||
self.player.do_send(ServerMessage::SetOpponentName {
|
self.player.do_send(ServerMessage::SetOpponentName {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
|
@ -47,6 +47,7 @@ pub enum ServerMessage {
|
|||||||
},
|
},
|
||||||
InvalidInviteCode,
|
InvalidInviteCode,
|
||||||
WaitingForAnotherPlayer,
|
WaitingForAnotherPlayer,
|
||||||
|
OpponentConnected,
|
||||||
SetOpponentName {
|
SetOpponentName {
|
||||||
name: String,
|
name: String,
|
||||||
},
|
},
|
||||||
|
@ -196,6 +196,9 @@ impl BotClient {
|
|||||||
log::debug!("Got invalid invite code!");
|
log::debug!("Got invalid invite code!");
|
||||||
return Ok(ClientEndResult::InvalidInviteCode);
|
return Ok(ClientEndResult::InvalidInviteCode);
|
||||||
}
|
}
|
||||||
|
ServerMessage::OpponentConnected => {
|
||||||
|
log::debug!("Opponent connected");
|
||||||
|
}
|
||||||
ServerMessage::QueryBoatsLayout { rules } => {
|
ServerMessage::QueryBoatsLayout { rules } => {
|
||||||
assert_eq!(&rules, &self.requested_rules);
|
assert_eq!(&rules, &self.requested_rules);
|
||||||
log::debug!("Server requested boats layout");
|
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 network_utils;
|
||||||
pub mod string_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()))
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user