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 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user