Fast Micro AI: simplify code by using new ai.aspect.attacks

Note that this requires that the enemy filter in the utils function now
matches the enemy units to be attacked, while previously it was set to
all enemy units NOT matching the filter. As this is not used anywhere
else, that’s not a problem, it’s just noted here for completeness.
This commit is contained in:
mattsc 2016-10-20 09:35:54 -07:00
parent 25eb3dd20f
commit fef404fdb9
3 changed files with 39 additions and 157 deletions

View file

@ -45,7 +45,7 @@ local function attack_filter(which, filter, is_leader)
elseif (which == 'enemy') then
return {
T.filter_side { T.enemy_of { side = wesnoth.current.side } },
{ "not", filter or {} }
{ "and", filter or {} }
}
else
return filter
@ -54,125 +54,6 @@ end
ca_fast_attack_utils.build_attack_filter = attack_filter
local function get_attack_filter_from_aspect(aspect, which, data, is_leader)
if (aspect.name == "composite_aspect") then
--print("Found composite aspect")
for facet in H.child_range(aspect, 'facet') do
local active = true
if facet.turns then
active = false
local turns = AH.split(facet.turns)
local current_turn = tostring(wesnoth.current.turn)
--print("Found facet with turns requirement (current turn is '" .. current_turn .. "')")
for i,v in ipairs(turns) do
if current_turn == v then
--print(" Matched with '" .. v .. "'")
active = true
break
end
end
end
if facet.time_of_day then
active = false
local times = AH.split(facet.time_of_day)
local current_time = wesnoth.get_time_of_day().id
--print("Found facet with time requirement (current time is '" .. current_time .. "')")
for i,v in ipairs(times) do
if current_time == v then
--print(" Matched with '" .. v .. "'")
active = true
break
end
end
end
if active then
return get_attack_filter_from_aspect(facet, which, data, is_leader)
end
end
elseif (aspect.name == "lua_aspect") then
--print("Found lua aspect")
local filter = load(aspect.code)(nil, H.get_child(aspect, 'args'), data)
if (type(filter[which]) == 'function') then
temporary_attacks_filter_fcn = filter[which]
local units = AH.get_live_units(attack_filter(which, {
lua_function = 'temporary_attacks_filter_fcn'
}, is_leader))
temporary_attacks_filter_fcn = nil
return units
else
return AH.get_live_units(attack_filter(which, filter[which], is_leader))
end
else -- Standard attacks aspect (though not name=standard_aspect)
--print("Found standard aspect")
return AH.get_live_units(attack_filter(which,
H.get_child(aspect, 'filter_' .. which), is_leader))
end
return AH.get_live_units(attack_filter(which, {}, is_leader))
end
function ca_fast_attack_utils.get_attackers(data, which)
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 == 'attacks') then
if (which == 'leader') then
return get_attack_filter_from_aspect(aspect, 'own', data, true)
else
return get_attack_filter_from_aspect(aspect, which, data)
end
end
end
return {}
end
--[[
This is a benchmarking function to compare the old, incorrect method of
fetching the attacks aspect to the new method and the standard method.
It's meant to be called from the Lua console.
Example usage:
$ my_ai = wesnoth.debug_ai(1).ai
$ FAU = wesnoth.dofile "ai/micro_ais/cas/ca_fast_attack_utils.lua"
$ FAU.test_attacks(my_ai, 2000)
]]
function ca_fast_attack_utils.test_attacks(my_ai, times)
local t1, t2 = os.clock()
for i = 1,times do
my_ai.get_attacks()
end
t2 = os.clock()
print("get_attacks() executed in average time " .. (os.difftime(t2,t1) / times))
t1 = os.clock()
for i = 1,times do
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 == 'attacks') then
local facet = H.get_child(aspect, 'facet')
if facet then
AH.get_live_units{
side = wesnoth.current.side,
canrecruit = false,
{ "and", H.get_child(facet, 'filter_own') }
}
AH.get_live_units{
side = wesnoth.current.side,
canrecruit = false,
{ "and", H.get_child(facet, 'filter_enemy') }
}
end
end
end
end
t2 = os.clock()
print("original sloppy method executed in time " .. (os.difftime(t2,t1) / times))
t1 = os.clock()
for i = 1,times do
ca_fast_attack_utils.get_attackers(nil, "own", false)
ca_fast_attack_utils.get_attackers(nil, "enemy", false)
end
t2 = os.clock()
print("new method executed in time " .. (os.difftime(t2,t1) / times))
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

View file

