diff --git a/rust/cli_player/src/ui_screens/configure_game_rules.rs b/rust/cli_player/src/ui_screens/configure_game_rules.rs index f539f3b..ab13e1f 100644 --- a/rust/cli_player/src/ui_screens/configure_game_rules.rs +++ b/rust/cli_player/src/ui_screens/configure_game_rules.rs @@ -130,31 +130,31 @@ impl GameRulesConfigurationScreen { KeyCode::Char(c) if ('0'..='9').contains(&c) => { let val = c.to_string().parse::().unwrap_or_default(); - if self.curr_field == EditingField::MapWidth { - if self.rules.map_width <= MAX_MAP_WIDTH { - self.rules.map_width *= 10; - self.rules.map_width += val; - } + if self.curr_field == EditingField::MapWidth + && self.rules.map_width <= MAX_MAP_WIDTH + { + self.rules.map_width *= 10; + self.rules.map_width += val; } - if self.curr_field == EditingField::MapHeight { - if self.rules.map_height <= MAX_MAP_HEIGHT { - self.rules.map_height *= 10; - self.rules.map_height += val; - } + if self.curr_field == EditingField::MapHeight + && self.rules.map_height <= MAX_MAP_HEIGHT + { + self.rules.map_height *= 10; + self.rules.map_height += val; } - if self.curr_field == EditingField::BoatsList { - if self.rules.boats_list().len() < MAX_BOATS_NUMBER { - self.rules.add_boat(val); - } + if self.curr_field == EditingField::BoatsList + && self.rules.boats_list().len() < MAX_BOATS_NUMBER + { + self.rules.add_boat(val); } if self.curr_field == EditingField::StrikeTimeout { let mut timeout = self.rules.strike_timeout.unwrap_or(0); if timeout <= MAX_STRIKE_TIMEOUT { timeout *= 10; - timeout += val; + timeout += val as u64; self.rules.strike_timeout = Some(timeout); } } diff --git a/rust/sea_battle_backend/src/consts.rs b/rust/sea_battle_backend/src/consts.rs index 960972f..8189baf 100644 --- a/rust/sea_battle_backend/src/consts.rs +++ b/rust/sea_battle_backend/src/consts.rs @@ -25,5 +25,5 @@ pub const INVITE_CODE_LENGTH: usize = 5; pub const MIN_PLAYER_NAME_LENGTH: usize = 1; pub const MAX_PLAYER_NAME_LENGTH: usize = 10; -pub const MIN_STRIKE_TIMEOUT: usize = 5; -pub const MAX_STRIKE_TIMEOUT: usize = 90; +pub const MIN_STRIKE_TIMEOUT: u64 = 5; +pub const MAX_STRIKE_TIMEOUT: u64 = 90; diff --git a/rust/sea_battle_backend/src/data/game_rules.rs b/rust/sea_battle_backend/src/data/game_rules.rs index b39ebd1..f62245d 100644 --- a/rust/sea_battle_backend/src/data/game_rules.rs +++ b/rust/sea_battle_backend/src/data/game_rules.rs @@ -16,7 +16,7 @@ pub struct GameRules { #[serde_as(as = "DisplayFromStr")] pub player_continue_on_hit: bool, #[serde_as(as = "NoneAsEmptyString")] - pub strike_timeout: Option, + pub strike_timeout: Option, pub bot_type: BotType, } diff --git a/rust/sea_battle_backend/src/data/play_config.rs b/rust/sea_battle_backend/src/data/play_config.rs index 4d45f92..22e50db 100644 --- a/rust/sea_battle_backend/src/data/play_config.rs +++ b/rust/sea_battle_backend/src/data/play_config.rs @@ -59,8 +59,8 @@ pub struct PlayConfiguration { pub ordinate_alphabet: &'static str, pub min_player_name_len: usize, pub max_player_name_len: usize, - pub min_strike_timeout: usize, - pub max_strike_timeout: usize, + pub min_strike_timeout: u64, + pub max_strike_timeout: u64, } impl Default for PlayConfiguration { diff --git a/rust/sea_battle_backend/src/game.rs b/rust/sea_battle_backend/src/game.rs index f8fa09c..e12ee6f 100644 --- a/rust/sea_battle_backend/src/game.rs +++ b/rust/sea_battle_backend/src/game.rs @@ -1,4 +1,5 @@ use std::sync::Arc; +use std::time::Duration; use actix::prelude::*; use actix::{Actor, Context, Handler}; @@ -6,6 +7,7 @@ use uuid::Uuid; use crate::bot_player::BotPlayer; use crate::data::*; +use crate::utils::time_utils::time; pub trait Player { fn get_name(&self) -> &str; @@ -51,6 +53,9 @@ pub trait Player { fn opponent_replaced_by_bot(&self); } +/// How often strike timeout controller is run +const STRIKE_TIMEOUT_CONTROL: Duration = Duration::from_secs(1); + fn opponent(index: usize) -> usize { match index { 0 => 1, @@ -85,6 +90,7 @@ pub struct Game { map_0: Option, map_1: Option, turn: usize, + curr_strike_request_started: u64, } impl Game { @@ -96,6 +102,7 @@ impl Game { map_0: None, map_1: None, turn: 0, + curr_strike_request_started: 0, } } @@ -130,10 +137,14 @@ impl Game { self.turn ); - self.request_fire(); + self.request_fire(true); } - fn request_fire(&self) { + fn request_fire(&mut self, reset_counter: bool) { + if reset_counter { + self.curr_strike_request_started = time(); + } + self.players[self.turn].request_fire(self.get_game_status_for_player(self.turn)); self.players[opponent(self.turn)] .opponent_must_fire(self.get_game_status_for_player(opponent(self.turn))); @@ -148,6 +159,27 @@ impl Game { .unwrap() } + /// Replace user for a fire in case of timeout + fn force_fire_in_case_of_timeout(&mut self) { + if self.status != GameStatus::Started || self.rules.strike_timeout.is_none() { + return; + } + + let timeout = self.rules.strike_timeout.unwrap_or_default(); + + if time() <= self.curr_strike_request_started + timeout { + return; + } + + // Determine target of fire + let target = self + .get_game_status_for_player(opponent(self.turn)) + .find_fire_coordinates_for_bot_type(self.rules.bot_type); + + // Fire as player + self.handle_fire(target); + } + fn player_map_mut(&mut self, id: usize) -> &mut GameMap { match id { 0 => self.map_0.as_mut(), @@ -166,7 +198,7 @@ impl Game { // Easiest case : player missed his fire if result == FireResult::Missed { self.turn = opponent(self.turn); - self.request_fire(); + self.request_fire(true); return; } @@ -188,7 +220,9 @@ impl Game { self.turn = opponent(self.turn); } - self.request_fire(); + self.request_fire( + result != FireResult::AlreadyTargetedPosition && result != FireResult::Rejected, + ); } fn handle_request_rematch(&mut self, player_id: Uuid) { @@ -234,6 +268,14 @@ impl Game { impl Actor for Game { type Context = Context; + + fn started(&mut self, ctx: &mut Self::Context) { + if self.rules.strike_timeout.is_some() { + ctx.run_interval(STRIKE_TIMEOUT_CONTROL, |act, _ctx| { + act.force_fire_in_case_of_timeout(); + }); + } + } } #[derive(Message)] @@ -387,7 +429,7 @@ impl Handler for Game { // Re-do current action if self.status == GameStatus::Started { - self.request_fire(); + self.request_fire(true); } else if self.status == GameStatus::WaitingForBoatsDisposition { self.players[offline_player].query_boats_layout(&self.rules); } diff --git a/rust/sea_battle_backend/src/utils/mod.rs b/rust/sea_battle_backend/src/utils/mod.rs index 0fb3fea..579e370 100644 --- a/rust/sea_battle_backend/src/utils/mod.rs +++ b/rust/sea_battle_backend/src/utils/mod.rs @@ -4,6 +4,7 @@ use std::io::ErrorKind; pub mod network_utils; pub mod string_utils; +pub mod time_utils; pub type Res = Result>; diff --git a/rust/sea_battle_backend/src/utils/time_utils.rs b/rust/sea_battle_backend/src/utils/time_utils.rs new file mode 100644 index 0000000..14e1652 --- /dev/null +++ b/rust/sea_battle_backend/src/utils/time_utils.rs @@ -0,0 +1,9 @@ +use std::time::{SystemTime, UNIX_EPOCH}; + +/// Get the current time since epoch +pub fn time() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() +}