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::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()?)
|
||||
}
|
||||
}
|
||||
|
@ -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(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
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 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,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user