Skip to content

Commit

Permalink
Fast Micro AI: add support for [avoid] tags
Browse files Browse the repository at this point in the history
This can either be an [avoid] tag inside the [micro_ai] tag itself, or
the [avoid] tag of the default AI (e.g. as defined in the side
definition).  If both are given, the former has priority.
  • Loading branch information
mattsc authored and mattsc committed Sep 6, 2015
1 parent 3f0839b commit f06030b
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 36 deletions.
32 changes: 32 additions & 0 deletions data/ai/micro_ais/cas/ca_fast_attack_utils.lua
@@ -1,5 +1,6 @@
local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local LS = wesnoth.require "lua/location_set.lua"

-- Functions to perform fast evaluation of attacks and attack combinations.
-- The emphasis with all of this is on speed, not elegance.
Expand All @@ -14,6 +15,37 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"

local ca_fast_attack_utils = {}

function ca_fast_attack_utils.get_avoid_map(cfg)
-- Get map of locations to be avoided.
-- Use [micro_ai][avoid] tag with priority over [ai][avoid].
-- If neither is given, return an empty location set.
-- Note that ai.get_avoid() cannot be used as it always returns an array,
-- even when the aspect is not set, and an empty array could also mean that
-- no hexes match the filter

local avoid_tag

if cfg.avoid then
avoid_tag = cfg.avoid
else
local ai_tag = H.get_child(wesnoth.sides[wesnoth.current.side].__cfg, 'ai')
for aspect in H.child_range(ai_tag, 'aspect') do
if (aspect.id == 'avoid') then
local facet = H.get_child(aspect, 'facet')
if facet then
avoid_tag = H.get_child(facet, 'value')
end
end
end
end

if avoid_tag then
return LS.of_pairs(wesnoth.get_locations(avoid_tag))
else
return LS.create()
end
end

function ca_fast_attack_utils.gamedata_setup()
-- Keep game data in a table for faster access.
-- This is currently re-done on every move. Could be optimized by only
Expand Down
7 changes: 6 additions & 1 deletion data/ai/micro_ais/cas/ca_fast_combat.lua
Expand Up @@ -54,6 +54,9 @@ function ca_fast_combat:evaluation(ai, cfg, self)
if (aggression > 1) then aggression = 1 end
local own_value_weight = 1. - aggression

-- Get the locations to be avoided
local avoid_map = FAU.get_avoid_map(cfg)

