Query boats layout
This commit is contained in:
parent
a9f29e24fe
commit
375127eeee
@ -14,6 +14,16 @@ use crate::ui_screens::utils::centered_rect_size;
|
||||
use crate::ui_screens::ScreenResult;
|
||||
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> {
|
||||
title: &'a str,
|
||||
msg: &'a str,
|
||||
|
@ -1,49 +1,159 @@
|
||||
use std::io;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crossterm::event;
|
||||
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::{Frame, Terminal};
|
||||
|
||||
use crate::client::Client;
|
||||
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;
|
||||
|
||||
enum GameStatus {
|
||||
Pending,
|
||||
WaitingForOpponentBoatsConfig,
|
||||
OpponentReady,
|
||||
Starting,
|
||||
}
|
||||
|
||||
pub struct GameScreen {
|
||||
client: Client,
|
||||
status: GameStatus,
|
||||
opponent_name: Option<String>,
|
||||
}
|
||||
|
||||
impl GameScreen {
|
||||
pub fn new(client: Client) -> Self {
|
||||
Self { client }
|
||||
Self {
|
||||
client,
|
||||
status: GameStatus::Pending,
|
||||
opponent_name: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn show<B: Backend>(
|
||||
mut self,
|
||||
terminal: &mut Terminal<B>,
|
||||
) -> io::Result<ScreenResult> {
|
||||
pub async fn show<B: Backend>(mut self, terminal: &mut Terminal<B>) -> Res<ScreenResult> {
|
||||
let mut last_tick = Instant::now();
|
||||
loop {
|
||||
// Update UI
|
||||
terminal.draw(|f| self.ui(f))?;
|
||||
|
||||
let timeout = TICK_RATE
|
||||
.checked_sub(last_tick.elapsed())
|
||||
.unwrap_or_else(|| Duration::from_secs(0));
|
||||
|
||||
// Handle terminal events
|
||||
if crossterm::event::poll(timeout)? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
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 {
|
||||
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(())
|
||||
}
|
||||
|
||||
/// 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>) {
|
||||
// Preprocess message
|
||||
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 crate::constants::*;
|
||||
use crate::ui_screens::confirm_dialog::confirm;
|
||||
use crate::ui_screens::utils::{centered_rect_size, centered_text};
|
||||
use crate::ui_screens::ScreenResult;
|
||||
use crate::ui_widgets::game_map_widget::{ColoredCells, GameMapWidget};
|
||||
@ -23,6 +24,7 @@ pub struct SetBoatsLayoutScreen<'a> {
|
||||
curr_boat: usize,
|
||||
layout: BoatsLayout,
|
||||
rules: &'a GameRules,
|
||||
confirm_on_cancel: bool,
|
||||
}
|
||||
|
||||
impl<'a> SetBoatsLayoutScreen<'a> {
|
||||
@ -32,9 +34,16 @@ impl<'a> SetBoatsLayoutScreen<'a> {
|
||||
layout: BoatsLayout::gen_random_for_rules(rules)
|
||||
.expect("Failed to generate initial boats layout"),
|
||||
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>(
|
||||
mut self,
|
||||
terminal: &mut Terminal<B>,
|
||||
@ -56,7 +65,13 @@ impl<'a> SetBoatsLayoutScreen<'a> {
|
||||
let event = event::read()?;
|
||||
if let Event::Key(key) = &event {
|
||||
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
|
||||
KeyCode::Char('n') => self.curr_boat += self.layout.number_of_boats() - 1,
|
||||
|
@ -26,6 +26,7 @@ pub enum StartMode {
|
||||
PlayRandom,
|
||||
}
|
||||
|
||||
/// The messages a client could send to the server
|
||||
#[derive(serde::Deserialize, serde::Serialize, Debug)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum ClientMessage {
|
||||
@ -37,6 +38,10 @@ pub enum ClientMessage {
|
||||
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)]
|
||||
#[rtype(result = "()")]
|
||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
||||
|
Loading…
Reference in New Issue
Block a user