Skip to content

Commit

Permalink
Merge pull request #298 from ozkriff/i297_keep_distance
Browse files Browse the repository at this point in the history
Teach AI to keep the distance for bomber and summoner
  • Loading branch information
ozkriff committed Jul 2, 2018
2 parents d84040e + 3c7014b commit 6eaf2c5
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 4 deletions.
87 changes: 83 additions & 4 deletions src/ai.rs
@@ -1,5 +1,5 @@
use core::command::{self, Command};
use core::map;
use core::map::{self, HexMap};
use core::movement::{self, Path, Pathfinder};
use core::state;
use core::utils::shuffle_vec;
Expand All @@ -9,6 +9,8 @@ use core::{self, check, ObjId, PlayerId, State};
pub struct Ai {
id: PlayerId,

distance_map: HexMap<bool>,

/// Each AI has its own Pathfinder because it's not a part of the game state.
pathfinder: Pathfinder,
}
Expand All @@ -18,10 +20,12 @@ impl Ai {
Self {
id,
pathfinder: Pathfinder::new(map_radius),
distance_map: HexMap::new(map_radius),
}
}

fn get_best_path(&mut self, state: &State, unit_id: ObjId) -> Option<Path> {
/// Finds shortest path to some enemy.
fn find_path_to_nearest_enemy(&mut self, state: &State, unit_id: ObjId) -> Option<Path> {
self.pathfinder.fill_map(state, unit_id);
let mut best_path = None;
let mut best_cost = movement::max_cost();
Expand All @@ -46,6 +50,50 @@ impl Ai {
best_path
}

fn find_path_to_preserve_distance(&mut self, state: &State, unit_id: ObjId) -> Option<Path> {
// clean the map
for pos in self.distance_map.iter() {
self.distance_map.set_tile(pos, false);
}

let distance_min = map::Distance(2);
let distance_max = map::Distance(4);
for pos in self.distance_map.iter() {
for &enemy_id in &state::enemy_agent_ids(state, self.id) {
let enemy_pos = state.parts().pos.get(enemy_id).0;
if map::distance_hex(pos, enemy_pos) <= distance_max {
self.distance_map.set_tile(pos, true);
}
}
for &enemy_id in &state::enemy_agent_ids(state, self.id) {
let enemy_pos = state.parts().pos.get(enemy_id).0;
if map::distance_hex(pos, enemy_pos) <= distance_min {
self.distance_map.set_tile(pos, false);
}
}
}

self.pathfinder.fill_map(state, unit_id);
// TODO: remove code duplication
let mut best_path = None;
let mut best_cost = movement::max_cost();
for pos in self.distance_map.iter() {
if !self.distance_map.tile(pos) {
continue;
}
let path = match self.pathfinder.path(pos) {
Some(path) => path,
None => continue,
};
let cost = path.cost_for(state, unit_id);
if best_cost > cost {
best_cost = cost;
best_path = Some(path);
}
}
best_path
}

fn try_throw_bomb(&self, state: &State, unit_id: ObjId) -> Option<Command> {
// TODO: find ability in the parts and use it here:
let ability = core::ability::Ability::Bomb(core::ability::Bomb(map::Distance(3)));
Expand Down Expand Up @@ -97,8 +145,29 @@ impl Ai {
None
}

fn try_to_move(&mut self, state: &State, unit_id: ObjId) -> Option<Command> {
let path = match self.get_best_path(state, unit_id) {
fn try_to_move_closer(&mut self, state: &State, unit_id: ObjId) -> Option<Command> {
let path = match self.find_path_to_nearest_enemy(state, unit_id) {
Some(path) => path,
None => return None,
};
let path = match path.truncate(state, unit_id) {
Some(path) => path,
None => return None,
};
let cost = path.cost_for(state, unit_id);
let agent = state.parts().agent.get(unit_id);
if agent.move_points < cost {
return None;
}
let command = Command::MoveTo(command::MoveTo { id: unit_id, path });
if check(state, &command).is_ok() {
return Some(command);
}
None
}

fn try_to_keep_distance(&mut self, state: &State, unit_id: ObjId) -> Option<Command> {
let path = match self.find_path_to_preserve_distance(state, unit_id) {
Some(path) => path,
None => return None,
};
Expand All @@ -118,6 +187,16 @@ impl Ai {
None
}

fn try_to_move(&mut self, state: &State, unit_id: ObjId) -> Option<Command> {
// TODO: Don't use type names, check its abilities/components
match state.parts().meta.get(unit_id).name.as_str() {
"imp" | "imp_toxic" => self.try_to_move_closer(state, unit_id),
// TODO: Summoner should keep a larger distance
"imp_bomber" | "imp_summoner" => self.try_to_keep_distance(state, unit_id),
meta => unimplemented!("unknown agent type: {}", meta),
}
}

pub fn command(&mut self, state: &State) -> Option<Command> {
for unit_id in state::players_agent_ids(state, self.id) {
if let Some(summon_command) = self.try_summon_imp(state, unit_id) {
Expand Down
38 changes: 38 additions & 0 deletions src/core/map.rs
Expand Up @@ -137,6 +137,44 @@ impl Iterator for HexIter {
}
}

/// Helper function to dump some kind of a map's state as an ascii image.
///
/// Output example:
///
/// ```plain
/// _ A A A o _
/// _ o A A A _ _
/// _ _ A A _ _ _ _
/// _ _ _ o _ A _ _ _
/// _ A _ _ _ _ A A _ _
/// _ _ _ _ _ _ A _ _ _ _
/// _ _ _ _ _ _ _ A _ _
/// _ A _ _ A o _ _ _
/// _ _ _ _ _ _ _ _
/// _ _ _ A A _ _
/// _ _ _ _ _ _
/// ```
///
#[allow(dead_code)]
pub fn dump_map<F: Fn(PosHex) -> char>(radius: Distance, f: F) {
let s = radius.0;
for r in -s..s + 1 {
for _ in -s..r {
print!(" ");
}
for q in -s..s + 1 {
let pos = PosHex { q, r };
if is_inboard(radius, pos) {
print!("{} ", f(pos));
} else {
print!(" ");
}
}
println!();
}
println!();
}

///
/// [-1, 0] [0, -1]
/// [-1, 1] [0, 0] [1, -1]
Expand Down

0 comments on commit 6eaf2c5

Please sign in to comment.