for i = #self.data.fast_combat_units,1,-1 do
local unit = self.data.fast_combat_units[i]
local unit_info = FAU.get_unit_info(unit, self.data.gamedata)
Expand All @@ -65,7 +68,9 @@ function ca_fast_combat:evaluation(ai, cfg, self)
if (#attacks > 0) then
local max_rating, best_target, best_dst = -9e99
for _,attack in ipairs(attacks) do
if (not excluded_enemies_map:get(attack.target.x, attack.target.y)) then
if (not excluded_enemies_map:get(attack.target.x, attack.target.y))
and (not avoid_map:get(attack.dst.x, attack.dst.y))
then
local target = wesnoth.get_unit(attack.target.x, attack.target.y)
local target_info = FAU.get_unit_info(target, self.data.gamedata)

Expand Down
78 changes: 44 additions & 34 deletions data/ai/micro_ais/cas/ca_fast_move.lua
@@ -1,5 +1,6 @@
local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local FAU = wesnoth.require "ai/micro_ais/cas/ca_fast_attack_utils.lua"

local ca_fast_move = {}

Expand All @@ -14,6 +15,9 @@ function ca_fast_move:execution(ai, cfg, self)
local move_cost_factor = cfg.move_cost_factor or 2.0
if (move_cost_factor < 1.1) then move_cost_factor = 1.1 end

-- Get the locations to be avoided
local avoid_map = FAU.get_avoid_map(cfg)

local all_units_MP = AH.get_units_with_moves { side = wesnoth.current.side, canrecruit = 'no' }
local units = {}
for _,unit in ipairs(all_units_MP) do
Expand All @@ -29,16 +33,18 @@ function ca_fast_move:execution(ai, cfg, self)
if leader and (village_value > 0) then
local villages = wesnoth.get_villages()

-- Eliminate villages owned by a side that is not an enemy
-- Eliminate villages in avoid_map and those owned by an allied side
-- Also remove unowned villages if the AI has no leader
for i = #villages,1,-1 do
local owner = wesnoth.get_village_owner(villages[i][1], villages[i][2])
if owner and (not wesnoth.is_enemy(owner, wesnoth.current.side)) then
table.remove(villages, i)
end

if (not leader) and (not owner) then
if avoid_map:get(villages[i][1], villages[i][2]) then
table.remove(villages, i)
else
local owner = wesnoth.get_village_owner(villages[i][1], villages[i][2])
if owner and (not wesnoth.is_enemy(owner, wesnoth.current.side)) then
table.remove(villages, i)
elseif (not leader) and (not owner) then
table.remove(villages, i)
end
end
end

Expand Down Expand Up @@ -110,8 +116,10 @@ function ca_fast_move:execution(ai, cfg, self)
end

for i_el,enemy_leader in ipairs(enemy_leaders) do
local goal = { x = enemy_leader.x, y = enemy_leader.y }
table.insert(goals, goal)
if (not avoid_map:get(enemy_leader.x, enemy_leader.y)) then
local goal = { x = enemy_leader.x, y = enemy_leader.y }
table.insert(goals, goal)
end
end

-- Putting information about all the units into the goals
Expand Down Expand Up @@ -196,35 +204,37 @@ function ca_fast_move:execution(ai, cfg, self)
local pre_ratings = {}
local max_rating, best_hex = -9e99
for _,loc in ipairs(reach) do
local rating = - H.distance_between(loc[1], loc[2], short_goal[1], short_goal[2])
local other_rating = - H.distance_between(loc[1], loc[2], goal.x, goal.y) / 10.
rating = rating + other_rating
if (not avoid_map:get(loc[1], loc[2])) then
local rating = - H.distance_between(loc[1], loc[2], short_goal[1], short_goal[2])
local other_rating = - H.distance_between(loc[1], loc[2], goal.x, goal.y) / 10.
rating = rating + other_rating

local unit_in_way
if (rating > max_rating) then
unit_in_way = wesnoth.get_unit(loc[1], loc[2])
if (unit_in_way == unit) then unit_in_way = nil end

if unit_in_way and (unit_in_way.side == unit.side) then
local reach = AH.get_reachable_unocc(unit_in_way)
if (reach:size() > 1) then
unit_in_way = nil
rating = rating - 0.01
other_rating = other_rating - 0.01
local unit_in_way
if (rating > max_rating) then
unit_in_way = wesnoth.get_unit(loc[1], loc[2])
if (unit_in_way == unit) then unit_in_way = nil end

if unit_in_way and (unit_in_way.side == unit.side) then
local reach = AH.get_reachable_unocc(unit_in_way)
if (reach:size() > 1) then
unit_in_way = nil
rating = rating - 0.01
other_rating = other_rating - 0.01
end
end
end
end

if (not unit_in_way) then
if cfg.dungeon_mode then
table.insert(pre_ratings, {
rating = rating,
other_rating = other_rating,
x = loc[1], y = loc[2]
})
else
if (rating > max_rating) then
max_rating, best_hex = rating, { loc[1], loc[2] }
if (not unit_in_way) then
if cfg.dungeon_mode then
table.insert(pre_ratings, {
rating = rating,
other_rating = other_rating,
x = loc[1], y = loc[2]
})
else
if (rating > max_rating) then
max_rating, best_hex = rating, { loc[1], loc[2] }
end
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion data/ai/micro_ais/micro_ai_wml_tag.lua
Expand Up @@ -460,7 +460,7 @@ function wesnoth.wml_actions.micro_ai(cfg)

elseif (cfg.ai_type == 'fast_ai') then
optional_keys = {
"attack_hidden_enemies", "dungeon_mode", "filter", "filter_second",
"attack_hidden_enemies", "avoid", "dungeon_mode", "filter", "filter_second",
"include_occupied_attack_hexes", "leader_weight", "move_cost_factor",
"weak_units_first", "skip_combat_ca", "skip_move_ca"
}
Expand Down

0 comments on commit f06030b

Please sign in to comment.