diff --git a/rust/cli_player/src/ui_screens/game_screen.rs b/rust/cli_player/src/ui_screens/game_screen.rs index 0dbf572..da8d824 100644 --- a/rust/cli_player/src/ui_screens/game_screen.rs +++ b/rust/cli_player/src/ui_screens/game_screen.rs @@ -12,6 +12,7 @@ use tui::{Frame, Terminal}; use sea_battle_backend::data::{Coordinates, CurrentGameMapStatus, CurrentGameStatus}; use sea_battle_backend::human_player_ws::{ClientMessage, ServerMessage}; +use sea_battle_backend::utils::time_utils::time; use sea_battle_backend::utils::Res; use crate::client::Client; @@ -95,6 +96,7 @@ pub struct GameScreen { invite_code: Option, status: GameStatus, opponent_name: Option, + game_last_update: u64, game: CurrentGameStatus, curr_shoot_position: Coordinates, last_opponent_fire_position: Coordinates, @@ -108,6 +110,7 @@ impl GameScreen { invite_code: None, status: GameStatus::Connecting, opponent_name: None, + game_last_update: 0, game: Default::default(), curr_shoot_position: Coordinates::new(0, 0), last_opponent_fire_position: Coordinates::invalid(), @@ -288,11 +291,13 @@ impl GameScreen { ServerMessage::OpponentMustFire { status } => { self.status = GameStatus::OpponentMustFire; + self.game_last_update = time(); self.game = status; } ServerMessage::RequestFire { status } => { self.status = GameStatus::MustFire; + self.game_last_update = time(); self.game = status; } @@ -303,11 +308,13 @@ impl GameScreen { } ServerMessage::LostGame { status } => { + self.game_last_update = time(); self.game = status; self.status = GameStatus::LostGame; } ServerMessage::WonGame { status } => { + self.game_last_update = time(); self.game = status; self.status = GameStatus::WonGame; } @@ -476,6 +483,17 @@ impl GameScreen { return HashMap::default(); } + // Add timeout (if required) + let mut timeout_str = String::new(); + if self.status == GameStatus::MustFire || self.status == GameStatus::OpponentMustFire { + if let Some(remaining) = self.game.remaining_time_for_strike { + let timeout = self.game_last_update + remaining; + if time() < timeout { + timeout_str = format!(" {} seconds left", timeout - time()); + } + } + } + // Draw main ui (default play UI) let player_map = self .player_map(&self.game.your_map, false) @@ -519,8 +537,10 @@ impl GameScreen { let buttons_width = buttons.iter().fold(0, |a, b| a + b.estimated_size().0 + 4); - let max_width = max(maps_width, status_text.len() as u16).max(buttons_width); - let total_height = 3 + maps_height + 3; + let max_width = max(maps_width, status_text.len() as u16) + .max(buttons_width) + .max(timeout_str.len() as u16); + let total_height = 3 + 1 + maps_height + 3; // Check if frame is too small if max_width > f.size().width || total_height > f.size().height { @@ -531,7 +551,8 @@ impl GameScreen { let chunks = Layout::default() .direction(Direction::Vertical) .constraints([ - Constraint::Length(3), + Constraint::Length(2), + Constraint::Length(2), Constraint::Length(maps_height), Constraint::Length(3), ]) @@ -541,6 +562,10 @@ impl GameScreen { let paragraph = Paragraph::new(status_text.as_str()); f.render_widget(paragraph, centered_text(&status_text, &chunks[0])); + // Render timeout + let paragraph = Paragraph::new(timeout_str.as_str()); + f.render_widget(paragraph, centered_text(&timeout_str, &chunks[1])); + // Render maps if show_both_maps { let maps_chunks = Layout::default() @@ -550,16 +575,16 @@ impl GameScreen { Constraint::Length(3), Constraint::Length(opponent_map_size.0), ]) - .split(chunks[1]); + .split(chunks[2]); 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]); + f.render_widget(opponent_map, chunks[2]); } else { - f.render_widget(player_map, chunks[1]); + f.render_widget(player_map, chunks[2]); drop(opponent_map); } } @@ -573,7 +598,7 @@ impl GameScreen { .map(|_| Constraint::Percentage(100 / buttons.len() as u16)) .collect::>(), ) - .split(chunks[2]); + .split(chunks[3]); for (idx, b) in buttons.into_iter().enumerate() { let target = centered_rect_size( diff --git a/rust/sea_battle_backend/src/data/current_game_status.rs b/rust/sea_battle_backend/src/data/current_game_status.rs index d8b8d8d..2294ae7 100644 --- a/rust/sea_battle_backend/src/data/current_game_status.rs +++ b/rust/sea_battle_backend/src/data/current_game_status.rs @@ -78,6 +78,7 @@ impl PrintableMap for PrintableCurrentGameMapStatus { #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Default)] pub struct CurrentGameStatus { + pub remaining_time_for_strike: Option, pub rules: GameRules, pub your_map: CurrentGameMapStatus, pub opponent_map: CurrentGameMapStatus, diff --git a/rust/sea_battle_backend/src/game.rs b/rust/sea_battle_backend/src/game.rs index 55b1907..cf51d71 100644 --- a/rust/sea_battle_backend/src/game.rs +++ b/rust/sea_battle_backend/src/game.rs @@ -257,6 +257,9 @@ impl Game { /// Get current game status for a specific player fn get_game_status_for_player(&self, id: usize) -> CurrentGameStatus { CurrentGameStatus { + remaining_time_for_strike: self.rules.strike_timeout.map(|v| { + ((self.curr_strike_request_started + v) as i64 - time() as i64).max(0) as u64 + }), rules: self.rules.clone(), your_map: self.player_map(id).current_map_status(false), opponent_map: self