Skip to content

Commit

Permalink
Micro AIs: avoid using table.remove
Browse files Browse the repository at this point in the history
It’s slow.  The inverse logic using table.insert is much faster,
especially for large tables.  Only kept table.remove in a couple places
where it doesn’t matter.
  • Loading branch information
mattsc committed Apr 10, 2014
1 parent a131572 commit 161470c
Show file tree
Hide file tree
Showing 8 changed files with 70 additions and 77 deletions.
13 changes: 9 additions & 4 deletions data/ai/micro_ais/cas/ca_forest_animals_move.lua
Expand Up @@ -46,13 +46,18 @@ function ca_forest_animals_move:execution(ai, cfg)

-- Get the locations of all the rabbit holes
W.store_items { variable = 'holes_wml' }
local holes = H.get_variable_array('holes_wml')
local all_items = H.get_variable_array('holes_wml')
W.clear_variable { name = 'holes_wml' }

-- If cfg.rabbit_hole_img is set, only items with that image or halo count as holes
if cfg.rabbit_hole_img then
if (holes[i].image ~= cfg.rabbit_hole_img) and (holes[i].halo ~= cfg.rabbit_hole_img) then
table.remove(holes, i)
local holes = {}
for _, item in ipairs(all_items) do
if cfg.rabbit_hole_img then
if (item.image == cfg.rabbit_hole_img) or (item.halo == cfg.rabbit_hole_img) then
table.insert(holes, item)
end
else
table.insert(holes, item)
end
end

Expand Down
23 changes: 10 additions & 13 deletions data/ai/micro_ais/cas/ca_forest_animals_new_rabbit.lua
Expand Up @@ -19,33 +19,30 @@ function ca_forest_animals_new_rabbit:execution(ai, cfg)

-- Get the locations of all items on that map (which could be rabbit holes)
W.store_items { variable = 'holes_wml' }
local holes = H.get_variable_array('holes_wml')
local all_items = H.get_variable_array('holes_wml')
W.clear_variable { name = 'holes_wml' }

