Forest Animals Micro AI: move only one unit per execution

There is essentially no time saving involved with doing it all in one
execution as all tables need to be reevaluated for each unit anyway in
order to adapt to potential changes due to ambushes or WML events. (The
latter is not all done yet, will be added in a follow-up commit.)
This commit is contained in:
mattsc 2016-10-11 10:09:52 -07:00
parent a1e602003f
commit 7834b25b57
2 changed files with 110 additions and 115 deletions

View file

@ -39,7 +39,7 @@ end
function ca_forest_animals_move:execution(cfg)
-- These animals run from any enemy
local forest_animals = get_forest_animals(cfg)
local unit = get_forest_animals(cfg)[1]
local enemies = wesnoth.get_units { { "filter_side", { { "enemy_of", {side = wesnoth.current.side } } } } }
-- Get the locations of all the rabbit holes
@ -62,109 +62,106 @@ function ca_forest_animals_move:execution(cfg)
local hole_map = LS.create()
for _,hole in ipairs(holes) do hole_map:insert(hole.x, hole.y, 1) end
-- Each unit moves independently
for _,unit in ipairs(forest_animals) do
-- Behavior is different depending on whether a predator is close or not
local close_enemies = {}
-- Behavior is different depending on whether a predator is close or not
local close_enemies = {}
for _,enemy in ipairs(enemies) do
if (H.distance_between(unit.x, unit.y, enemy.x, enemy.y) <= unit.max_moves+1) then
table.insert(close_enemies, enemy)
end
end
-- If no close enemies, do a random move
local wander_terrain = H.get_child(cfg, "filter_location") or {}
if (not close_enemies[1]) then
local reach = AH.get_reachable_unocc(unit)
local width, height = wesnoth.get_map_size()
local wander_locs = wesnoth.get_locations {
x = '1-' .. width,
y = '1-' .. height,
{ "and", wander_terrain }
}
local locs_map = LS.of_pairs(wander_locs)
local reachable_wander_terrain = {}
reach:iter( function(x, y, v)
if locs_map:get(x,y) then
table.insert(reachable_wander_terrain, {x, y})
end
end)
-- Choose one of the possible locations at random
if reachable_wander_terrain[1] then
local rand = math.random(#reachable_wander_terrain)
-- This is not a full move, as running away might happen next
if (unit.x ~= reachable_wander_terrain[rand][1]) or (unit.y ~= reachable_wander_terrain[rand][2]) then
AH.checked_move(ai, unit, reachable_wander_terrain[rand][1], reachable_wander_terrain[rand][2])
end
else -- Or if no close reachable terrain was found, move toward the closest
local min_dist, best_hex = 9e99
for _,loc in ipairs(wander_locs) do
local dist = H.distance_between(loc[1], loc[2], unit.x, unit.y)
if dist < min_dist then
best_hex, min_dist = loc, dist
end
end
if (best_hex) then
local x,y = wesnoth.find_vacant_tile(best_hex[1], best_hex[2], unit)
local next_hop = AH.next_hop(unit, x, y)
if (unit.x ~= next_hop[1]) or (unit.y ~= next_hop[2]) then
AH.checked_move(ai, unit, next_hop[1], next_hop[2])
end
end
end
end
-- Now we check for close enemies again, as we might just have moved within reach of some
local close_enemies = {}
if unit and unit.valid then
for _,enemy in ipairs(enemies) do
if (H.distance_between(unit.x, unit.y, enemy.x, enemy.y) <= unit.max_moves+1) then
table.insert(close_enemies, enemy)
end
end
-- If no close enemies, do a random move
local wander_terrain = H.get_child(cfg, "filter_location") or {}
if (not close_enemies[1]) then
local reach = AH.get_reachable_unocc(unit)
local width, height = wesnoth.get_map_size()
local wander_locs = wesnoth.get_locations {
x = '1-' .. width,
y = '1-' .. height,
{ "and", wander_terrain }
}
local locs_map = LS.of_pairs(wander_locs)
local reachable_wander_terrain = {}
reach:iter( function(x, y, v)
if locs_map:get(x,y) then
table.insert(reachable_wander_terrain, {x, y})
end
end)
-- Choose one of the possible locations at random
if reachable_wander_terrain[1] then
local rand = math.random(#reachable_wander_terrain)
-- This is not a full move, as running away might happen next
if (unit.x ~= reachable_wander_terrain[rand][1]) or (unit.y ~= reachable_wander_terrain[rand][2]) then
AH.checked_move(ai, unit, reachable_wander_terrain[rand][1], reachable_wander_terrain[rand][2])
end
else -- Or if no close reachable terrain was found, move toward the closest
local min_dist, best_hex = 9e99
for _,loc in ipairs(wander_locs) do
local dist = H.distance_between(loc[1], loc[2], unit.x, unit.y)
if dist < min_dist then
best_hex, min_dist = loc, dist
end
end
if (best_hex) then
local x,y = wesnoth.find_vacant_tile(best_hex[1], best_hex[2], unit)
local next_hop = AH.next_hop(unit, x, y)
if (unit.x ~= next_hop[1]) or (unit.y ~= next_hop[2]) then
AH.checked_move(ai, unit, next_hop[1], next_hop[2])
end
end
end
end
-- Now we check for close enemies again, as we might just have moved within reach of some
local close_enemies = {}
if unit and unit.valid then
for _,enemy in ipairs(enemies) do
if (H.distance_between(unit.x, unit.y, enemy.x, enemy.y) <= unit.max_moves+1) then
table.insert(close_enemies, enemy)
end
end
end
-- If there are close enemies, run away (and rabbits disappear into holes)
local rabbit_type = cfg.rabbit_type or "no_unit_of_this_type"
if close_enemies[1] then
-- Calculate the hex that maximizes distance of unit from enemies
-- Returns nil if the only hex that can be reached is the one the unit is on
local farthest_hex = AH.find_best_move(unit, function(x, y)
local rating = 0
for _,enemy in ipairs(close_enemies) do
local dist = H.distance_between(enemy.x, enemy.y, x, y)
rating = rating - 1 / dist^2
end
-- If this is a rabbit, try to go for holes
if (unit.type == rabbit_type) and hole_map:get(x, y) then
rating = rating + 1000
-- But if possible, go to another hole if unit is on one
if (x == unit.x) and (y == unit.y) then rating = rating - 10 end
end
return rating
end)
AH.movefull_stopunit(ai, unit, farthest_hex)
-- If this is a rabbit ending on a hole -> disappears
if unit and unit.valid
and (unit.type == rabbit_type) and hole_map:get(farthest_hex[1], farthest_hex[2])
then
local command = "wesnoth.erase_unit(x1, y1)"
ai.synced_command(command, farthest_hex[1], farthest_hex[2])
end
end
-- Finally, take moves away, as only partial move might have been done
-- Also take attacks away, as these units never attack
if unit and unit.valid then AH.checked_stopunit_all(ai, unit) end
end
-- If there are close enemies, run away (and rabbits disappear into holes)
local rabbit_type = cfg.rabbit_type or "no_unit_of_this_type"
if close_enemies[1] then
-- Calculate the hex that maximizes distance of unit from enemies
-- Returns nil if the only hex that can be reached is the one the unit is on
local farthest_hex = AH.find_best_move(unit, function(x, y)
local rating = 0
for _,enemy in ipairs(close_enemies) do
local dist = H.distance_between(enemy.x, enemy.y, x, y)
rating = rating - 1 / dist^2
end
-- If this is a rabbit, try to go for holes
if (unit.type == rabbit_type) and hole_map:get(x, y) then
rating = rating + 1000
-- But if possible, go to another hole if unit is on one
if (x == unit.x) and (y == unit.y) then rating = rating - 10 end
end
return rating
end)
AH.movefull_stopunit(ai, unit, farthest_hex)
-- If this is a rabbit ending on a hole -> disappears
if unit and unit.valid
and (unit.type == rabbit_type) and hole_map:get(farthest_hex[1], farthest_hex[2])
then
local command = "wesnoth.erase_unit(x1, y1)"
ai.synced_command(command, farthest_hex[1], farthest_hex[2])
end
end
-- Finally, take moves away, as only partial move might have been done
-- Also take attacks away, as these units never attack
if unit and unit.valid then AH.checked_stopunit_all(ai, unit) end
end
return ca_forest_animals_move

View file

@ -30,27 +30,25 @@ function ca_forest_animals_tusklet_move:evaluation(cfg)
end
function ca_forest_animals_tusklet_move:execution(cfg)
local tusklets = get_tusklets(cfg)
local tusklet = get_tusklets(cfg)[1]
local tuskers = get_tuskers(cfg)
for _,tusklet in ipairs(tusklets) do
local goto_tusker, min_dist = {}, 9e99
for _,tusker in ipairs(tuskers) do
local dist = H.distance_between(tusker.x, tusker.y, tusklet.x, tusklet.y)
if (dist < min_dist) then
min_dist, goto_tusker = dist, tusker
end
local goto_tusker, min_dist = {}, 9e99
for _,tusker in ipairs(tuskers) do
local dist = H.distance_between(tusker.x, tusker.y, tusklet.x, tusklet.y)
if (dist < min_dist) then
min_dist, goto_tusker = dist, tusker
end
local best_hex = AH.find_best_move(tusklet, function(x, y)
return - H.distance_between(x, y, goto_tusker.x, goto_tusker.y)
end)
AH.movefull_stopunit(ai, tusklet, best_hex)
-- Also make sure tusklets never attack
if tusklet and tusklet.valid then AH.checked_stopunit_all(ai, tusklet) end
end
local best_hex = AH.find_best_move(tusklet, function(x, y)
return - H.distance_between(x, y, goto_tusker.x, goto_tusker.y)
end)
AH.movefull_stopunit(ai, tusklet, best_hex)
-- Also make sure tusklets never attack
if tusklet and tusklet.valid then AH.checked_stopunit_all(ai, tusklet) end
end
return ca_forest_animals_tusklet_move