Implement strike timeout on server side

This commit is contained in:
Pierre HUBERT 2022-10-17 07:59:42 +02:00
parent ba1ed84b33
commit 42b0d84f9d
7 changed files with 77 additions and 25 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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()
}