Display maps on play
This commit is contained in:
parent
375127eeee
commit
26d5f85c3c
@ -1,10 +1,15 @@
|
||||
use std::cmp::max;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crossterm::event;
|
||||
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::utils::Res;
|
||||
use tui::backend::Backend;
|
||||
use tui::layout::{Constraint, Direction, Layout};
|
||||
use tui::style::Color;
|
||||
use tui::widgets::Paragraph;
|
||||
use tui::{Frame, Terminal};
|
||||
|
||||
use crate::client::Client;
|
||||
@ -12,19 +17,44 @@ 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::utils::{centered_rect_size, centered_text};
|
||||
use crate::ui_screens::ScreenResult;
|
||||
use crate::ui_widgets::game_map_widget::{ColoredCells, GameMapWidget};
|
||||
|
||||
#[derive(Eq, PartialEq)]
|
||||
enum GameStatus {
|
||||
Pending,
|
||||
WaitingForOpponentBoatsConfig,
|
||||
OpponentReady,
|
||||
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 {
|
||||
client: Client,
|
||||
status: GameStatus,
|
||||
opponent_name: Option<String>,
|
||||
game_status: CurrentGameStatus,
|
||||
}
|
||||
|
||||
impl GameScreen {
|
||||
@ -33,6 +63,7 @@ impl GameScreen {
|
||||
client,
|
||||
status: GameStatus::Pending,
|
||||
opponent_name: None,
|
||||
game_status: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,6 +99,7 @@ impl GameScreen {
|
||||
ServerMessage::InvalidInviteCode => unimplemented!(),
|
||||
ServerMessage::WaitingForAnotherPlayer => unimplemented!(),
|
||||
ServerMessage::OpponentConnected => unimplemented!(),
|
||||
|
||||
ServerMessage::SetOpponentName { name } => self.opponent_name = Some(name),
|
||||
|
||||
ServerMessage::QueryBoatsLayout { rules } => {
|
||||
@ -103,10 +135,19 @@ impl GameScreen {
|
||||
self.status = GameStatus::Starting;
|
||||
}
|
||||
|
||||
ServerMessage::OpponentMustFire { .. } => {}
|
||||
ServerMessage::RequestFire { .. } => {}
|
||||
ServerMessage::OpponentMustFire { status } => {
|
||||
self.status = GameStatus::OpponentMustFire;
|
||||
self.game_status = status;
|
||||
}
|
||||
|
||||
ServerMessage::RequestFire { status } => {
|
||||
self.status = GameStatus::MustFire;
|
||||
self.game_status = status;
|
||||
}
|
||||
|
||||
ServerMessage::FireResult { .. } => {}
|
||||
ServerMessage::OpponentFireResult { .. } => {}
|
||||
|
||||
ServerMessage::LostGame { .. } => {}
|
||||
ServerMessage::WonGame { .. } => {}
|
||||
ServerMessage::OpponentRequestedRematch => {}
|
||||
@ -123,37 +164,138 @@ impl GameScreen {
|
||||
}
|
||||
}
|
||||
|
||||
fn can_fire(&self) -> bool {
|
||||
matches!(self.status, GameStatus::MustFire)
|
||||
}
|
||||
|
||||
fn opponent_name(&self) -> &str {
|
||||
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>) {
|
||||
// If game is still starting
|
||||
if matches!(self.status, GameStatus::Pending) {
|
||||
PopupScreen::new("Game is pending...").show_in_frame(f);
|
||||
let status_text = self
|
||||
.status
|
||||
.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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
// Draw main ui (default play UI)
|
||||
let player_map = self
|
||||
.player_map(&self.game_status.your_map)
|
||||
.set_title("YOUR map");
|
||||
|
||||
let mut opponent_map = self
|
||||
.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;
|
||||
}
|
||||
|
||||
// If game is still starting
|
||||
if matches!(self.status, GameStatus::OpponentReady) {
|
||||
PopupScreen::new(&format!("{} is ready!", self.opponent_name())).show_in_frame(f);
|
||||
return;
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([
|
||||
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
|
||||
if matches!(self.status, GameStatus::Starting) {
|
||||
PopupScreen::new("Game is starting...").show_in_frame(f);
|
||||
return;
|
||||
}
|
||||
// Render buttons
|
||||
// TODO : at the end of the game
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user