Implement strike timeout on server side
This commit is contained in:
		@@ -130,31 +130,31 @@ impl GameRulesConfigurationScreen {
 | 
				
			|||||||
                        KeyCode::Char(c) if ('0'..='9').contains(&c) => {
 | 
					                        KeyCode::Char(c) if ('0'..='9').contains(&c) => {
 | 
				
			||||||
                            let val = c.to_string().parse::<usize>().unwrap_or_default();
 | 
					                            let val = c.to_string().parse::<usize>().unwrap_or_default();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            if self.curr_field == EditingField::MapWidth {
 | 
					                            if self.curr_field == EditingField::MapWidth
 | 
				
			||||||
                                if self.rules.map_width <= MAX_MAP_WIDTH {
 | 
					                                && self.rules.map_width <= MAX_MAP_WIDTH
 | 
				
			||||||
 | 
					                            {
 | 
				
			||||||
                                self.rules.map_width *= 10;
 | 
					                                self.rules.map_width *= 10;
 | 
				
			||||||
                                self.rules.map_width += val;
 | 
					                                self.rules.map_width += val;
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            if self.curr_field == EditingField::MapHeight {
 | 
					                            if self.curr_field == EditingField::MapHeight
 | 
				
			||||||
                                if self.rules.map_height <= MAX_MAP_HEIGHT {
 | 
					                                && self.rules.map_height <= MAX_MAP_HEIGHT
 | 
				
			||||||
 | 
					                            {
 | 
				
			||||||
                                self.rules.map_height *= 10;
 | 
					                                self.rules.map_height *= 10;
 | 
				
			||||||
                                self.rules.map_height += val;
 | 
					                                self.rules.map_height += val;
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            if self.curr_field == EditingField::BoatsList {
 | 
					                            if self.curr_field == EditingField::BoatsList
 | 
				
			||||||
                                if self.rules.boats_list().len() < MAX_BOATS_NUMBER {
 | 
					                                && self.rules.boats_list().len() < MAX_BOATS_NUMBER
 | 
				
			||||||
 | 
					                            {
 | 
				
			||||||
                                self.rules.add_boat(val);
 | 
					                                self.rules.add_boat(val);
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            if self.curr_field == EditingField::StrikeTimeout {
 | 
					                            if self.curr_field == EditingField::StrikeTimeout {
 | 
				
			||||||
                                let mut timeout = self.rules.strike_timeout.unwrap_or(0);
 | 
					                                let mut timeout = self.rules.strike_timeout.unwrap_or(0);
 | 
				
			||||||
                                if timeout <= MAX_STRIKE_TIMEOUT {
 | 
					                                if timeout <= MAX_STRIKE_TIMEOUT {
 | 
				
			||||||
                                    timeout *= 10;
 | 
					                                    timeout *= 10;
 | 
				
			||||||
                                    timeout += val;
 | 
					                                    timeout += val as u64;
 | 
				
			||||||
                                    self.rules.strike_timeout = Some(timeout);
 | 
					                                    self.rules.strike_timeout = Some(timeout);
 | 
				
			||||||
                                }
 | 
					                                }
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,5 +25,5 @@ pub const INVITE_CODE_LENGTH: usize = 5;
 | 
				
			|||||||
pub const MIN_PLAYER_NAME_LENGTH: usize = 1;
 | 
					pub const MIN_PLAYER_NAME_LENGTH: usize = 1;
 | 
				
			||||||
pub const MAX_PLAYER_NAME_LENGTH: usize = 10;
 | 
					pub const MAX_PLAYER_NAME_LENGTH: usize = 10;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const MIN_STRIKE_TIMEOUT: usize = 5;
 | 
					pub const MIN_STRIKE_TIMEOUT: u64 = 5;
 | 
				
			||||||
pub const MAX_STRIKE_TIMEOUT: usize = 90;
 | 
					pub const MAX_STRIKE_TIMEOUT: u64 = 90;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,7 @@ pub struct GameRules {
 | 
				
			|||||||
    #[serde_as(as = "DisplayFromStr")]
 | 
					    #[serde_as(as = "DisplayFromStr")]
 | 
				
			||||||
    pub player_continue_on_hit: bool,
 | 
					    pub player_continue_on_hit: bool,
 | 
				
			||||||
    #[serde_as(as = "NoneAsEmptyString")]
 | 
					    #[serde_as(as = "NoneAsEmptyString")]
 | 
				
			||||||
    pub strike_timeout: Option<usize>,
 | 
					    pub strike_timeout: Option<u64>,
 | 
				
			||||||
    pub bot_type: BotType,
 | 
					    pub bot_type: BotType,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -59,8 +59,8 @@ pub struct PlayConfiguration {
 | 
				
			|||||||
    pub ordinate_alphabet: &'static str,
 | 
					    pub ordinate_alphabet: &'static str,
 | 
				
			||||||
    pub min_player_name_len: usize,
 | 
					    pub min_player_name_len: usize,
 | 
				
			||||||
    pub max_player_name_len: usize,
 | 
					    pub max_player_name_len: usize,
 | 
				
			||||||
    pub min_strike_timeout: usize,
 | 
					    pub min_strike_timeout: u64,
 | 
				
			||||||
    pub max_strike_timeout: usize,
 | 
					    pub max_strike_timeout: u64,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for PlayConfiguration {
 | 
					impl Default for PlayConfiguration {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
use std::sync::Arc;
 | 
					use std::sync::Arc;
 | 
				
			||||||
 | 
					use std::time::Duration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use actix::prelude::*;
 | 
					use actix::prelude::*;
 | 
				
			||||||
use actix::{Actor, Context, Handler};
 | 
					use actix::{Actor, Context, Handler};
 | 
				
			||||||
@@ -6,6 +7,7 @@ use uuid::Uuid;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use crate::bot_player::BotPlayer;
 | 
					use crate::bot_player::BotPlayer;
 | 
				
			||||||
use crate::data::*;
 | 
					use crate::data::*;
 | 
				
			||||||
 | 
					use crate::utils::time_utils::time;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub trait Player {
 | 
					pub trait Player {
 | 
				
			||||||
    fn get_name(&self) -> &str;
 | 
					    fn get_name(&self) -> &str;
 | 
				
			||||||
@@ -51,6 +53,9 @@ pub trait Player {
 | 
				
			|||||||
    fn opponent_replaced_by_bot(&self);
 | 
					    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 {
 | 
					fn opponent(index: usize) -> usize {
 | 
				
			||||||
    match index {
 | 
					    match index {
 | 
				
			||||||
        0 => 1,
 | 
					        0 => 1,
 | 
				
			||||||
@@ -85,6 +90,7 @@ pub struct Game {
 | 
				
			|||||||
    map_0: Option<GameMap>,
 | 
					    map_0: Option<GameMap>,
 | 
				
			||||||
    map_1: Option<GameMap>,
 | 
					    map_1: Option<GameMap>,
 | 
				
			||||||
    turn: usize,
 | 
					    turn: usize,
 | 
				
			||||||
 | 
					    curr_strike_request_started: u64,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Game {
 | 
					impl Game {
 | 
				
			||||||
@@ -96,6 +102,7 @@ impl Game {
 | 
				
			|||||||
            map_0: None,
 | 
					            map_0: None,
 | 
				
			||||||
            map_1: None,
 | 
					            map_1: None,
 | 
				
			||||||
            turn: 0,
 | 
					            turn: 0,
 | 
				
			||||||
 | 
					            curr_strike_request_started: 0,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -130,10 +137,14 @@ impl Game {
 | 
				
			|||||||
            self.turn
 | 
					            self.turn
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.request_fire();
 | 
					        self.request_fire(true);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn request_fire(&mut self, reset_counter: bool) {
 | 
				
			||||||
 | 
					        if reset_counter {
 | 
				
			||||||
 | 
					            self.curr_strike_request_started = time();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn request_fire(&self) {
 | 
					 | 
				
			||||||
        self.players[self.turn].request_fire(self.get_game_status_for_player(self.turn));
 | 
					        self.players[self.turn].request_fire(self.get_game_status_for_player(self.turn));
 | 
				
			||||||
        self.players[opponent(self.turn)]
 | 
					        self.players[opponent(self.turn)]
 | 
				
			||||||
            .opponent_must_fire(self.get_game_status_for_player(opponent(self.turn)));
 | 
					            .opponent_must_fire(self.get_game_status_for_player(opponent(self.turn)));
 | 
				
			||||||
@@ -148,6 +159,27 @@ impl Game {
 | 
				
			|||||||
        .unwrap()
 | 
					        .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 {
 | 
					    fn player_map_mut(&mut self, id: usize) -> &mut GameMap {
 | 
				
			||||||
        match id {
 | 
					        match id {
 | 
				
			||||||
            0 => self.map_0.as_mut(),
 | 
					            0 => self.map_0.as_mut(),
 | 
				
			||||||
@@ -166,7 +198,7 @@ impl Game {
 | 
				
			|||||||
        // Easiest case : player missed his fire
 | 
					        // Easiest case : player missed his fire
 | 
				
			||||||
        if result == FireResult::Missed {
 | 
					        if result == FireResult::Missed {
 | 
				
			||||||
            self.turn = opponent(self.turn);
 | 
					            self.turn = opponent(self.turn);
 | 
				
			||||||
            self.request_fire();
 | 
					            self.request_fire(true);
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -188,7 +220,9 @@ impl Game {
 | 
				
			|||||||
            self.turn = opponent(self.turn);
 | 
					            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) {
 | 
					    fn handle_request_rematch(&mut self, player_id: Uuid) {
 | 
				
			||||||
@@ -234,6 +268,14 @@ impl Game {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
impl Actor for Game {
 | 
					impl Actor for Game {
 | 
				
			||||||
    type Context = Context<Self>;
 | 
					    type Context = Context<Self>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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)]
 | 
					#[derive(Message)]
 | 
				
			||||||
@@ -387,7 +429,7 @@ impl Handler<PlayerLeftGame> for Game {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            // Re-do current action
 | 
					            // Re-do current action
 | 
				
			||||||
            if self.status == GameStatus::Started {
 | 
					            if self.status == GameStatus::Started {
 | 
				
			||||||
                self.request_fire();
 | 
					                self.request_fire(true);
 | 
				
			||||||
            } else if self.status == GameStatus::WaitingForBoatsDisposition {
 | 
					            } else if self.status == GameStatus::WaitingForBoatsDisposition {
 | 
				
			||||||
                self.players[offline_player].query_boats_layout(&self.rules);
 | 
					                self.players[offline_player].query_boats_layout(&self.rules);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,7 @@ use std::io::ErrorKind;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
pub mod network_utils;
 | 
					pub mod network_utils;
 | 
				
			||||||
pub mod string_utils;
 | 
					pub mod string_utils;
 | 
				
			||||||
 | 
					pub mod time_utils;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub type Res<E = ()> = Result<E, Box<dyn Error>>;
 | 
					pub type Res<E = ()> = Result<E, Box<dyn Error>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										9
									
								
								rust/sea_battle_backend/src/utils/time_utils.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								rust/sea_battle_backend/src/utils/time_utils.rs
									
									
									
									
									
										Normal file
									
								
							@@ -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()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user