-- Eliminate all holes that have an enemy within 'rabbit_enemy_distance' hexes
-- We also add a random number to the ones we keep, for selection of the holes later
--print('before:', #holes)
for i = #holes,1,-1 do
local holes = {}
for _, item in ipairs(all_items) do
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_location", { x = holes[i].x, y = holes[i].y, radius = rabbit_enemy_distance } }
{ "filter_location", { x = item.x, y = item.y, radius = rabbit_enemy_distance } }
}
if enemies[1] then
table.remove(holes, i)
else
if (not enemies[1]) then
-- If cfg.rabbit_hole_img is set, only items with that image or halo count as holes
if cfg.rabbit_hole_img then
if (holes[i].image ~= cfg.rabbit_hole_img) and (holes[i].halo ~= cfg.rabbit_hole_img) then
table.remove(holes, i)
else
holes[i].random = math.random(100)
if (item.image == cfg.rabbit_hole_img) or (item.halo == cfg.rabbit_hole_img) then
item.random = math.random(100)
table.insert(holes, item)
end
else
holes[i].random = math.random(100)
item.random = math.random(100)
table.insert(holes, item)
end
end
end
--print('after:', #holes)
table.sort(holes, function(a, b) return a.random > b.random end)

local rabbits = wesnoth.get_units { side = wesnoth.current.side, type = cfg.rabbit_type }
Expand Down
26 changes: 15 additions & 11 deletions data/ai/micro_ais/cas/ca_goto.lua
Expand Up @@ -29,44 +29,48 @@ function ca_goto:evaluation(ai, cfg, self)
-- For convenience, we check for locations here, and just pass that to the exec function
-- This is mostly to make the unique_goals option easier
local width, height = wesnoth.get_map_size()
local locs = wesnoth.get_locations {
local all_locs = wesnoth.get_locations {
x = '1-' .. width,
y = '1-' .. height,
{ "and", cfg.filter_location }
}
--print('#locs org', #locs)
if (#locs == 0) then return 0 end
if (#all_locs == 0) then return 0 end

-- If 'unique_goals' is set, check whether there are locations left to go to.
-- This does not have to be a persistent variable
local locs = {}
if cfg.unique_goals then
-- First, some cleanup of previous turn data
local str = 'goals_taken_' .. (wesnoth.current.turn - 1)
self.data[str] = nil

-- Now on to the current turn
local str = 'goals_taken_' .. wesnoth.current.turn
for i = #locs,1,-1 do
if self.data[str] and self.data[str]:get(locs[i][1], locs[i][2]) then
table.remove(locs, i)
for _, loc in ipairs(all_locs) do
if (not self.data[str]) or (not self.data[str]:get(loc[1], loc[2])) then
table.insert(locs, loc)
end
end
else
locs = all_locs
end
--print('#locs mod', #locs)
if (not locs[1]) then return 0 end

-- Find the goto units
local units = wesnoth.get_units { side = wesnoth.current.side,
local all_units = wesnoth.get_units { side = wesnoth.current.side,
{ "and", cfg.filter }, formula = '$this_unit.moves > 0'
}

-- Exclude released units
local units = {}
if cfg.release_unit_at_goal then
for i_unit=#units,1,-1 do
if MAIUV.get_mai_unit_variables(units[i_unit], cfg.ai_id, "release") then
table.remove(units, i_unit)
for _, unit in ipairs(all_units) do
if (not MAIUV.get_mai_unit_variables(unit, cfg.ai_id, "release")) then
table.insert(units, unit)
end
end
else
units = all_units
end
if (not units[1]) then return 0 end

Expand Down
39 changes: 19 additions & 20 deletions data/ai/micro_ais/cas/ca_messenger_attack.lua
Expand Up @@ -16,26 +16,26 @@ local function messenger_find_enemies_in_way(unit, goal_x, goal_y)
-- If unit cannot get there:
if cost >= 42424242 then return end

-- Exclude the hex the unit is currently on
table.remove(path, 1)
if (not path[1]) then return end
-- The second path hex is the first that is important for the following analysis
if (not path[2]) then return end

-- Is there an enemy unit on the first path hex itself?
-- Is there an enemy unit on the second path hex?
-- This would be caught by the adjacent hex check later, but not in the right order
local enemy = wesnoth.get_units { x = path[1][1], y = path[1][2],
local enemy = wesnoth.get_units { x = path[2][1], y = path[2][2],
{ "filter_side", { {"enemy_of", {side = wesnoth.current.side} } } }
}[1]
if enemy then
--print(' enemy on first path hex:',enemy.id)
--print(' enemy on second path hex:',enemy.id)
return enemy
end

-- After that, go through adjacent hexes of all the other path hexes
for i, p in ipairs(path) do
local sub_path, sub_cost = wesnoth.find_path( unit, p[1], p[2], { ignore_units = true })
for i = 2, #path do
local path_hex = path[i]
local sub_path, sub_cost = wesnoth.find_path( unit, path_hex[1], path_hex[2], { ignore_units = true })
if sub_cost <= unit.moves then
-- Check for enemy units on one of the adjacent hexes (which includes 2 hexes on path)
for x, y in H.adjacent_tiles(p[1], p[2]) do
for x, y in H.adjacent_tiles(path_hex[1], path_hex[2]) do
local enemy = wesnoth.get_units { x = x, y = y,
{ "filter_side", { {"enemy_of", {side = wesnoth.current.side} } } }
}[1]
Expand Down Expand Up @@ -69,26 +69,25 @@ local function messenger_find_clearing_attack(unit, goal_x, goal_y, cfg)

-- Find all units that can attack this enemy
local filter = cfg.filter or { id = cfg.id }
local my_units = wesnoth.get_units {
local all_units = wesnoth.get_units {
side = wesnoth.current.side,
formula = '$this_unit.attacks_left > 0',
{ "not", filter },
{ "and", cfg.filter_second }
}

-- Eliminate units without attacks
for i = #my_units,1,-1 do
if (not H.get_child(my_units[i].__cfg, 'attack')) then
table.remove(my_units, i)
-- Only keep units that have attacks and have attacks left
local units = {}
for _, unit in ipairs(all_units) do
if (unit.attacks_left > 0) and (H.get_child(unit.__cfg, 'attack')) then
table.insert(units, unit)
end
end
--print('#my_units', #my_units)

if (not my_units[1]) then return end
if (not units[1]) then return end

local my_attacks = AH.get_attacks(my_units, { simulate_combat = true })
local attacks = AH.get_attacks(units, { simulate_combat = true })

for i, att in ipairs(my_attacks) do
for i, att in ipairs(attacks) do
if (att.target.x == enemy_in_way.x) and (att.target.y == enemy_in_way.y) then

-- Rating: expected HP of attacker and defender
Expand All @@ -108,7 +107,7 @@ local function messenger_find_clearing_attack(unit, goal_x, goal_y, cfg)
-- If we got here, that means there's an enemy in the way, but none of the units can reach it
--> try to fight our way to that enemy
--print('Find different attack to get to enemy in way')
for i, att in ipairs(my_attacks) do
for i, att in ipairs(attacks) do

-- Rating: expected HP of attacker and defender
local rating = att.att_stats.average_hp - 2 * att.def_stats.average_hp
Expand Down
19 changes: 6 additions & 13 deletions data/ai/micro_ais/cas/ca_simple_attack.lua
Expand Up @@ -6,21 +6,14 @@ local LS = wesnoth.require "lua/location_set.lua"
local ca_simple_attack = {}

function ca_simple_attack:evaluation(ai, cfg, self)

-- Find all units that can attack and match the SUF
local units = wesnoth.get_units {
side = wesnoth.current.side,
formula = '$this_unit.attacks_left > 0',
{ "and", cfg.filter }
}

-- Eliminate units without attacks
for i = #units,1,-1 do
if (not H.get_child(units[i].__cfg, 'attack')) then
table.remove(units, i)
local all_units = wesnoth.get_units { side = wesnoth.current.side, { "and", cfg.filter } }
-- Only keep units that have attacks and have attacks left
local units = {}
for _, unit in ipairs(all_units) do
if (unit.attacks_left > 0) and (H.get_child(unit.__cfg, 'attack')) then
table.insert(units, unit)
end
end
--print('#units', #units)
if (not units[1]) then return 0 end

-- Get all possible attacks
Expand Down
6 changes: 2 additions & 4 deletions data/ai/micro_ais/cas/ca_swarm_move.lua
Expand Up @@ -32,10 +32,8 @@ function swarm_move:execution(ai, cfg)
}
--print('#units, #units_no_moves, #enemies', #units, #units_no_moves, #enemies)

-- pick a random unit and remove it from 'units'
local rand = math.random(#units)
local unit = units[rand]
table.remove(units, rand)
-- pick one unit at random
local unit = units[math.random(#units)]

-- Find best place for that unit to move to
local best_hex = AH.find_best_move(unit, function(x, y)
Expand Down
18 changes: 8 additions & 10 deletions data/ai/micro_ais/cas/ca_wolves_multipacks_attack.lua
Expand Up @@ -43,20 +43,18 @@ function ca_wolves_multipacks_attack:execution(ai, cfg)

-- ... and check if any targets are in reach
local attacks = {}
if wolves[1] then attacks = AH.get_attacks(wolves, { simulate_combat = true }) end
--print('pack, wolves, attacks:', pack_number, #wolves, #attacks)
if wolves[1] then all_attacks = AH.get_attacks(wolves, { simulate_combat = true }) end
--print('pack, wolves, attacks:', pack_number, #wolves, #all_attacks)

-- Eliminate targets that would split up the wolves by more than 3 hexes
-- This also takes care of wolves joining as a pack rather than attacking individually
for i=#attacks,1,-1 do
--print(i, attacks[i].x, attacks[i].y)
local attacks = {}
for _, attack in ipairs(all_attacks) do
for j,w in ipairs(wolves) do
local nh = AH.next_hop(w, attacks[i].dst.x, attacks[i].dst.y)
local d = H.distance_between(nh[1], nh[2], attacks[i].dst.x, attacks[i].dst.y)
--print(' ', i, w.x, w.y, d)
if d > 3 then
table.remove(attacks, i)
--print('Removing attack')
local nh = AH.next_hop(w, attack.dst.x, attack.dst.y)
local d = H.distance_between(nh[1], nh[2], attack.dst.x, attack.dst.y)
if d <= 3 then
table.insert(attacks, attack)
break
end
end
Expand Down
3 changes: 1 addition & 2 deletions data/ai/micro_ais/cas/ca_wolves_multipacks_functions.lua
Expand Up @@ -121,8 +121,7 @@ function wolves_multipacks_functions.assign_packs(cfg)
end
-- Now insert the best pack into that 'packs' array
packs[new_pack] = {}
-- Need to count down for table.remove to work correctly
for i = pack_size,1,-1 do
for i = 1,pack_size do
table.insert(packs[new_pack], { x = best_wolves[i].x, y = best_wolves[i].y, id = best_wolves[i].id })
MAIUV.set_mai_unit_variables(best_wolves[i], cfg.ai_id, { pack = new_pack })
end
Expand Down

0 comments on commit 161470c

Please sign in to comment.