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) => {
let val = c.to_string().parse::<usize>().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);
}
}

View File

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

View File

@ -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<usize>,
pub strike_timeout: Option<u64>,
pub bot_type: BotType,
}

View File

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

View File

@ -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<GameMap>,
map_1: Option<GameMap>,
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<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)]
@ -387,7 +429,7 @@ impl Handler<PlayerLeftGame> 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);
}

View File

@ -4,6 +4,7 @@ use std::io::ErrorKind;
pub mod network_utils;
pub mod string_utils;
pub mod time_utils;
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()
}