Query boats layout
This commit is contained in:
		@@ -14,6 +14,16 @@ use crate::ui_screens::utils::centered_rect_size;
 | 
				
			|||||||
use crate::ui_screens::ScreenResult;
 | 
					use crate::ui_screens::ScreenResult;
 | 
				
			||||||
use crate::ui_widgets::button_widget::ButtonWidget;
 | 
					use crate::ui_widgets::button_widget::ButtonWidget;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Convenience function to ask for user confirmation
 | 
				
			||||||
 | 
					pub fn confirm<B: Backend>(terminal: &mut Terminal<B>, msg: &str) -> bool {
 | 
				
			||||||
 | 
					    matches!(
 | 
				
			||||||
 | 
					        ConfirmDialogScreen::new(msg)
 | 
				
			||||||
 | 
					            .show(terminal)
 | 
				
			||||||
 | 
					            .unwrap_or(ScreenResult::Canceled),
 | 
				
			||||||
 | 
					        ScreenResult::Ok(true)
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct ConfirmDialogScreen<'a> {
 | 
					pub struct ConfirmDialogScreen<'a> {
 | 
				
			||||||
    title: &'a str,
 | 
					    title: &'a str,
 | 
				
			||||||
    msg: &'a str,
 | 
					    msg: &'a str,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,49 +1,159 @@
 | 
				
			|||||||
use std::io;
 | 
					 | 
				
			||||||
use std::time::{Duration, Instant};
 | 
					use std::time::{Duration, Instant};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crossterm::event;
 | 
					use crossterm::event;
 | 
				
			||||||
use crossterm::event::{Event, KeyCode};
 | 
					use crossterm::event::{Event, KeyCode};
 | 
				
			||||||
 | 
					use sea_battle_backend::human_player_ws::{ClientMessage, ServerMessage};
 | 
				
			||||||
 | 
					use sea_battle_backend::utils::Res;
 | 
				
			||||||
use tui::backend::Backend;
 | 
					use tui::backend::Backend;
 | 
				
			||||||
use tui::{Frame, Terminal};
 | 
					use tui::{Frame, Terminal};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::client::Client;
 | 
					use crate::client::Client;
 | 
				
			||||||
use crate::constants::*;
 | 
					use crate::constants::*;
 | 
				
			||||||
 | 
					use crate::ui_screens::confirm_dialog::confirm;
 | 
				
			||||||
 | 
					use crate::ui_screens::popup_screen::PopupScreen;
 | 
				
			||||||
 | 
					use crate::ui_screens::set_boats_layout::SetBoatsLayoutScreen;
 | 
				
			||||||
use crate::ui_screens::ScreenResult;
 | 
					use crate::ui_screens::ScreenResult;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum GameStatus {
 | 
				
			||||||
 | 
					    Pending,
 | 
				
			||||||
 | 
					    WaitingForOpponentBoatsConfig,
 | 
				
			||||||
 | 
					    OpponentReady,
 | 
				
			||||||
 | 
					    Starting,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct GameScreen {
 | 
					pub struct GameScreen {
 | 
				
			||||||
    client: Client,
 | 
					    client: Client,
 | 
				
			||||||
 | 
					    status: GameStatus,
 | 
				
			||||||
 | 
					    opponent_name: Option<String>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl GameScreen {
 | 
					impl GameScreen {
 | 
				
			||||||
    pub fn new(client: Client) -> Self {
 | 
					    pub fn new(client: Client) -> Self {
 | 
				
			||||||
        Self { client }
 | 
					        Self {
 | 
				
			||||||
 | 
					            client,
 | 
				
			||||||
 | 
					            status: GameStatus::Pending,
 | 
				
			||||||
 | 
					            opponent_name: None,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub async fn show<B: Backend>(
 | 
					    pub async fn show<B: Backend>(mut self, terminal: &mut Terminal<B>) -> Res<ScreenResult> {
 | 
				
			||||||
        mut self,
 | 
					 | 
				
			||||||
        terminal: &mut Terminal<B>,
 | 
					 | 
				
			||||||
    ) -> io::Result<ScreenResult> {
 | 
					 | 
				
			||||||
        let mut last_tick = Instant::now();
 | 
					        let mut last_tick = Instant::now();
 | 
				
			||||||
        loop {
 | 
					        loop {
 | 
				
			||||||
 | 
					            // Update UI
 | 
				
			||||||
            terminal.draw(|f| self.ui(f))?;
 | 
					            terminal.draw(|f| self.ui(f))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let timeout = TICK_RATE
 | 
					            let timeout = TICK_RATE
 | 
				
			||||||
                .checked_sub(last_tick.elapsed())
 | 
					                .checked_sub(last_tick.elapsed())
 | 
				
			||||||
                .unwrap_or_else(|| Duration::from_secs(0));
 | 
					                .unwrap_or_else(|| Duration::from_secs(0));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Handle terminal events
 | 
				
			||||||
            if crossterm::event::poll(timeout)? {
 | 
					            if crossterm::event::poll(timeout)? {
 | 
				
			||||||
                if let Event::Key(key) = event::read()? {
 | 
					                if let Event::Key(key) = event::read()? {
 | 
				
			||||||
                    match key.code {
 | 
					                    match key.code {
 | 
				
			||||||
                        KeyCode::Char('q') => return Ok(ScreenResult::Canceled),
 | 
					                        // Leave game
 | 
				
			||||||
 | 
					                        KeyCode::Char('q')
 | 
				
			||||||
 | 
					                            if confirm(terminal, "Do you really want to leave game?") =>
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            return Ok(ScreenResult::Canceled);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
                        _ => {}
 | 
					                        _ => {}
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Handle incoming messages
 | 
				
			||||||
 | 
					            while let Some(msg) = self.client.try_recv_next_message().await? {
 | 
				
			||||||
 | 
					                match msg {
 | 
				
			||||||
 | 
					                    ServerMessage::SetInviteCode { .. } => unimplemented!(),
 | 
				
			||||||
 | 
					                    ServerMessage::InvalidInviteCode => unimplemented!(),
 | 
				
			||||||
 | 
					                    ServerMessage::WaitingForAnotherPlayer => unimplemented!(),
 | 
				
			||||||
 | 
					                    ServerMessage::OpponentConnected => unimplemented!(),
 | 
				
			||||||
 | 
					                    ServerMessage::SetOpponentName { name } => self.opponent_name = Some(name),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    ServerMessage::QueryBoatsLayout { rules } => {
 | 
				
			||||||
 | 
					                        match SetBoatsLayoutScreen::new(&rules)
 | 
				
			||||||
 | 
					                            .set_confirm_on_cancel(true)
 | 
				
			||||||
 | 
					                            .show(terminal)?
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            ScreenResult::Ok(layout) => {
 | 
				
			||||||
 | 
					                                self.client
 | 
				
			||||||
 | 
					                                    .send_message(&ClientMessage::BoatsLayout { layout })
 | 
				
			||||||
 | 
					                                    .await?
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                            ScreenResult::Canceled => {
 | 
				
			||||||
 | 
					                                return Ok(ScreenResult::Canceled);
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        };
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    ServerMessage::RejectedBoatsLayout { .. } => {
 | 
				
			||||||
 | 
					                        PopupScreen::new("Server rejected boats layout!! (is your version of SeaBattle up to date?)")
 | 
				
			||||||
 | 
					                            .show(terminal)?;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    ServerMessage::WaitingForOtherPlayerConfiguration => {
 | 
				
			||||||
 | 
					                        self.status = GameStatus::WaitingForOpponentBoatsConfig;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    ServerMessage::OpponentReady => {
 | 
				
			||||||
 | 
					                        self.status = GameStatus::OpponentReady;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    ServerMessage::GameStarting => {
 | 
				
			||||||
 | 
					                        self.status = GameStatus::Starting;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    ServerMessage::OpponentMustFire { .. } => {}
 | 
				
			||||||
 | 
					                    ServerMessage::RequestFire { .. } => {}
 | 
				
			||||||
 | 
					                    ServerMessage::FireResult { .. } => {}
 | 
				
			||||||
 | 
					                    ServerMessage::OpponentFireResult { .. } => {}
 | 
				
			||||||
 | 
					                    ServerMessage::LostGame { .. } => {}
 | 
				
			||||||
 | 
					                    ServerMessage::WonGame { .. } => {}
 | 
				
			||||||
 | 
					                    ServerMessage::OpponentRequestedRematch => {}
 | 
				
			||||||
 | 
					                    ServerMessage::OpponentAcceptedRematch => {}
 | 
				
			||||||
 | 
					                    ServerMessage::OpponentRejectedRematch => {}
 | 
				
			||||||
 | 
					                    ServerMessage::OpponentLeftGame => {}
 | 
				
			||||||
 | 
					                    ServerMessage::OpponentReplacedByBot => {}
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if last_tick.elapsed() >= TICK_RATE {
 | 
					            if last_tick.elapsed() >= TICK_RATE {
 | 
				
			||||||
                last_tick = Instant::now();
 | 
					                last_tick = Instant::now();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn ui<B: Backend>(&mut self, f: &mut Frame<B>) {}
 | 
					    fn opponent_name(&self) -> &str {
 | 
				
			||||||
 | 
					        self.opponent_name.as_deref().unwrap_or("opponent")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn ui<B: Backend>(&mut self, f: &mut Frame<B>) {
 | 
				
			||||||
 | 
					        // If game is still starting
 | 
				
			||||||
 | 
					        if matches!(self.status, GameStatus::Pending) {
 | 
				
			||||||
 | 
					            PopupScreen::new("Game is pending...").show_in_frame(f);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // If game is still starting
 | 
				
			||||||
 | 
					        if matches!(self.status, GameStatus::WaitingForOpponentBoatsConfig) {
 | 
				
			||||||
 | 
					            PopupScreen::new(&format!(
 | 
				
			||||||
 | 
					                "Waiting for boats configuration of {}...",
 | 
				
			||||||
 | 
					                self.opponent_name()
 | 
				
			||||||
 | 
					            ))
 | 
				
			||||||
 | 
					            .show_in_frame(f);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // If game is still starting
 | 
				
			||||||
 | 
					        if matches!(self.status, GameStatus::OpponentReady) {
 | 
				
			||||||
 | 
					            PopupScreen::new(&format!("{} is ready!", self.opponent_name())).show_in_frame(f);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // If game is still starting
 | 
				
			||||||
 | 
					        if matches!(self.status, GameStatus::Starting) {
 | 
				
			||||||
 | 
					            PopupScreen::new("Game is starting...").show_in_frame(f);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -67,6 +67,12 @@ impl<'a> PopupScreen<'a> {
 | 
				
			|||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Show message once message in a frame, without polling messages
 | 
				
			||||||
 | 
					    pub fn show_in_frame<B: Backend>(mut self, frame: &mut Frame<B>) {
 | 
				
			||||||
 | 
					        self.can_close = false;
 | 
				
			||||||
 | 
					        self.ui(frame)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn ui<B: Backend>(&mut self, f: &mut Frame<B>) {
 | 
					    fn ui<B: Backend>(&mut self, f: &mut Frame<B>) {
 | 
				
			||||||
        // Preprocess message
 | 
					        // Preprocess message
 | 
				
			||||||
        let lines = textwrap::wrap(self.msg, f.size().width as usize - 20);
 | 
					        let lines = textwrap::wrap(self.msg, f.size().width as usize - 20);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,6 +13,7 @@ use tui::{Frame, Terminal};
 | 
				
			|||||||
use sea_battle_backend::data::*;
 | 
					use sea_battle_backend::data::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::constants::*;
 | 
					use crate::constants::*;
 | 
				
			||||||
 | 
					use crate::ui_screens::confirm_dialog::confirm;
 | 
				
			||||||
use crate::ui_screens::utils::{centered_rect_size, centered_text};
 | 
					use crate::ui_screens::utils::{centered_rect_size, centered_text};
 | 
				
			||||||
use crate::ui_screens::ScreenResult;
 | 
					use crate::ui_screens::ScreenResult;
 | 
				
			||||||
use crate::ui_widgets::game_map_widget::{ColoredCells, GameMapWidget};
 | 
					use crate::ui_widgets::game_map_widget::{ColoredCells, GameMapWidget};
 | 
				
			||||||
@@ -23,6 +24,7 @@ pub struct SetBoatsLayoutScreen<'a> {
 | 
				
			|||||||
    curr_boat: usize,
 | 
					    curr_boat: usize,
 | 
				
			||||||
    layout: BoatsLayout,
 | 
					    layout: BoatsLayout,
 | 
				
			||||||
    rules: &'a GameRules,
 | 
					    rules: &'a GameRules,
 | 
				
			||||||
 | 
					    confirm_on_cancel: bool,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl<'a> SetBoatsLayoutScreen<'a> {
 | 
					impl<'a> SetBoatsLayoutScreen<'a> {
 | 
				
			||||||
@@ -32,9 +34,16 @@ impl<'a> SetBoatsLayoutScreen<'a> {
 | 
				
			|||||||
            layout: BoatsLayout::gen_random_for_rules(rules)
 | 
					            layout: BoatsLayout::gen_random_for_rules(rules)
 | 
				
			||||||
                .expect("Failed to generate initial boats layout"),
 | 
					                .expect("Failed to generate initial boats layout"),
 | 
				
			||||||
            rules,
 | 
					            rules,
 | 
				
			||||||
 | 
					            confirm_on_cancel: false,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Specify whether user should confirm his choice to cancel screen or not
 | 
				
			||||||
 | 
					    pub fn set_confirm_on_cancel(mut self, confirm: bool) -> Self {
 | 
				
			||||||
 | 
					        self.confirm_on_cancel = confirm;
 | 
				
			||||||
 | 
					        self
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn show<B: Backend>(
 | 
					    pub fn show<B: Backend>(
 | 
				
			||||||
        mut self,
 | 
					        mut self,
 | 
				
			||||||
        terminal: &mut Terminal<B>,
 | 
					        terminal: &mut Terminal<B>,
 | 
				
			||||||
@@ -56,7 +65,13 @@ impl<'a> SetBoatsLayoutScreen<'a> {
 | 
				
			|||||||
                let event = event::read()?;
 | 
					                let event = event::read()?;
 | 
				
			||||||
                if let Event::Key(key) = &event {
 | 
					                if let Event::Key(key) = &event {
 | 
				
			||||||
                    match key.code {
 | 
					                    match key.code {
 | 
				
			||||||
                        KeyCode::Char('q') => return Ok(ScreenResult::Canceled),
 | 
					                        KeyCode::Char('q') => {
 | 
				
			||||||
 | 
					                            if !self.confirm_on_cancel
 | 
				
			||||||
 | 
					                                || confirm(terminal, "Do you really want to quit?")
 | 
				
			||||||
 | 
					                            {
 | 
				
			||||||
 | 
					                                return Ok(ScreenResult::Canceled);
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        // Select next boat
 | 
					                        // Select next boat
 | 
				
			||||||
                        KeyCode::Char('n') => self.curr_boat += self.layout.number_of_boats() - 1,
 | 
					                        KeyCode::Char('n') => self.curr_boat += self.layout.number_of_boats() - 1,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,6 +26,7 @@ pub enum StartMode {
 | 
				
			|||||||
    PlayRandom,
 | 
					    PlayRandom,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// The messages a client could send to the server
 | 
				
			||||||
#[derive(serde::Deserialize, serde::Serialize, Debug)]
 | 
					#[derive(serde::Deserialize, serde::Serialize, Debug)]
 | 
				
			||||||
#[serde(tag = "type")]
 | 
					#[serde(tag = "type")]
 | 
				
			||||||
pub enum ClientMessage {
 | 
					pub enum ClientMessage {
 | 
				
			||||||
@@ -37,6 +38,10 @@ pub enum ClientMessage {
 | 
				
			|||||||
    RejectRematch,
 | 
					    RejectRematch,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// The list of messages that can be sent from the server to the client
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Messages types are ordered in the enum in a "kind of" chronogical order: most messages should be
 | 
				
			||||||
 | 
					/// sent only if the messages type below it have not already been sent.
 | 
				
			||||||
#[derive(Message)]
 | 
					#[derive(Message)]
 | 
				
			||||||
#[rtype(result = "()")]
 | 
					#[rtype(result = "()")]
 | 
				
			||||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
 | 
					#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user