Ready to implement game screen

This commit is contained in:
Pierre HUBERT 2022-10-15 11:45:45 +02:00
parent 4af2585a8b
commit 19993c560a
4 changed files with 136 additions and 11 deletions

View File

@ -1,20 +1,26 @@
use crate::cli_args::cli_args;
use crate::server;
use futures::stream::{SplitSink, SplitStream};
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 std::sync::mpsc;
use std::sync::mpsc::TryRecvError;
use tokio::net::TcpStream;
use tokio_tungstenite::tungstenite::Message;
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
type WsStream = WebSocketStream<MaybeTlsStream<TcpStream>>;
/// 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>>,
sink: SplitSink<WsStream, Message>,
receiver: mpsc::Receiver<ServerMessage>,
}
impl Client {
@ -44,15 +50,36 @@ impl Client {
url.push_str(uri);
log::debug!("Connecting to {}", url);
// Connect to websocket & split streams
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
async fn recv_next_msg(&mut self) -> Res<ServerMessage> {
async fn recv_next_msg(stream: &mut SplitStream<WsStream>) -> Res<ServerMessage> {
loop {
let chunk = match self.socket.next().await {
let chunk = match stream.next().await {
None => return Err(boxed_error("No more message in queue!")),
Some(d) => d,
};
@ -87,9 +114,23 @@ impl Client {
/// Send a message through the stream
pub async fn send_message(&mut self, msg: &ClientMessage) -> Res {
self.socket
self.sink
.send(Message::Text(serde_json::to_string(&msg)?))
.await?;
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()?)
}
}

View File

@ -2,7 +2,6 @@ use std::error::Error;
use std::io;
use std::io::ErrorKind;
use cli_player::cli_args::{cli_args, TestDevScreen};
use crossterm::event::DisableMouseCapture;
use crossterm::event::EnableMouseCapture;
use crossterm::execute;
@ -13,9 +12,15 @@ use env_logger::Env;
use tui::backend::{Backend, CrosstermBackend};
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::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 sea_battle_backend::data::GameRules;
use sea_battle_backend::human_player_ws::ServerMessage;
/// Test code screens
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>> {
if let Some(d) = cli_args().dev_screen {
run_dev(terminal, d).await
} else {
// TODO : run app
Ok(())
return run_dev(terminal, d).await;
}
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(()),
}
}
}

View 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>) {}
}

View File

@ -2,6 +2,7 @@ use std::fmt::Debug;
pub mod configure_game_rules;
pub mod confirm_dialog;
pub mod game_screen;
pub mod input_screen;
pub mod popup_screen;
pub mod select_bot_type;
@ -10,7 +11,7 @@ pub mod set_boats_layout;
pub mod utils;
#[derive(Debug)]
pub enum ScreenResult<E> {
pub enum ScreenResult<E = ()> {
Ok(E),
Canceled,
}