Animals Micro AIs: code cleanup

This commit is contained in:
mattsc 2014-04-14 11:43:19 -07:00
parent e13f60e86e
commit 326d998728
5 changed files with 92 additions and 123 deletions

View file

@ -19,38 +19,34 @@ function ca_big_animals:evaluation(ai, cfg)
end
function ca_big_animals:execution(ai, cfg)
-- Big animals just move toward goal that gets set occasionally
-- Avoid the other big animals (bears, yetis, spiders) and the dogs, otherwise attack whatever is in their range
-- The only difference in behavior is the area in which the units move
-- Big animals just move toward a goal that gets (re)set occasionally
-- and attack whatever is in their range (except for some units that they avoid)
local big_animals = get_big_animals(cfg)
local avoid = LS.of_pairs(wesnoth.get_locations { radius = 1,
local avoid_map = LS.of_pairs(wesnoth.get_locations { radius = 1,
{ "filter", { { "and", cfg.avoid_unit },
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } }
} }
})
--AH.put_labels(avoid)
for i,unit in ipairs(big_animals) do
for _,unit in ipairs(big_animals) do
local goal = MAIUV.get_mai_unit_variables(unit, cfg.ai_id)
-- Unit gets a new goal if none exist or on any move with 10% random chance
-- Unit gets a new goal if none is set or on any move with a 10% random chance
local r = math.random(10)
if (not goal.goal_x) or (r == 1) then
local locs = AH.get_passable_locations(cfg.filter_location or {})
local rand = math.random(#locs)
--print(type, ': #locs', #locs, rand)
goal.goal_x, goal.goal_y = locs[rand][1], locs[rand][2]
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, goal)
end
--print('Big animal goto: ', unit.id, goal.goal_x, goal.goal_y, r)
-- hexes the unit can reach
local reach_map = AH.get_reachable_unocc(unit)
local wander_terrain = cfg.filter_location_wander or {}
reach_map:iter( function(x, y, v)
-- Remove tiles that do not comform to the wander terrain filter
if (not wesnoth.match_location(x, y, wander_terrain) ) then
if (not wesnoth.match_location(x, y, wander_terrain)) then
reach_map:remove(x, y)
end
end)
@ -58,56 +54,52 @@ function ca_big_animals:execution(ai, cfg)
-- Now find the one of these hexes that is closest to the goal
local max_rating, best_hex = -9e99, {}
reach_map:iter( function(x, y, v)
-- Distance from goal is first rating
local rating = - H.distance_between(x, y, goal.goal_x, goal.goal_y)
-- Proximity to an enemy unit is a plus
local enemy_hp = 500
for xa, ya in H.adjacent_tiles(x, y) do
for xa,ya in H.adjacent_tiles(x, y) do
local enemy = wesnoth.get_unit(xa, ya)
if enemy and wesnoth.is_enemy(enemy.side, wesnoth.current.side) then
if (enemy.hitpoints < enemy_hp) then enemy_hp = enemy.hitpoints end
end
end
rating = rating + 500 - enemy_hp -- prefer attack on weakest enemy
rating = rating + 500 - enemy_hp -- Prefer attack on weakest enemy
-- However, hexes that enemy bears, yetis and spiders can reach get a massive negative hit
-- meaning that they will only ever be chosen if there's no way around them
if avoid:get(x, y) then rating = rating - 1000 end
-- Hexes reachable by units to be be avoided get a massive negative hit
if avoid_map:get(x, y) then rating = rating - 1000 end
reach_map:insert(x, y, rating)
if (rating > max_rating) then
max_rating, best_hex = rating, { x, y }
end
end)
--print(' best_hex: ', best_hex[1], best_hex[2])
--AH.put_labels(reach_map)
if (best_hex[1] ~= unit.x) or (best_hex[2] ~= unit.y) then
AH.checked_move(ai, unit, best_hex[1], best_hex[2]) -- partial move only
AH.checked_move(ai, unit, best_hex[1], best_hex[2]) -- Partial move only
if (not unit) or (not unit.valid) then return end
else -- If animal did not move, we need to stop it (also delete the goal)
else -- If unit did not move, we need to stop it (also delete the goal)
AH.checked_stopunit_moves(ai, unit)
if (not unit) or (not unit.valid) then return end
MAIUV.delete_mai_unit_variables(unit, cfg.ai_id)
end
-- Or if this gets the unit to the goal, we also delete the goal
-- If this gets the unit to the goal, we also delete the goal
if (unit.x == goal.goal_x) and (unit.y == goal.goal_y) then
MAIUV.delete_mai_unit_variables(unit, cfg.ai_id)
end
-- Finally, if the unit ended up next to enemies, attack the weakest of those
local min_hp, target = 9e99, {}
for x, y in H.adjacent_tiles(unit.x, unit.y) do
local enemy = wesnoth.get_unit(x, y)
local min_hp, target = 9e99
for xa,ya in H.adjacent_tiles(unit.x, unit.y) do
local enemy = wesnoth.get_unit(xa, ya)
if enemy and wesnoth.is_enemy(enemy.side, wesnoth.current.side) then
if (enemy.hitpoints < min_hp) then
min_hp, target = enemy.hitpoints, enemy
end
end
end
if target.id then
if target then
AH.checked_attack(ai, unit, target)
end
end

View file

@ -4,7 +4,7 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
local LS = wesnoth.require "lua/location_set.lua"
local function get_forest_animals(cfg)
-- We want the deer/rabbits to move first, tuskers later
-- We want the deer/rabbits to move first, tuskers afterward
local deer_type = cfg.deer_type or "no_unit_of_this_type"
local rabbit_type = cfg.rabbit_type or "no_unit_of_this_type"
local forest_animals = AH.get_units_with_moves {
@ -14,16 +14,16 @@ local function get_forest_animals(cfg)
local tusker_type = cfg.tusker_type or "no_unit_of_this_type"
local all_tuskers = wesnoth.get_units { side = wesnoth.current.side, type = tusker_type }
for i,t in ipairs(all_tuskers) do
if (t.moves > 0) then table.insert(forest_animals, t) end
for _,tusker in ipairs(all_tuskers) do
if (tusker.moves > 0) then table.insert(forest_animals, tusker) end
end
-- Tusklets get moved by this CA if there are no tuskers left
if not all_tuskers[1] then
local tusklet_type = cfg.tusklet_type or "no_unit_of_this_type"
local tusklets = wesnoth.get_units { side = wesnoth.current.side, type = tusklet_type }
for i,t in ipairs(tusklets) do
if (t.moves > 0) then table.insert(forest_animals, t) end
for _,tusklet in ipairs(tusklets) do
if (tusklet.moves > 0) then table.insert(forest_animals, tusklet) end
end
end
@ -38,10 +38,9 @@ function ca_forest_animals_move:evaluation(ai, cfg)
end
function ca_forest_animals_move:execution(ai, cfg)
local forest_animals = get_forest_animals(cfg)
-- These animals run from any enemy
local enemies = wesnoth.get_units { { "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} } }
local forest_animals = get_forest_animals(cfg)
local enemies = wesnoth.get_units { { "filter_side", { { "enemy_of", {side = wesnoth.current.side } } } } }
-- Get the locations of all the rabbit holes
W.store_items { variable = 'holes_wml' }
@ -49,73 +48,64 @@ function ca_forest_animals_move:execution(ai, cfg)
W.clear_variable { name = 'holes_wml' }
-- If cfg.rabbit_hole_img is set, only items with that image or halo count as holes
local holes = {}
for _, item in ipairs(all_items) do
if cfg.rabbit_hole_img then
local holes
if cfg.rabbit_hole_img then
for _,item in ipairs(all_items) do
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
else
holes = all_items
end
local hole_map = LS.create()
for i,h in ipairs(holes) do hole_map:insert(h.x, h.y, 1) end
--AH.put_labels(hole_map)
for _,hole in ipairs(holes) do hole_map:insert(hole.x, hole.y, 1) end
-- Each unit moves independently
for i,unit in ipairs(forest_animals) do
--print('Unit', i, unit.x, unit.y)
for _,unit in ipairs(forest_animals) do
-- Behavior is different depending on whether a predator is close or not
local close_enemies = {}
for j,e in ipairs(enemies) do
if (H.distance_between(unit.x, unit.y, e.x, e.y) <= unit.max_moves+1) then
table.insert(close_enemies, e)
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
--print(' #close_enemies', #close_enemies)
-- If no close enemies, do a random move
local wander_terrain = cfg.filter_location or {}
if (not close_enemies[1]) then
-- All hexes the unit can reach that are unoccupied
local reach = AH.get_reachable_unocc(unit)
local locs = wesnoth.get_locations(wander_terrain)
local locs_map = LS.of_pairs(locs)
--print(' #all reachable', reach:size())
local wander_locs = wesnoth.get_locations(wander_terrain)
local locs_map = LS.of_pairs(wander_locs)
-- Select only those that satisfy wander_terrain
local reachable_terrain = {}
local reachable_wander_terrain = {}
reach:iter( function(x, y, v)
local terrain = wesnoth.get_terrain(x,y)
--print(x, y, terrain)
if locs_map:get(x,y) then -- doesn't work with '^', so start search at char 2
table.insert(reachable_terrain, {x, y})
if locs_map:get(x,y) then
table.insert(reachable_wander_terrain, {x, y})
end
end)
--print(' #reachable_terrain', #reachable_terrain)
-- Choose one of the possible locations at random
if reachable_terrain[1] then
local rand = math.random(#reachable_terrain)
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_terrain[rand][1]) or (unit.y ~= reachable_terrain[rand][2]) then
AH.checked_move(ai, unit, reachable_terrain[rand][1], reachable_terrain[rand][2])
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 locs = wesnoth.get_locations(wander_terrain)
else -- Or if no close reachable terrain was found, move toward the closest
local best_hex, min_dist = {}, 9e99
for j,l in ipairs(locs) do
local d = H.distance_between(l[1], l[2], unit.x, unit.y)
if d < min_dist then
best_hex, min_dist = l,d
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[1]) then
local x,y = wesnoth.find_vacant_tile(best_hex[1], best_hex[2], unit)
local next_hop = AH.next_hop(unit, x, y)
--print(next_hop[1], next_hop[2])
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
@ -125,17 +115,13 @@ function ca_forest_animals_move:execution(ai, cfg)
-- Now we check for close enemies again, as we might just have moved within reach of some
local close_enemies = {}
-- We use a trick here to exclude the case when the unit might have been
-- removed in an event above
if unit and unit.valid then
for j,e in ipairs(enemies) do
if (H.distance_between(unit.x, unit.y, e.x, e.y) <= unit.max_moves+1) then
table.insert(close_enemies, e)
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
--print(' #close_enemies after move', #close_enemies, #enemies, unit.id)
-- If there are close enemies, run away (and rabbits disappear into holes)
local rabbit_type = cfg.rabbit_type or "no_unit_of_this_type"
@ -144,33 +130,34 @@ function ca_forest_animals_move:execution(ai, cfg)
-- 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 i,e in ipairs(close_enemies) do
local d = H.distance_between(e.x, e.y, x, y)
rating = rating - 1 / d^2
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
-- 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)
--print(' farthest_hex: ', farthest_hex[1], farthest_hex[2])
-- This will always find at least the hex the unit is on
-- so no check is necessary
AH.movefull_stopunit(ai, unit, farthest_hex)
-- If this is a rabbit ending on a hole -> disappears
if (unit.type == rabbit_type) and hole_map:get(farthest_hex[1], farthest_hex[2]) then
if unit and unit.valid
and (unit.type == rabbit_type) and hole_map:get(farthest_hex[1], farthest_hex[2])
then
local command = "wesnoth.put_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 attacks, as these units never attack
-- Also take attacks away, as these units never attack
if unit and unit.valid then AH.checked_stopunit_all(ai, unit) end
end
end

View file

@ -5,9 +5,8 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
local ca_forest_animals_new_rabbit = {}
function ca_forest_animals_new_rabbit:evaluation(ai, cfg)
-- Put new rabbits out the if there are fewer than cfg.rabbit_number
-- but only if cfg.rabbit_type is set, otherwise do nothing
-- If this gets executed, we'll let the CA black-list itself
-- Put new rabbits on map if there are fewer than cfg.rabbit_number
-- To end this, we'll let the CA black-list itself
if (not cfg.rabbit_type) then return 0 end
return cfg.ca_score
@ -25,11 +24,12 @@ function ca_forest_animals_new_rabbit:execution(ai, cfg)
-- 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
local holes = {}
for _, item in ipairs(all_items) do
for _,item in ipairs(all_items) do
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
{ "filter_location", { x = item.x, y = item.y, radius = rabbit_enemy_distance } }
}
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
@ -46,13 +46,10 @@ function ca_forest_animals_new_rabbit:execution(ai, cfg)
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 }
--print('total number:', number)
number = number - #rabbits
--print('to add number:', number)
number = math.min(number, #holes)
--print('to add number possible:', number)
-- Now we just can take the first 'number' (randomized) holes
-- Now we simply take the first 'number' (randomized) holes
local tmp_unit = wesnoth.get_units { side = wesnoth.current.side }[1]
for i = 1,number do
local x, y = -1, -1
@ -66,7 +63,7 @@ function ca_forest_animals_new_rabbit:execution(ai, cfg)
.. wesnoth.current.side
.. ", type = '"
.. cfg.rabbit_type
.. "' } )"
.. "' })"
ai.synced_command(command, x, y)
end
end

View file

@ -22,9 +22,7 @@ local ca_forest_animals_tusker_attack = {}
function ca_forest_animals_tusker_attack:evaluation(ai, cfg)
-- Check whether there is an enemy next to a tusklet and attack it ("protective parents" AI)
-- Both cfg.tusker_type and cfg.tusklet_type need to be set for this to kick in
if (not cfg.tusker_type) or (not cfg.tusklet_type) then return 0 end
if (not get_tuskers(cfg)[1]) then return 0 end
if (not get_adjacent_enemies(cfg)[1]) then return 0 end
return cfg.ca_score
@ -35,37 +33,37 @@ function ca_forest_animals_tusker_attack:execution(ai, cfg)
local adjacent_enemies = get_adjacent_enemies(cfg)
-- Find the closest enemy to any tusker
local min_dist, attacker, target = 9e99, {}, {}
for i,t in ipairs(tuskers) do
for j,e in ipairs(adjacent_enemies) do
local dist = H.distance_between(t.x, t.y, e.x, e.y)
local min_dist, attacker, target = 9e99
for _,tusker in ipairs(tuskers) do
for _,enemy in ipairs(adjacent_enemies) do
local dist = H.distance_between(tusker.x, tusker.y, enemy.x, enemy.y)
if (dist < min_dist) then
min_dist, attacker, target = dist, t, e
min_dist, attacker, target = dist, tusker, enemy
end
end
end
--print(attacker.id, target.id)
-- The tusker moves as close to enemy as possible
-- Closeness to tusklets is secondary criterion
local adj_tusklets = wesnoth.get_units { side = wesnoth.current.side, type = cfg.tusklet_type,
local adj_tusklets = wesnoth.get_units {
side = wesnoth.current.side,
type = cfg.tusklet_type,
{ "filter_adjacent", { id = target.id } }
}
local best_hex = AH.find_best_move(attacker, function(x, y)
local rating = - H.distance_between(x, y, target.x, target.y)
for i,t in ipairs(adj_tusklets) do
if (H.distance_between(x, y, t.x, t.y) == 1) then rating = rating + 0.1 end
for _,tusklet in ipairs(adj_tusklets) do
if (H.distance_between(x, y, tusklet.x, tusklet.y) == 1) then rating = rating + 0.1 end
end
return rating
end)
--print('attacker', attacker.x, attacker.y, ' -> ', best_hex[1], best_hex[2])
AH.movefull_stopunit(ai, attacker, best_hex)
if (not attacker) or (not attacker.valid) then return end
if (not target) or (not target.valid) then return end
-- If adjacent, attack
local dist = H.distance_between(attacker.x, attacker.y, target.x, target.y)
if (dist == 1) then
AH.checked_attack(ai, attacker, target)

View file

@ -21,11 +21,9 @@ local ca_forest_animals_tusklet_move = {}
function ca_forest_animals_tusklet_move:evaluation(ai, cfg)
-- Tusklets will simply move toward the closest tusker, without regard for anything else
-- Except if no tuskers are left, in which case the previous CA takes over and does a random move
-- Except if no tuskers are left, in which case ca_forest_animals_move takes over and does a random move
-- Both cfg.tusker_type and cfg.tusklet_type need to be set for this to kick in
if (not cfg.tusker_type) or (not cfg.tusklet_type) then return 0 end
if (not get_tusklets(cfg)[1]) then return 0 end
if (not get_tuskers(cfg)[1]) then return 0 end
return cfg.ca_score
@ -35,22 +33,19 @@ function ca_forest_animals_tusklet_move:execution(ai, cfg)
local tusklets = get_tusklets(cfg)
local tuskers = get_tuskers(cfg)
for i,tusklet in ipairs(tusklets) do
-- find closest tusker
local goto_tusker, min_dist = {}, 9999
for i,t in ipairs(tuskers) do
local dist = H.distance_between(t.x, t.y, tusklet.x, tusklet.y)
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, t
min_dist, goto_tusker = dist, tusker
end
end
--print('closets tusker:', goto_tusker.x, goto_tusker.y, goto_tusker.id)
-- Move tusklet toward that tusker
local best_hex = AH.find_best_move(tusklet, function(x, y)
return -H.distance_between(x, y, goto_tusker.x, goto_tusker.y)
return - H.distance_between(x, y, goto_tusker.x, goto_tusker.y)
end)
--print('tusklet', tusklet.x, tusklet.y, ' -> ', best_hex[1], best_hex[2])
AH.movefull_stopunit(ai, tusklet, best_hex)
-- Also make sure tusklets never attack