Display maps on play
This commit is contained in:
		@@ -1,10 +1,15 @@
 | 
				
			|||||||
 | 
					use std::cmp::max;
 | 
				
			||||||
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::data::{CurrentGameMapStatus, CurrentGameStatus};
 | 
				
			||||||
use sea_battle_backend::human_player_ws::{ClientMessage, ServerMessage};
 | 
					use sea_battle_backend::human_player_ws::{ClientMessage, ServerMessage};
 | 
				
			||||||
use sea_battle_backend::utils::Res;
 | 
					use sea_battle_backend::utils::Res;
 | 
				
			||||||
use tui::backend::Backend;
 | 
					use tui::backend::Backend;
 | 
				
			||||||
 | 
					use tui::layout::{Constraint, Direction, Layout};
 | 
				
			||||||
 | 
					use tui::style::Color;
 | 
				
			||||||
 | 
					use tui::widgets::Paragraph;
 | 
				
			||||||
use tui::{Frame, Terminal};
 | 
					use tui::{Frame, Terminal};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::client::Client;
 | 
					use crate::client::Client;
 | 
				
			||||||
@@ -12,19 +17,44 @@ use crate::constants::*;
 | 
				
			|||||||
use crate::ui_screens::confirm_dialog::confirm;
 | 
					use crate::ui_screens::confirm_dialog::confirm;
 | 
				
			||||||
use crate::ui_screens::popup_screen::PopupScreen;
 | 
					use crate::ui_screens::popup_screen::PopupScreen;
 | 
				
			||||||
use crate::ui_screens::set_boats_layout::SetBoatsLayoutScreen;
 | 
					use crate::ui_screens::set_boats_layout::SetBoatsLayoutScreen;
 | 
				
			||||||
 | 
					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};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Eq, PartialEq)]
 | 
				
			||||||
enum GameStatus {
 | 
					enum GameStatus {
 | 
				
			||||||
    Pending,
 | 
					    Pending,
 | 
				
			||||||
    WaitingForOpponentBoatsConfig,
 | 
					    WaitingForOpponentBoatsConfig,
 | 
				
			||||||
    OpponentReady,
 | 
					    OpponentReady,
 | 
				
			||||||
    Starting,
 | 
					    Starting,
 | 
				
			||||||
 | 
					    MustFire,
 | 
				