@ -11,15 +11,23 @@ function ca_fast_combat:evaluation(cfg, data)
local filter_own = H.get_child(cfg, "filter")
local filter_enemy = H.get_child(cfg, "filter_second")
local excluded_enemies
local enemies
local units_sorted = true
if (not filter_own) and (not filter_enemy) then
local attacks_aspect = ai.aspects.attacks
if (not data.fast_combat_units) or (not data.fast_combat_units[1]) then
data.fast_combat_units = FAU.get_attackers(data, "own")
-- Leader is dealt with in a separate CA
data.fast_combat_units = {}
for _,unit in ipairs(attacks_aspect.own) do
if (not unit.canrecruit) then
table.insert(data.fast_combat_units, unit)
end
end
if (not data.fast_combat_units[1]) then return 0 end
units_sorted = false
end
excluded_enemies = FAU.get_attackers(data, "enemy")
enemies = attacks_aspect.enemy
else
if (not data.fast_combat_units) or (not data.fast_combat_units[1]) then
data.fast_combat_units = AH.get_live_units(
@ -28,11 +36,9 @@ function ca_fast_combat:evaluation(cfg, data)
if (not data.fast_combat_units[1]) then return 0 end
units_sorted = false
end
if filter_enemy then
excluded_enemies = AH.get_live_units(
FAU.build_attack_filter("enemy", filter_enemy)
)
end
enemies = AH.get_live_units(
FAU.build_attack_filter("enemy", filter_enemy)
)
end
if not units_sorted then
@ -44,13 +50,9 @@ function ca_fast_combat:evaluation(cfg, data)
end
end
local excluded_enemies_map = LS.create()
-- Exclude enemies not matching [filter_enemy]
if excluded_enemies then
for _,e in ipairs(excluded_enemies) do
excluded_enemies_map:insert(e.x, e.y)
end
local enemy_map = LS.create()
for _,e in ipairs(enemies) do
enemy_map:insert(e.x, e.y)
end
-- Exclude hidden enemies, except if attack_hidden_enemies=yes is set in [micro_ai] tag
@ -61,7 +63,7 @@ function ca_fast_combat:evaluation(cfg, data)
}
for _,e in ipairs(hidden_enemies) do
excluded_enemies_map:insert(e.x, e.y)
enemy_map:remove(e.x, e.y)
end
end
@ -83,7 +85,7 @@ function ca_fast_combat:evaluation(cfg, data)
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))
if enemy_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)

View file

@ -20,32 +20,31 @@ function ca_fast_combat_leader:evaluation(cfg, data)
local filter_own = H.get_child(cfg, "filter")
local filter_enemy = H.get_child(cfg, "filter_second")
local excluded_enemies, leader
local enemies, leader
if (not filter_own) and (not filter_enemy) then
leader = FAU.get_attackers(data, "leader")[1]
local attacks_aspect = ai.aspects.attacks
for _,unit in ipairs(attacks_aspect.own) do
if unit.canrecruit and (unit.attacks_left > 0) and (#unit.attacks > 0) then
leader = unit
break
end
end
if (not leader) then return 0 end
excluded_enemies = FAU.get_attackers(data, "enemy")
enemies = attacks_aspect.enemy
else
leader = AH.get_live_units(
FAU.build_attack_filter("leader", filter_own)
)[1]
if (not leader) then return 0 end
if filter_enemy then
excluded_enemies = AH.get_live_units(
FAU.build_attack_filter("enemy", filter_enemy)
)
end
if (not leader) or (leader.attacks_left == 0) or (#leader.attacks == 0) then return 0 end
enemies = AH.get_live_units(
FAU.build_attack_filter("enemy", filter_enemy)
)
end
if (leader.attacks_left == 0) or (#leader.attacks == 0) then return 0 end
local excluded_enemies_map = LS.create()
-- Exclude enemies not matching [filter_enemy]
if excluded_enemies then
for _,e in ipairs(excluded_enemies) do
excluded_enemies_map:insert(e.x, e.y)
end
local enemy_map = LS.create()
for _,e in ipairs(enemies) do
enemy_map:insert(e.x, e.y)
end
-- Exclude hidden enemies, except if attack_hidden_enemies=yes is set in [micro_ai] tag
@ -56,7 +55,7 @@ function ca_fast_combat_leader:evaluation(cfg, data)
}
for _,e in ipairs(hidden_enemies) do
excluded_enemies_map:insert(e.x, e.y)
enemy_map:remove(e.x, e.y)
end
end
@ -120,7 +119,7 @@ function ca_fast_combat_leader:evaluation(cfg, data)
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))
if enemy_map:get(attack.target.x, attack.target.y)
and (not avoid_map:get(attack.dst.x, attack.dst.y))
then
-- First check if the threat against the leader at this hex