From f098bd961e5d872601dece5e9f30e8991e313c3c Mon Sep 17 00:00:00 2001 From: Timo Schneider Date: Sat, 18 May 2024 13:24:47 +0200 Subject: [PATCH] little cleanup + new strat --- src/logic/strategy.rs | 94 +++++++++++++++++++++++++------------- src/models.rs | 1 + src/models/base.rs | 67 ++++++++++++++++++++------- src/models/board_action.rs | 15 +++++- src/models/target.rs | 13 ++++++ 5 files changed, 139 insertions(+), 51 deletions(-) create mode 100644 src/models/target.rs diff --git a/src/logic/strategy.rs b/src/logic/strategy.rs index 40af538..bc05f0d 100644 --- a/src/logic/strategy.rs +++ b/src/logic/strategy.rs @@ -1,62 +1,90 @@ use crate::models::{game_state::GameState, player_action::PlayerAction}; use crate::models::base::Base; +use crate::models::target::Target; pub fn decide(game_state: GameState) -> Vec { + // all planned attacks let mut attacks: Vec = Vec::new(); + // a list of all friendly and opponent bases let mut own_bases: Vec = Vec::new(); let mut opponent_bases: Vec = Vec::new(); - for base in game_state.bases { + // iterate over all bases + game_state.bases.iter().for_each(|base| { + // sort them into owned and opponent bases if base.player == game_state.game.player { - own_bases.push(base); + own_bases.push(base.clone()); } else { - opponent_bases.push(base); + opponent_bases.push(base.clone()); } - } + }); - for base in own_bases { - let mut target: Option<(Base, u32)> = None; - for opponent in opponent_bases.clone() { - let req = base.required_to_defeat(&opponent, &game_state.actions, &game_state.config); - if req > 0 && req < base.population && base.population_in_n_ticks(base.distance_to(&opponent), &game_state.config, &game_state.actions) > 5 { - if let Some(target_some) = target { - if (target_some.1 >= req && target_some.0.distance_to(&base) > opponent.distance_to(&base)) || ((target_some.0.uid != 0 && base.uid == 0) && opponent.distance_to(&base) < 10 && target_some.0.distance_to(&base) > opponent.distance_to(&base)) { - target = Some((opponent, req)); + // iterate through all owned bases + own_bases.iter().for_each(|base| { + // the target of the base, currently none + let mut target: Option = None; + + // iterate over all opponents as possible targets + opponent_bases.iter().for_each(|opponent| { + // calculate the required bits to conquer the opponents base + let required_bits: u32 = base.required_to_defeat(&opponent, &game_state.config, &game_state.actions); + + // check that the base could be conquered with at least 1/4 of the population remaining in base + if required_bits + (game_state.config.base_levels[base.level as usize].max_population / 4) < base.population { + // check if there is already a target + if let Some(target_value) = target { + // get if the new target is closer than the old one + let is_closer: bool = base.distance_to(&opponent) < base.distance_to(&target_value.base); + // check if the new target is closer and the required bits are less or the game is free and within grace period + if is_closer && (required_bits <= target_value.required_bits || (base.uid == 0 && opponent.distance_to(&base) < game_state.config.paths.grace_period)) { + // set the new target + target = Some(Target::new(base.clone(), required_bits)); } } + // there is no target yet, so this is the first one else { - target = Some((opponent, req)); + // set the new target + target = Some(Target::new(base.clone(), required_bits)); } } + }); + + let mut consider_upgrade: bool = false; + // check if a target has been selected + if let Some(target) = target { + //check if the base would die until the attack reaches its target + if !base.will_die_within_in_n_ticks(base.distance_to(&target.base), &game_state.config, &game_state.actions) { + // attack + attacks.push(PlayerAction { + src: base.uid, + dest: target.base.uid, + amount: target.required_bits + 3, + }); + } + // consider upgrade + else { + consider_upgrade = true; + } + } + // consider upgrade + else { + consider_upgrade = true; } - if let Some(target) = target { - if base.population_in_n_ticks(base.distance_to(&target.0), &game_state.config, &game_state.actions) > target.1 + 3 * game_state.config.paths.death_rate + 1 { - attacks.push(PlayerAction { - src: base.uid, - dest: target.0.uid, - amount: target.1 + 3 * game_state.config.paths.death_rate, - }); - } - else if base.population > game_state.config.base_levels[base.level as usize].max_population -1 { - attacks.push(PlayerAction { - src: base.uid, - dest: base.uid, - amount: base.population - (game_state.config.base_levels[base.level as usize].max_population - 3), - }); - } - } - else if base.population > game_state.config.base_levels[base.level as usize].max_population -1 { + // check if the max population is at its limit and no attacks were made + if consider_upgrade && base.population > game_state.config.base_levels[base.level as usize].max_population - 1 { + // upgrade with all bits over limit attacks.push(PlayerAction { src: base.uid, dest: base.uid, - amount: base.population - (game_state.config.base_levels[base.level as usize].max_population - 3), + amount: base.population - (game_state.config.base_levels[base.level as usize].max_population - 1), }); } - println!("{:?}", target); - } + }); + + // return attacks return attacks; } diff --git a/src/models.rs b/src/models.rs index 6dcda15..fd9aa0e 100644 --- a/src/models.rs +++ b/src/models.rs @@ -8,4 +8,5 @@ pub mod path_config; pub mod player_action; pub mod position; pub mod progress; +pub mod target; diff --git a/src/models/base.rs b/src/models/base.rs index 6a0f263..35a9e9f 100644 --- a/src/models/base.rs +++ b/src/models/base.rs @@ -27,41 +27,74 @@ impl Default for Base { } impl Base { - pub fn population_in_n_ticks(&self, ticks: u32, config: &GameConfig, attacks: &Vec) -> u32 { + // get base population from base in n ticks with current config and attacks on the board + fn raw_population_in_n_ticks(&self, ticks: u32, config: &GameConfig, attacks: &Vec) -> i32 { + // set the population in future to the current population let mut population_in_future: i32 = self.population as i32; - if self.uid != 0 { population_in_future += (ticks + 1) as i32 * config.base_levels[self.level as usize].spawn_rate as i32; } - for attack in attacks { + + // check if the base has a passive generation rate + if self.uid != 0 { + // add the spawned bits to the population + population_in_future += (ticks * config.base_levels[self.level as usize].spawn_rate) as i32; + } + + // iterate through all attacks + attacks.iter().for_each(|attack| { + // check if the attack will arrive in time if attack.arrival_in_ticks() >= ticks { + // get the amount of bits in the attack on arrival let val_on_target: i32 = attack.amount_at_target(&config.paths) as i32; + + // check if the attack is owned by the base if attack.player == self.player { + // the attack will contribute to the population population_in_future += val_on_target; } else { + // the attack will fight with the base population_in_future -= val_on_target; } } - } - return population_in_future.abs() as u32; + }); + // return abs of population, because on negative population it is likely to be conquered by an opponent + return population_in_future; } + pub fn population_in_n_ticks(&self, ticks: u32, config: &GameConfig, attacks: &Vec) -> u32 { + return self.raw_population_in_n_ticks(ticks, config, attacks).abs() as u32; + } + + pub fn will_die_within_in_n_ticks(&self, ticks: u32, config: &GameConfig, attacks: &Vec) -> bool { + return self.raw_population_in_n_ticks(ticks, config, attacks) < 0; + } + + // get the distance to another base pub fn distance_to(&self, base: &Base) -> u32 { - return (((base.position.x - self.position.x).pow(2) + (base.position.y - self.position.y).pow(2) + (base.position.z - self.position.z).pow(2)) as f64).powf(1.0 / 2.0) as u32; + // calculate the Euclidean distance between the bases + return (( (base.position.x - self.position.x).pow(2) + + (base.position.y - self.position.y).pow(2) + + (base.position.z - self.position.z).pow(2) + ) as f64).sqrt() as u32; } - pub fn required_to_defeat(&self, base: &Base, attacks: &Vec, game_config: &GameConfig) -> u32 { - let d: u32 = self.distance_to(base); + // get the amount of bits required to defeat another base + pub fn required_to_defeat(&self, target_base: &Base, game_config: &GameConfig, attacks: &Vec) -> u32 { + // get the distance between the bases + let d: u32 = self.distance_to(target_base); - let mut pop = base.population_in_n_ticks(d, game_config, attacks); + // get the population of the target base when an own attack would arrive + let population: u32 = target_base.population_in_n_ticks(d, game_config, attacks); - if base.uid == 0 && base.level == 4 { - pop = 1; - } - else { - pop += 3; + // to defeat a base there has to be at least one more bit arriving than the current population is + let mut requirement: u32 = population + 1; + + // check if the distance is greater than the grace period + if d > game_config.paths.grace_period { + // add the loss over the additional distance + requirement += (d - game_config.paths.grace_period) * game_config.paths.death_rate } - if d < game_config.paths.grace_period {return pop} - - return pop + (d - game_config.paths.grace_period) * game_config.paths.death_rate + // return required bits + return requirement; } } diff --git a/src/models/board_action.rs b/src/models/board_action.rs index 76356d6..950e11c 100644 --- a/src/models/board_action.rs +++ b/src/models/board_action.rs @@ -27,13 +27,26 @@ impl Default for BoardAction { } impl BoardAction { + // get the remaining ticks until arrival pub fn arrival_in_ticks(&self) -> u32 { + // return the remaining ticks return self.progress.distance - self.progress.traveled; } + + // get the amount of bits that reach a base pub fn amount_at_target(&self, config: &PathConfig) -> u32 { - if self.progress.distance < config.grace_period { return self.amount; } + // if the distance is smaller than the grace period all bits will reach their destination + if self.progress.distance < config.grace_period { + return self.amount; + } + + // get the number of bits that will die until their destination let deaths: u32 = config.death_rate * (self.progress.distance - config.grace_period); + + // there are more deaths than the bits in the attack, no bit will reach the destination if deaths > self.amount { return 0 } + + // return the bits at destination return self.amount - deaths; } } diff --git a/src/models/target.rs b/src/models/target.rs new file mode 100644 index 0000000..b3cf557 --- /dev/null +++ b/src/models/target.rs @@ -0,0 +1,13 @@ +use crate::models::base::Base; + +#[derive(Copy, Clone)] +pub struct Target { + pub base: Base, + pub required_bits: u32, +} + +impl Target { + pub fn new(base: Base, required_bits: u32) -> Target { + return Target {base, required_bits}; + } +} \ No newline at end of file