Ready to implement game screen
This commit is contained in:
parent
4af2585a8b
commit
19993c560a
@ -1,20 +1,26 @@
|
|||||||
use crate::cli_args::cli_args;
|
use crate::cli_args::cli_args;
|
||||||
use crate::server;
|
use crate::server;
|
||||||
|
use futures::stream::{SplitSink, SplitStream};
|
||||||
use futures::{SinkExt, StreamExt};
|
use futures::{SinkExt, StreamExt};
|
||||||
use sea_battle_backend::data::GameRules;
|
use sea_battle_backend::data::GameRules;
|
||||||
use sea_battle_backend::human_player_ws::{ClientMessage, ServerMessage};
|
use sea_battle_backend::human_player_ws::{ClientMessage, ServerMessage};
|
||||||
use sea_battle_backend::server::BotPlayQuery;
|
use sea_battle_backend::server::BotPlayQuery;
|
||||||
use sea_battle_backend::utils::{boxed_error, Res};
|
use sea_battle_backend::utils::{boxed_error, Res};
|
||||||
|
use std::sync::mpsc;
|
||||||
|
use std::sync::mpsc::TryRecvError;
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio_tungstenite::tungstenite::Message;
|
use tokio_tungstenite::tungstenite::Message;
|
||||||
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
|
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
|
||||||
|
|
||||||
|
type WsStream = WebSocketStream<MaybeTlsStream<TcpStream>>;
|
||||||
|
|
||||||
/// Connection client
|
/// Connection client
|
||||||
///
|
///
|
||||||
/// This structure acts as a wrapper around websocket connection that handles automatically parsing
|
/// This structure acts as a wrapper around websocket connection that handles automatically parsing
|
||||||
/// of incoming messages and encoding of outgoing messages
|
/// of incoming messages and encoding of outgoing messages
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
socket: WebSocketStream<MaybeTlsStream<TcpStream>>,
|
sink: SplitSink<WsStream, Message>,
|
||||||
|
receiver: mpsc::Receiver<ServerMessage>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
@ -44,15 +50,36 @@ impl Client {
|
|||||||
url.push_str(uri);
|
url.push_str(uri);
|
||||||
log::debug!("Connecting to {}", url);
|
log::debug!("Connecting to {}", url);
|
||||||
|
|
||||||
|
// Connect to websocket & split streams
|
||||||
let (socket, _) = tokio_tungstenite::connect_async(url).await?;
|
let (socket, _) = tokio_tungstenite::connect_async(url).await?;
|
||||||
|
let (sink, mut stream) = socket.split();
|
||||||
|
|
||||||
Ok(Self { socket })
|
// Receive server message on a separate task
|
||||||
|
let (sender, receiver) = mpsc::channel();
|
||||||
|
tokio::task::spawn(async move {
|
||||||
|
loop {
|
||||||
|
match Self::recv_next_msg(&mut stream).await {
|
||||||
|
Ok(msg) => {
|
||||||
|
if let Err(e) = sender.send(msg.clone()) {
|
||||||
|
log::error!("Failed to forward ws message! {} (msg={:?})", e, msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed receive next message from websocket! {}", e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(Self { sink, receiver })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Receive next message from stream
|
/// Receive next message from stream
|
||||||
async fn recv_next_msg(&mut self) -> Res<ServerMessage> {
|
async fn recv_next_msg(stream: &mut SplitStream<WsStream>) -> Res<ServerMessage> {
|
||||||
loop {
|
loop {
|
||||||
let chunk = match self.socket.next().await {
|
let chunk = match stream.next().await {
|
||||||
None => return Err(boxed_error("No more message in queue!")),
|
None => return Err(boxed_error("No more message in queue!")),
|
||||||
Some(d) => d,
|
Some(d) => d,
|
||||||
};
|
};
|
||||||
@ -87,9 +114,23 @@ impl Client {
|
|||||||
|
|
||||||
/// Send a message through the stream
|
/// Send a message through the stream
|
||||||
pub async fn send_message(&mut self, msg: &ClientMessage) -> Res {
|
pub async fn send_message(&mut self, msg: &ClientMessage) -> Res {
|
||||||
self.socket
|
self.sink
|
||||||
.send(Message::Text(serde_json::to_string(&msg)?))
|
.send(Message::Text(serde_json::to_string(&msg)?))
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Try to receive next message from websocket, in a non-blocking way
|
||||||
|
pub async fn try_recv_next_message(&self) -> Res<Option<ServerMessage>> {
|
||||||
|
match self.receiver.try_recv() {
|
||||||
|
Ok(msg) => Ok(Some(msg)),
|
||||||
|
Err(TryRecvError::Empty) => Ok(None),
|
||||||
|
Err(TryRecvError::Disconnected) => Err(boxed_error("Receiver channel disconnected!")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Block until the next message from websocket is availabl
|
||||||
|
pub async fn recv_next_message(&self) -> Res<ServerMessage> {
|
||||||
|
Ok(self.receiver.recv()?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ use std::error::Error;
|
|||||||
use std::io;
|
use std::io;
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
|
|
||||||
use cli_player::cli_args::{cli_args, TestDevScreen};
|
|
||||||
use crossterm::event::DisableMouseCapture;
|
use crossterm::event::DisableMouseCapture;
|
||||||
use crossterm::event::EnableMouseCapture;
|
use crossterm::event::EnableMouseCapture;
|
||||||
use crossterm::execute;
|
use crossterm::execute;
|
||||||
@ -13,9 +12,15 @@ use env_logger::Env;
|
|||||||
use tui::backend::{Backend, CrosstermBackend};
|
use tui::backend::{Backend, CrosstermBackend};
|
||||||
use tui::Terminal;
|
use tui::Terminal;
|
||||||
|
|
||||||
|
use cli_player::cli_args::{cli_args, TestDevScreen};
|
||||||
|
use cli_player::client::Client;
|
||||||
use cli_player::server::start_server_if_missing;
|
use cli_player::server::start_server_if_missing;
|
||||||
|
use cli_player::ui_screens::configure_game_rules::GameRulesConfigurationScreen;
|
||||||
|
use cli_player::ui_screens::game_screen::GameScreen;
|
||||||
|
use cli_player::ui_screens::select_play_mode::{SelectPlayModeResult, SelectPlayModeScreen};
|
||||||
use cli_player::ui_screens::*;
|
use cli_player::ui_screens::*;
|
||||||
use sea_battle_backend::data::GameRules;
|
use sea_battle_backend::data::GameRules;
|
||||||
|
use sea_battle_backend::human_player_ws::ServerMessage;
|
||||||
|
|
||||||
/// Test code screens
|
/// Test code screens
|
||||||
async fn run_dev<B: Backend>(
|
async fn run_dev<B: Backend>(
|
||||||
@ -66,10 +71,39 @@ async fn run_dev<B: Backend>(
|
|||||||
|
|
||||||
async fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> Result<(), Box<dyn Error>> {
|
async fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> Result<(), Box<dyn Error>> {
|
||||||
if let Some(d) = cli_args().dev_screen {
|
if let Some(d) = cli_args().dev_screen {
|
||||||
run_dev(terminal, d).await
|
return run_dev(terminal, d).await;
|
||||||
} else {
|
}
|
||||||
// TODO : run app
|
|
||||||
Ok(())
|
let mut rules = GameRules::default();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match SelectPlayModeScreen::default().show(terminal)? {
|
||||||
|
// TODO : Play against random player
|
||||||
|
ScreenResult::Ok(SelectPlayModeResult::PlayRandom) => todo!(),
|
||||||
|
|
||||||
|
// Play against bot
|
||||||
|
ScreenResult::Ok(SelectPlayModeResult::PlayAgainstBot) => {
|
||||||
|
// First, ask for custom rules
|
||||||
|
rules = match GameRulesConfigurationScreen::new(rules.clone()).show(terminal)? {
|
||||||
|
ScreenResult::Ok(r) => r,
|
||||||
|
ScreenResult::Canceled => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Then connect to server
|
||||||
|
let client = Client::start_bot_play(&rules).await?;
|
||||||
|
|
||||||
|
// Wait for the server to become ready
|
||||||
|
while !matches!(
|
||||||
|
client.recv_next_message().await?,
|
||||||
|
ServerMessage::OpponentConnected
|
||||||
|
) {}
|
||||||
|
|
||||||
|
// Display game screen
|
||||||
|
GameScreen::new(client).show(terminal).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScreenResult::Canceled | ScreenResult::Ok(SelectPlayModeResult::Exit) => return Ok(()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
49
rust/cli_player/src/ui_screens/game_screen.rs
Normal file
49
rust/cli_player/src/ui_screens/game_screen.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
use std::io;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use crossterm::event;
|
||||||
|
use crossterm::event::{Event, KeyCode};
|
||||||
|
use tui::backend::Backend;
|
||||||
|
use tui::{Frame, Terminal};
|
||||||
|
|
||||||
|
use crate::client::Client;
|
||||||
|
use crate::constants::*;
|
||||||
|
use crate::ui_screens::ScreenResult;
|
||||||
|
|
||||||
|
pub struct GameScreen {
|
||||||
|
client: Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GameScreen {
|
||||||
|
pub fn new(client: Client) -> Self {
|
||||||
|
Self { client }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn show<B: Backend>(
|
||||||
|
mut self,
|
||||||
|
terminal: &mut Terminal<B>,
|
||||||
|
) -> io::Result<ScreenResult> {
|
||||||
|
let mut last_tick = Instant::now();
|
||||||
|
loop {
|
||||||
|
terminal.draw(|f| self.ui(f))?;
|
||||||
|
|
||||||
|
let timeout = TICK_RATE
|
||||||
|
.checked_sub(last_tick.elapsed())
|
||||||
|
.unwrap_or_else(|| Duration::from_secs(0));
|
||||||
|
|
||||||
|
if crossterm::event::poll(timeout)? {
|
||||||
|
if let Event::Key(key) = event::read()? {
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Char('q') => return Ok(ScreenResult::Canceled),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if last_tick.elapsed() >= TICK_RATE {
|
||||||
|
last_tick = Instant::now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ui<B: Backend>(&mut self, f: &mut Frame<B>) {}
|
||||||
|
}
|
@ -2,6 +2,7 @@ use std::fmt::Debug;
|
|||||||
|
|
||||||
pub mod configure_game_rules;
|
pub mod configure_game_rules;
|
||||||
pub mod confirm_dialog;
|
pub mod confirm_dialog;
|
||||||
|
pub mod game_screen;
|
||||||
pub mod input_screen;
|
pub mod input_screen;
|
||||||
pub mod popup_screen;
|
pub mod popup_screen;
|
||||||
pub mod select_bot_type;
|
pub mod select_bot_type;
|
||||||
@ -10,7 +11,7 @@ pub mod set_boats_layout;
|
|||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ScreenResult<E> {
|
pub enum ScreenResult<E = ()> {
|
||||||
Ok(E),
|
Ok(E),
|
||||||
Canceled,
|
Canceled,
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user