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:
parent
25eb3dd20f
commit
fef404fdb9
3 changed files with 39 additions and 157 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue