Display maps on play

This commit is contained in:
Pierre HUBERT 2022-10-15 14:46:10 +02:00
parent 375127eeee
commit 26d5f85c3c

View File

@ -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
}
}