Fast MAI: Correctly honour the attacks aspect if set

This commit is contained in:
Celtic Minstrel 2016-03-29 23:35:58 -04:00
parent d535ec4fa9
commit 85a498ba15
3 changed files with 179 additions and 46 deletions

View file

@ -1,6 +1,7 @@
local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local LS = wesnoth.require "lua/location_set.lua"
local T = H.set_wml_tag_metatable{}
-- Functions to perform fast evaluation of attacks and attack combinations.
-- The emphasis with all of this is on speed, not elegance.
@ -29,6 +30,148 @@ function ca_fast_attack_utils.get_avoid_map(cfg)
return LS.of_pairs(wesnoth.get_locations(avoid_tag))
end
local function attack_filter(which, filter, is_leader)
if (which == 'leader') then
which = 'own'
is_leader = true
end
if (which == 'own') then
return {
side = wesnoth.current.side,
canrecruit = is_leader,
{ "and", filter or {} }
}
elseif (which == 'enemy') then
return {
T.filter_side { T.enemy_of { side = wesnoth.current.side } },
{ "not", filter or {} }
}
else
return filter
end
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 = loadstring(aspect.code)(nil, H.get_child(aspect, 'args'), data)
if (type(filter[which]) == 'function') then
temporary_attacks_filter_fcn = filter[which]
local units = wesnoth.get_units(attack_filter(which, {
lua_function = 'temporary_attacks_filter_fcn'
}, is_leader))
temporary_attacks_filter_fcn = nil
return units
else
return wesnoth.get_units(attack_filter(which, filter[which], is_leader))
end
else -- Standard attacks aspect (though not name=standard_aspect)
--print("Found standard aspect")
return wesnoth.get_units(attack_filter(which,
H.get_child(aspect, 'filter_' .. which), is_leader))
end
return wesnoth.get_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
wesnoth.get_units{
side = wesnoth.current.side,
canrecruit = false,
{ "and", H.get_child(facet, 'filter_own') }
}
wesnoth.get_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,28 +11,31 @@ 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 units_sorted = true
if (not filter_own) and (not filter_enemy) then
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
filter_own = H.get_child(facet, 'filter_own')
filter_enemy = H.get_child(facet, 'filter_enemy')
end
end
if (not data.fast_combat_units) or (not data.fast_combat_units[1]) then
data.fast_combat_units = FAU.get_attackers(data, "own")
if (not data.fast_combat_units[1]) then return 0 end
units_sorted = false
end
excluded_enemies = FAU.get_attackers(data, "enemy")
else
if (not data.fast_combat_units) or (not data.fast_combat_units[1]) then
data.fast_combat_units = wesnoth.get_units(
FAU.build_attack_filter("own", filter_own)
)
if (not data.fast_combat_units[1]) then return 0 end
units_sorted = false
end
if filter_enemy then
excluded_enemies = wesnoth.get_units(
FAU.build_attack_filter("enemy", filter_enemy)
)
end
end
if (not data.fast_combat_units) or (not data.fast_combat_units[1]) then
data.fast_combat_units = wesnoth.get_units {
side = wesnoth.current.side,
canrecruit = 'no',
{ "and", filter_own }
}
if (not data.fast_combat_units[1]) then return 0 end
if not units_sorted then
-- For speed reasons, we'll go through the arrays from the end, so they are sorted backwards
if cfg.weak_units_first then
table.sort(data.fast_combat_units, function(a,b) return a.hitpoints > b.hitpoints end)
@ -44,12 +47,7 @@ function ca_fast_combat:evaluation(cfg, data)
local excluded_enemies_map = LS.create()
-- Exclude enemies not matching [filter_enemy]
if filter_enemy then
local excluded_enemies = wesnoth.get_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
{ "not", filter_enemy }
}
if excluded_enemies then
for _,e in ipairs(excluded_enemies) do
excluded_enemies_map:insert(e.x, e.y)
end

View file

@ -20,37 +20,29 @@ 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
if (not filter_own) and (not filter_enemy) then
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
filter_own = H.get_child(facet, 'filter_own')
filter_enemy = H.get_child(facet, 'filter_enemy')
end
end
leader = FAU.get_attackers(data, "leader")[1]
if (not leader) then return 0 end
excluded_enemies = FAU.get_attackers(data, "enemy")
else
leader = wesnoth.get_units(
FAU.build_attack_filter("leader", filter_own)
)[1]
if (not leader) then return 0 end
if filter_enemy then
excluded_enemies = wesnoth.get_units(
FAU.build_attack_filter("enemy", filter_enemy)
)
end
end
local leader = wesnoth.get_units {
side = wesnoth.current.side,
canrecruit = 'yes',
{ "and", filter_own }
}[1]
if (not leader) then return 0 end
if (leader.attacks_left == 0) or (not H.get_child(leader.__cfg, 'attack')) then return 0 end
local excluded_enemies_map = LS.create()
-- Exclude enemies not matching [filter_enemy]
if filter_enemy then
local excluded_enemies = wesnoth.get_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
{ "not", filter_enemy }
}
if excluded_enemies then
for _,e in ipairs(excluded_enemies) do
excluded_enemies_map:insert(e.x, e.y)
end