			||||||
 | 
					    OpponentMustFire,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl GameStatus {
 | 
				
			||||||
 | 
					    pub fn can_show_game_maps(&self) -> bool {
 | 
				
			||||||
 | 
					        self != &GameStatus::Pending
 | 
				
			||||||
 | 
					            && self != &GameStatus::WaitingForOpponentBoatsConfig
 | 
				
			||||||
 | 
					            && self != &GameStatus::OpponentReady
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn status_text(&self) -> &str {
 | 
				
			||||||
 | 
					        match self {
 | 
				
			||||||
 | 
					            GameStatus::Pending => "Game is pending...",
 | 
				
			||||||
 | 
					            GameStatus::WaitingForOpponentBoatsConfig => "Waiting for ### boats configuration",
 | 
				
			||||||
 | 
					            GameStatus::OpponentReady => "### is ready!",
 | 
				
			||||||
 | 
					            GameStatus::Starting => "Game is starting...",
 | 
				
			||||||
 | 
					            GameStatus::MustFire => "You must fire!",
 | 
				
			||||||
 | 
					            GameStatus::OpponentMustFire => "### must fire!",
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct GameScreen {
 | 
					pub struct GameScreen {
 | 
				
			||||||
    client: Client,
 | 
					    client: Client,
 | 
				
			||||||
    status: GameStatus,
 | 
					    status: GameStatus,
 | 
				
			||||||
    opponent_name: Option<String>,
 | 
					    opponent_name: Option<String>,
 | 
				
			||||||
 | 
					    game_status: CurrentGameStatus,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl GameScreen {
 | 
					impl GameScreen {
 | 
				
			||||||
@@ -33,6 +63,7 @@ impl GameScreen {
 | 
				
			|||||||
            client,
 | 
					            client,
 | 
				
			||||||
            status: GameStatus::Pending,
 | 
					            status: GameStatus::Pending,
 | 
				
			||||||
            opponent_name: None,
 | 
					            opponent_name: None,
 | 
				
			||||||
 | 
					            game_status: Default::default(),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -68,6 +99,7 @@ impl GameScreen {
 | 
				
			|||||||
                    ServerMessage::InvalidInviteCode => unimplemented!(),
 | 
					                    ServerMessage::InvalidInviteCode => unimplemented!(),
 | 
				
			||||||
                    ServerMessage::WaitingForAnotherPlayer => unimplemented!(),
 | 
					                    ServerMessage::WaitingForAnotherPlayer => unimplemented!(),
 | 
				
			||||||
                    ServerMessage::OpponentConnected => unimplemented!(),
 | 
					                    ServerMessage::OpponentConnected => unimplemented!(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    ServerMessage::SetOpponentName { name } => self.opponent_name = Some(name),
 | 
					                    ServerMessage::SetOpponentName { name } => self.opponent_name = Some(name),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    ServerMessage::QueryBoatsLayout { rules } => {
 | 
					                    ServerMessage::QueryBoatsLayout { rules } => {
 | 
				
			||||||
@@ -103,10 +135,19 @@ impl GameScreen {
 | 
				
			|||||||
                        self.status = GameStatus::Starting;
 | 
					                        self.status = GameStatus::Starting;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    ServerMessage::OpponentMustFire { .. } => {}
 | 
					                    ServerMessage::OpponentMustFire { status } => {
 | 
				
			||||||
                    ServerMessage::RequestFire { .. } => {}
 | 
					                        self.status = GameStatus::OpponentMustFire;
 | 
				
			||||||
 | 
					                        self.game_status = status;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    ServerMessage::RequestFire { status } => {
 | 
				
			||||||
 | 
					                        self.status = GameStatus::MustFire;
 | 
				
			||||||
 | 
					                        self.game_status = status;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    ServerMessage::FireResult { .. } => {}
 | 
					                    ServerMessage::FireResult { .. } => {}
 | 
				
			||||||
                    ServerMessage::OpponentFireResult { .. } => {}
 | 
					                    ServerMessage::OpponentFireResult { .. } => {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    ServerMessage::LostGame { .. } => {}
 | 
					                    ServerMessage::LostGame { .. } => {}
 | 
				
			||||||
                    ServerMessage::WonGame { .. } => {}
 | 
					                    ServerMessage::WonGame { .. } => {}
 | 
				
			||||||
                    ServerMessage::OpponentRequestedRematch => {}
 | 
					                    ServerMessage::OpponentRequestedRematch => {}
 | 
				
			||||||
@@ -123,37 +164,138 @@ impl GameScreen {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn can_fire(&self) -> bool {
 | 
				
			||||||
 | 
					        matches!(self.status, GameStatus::MustFire)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn opponent_name(&self) -> &str {
 | 
					    fn opponent_name(&self) -> &str {
 | 
				
			||||||
        self.opponent_name.as_deref().unwrap_or("opponent")
 | 
					        self.opponent_name.as_deref().unwrap_or("opponent")
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn player_map(&self, map: &CurrentGameMapStatus) -> GameMapWidget {
 | 
				
			||||||
 | 
					        // Sunk boats
 | 
				
			||||||
 | 
					        let sunk_boats = ColoredCells {
 | 
				
			||||||
 | 
					            color: Color::Red,
 | 
				
			||||||
 | 
					            cells: map
 | 
				
			||||||
 | 
					                .sunk_boats
 | 
				
			||||||
 | 
					                .iter()
 | 
				
			||||||
 | 
					                .flat_map(|b| b.all_coordinates())
 | 
				
			||||||
 | 
					                .collect::<Vec<_>>(),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Touched boats
 | 
				
			||||||
 | 
					        let touched_areas = ColoredCells {
 | 
				
			||||||
 | 
					            color: Color::Rgb(245, 45, 7),
 | 
				
			||||||
 | 
					            cells: map.successful_strikes.clone(),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Failed strikes
 | 
				
			||||||
 | 
					        let failed_strikes = ColoredCells {
 | 
				
			||||||
 | 
					            color: Color::Gray,
 | 
				
			||||||
 | 
					            cells: map.failed_strikes.clone(),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Boats
 | 
				
			||||||
 | 
					        let boats = ColoredCells {
 | 
				
			||||||
 | 
					            color: Color::Blue,
 | 
				
			||||||
 | 
					            cells: map
 | 
				
			||||||
 | 
					                .boats
 | 
				
			||||||
 | 
					                .0
 | 
				
			||||||
 | 
					                .iter()
 | 
				
			||||||
 | 
					                .flat_map(|b| b.all_coordinates())
 | 
				
			||||||
 | 
					                .collect::<Vec<_>>(),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        GameMapWidget::new(&self.game_status.rules)
 | 
				
			||||||
 | 
					            .add_colored_cells(sunk_boats)
 | 
				
			||||||
 | 
					            .add_colored_cells(touched_areas)
 | 
				
			||||||
 | 
					            .add_colored_cells(failed_strikes)
 | 
				
			||||||
 | 
					            .add_colored_cells(boats)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn ui<B: Backend>(&mut self, f: &mut Frame<B>) {
 | 
					    fn ui<B: Backend>(&mut self, f: &mut Frame<B>) {
 | 
				
			||||||
        // If game is still starting
 | 
					        let status_text = self
 | 
				
			||||||
        if matches!(self.status, GameStatus::Pending) {
 | 
					            .status
 | 
				
			||||||
            PopupScreen::new("Game is pending...").show_in_frame(f);
 | 
					            .status_text()
 | 
				
			||||||
 | 
					            .replace("###", self.opponent_name());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // If the game is in a state where game maps can not be shown
 | 
				
			||||||
 | 
					        if !self.status.can_show_game_maps() {
 | 
				
			||||||
 | 
					            PopupScreen::new(&status_text).show_in_frame(f);
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // If game is still starting
 | 
					        // Draw main ui (default play UI)
 | 
				
			||||||
        if matches!(self.status, GameStatus::WaitingForOpponentBoatsConfig) {
 | 
					        let player_map = self
 | 
				
			||||||
            PopupScreen::new(&format!(
 | 
					            .player_map(&self.game_status.your_map)
 | 
				
			||||||
                "Waiting for boats configuration of {}...",
 | 
					            .set_title("YOUR map");
 | 
				
			||||||
                self.opponent_name()
 | 
					
 | 
				
			||||||
            ))
 | 
					        let mut opponent_map = self
 | 
				
			||||||
            .show_in_frame(f);
 | 
					            .player_map(&self.game_status.opponent_map)
 | 
				
			||||||
 | 
					            .set_title(self.opponent_name());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.can_fire() {
 | 
				
			||||||
 | 
					            opponent_map = opponent_map
 | 
				
			||||||
 | 
					                .set_legend("Use arrows + enter\nor click on the place\nwhere you want\nto shoot");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Show both maps if there is enough room on the screen
 | 
				
			||||||
 | 
					        let player_map_size = player_map.estimated_size();
 | 
				
			||||||
 | 
					        let opponent_map_size = opponent_map.estimated_size();
 | 
				
			||||||
 | 
					        let both_maps_width = player_map_size.0 + opponent_map_size.0 + 3;
 | 
				
			||||||
 | 
					        let show_both_maps = both_maps_width <= f.size().width;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let maps_height = max(player_map_size.1, opponent_map_size.1);
 | 
				
			||||||
 | 
					        let maps_width = match show_both_maps {
 | 
				
			||||||
 | 
					            true => both_maps_width,
 | 
				
			||||||
 | 
					            false => max(player_map_size.0, opponent_map_size.0),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let max_width = max(maps_width, status_text.len() as u16);
 | 
				
			||||||
 | 
					        let total_height = 3 + maps_height + 3;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Check if frame is too small
 | 
				
			||||||
 | 
					        if max_width > f.size().width || total_height > f.size().height {
 | 
				
			||||||
 | 
					            PopupScreen::new("Screen too small!").show_in_frame(f);
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // If game is still starting
 | 
					        let chunks = Layout::default()
 | 
				
			||||||
        if matches!(self.status, GameStatus::OpponentReady) {
 | 
					            .direction(Direction::Vertical)
 | 
				
			||||||
            PopupScreen::new(&format!("{} is ready!", self.opponent_name())).show_in_frame(f);
 | 
					            .constraints([
 | 
				
			||||||
            return;
 | 
					                Constraint::Length(3),
 | 
				
			||||||
 | 
					                Constraint::Length(maps_height),
 | 
				
			||||||
 | 
					                Constraint::Length(3),
 | 
				
			||||||
 | 
					            ])
 | 
				
			||||||
 | 
					            .split(centered_rect_size(max_width, total_height, &f.size()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Render status
 | 
				
			||||||
 | 
					        let paragraph = Paragraph::new(status_text.as_str());
 | 
				
			||||||
 | 
					        f.render_widget(paragraph, centered_text(&status_text, &chunks[0]));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Render maps
 | 
				
			||||||
 | 
					        if show_both_maps {
 | 
				
			||||||
 | 
					            let maps_chunks = Layout::default()
 | 
				
			||||||
 | 
					                .direction(Direction::Horizontal)
 | 
				
			||||||
 | 
					                .constraints([
 | 
				
			||||||
 | 
					                    Constraint::Length(player_map_size.0),
 | 
				
			||||||
 | 
					                    Constraint::Length(3),
 | 
				
			||||||
 | 
					                    Constraint::Length(opponent_map_size.0),
 | 
				
			||||||
 | 
					                ])
 | 
				
			||||||
 | 
					                .split(chunks[1]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            f.render_widget(player_map, maps_chunks[0]);
 | 
				
			||||||
 | 
					            f.render_widget(opponent_map, maps_chunks[2]);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            // Render a single map
 | 
				
			||||||
 | 
					            if self.can_fire() {
 | 
				
			||||||
 | 
					                f.render_widget(opponent_map, chunks[1]);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                f.render_widget(player_map, chunks[1]);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // If game is still starting
 | 
					        // Render buttons
 | 
				
			||||||
        if matches!(self.status, GameStatus::Starting) {
 | 
					        // TODO : at the end of the game
 | 
				
			||||||
            PopupScreen::new("Game is starting...").show_in_frame(f);
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user