Micro AIs: avoid code duplication between eval and exec functions

This commit is contained in:
mattsc 2014-04-09 18:23:46 -07:00
parent 6f40ed8046
commit 7fc8e9797b
23 changed files with 492 additions and 484 deletions

View file

@ -3,16 +3,19 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
local LS = wesnoth.require "lua/location_set.lua"
local MAIUV = wesnoth.require "ai/micro_ais/micro_ai_unit_variables.lua"
local ca_big_animals = {}
function ca_big_animals:evaluation(ai, cfg)
local units = wesnoth.get_units {
local function get_big_animals(cfg)
local big_animals = wesnoth.get_units {
side = wesnoth.current.side,
{ "and" , cfg.filter },
formula = '$this_unit.moves > 0'
}
return big_animals
end
if units[1] then return cfg.ca_score end
local ca_big_animals = {}
function ca_big_animals:evaluation(ai, cfg)
if get_big_animals(cfg)[1] then return cfg.ca_score end
return 0
end
@ -21,11 +24,7 @@ function ca_big_animals:execution(ai, cfg)
-- 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
local units = wesnoth.get_units {
side = wesnoth.current.side,
{ "and" , cfg.filter },
formula = '$this_unit.moves > 0'
}
local big_animals = get_big_animals(cfg)
local avoid = LS.of_pairs(wesnoth.get_locations { radius = 1,
{ "filter", { { "and", cfg.avoid_unit },
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
@ -33,7 +32,7 @@ function ca_big_animals:execution(ai, cfg)
})
--AH.put_labels(avoid)
for i,unit in ipairs(units) do
for i,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

View file

@ -1,41 +1,38 @@
local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local ca_coward = {}
function ca_coward:evaluation(ai, cfg)
local function get_coward(cfg)
local filter = cfg.filter or { id = cfg.id }
local unit = wesnoth.get_units({
local coward = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", filter },
formula = '$this_unit.moves > 0' }
)[1]
return coward
end
if unit then return cfg.ca_score end
local ca_coward = {}
function ca_coward:evaluation(ai, cfg)
if get_coward(cfg) then return cfg.ca_score end
return 0
end
-- cfg parameters: id, distance, seek_x, seek_y, avoid_x, avoid_y
function ca_coward:execution(ai, cfg)
local filter = cfg.filter or { id = cfg.id }
local unit = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", filter },
formula = '$this_unit.moves > 0' }
)[1]
local reach = wesnoth.find_reach(unit)
local coward = get_coward(cfg)
local reach = wesnoth.find_reach(coward)
-- enemy units within reach
local filter_second = cfg.filter_second or { { "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} } }
local enemies = wesnoth.get_units {
{ "and", filter_second },
{ "filter_location", {x = unit.x, y = unit.y, radius = cfg.distance} }
{ "filter_location", {x = coward.x, y = coward.y, radius = cfg.distance} }
}
-- if no enemies are within reach: keep unit from doing anything and exit
if not enemies[1] then
AH.checked_stopunit_all(ai, unit)
AH.checked_stopunit_all(ai, coward)
return
end
@ -43,7 +40,7 @@ function ca_coward:execution(ai, cfg)
for i,r in ipairs(reach) do
-- only consider unoccupied hexes
local occ_hex = wesnoth.get_units { x=r[1], y=r[2], { "not", { id = unit.id } } }[1]
local occ_hex = wesnoth.get_units { x=r[1], y=r[2], { "not", { id = coward.id } } }[1]
if not occ_hex then
-- Find combined distance weighting of all enemy units within distance
local value = 0
@ -102,10 +99,10 @@ function ca_coward:execution(ai, cfg)
end
--items.place_image(mx, my, "items/ring-gold.png")
AH.movefull_stopunit(ai, unit, mx, my)
if (not unit) or (not unit.valid) then return end
AH.movefull_stopunit(ai, coward, mx, my)
if (not coward) or (not coward.valid) then return end
AH.checked_stopunit_all(ai, unit)
AH.checked_stopunit_all(ai, coward)
end
return ca_coward

View file

@ -3,48 +3,46 @@ local W = H.set_wml_action_metatable {}
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
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 = wesnoth.get_units {
side = wesnoth.current.side,
type = deer_type .. ',' .. rabbit_type,
formula = '$this_unit.moves > 0'
}
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
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
end
end
return forest_animals
end
local ca_forest_animals_move = {}
function ca_forest_animals_move:evaluation(ai, cfg)
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 tusker_type = cfg.tusker_type or "no_unit_of_this_type"
local tusklet_type = cfg.tusklet_type or "no_unit_of_this_type"
local units = wesnoth.get_units { side = wesnoth.current.side,
type = deer_type .. ',' .. rabbit_type .. ',' .. tusker_type, formula = '$this_unit.moves > 0' }
local tusklets = wesnoth.get_units { side = wesnoth.current.side, type = tusklet_type, formula = '$this_unit.moves > 0' }
local all_tuskers = wesnoth.get_units { side = wesnoth.current.side, type = tusker_type }
-- If there are deer, rabbits or tuskers with moves left -> good
if units[1] then return cfg.ca_score end
-- Or, we move tusklets with this CA, if no tuskers are left (counting those without moves also)
if (not all_tuskers[1]) and tusklets[1] then return cfg.ca_score end
if get_forest_animals(cfg)[1] then return cfg.ca_score end
return 0
end
function ca_forest_animals_move:execution(ai, cfg)
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 tusker_type = cfg.tusker_type or "no_unit_of_this_type"
local tusklet_type = cfg.tusklet_type or "no_unit_of_this_type"
local wander_terrain = cfg.filter_location or {}
-- We want the deer/rabbits to move first, tuskers later
local units = wesnoth.get_units { side = wesnoth.current.side, type = deer_type .. ',' .. rabbit_type, formula = '$this_unit.moves > 0' }
local tuskers = wesnoth.get_units { side = wesnoth.current.side, type = tusker_type, formula = '$this_unit.moves > 0' }
for i,t in ipairs(tuskers) do table.insert(units, t) end
-- Also add tusklets if there are no tuskers left
local all_tuskers = wesnoth.get_units { side = wesnoth.current.side, type = tusker_type }
if not all_tuskers[1] then
local tusklets = wesnoth.get_units { side = wesnoth.current.side, type = tusklet_type, formula = '$this_unit.moves > 0' }
for i,t in ipairs(tusklets) do table.insert(units, t) end
end
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} }} } }
--print('#units, enemies', #units, #enemies)
-- Get the locations of all the rabbit holes
W.store_items { variable = 'holes_wml' }
@ -63,7 +61,7 @@ function ca_forest_animals_move:execution(ai, cfg)
--AH.put_labels(hole_map)
-- Each unit moves independently
for i,unit in ipairs(units) do
for i,unit in ipairs(forest_animals) do
--print('Unit', i, unit.x, unit.y)
-- Behavior is different depending on whether a predator is close or not
local close_enemies = {}
@ -75,6 +73,7 @@ function ca_forest_animals_move:execution(ai, cfg)
--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)
@ -135,6 +134,7 @@ function ca_forest_animals_move:execution(ai, cfg)
--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"
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

View file

@ -1,6 +1,23 @@
local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local function get_tuskers(cfg)
local tuskers = wesnoth.get_units {
side = wesnoth.current.side,
type = cfg.tusker_type,
formula = '$this_unit.moves > 0'
}
return tuskers
end
local function get_adjacent_enemies(cfg)
local adjacent_enemies = wesnoth.get_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
{ "filter_adjacent", { side = wesnoth.current.side, type = cfg.tusklet_type } }
}
return adjacent_enemies
end
local ca_forest_animals_tusker_attack = {}
function ca_forest_animals_tusker_attack:evaluation(ai, cfg)
@ -9,28 +26,19 @@ function ca_forest_animals_tusker_attack:evaluation(ai, cfg)
-- 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
local tuskers = wesnoth.get_units { side = wesnoth.current.side, type = cfg.tusker_type, formula = '$this_unit.moves > 0' }
local adj_enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_adjacent", { side = wesnoth.current.side, type = cfg.tusklet_type } }
}
--print('#tuskers, #adj_enemies', #tuskers, #adj_enemies)
if tuskers[1] and adj_enemies[1] then return cfg.ca_score end
return 0
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
end
function ca_forest_animals_tusker_attack:execution(ai, cfg)
local tuskers = wesnoth.get_units { side = wesnoth.current.side, type = cfg.tusker_type, formula = '$this_unit.moves > 0' }
local adj_enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_adjacent", { side = wesnoth.current.side, type = cfg.tusklet_type } }
}
local tuskers = get_tuskers(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(adj_enemies) do
for j,e in ipairs(adjacent_enemies) do
local dist = H.distance_between(t.x, t.y, e.x, e.y)
if (dist < min_dist) then
min_dist, attacker, target = dist, t, e

View file

@ -1,6 +1,23 @@
local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local function get_tusklets(cfg)
local tusklets = wesnoth.get_units {
side = wesnoth.current.side,
type = cfg.tusklet_type,
formula = '$this_unit.moves > 0'
}
return tusklets
end
local function get_tuskers(cfg)
local tuskers = wesnoth.get_units {
side = wesnoth.current.side,
type = cfg.tusker_type
}
return tuskers
end
local ca_forest_animals_tusklet_move = {}
function ca_forest_animals_tusklet_move:evaluation(ai, cfg)
@ -10,17 +27,14 @@ function ca_forest_animals_tusklet_move:evaluation(ai, cfg)
-- 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
local tusklets = wesnoth.get_units { side = wesnoth.current.side, type = cfg.tusklet_type, formula = '$this_unit.moves > 0' }
local tuskers = wesnoth.get_units { side = wesnoth.current.side, type = cfg.tusker_type }
if tusklets[1] and tuskers[1] then return cfg.ca_score end
return 0
if (not get_tusklets(cfg)[1]) then return 0 end
if (not get_tuskers(cfg)[1]) then return 0 end
return cfg.ca_score
end
function ca_forest_animals_tusklet_move:execution(ai, cfg)
local tusklets = wesnoth.get_units { side = wesnoth.current.side, type = cfg.tusklet_type, formula = '$this_unit.moves > 0' }
local tuskers = wesnoth.get_units { side = wesnoth.current.side, type = cfg.tusker_type }
--print('#tusklets, #tuskers', #tusklets, #tuskers)
local tusklets = get_tusklets(cfg)
local tuskers = get_tuskers(cfg)
for i,tusklet in ipairs(tusklets) do
-- find closest tusker

View file

@ -4,11 +4,18 @@ local LS = wesnoth.require "lua/location_set.lua"
local MAIUV = wesnoth.require "ai/micro_ais/micro_ai_unit_variables.lua"
local MAISD = wesnoth.require "ai/micro_ais/micro_ai_self_data.lua"
local function get_hang_out_units(cfg)
local units = wesnoth.get_units {
side = wesnoth.current.side,
{ "and", cfg.filter },
formula = '$this_unit.moves > 0'
}
return units
end
local ca_hang_out = {}
function ca_hang_out:evaluation(ai, cfg, self)
cfg = cfg or {}
-- Return 0 if the mobilize condition has previously been met
if MAISD.get_mai_self_data(self.data, cfg.ai_id, "mobilize_units") then
return 0
@ -29,22 +36,12 @@ function ca_hang_out:evaluation(ai, cfg, self)
return 0
end
local units = wesnoth.get_units { side = wesnoth.current.side,
{ "and", cfg.filter }, formula = '$this_unit.moves > 0'
}
if units[1] then
return cfg.ca_score
end
if get_hang_out_units(cfg)[1] then return cfg.ca_score end
return 0
end
function ca_hang_out:execution(ai, cfg, self)
cfg = cfg or {}
local units = wesnoth.get_units { side = wesnoth.current.side,
{ "and", cfg.filter }, formula = '$this_unit.moves > 0'
}
--print('#unit', #units)
local units = get_hang_out_units(cfg)
-- Get the locations close to which the units should hang out
-- cfg.filter_location defaults to the location of the side leader(s)

View file

@ -1,40 +1,55 @@
local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local function get_sheep(cfg)
local sheep = wesnoth.get_units {
side = wesnoth.current.side,
{ "and", cfg.filter_second }
}
return sheep
end
local function get_dogs(cfg)
local dogs = wesnoth.get_units {
side = wesnoth.current.side,
{ "and", cfg.filter },
formula = '$this_unit.moves > 0'
}
return dogs
end
local function get_enemies(cfg, radius)
local enemies = wesnoth.get_units {
{ "filter_side", { {"enemy_of", {side = wesnoth.current.side} } } },
{ "filter_location",
{ radius = radius,
{ "filter", { side = wesnoth.current.side, { "and", cfg.filter_second } } } }
}
}
return enemies
end
local ca_herding_attack_close_enemy = {}
function ca_herding_attack_close_enemy:evaluation(ai, cfg)
-- Any enemy within attention_distance (default = 8) hexes of a sheep will get the dogs' attention
-- with enemies within attack_distance (default: 4) being attacked
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_location",
{ radius = (cfg.attention_distance or 8),
{ "filter", { side = wesnoth.current.side, {"and", cfg.filter_second} } } }
}
}
local dogs = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter},
formula = '$this_unit.moves > 0'
}
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second} }
if not get_sheep(cfg)[1] then return 0 end
if not get_dogs(cfg)[1] then return 0 end
if enemies[1] and dogs[1] and sheep[1] then return cfg.ca_score end
local radius = cfg.attention_distance or 8
if get_enemies(cfg, radius)[1] then return cfg.ca_score end
return 0
end
function ca_herding_attack_close_enemy:execution(ai, cfg)
local dogs = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter},
formula = '$this_unit.moves > 0' }
local sheep = get_sheep(cfg)
local dogs = get_dogs(cfg)
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second} }
-- We start with enemies within attack_distance (default: 4) hexes, which will be attacked
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_location",
{ radius = (cfg.attack_distance or 4),
{ "filter", { side = wesnoth.current.side, {"and", cfg.filter_second} } } }
}
}
local radius = cfg.attack_distance or 4
local enemies = get_enemies(cfg, radius)
max_rating, best_dog, best_enemy, best_hex = -9e99, {}, {}, {}
for i,e in ipairs(enemies) do
@ -75,13 +90,8 @@ function ca_herding_attack_close_enemy:execution(ai, cfg)
-- If we got here, no enemies to attack where found, so we go on to block other enemies
--print('Dogs: No enemies close enough to warrant attack')
-- Now we get all enemies within attention_distance hexes
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_location",
{ radius = (cfg.attention_distance or 8),
{ "filter", { side = wesnoth.current.side, {"and", cfg.filter_second} } } }
}
}
local radius = cfg.attention_distance or 8
local enemies = get_enemies(cfg, radius)
-- Find closest sheep/enemy pair first
local min_dist, closest_sheep, closest_enemy = 9e99, {}, {}

View file

@ -2,27 +2,28 @@ 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 function get_dog(cfg)
local dogs = wesnoth.get_units {
side = wesnoth.current.side,
{ "and", cfg.filter },
formula = '$this_unit.moves > 0',
{ "not", { { "filter_adjacent", { side = wesnoth.current.side, {"and", cfg.filter_second} } } } }
}
return dogs[1]
end
local ca_herding_dog_move = {}
function ca_herding_dog_move:evaluation(ai, cfg)
-- As a final step, any dog not adjacent to a sheep moves within herding_perimeter
local dogs = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter},
formula = '$this_unit.moves > 0',
{ "not", { { "filter_adjacent", { side = wesnoth.current.side, {"and", cfg.filter_second} } } } }
}
if dogs[1] then return cfg.ca_score end
if get_dog(cfg) then return cfg.ca_score end
return 0
end
function ca_herding_dog_move:execution(ai, cfg)
-- We simply move the first dog first
local dog = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter},
formula = '$this_unit.moves > 0',
{ "not", { { "filter_adjacent", { side = wesnoth.current.side, {"and", cfg.filter_second} } } } }
}[1]
-- We simply move the first dog first, order does not matter
local dog = get_dog(cfg)
local herding_perimeter = LS.of_pairs(wesnoth.get_locations(cfg.filter_location))
--AH.put_labels(herding_perimeter)
-- Find average distance of herding_perimeter from center
local av_dist = 0

View file

@ -3,46 +3,44 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
local herding_area = wesnoth.require "ai/micro_ais/cas/ca_herding_f_herding_area.lua"
local function get_dogs(cfg)
local dogs = wesnoth.get_units {
side = wesnoth.current.side,
{ "and", cfg.filter },
formula = '$this_unit.moves > 0'
}
return dogs
end
local function get_sheep_to_herd(cfg)
local all_sheep = wesnoth.get_units {
side = wesnoth.current.side,
{ "and", cfg.filter_second },
{ "not", { { "filter_adjacent", { side = wesnoth.current.side, {"and", cfg.filter} } } } }
}
local sheep_to_herd = {}
local herding_area = herding_area(cfg)
for i,s in ipairs(all_sheep) do
if (not herding_area:get(s.x, s.y)) then table.insert(sheep_to_herd, s) end
end
return sheep_to_herd
end
local ca_herding_herd_sheep = {}
function ca_herding_herd_sheep:evaluation(ai, cfg)
-- If dogs have moves left, and there is a sheep with moves left outside the
-- herding area, chase it back
-- We'll do a bunch of nested if's, to speed things up
local dogs = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter}, formula = '$this_unit.moves > 0' }
if dogs[1] then
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second},
{ "not", { { "filter_adjacent", { side = wesnoth.current.side, {"and", cfg.filter} } } } }
}
if sheep[1] then
local herding_area = herding_area(cfg)
for i,s in ipairs(sheep) do
-- If a sheep is found outside the herding area, we want to chase it back
if (not herding_area:get(s.x, s.y)) then return cfg.ca_score end
end
end
if get_dogs(cfg)[1] then
if get_sheep_to_herd(cfg)[1] then return cfg.ca_score end
end
-- If we got here, no valid dog/sheep combos were found
return 0
end
function ca_herding_herd_sheep:execution(ai, cfg)
local dogs = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter}, formula = '$this_unit.moves > 0' }
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second},
{ "not", { { "filter_adjacent", { side = wesnoth.current.side, {"and", cfg.filter} } } } }
}
local herding_area = herding_area(cfg)
local sheep_to_herd = {}
for i,s in ipairs(sheep) do
-- If a sheep is found outside the herding area, we want to chase it back
if (not herding_area:get(s.x, s.y)) then table.insert(sheep_to_herd, s) end
end
sheep = nil
local dogs = get_dogs(cfg)
local sheep_to_herd = get_sheep_to_herd(cfg)
-- Find the farthest out sheep that the dogs can get to (and that has moves left)
-- Find all sheep that have stepped out of bound
local max_rating, best_dog, best_hex = -9e99, {}, {}
local c_x, c_y = cfg.herd_x, cfg.herd_y
for i,s in ipairs(sheep_to_herd) do

View file

@ -4,18 +4,26 @@ local LS = wesnoth.require "lua/location_set.lua"
local herding_area = wesnoth.require "ai/micro_ais/cas/ca_herding_f_herding_area.lua"
local function get_next_sheep(cfg)
local sheep = wesnoth.get_units {
side = wesnoth.current.side,
{ "and", cfg.filter_second },
formula = '$this_unit.moves > 0'
}
return sheep[1]
end
local ca_herding_sheep_move = {}
function ca_herding_sheep_move:evaluation(ai, cfg)
-- If nothing else is to be done, the sheep do a random move
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second}, formula = '$this_unit.moves > 0' }
if sheep[1] then return cfg.ca_score end
if get_next_sheep(cfg) then return cfg.ca_score end
return 0
end
function ca_herding_sheep_move:execution(ai, cfg)
-- We simply move the first sheep first
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second}, formula = '$this_unit.moves > 0' }[1]
-- We simply move the first sheep first, the order does not matter
local sheep = get_next_sheep(cfg)
local reach_map = AH.get_reachable_unocc(sheep)
-- Exclude those that are next to a dog

View file

@ -1,26 +1,27 @@
local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local ca_herding_sheep_runs_dog = {}
function ca_herding_sheep_runs_dog:evaluation(ai, cfg)
-- Any sheep with moves left next to a dog runs aways
local function get_next_sheep(cfg)
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second},
formula = '$this_unit.moves > 0',
{ "filter_adjacent", { side = wesnoth.current.side, {"and", cfg.filter} } }
}
return sheep[1]
end
if sheep[1] then return cfg.ca_score end
local ca_herding_sheep_runs_dog = {}
function ca_herding_sheep_runs_dog:evaluation(ai, cfg)
-- Any sheep with moves left next to a dog runs away
if get_next_sheep(cfg) then return cfg.ca_score end
return 0
end
function ca_herding_sheep_runs_dog:execution(ai, cfg)
-- simply get the first sheep
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second},
formula = '$this_unit.moves > 0',
{ "filter_adjacent", { side = wesnoth.current.side, {"and", cfg.filter} } }
}[1]
-- and the first dog it is adjacent to
-- Simply get the first sheep, order does not matter
local sheep = get_next_sheep(cfg)
-- Get the first dog it is adjacent to
local dog = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter},
{ "filter_adjacent", { x = sheep.x, y = sheep.y } }
}[1]

View file

@ -1,39 +1,32 @@
local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local function get_next_sheep(cfg)
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second},
formula = '$this_unit.moves > 0',
{ "filter_location",
{
{ "filter", { { "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} } }
},
radius = (cfg.attention_distance or 8)
}
}
}
return sheep[1]
end
local ca_herding_sheep_runs_enemy = {}
function ca_herding_sheep_runs_enemy:evaluation(ai, cfg)
-- Sheep runs from any enemy within attention_distance hexes (after the dogs have moved in)
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second},
formula = '$this_unit.moves > 0',
{ "filter_location",
{
{ "filter", { { "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} } }
},
radius = (cfg.attention_distance or 8)
}
}
}
if sheep[1] then return cfg.ca_score end
if get_next_sheep(cfg) then return cfg.ca_score end
return 0
end
function ca_herding_sheep_runs_enemy:execution(ai, cfg)
local sheep = wesnoth.get_units { side = wesnoth.current.side, {"and", cfg.filter_second},
formula = '$this_unit.moves > 0',
{ "filter_location",
{
{ "filter", { { "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} } }
},
radius = (cfg.attention_distance or 8)
}
}
}
-- Simply start with the first of the sheep, order does not matter
local sheep = get_next_sheep(cfg)
-- Simply start with the first of these sheep
sheep = sheep[1]
-- And find the close enemies
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },

View file

@ -3,21 +3,19 @@ local W = H.set_wml_action_metatable {}
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local MAIUV = wesnoth.require "ai/micro_ais/micro_ai_unit_variables.lua"
local ca_hunter = {}
local function hunter_attack_weakest_adj_enemy(ai, unit)
-- Attack the enemy with the fewest hitpoints adjacent to 'unit', if there is one
local function hunter_attack_weakest_adj_enemy(ai, hunter)
-- Attack the enemy with the fewest hitpoints adjacent to 'hunter', if there is one
-- Returns status of the attack:
-- 'attacked': if a unit was attacked
-- 'killed': if a unit was killed
-- 'no_attack': if no unit was attacked
-- First check that the unit exists and has attacks left
if (not unit.valid) then return 'no_attack' end
if (unit.attacks_left == 0) then return 'no_attack' end
-- First check that the hunter exists and has attacks left
if (not hunter.valid) then return 'no_attack' end
if (hunter.attacks_left == 0) then return 'no_attack' end
local min_hp, target = 9e99, {}
for x, y in H.adjacent_tiles(unit.x, unit.y) do
for x, y in H.adjacent_tiles(hunter.x, hunter.y) do
local enemy = wesnoth.get_unit(x, y)
if enemy and wesnoth.is_enemy(enemy.side, wesnoth.current.side) then
if (enemy.hitpoints < min_hp) then
@ -27,8 +25,8 @@ local function hunter_attack_weakest_adj_enemy(ai, unit)
end
if target.id then
--W.message { speaker = unit.id, message = 'Attacking weakest adjacent enemy' }
AH.checked_attack(ai, unit, target)
--W.message { speaker = hunter.id, message = 'Attacking weakest adjacent enemy' }
AH.checked_attack(ai, hunter, target)
if target.valid then
return 'attacked'
else
@ -39,15 +37,20 @@ local function hunter_attack_weakest_adj_enemy(ai, unit)
return 'no_attack'
end
function ca_hunter:evaluation(ai, cfg)
local function get_hunter(cfg)
local filter = cfg.filter or { id = cfg.id }
local unit = wesnoth.get_units({
local hunter = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", filter },
formula = '$this_unit.moves > 0' }
)[1]
return hunter
end
if unit then return cfg.ca_score end
local ca_hunter = {}
function ca_hunter:evaluation(ai, cfg)
if get_hunter(cfg) then return cfg.ca_score end
return 0
end
@ -57,30 +60,25 @@ function ca_hunter:execution(ai, cfg)
-- hunting_ground, then retreats to
-- position given by 'home_x,home_y' for 'rest_turns' turns, or until fully healed
local filter = cfg.filter or { id = cfg.id }
local unit = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", filter },
formula = '$this_unit.moves > 0' }
)[1]
local hunter = get_hunter(cfg)
-- If hunting_status is not set for the unit -> default behavior -> hunting
local hunter_vars = MAIUV.get_mai_unit_variables(unit, cfg.ai_id)
-- If hunting_status is not set for the hunter -> default behavior -> hunting
local hunter_vars = MAIUV.get_mai_unit_variables(hunter, cfg.ai_id)
if (not hunter_vars.hunting_status) then
-- Unit gets a new goal if none exist or on any move with 10% random chance
-- Hunter gets a new goal if none exist or on any move with 10% random chance
local r = math.random(10)
if (not hunter_vars.goal_x) or (r <= 1) then
-- 'locs' includes border hexes, but that does not matter here
locs = AH.get_passable_locations((cfg.filter_location or {}), unit)
locs = AH.get_passable_locations((cfg.filter_location or {}), hunter)
local rand = math.random(#locs)
--print('#locs', #locs, rand)
hunter_vars.goal_x, hunter_vars.goal_y = locs[rand][1], locs[rand][2]
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, hunter_vars)
MAIUV.set_mai_unit_variables(hunter, cfg.ai_id, hunter_vars)
end
--print('Hunter goto: ', hunter_vars.goal_x, hunter_vars.goal_y, r)
-- Hexes the unit can reach
local reach_map = AH.get_reachable_unocc(unit)
-- Hexes the hunter can reach
local reach_map = AH.get_reachable_unocc(hunter)
-- Now find the one of these hexes that is closest to the goal
local max_rating, best_hex = -9e99, {}
@ -106,32 +104,32 @@ function ca_hunter:execution(ai, cfg)
--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
if (not unit) or (not unit.valid) then return end
if (best_hex[1] ~= hunter.x) or (best_hex[2] ~= hunter.y) then
AH.checked_move(ai, hunter, best_hex[1], best_hex[2]) -- partial move only
if (not hunter) or (not hunter.valid) then return end
else -- If hunter 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
AH.checked_stopunit_moves(ai, hunter)
if (not hunter) or (not hunter.valid) then return end
hunter_vars.goal_x, hunter_vars.goal_y = nil, nil
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, hunter_vars)
MAIUV.set_mai_unit_variables(hunter, cfg.ai_id, hunter_vars)
end
-- Or if this gets the unit to the goal, we also delete the goal
if (unit.x == hunter_vars.goal_x) and (unit.y == hunter_vars.goal_y) then
-- Or if this gets the hunter to the goal, we also delete the goal
if (hunter.x == hunter_vars.goal_x) and (hunter.y == hunter_vars.goal_y) then
hunter_vars.goal_x, hunter_vars.goal_y = nil, nil
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, hunter_vars)
MAIUV.set_mai_unit_variables(hunter, cfg.ai_id, hunter_vars)
end
-- Finally, if the unit ended up next to enemies, attack the weakest of those
local attack_status = hunter_attack_weakest_adj_enemy(ai, unit)
-- Finally, if the hunter ended up next to enemies, attack the weakest of those
local attack_status = hunter_attack_weakest_adj_enemy(ai, hunter)
-- If the enemy was killed, hunter returns home
if unit.valid and (attack_status == 'killed') then
if hunter.valid and (attack_status == 'killed') then
hunter_vars.goal_x, hunter_vars.goal_y = nil, nil
hunter_vars.hunting_status = 'return'
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, hunter_vars)
MAIUV.set_mai_unit_variables(hunter, cfg.ai_id, hunter_vars)
if cfg.show_messages then
W.message { speaker = unit.id, message = 'Now that I have eaten, I will go back home.' }
W.message { speaker = hunter.id, message = 'Now that I have eaten, I will go back home.' }
end
end
@ -139,41 +137,41 @@ function ca_hunter:execution(ai, cfg)
return
end
-- If we got here, this means the unit is either returning, or resting
-- If we got here, this means the hunter is either returning, or resting
if (hunter_vars.hunting_status == 'return') then
goto_x, goto_y = wesnoth.find_vacant_tile(cfg.home_x, cfg.home_y)
--print('Go home:', home_x, home_y, goto_x, goto_y)
local next_hop = AH.next_hop(unit, goto_x, goto_y)
local next_hop = AH.next_hop(hunter, goto_x, goto_y)
if next_hop then
--print(next_hop[1], next_hop[2])
AH.movefull_stopunit(ai, unit, next_hop)
if (not unit) or (not unit.valid) then return end
AH.movefull_stopunit(ai, hunter, next_hop)
if (not hunter) or (not hunter.valid) then return end
-- If there's an enemy on the 'home' hex and we got right next to it, attack that enemy
if (H.distance_between(cfg.home_x, cfg.home_y, next_hop[1], next_hop[2]) == 1) then
local enemy = wesnoth.get_unit(cfg.home_x, cfg.home_y)
if enemy and wesnoth.is_enemy(enemy.side, unit.side) then
if enemy and wesnoth.is_enemy(enemy.side, hunter.side) then
if cfg.show_messages then
W.message { speaker = unit.id, message = 'Get out of my home!' }
W.message { speaker = hunter.id, message = 'Get out of my home!' }
end
AH.checked_attack(ai, unit, enemy)
if (not unit) or (not unit.valid) then return end
AH.checked_attack(ai, hunter, enemy)
if (not hunter) or (not hunter.valid) then return end
end
end
end
-- We also attack the weakest adjacent enemy, if still possible
hunter_attack_weakest_adj_enemy(ai, unit)
if (not unit) or (not unit.valid) then return end
hunter_attack_weakest_adj_enemy(ai, hunter)
if (not hunter) or (not hunter.valid) then return end
-- If the unit got home, start the resting counter
if (unit.x == cfg.home_x) and (unit.y == cfg.home_y) then
-- If the hunter got home, start the resting counter
if (hunter.x == cfg.home_x) and (hunter.y == cfg.home_y) then
hunter_vars.hunting_status = 'resting'
hunter_vars.resting_until = wesnoth.current.turn + (cfg.rest_turns or 3)
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, hunter_vars)
MAIUV.set_mai_unit_variables(hunter, cfg.ai_id, hunter_vars)
if cfg.show_messages then
W.message { speaker = unit.id, message = 'I made it home - resting now until the end of Turn ' .. hunter_vars.resting_until .. ' or until fully healed.' }
W.message { speaker = hunter.id, message = 'I made it home - resting now until the end of Turn ' .. hunter_vars.resting_until .. ' or until fully healed.' }
end
end
@ -183,29 +181,29 @@ function ca_hunter:execution(ai, cfg)
-- If we got here, the only remaining action is resting
if (hunter_vars.hunting_status == 'resting') then
-- So all we need to do is take moves away from the unit
AH.checked_stopunit_moves(ai, unit)
if (not unit) or (not unit.valid) then return end
-- So all we need to do is take moves away from the hunter
AH.checked_stopunit_moves(ai, hunter)
if (not hunter) or (not hunter.valid) then return end
-- However, we do also attack the weakest adjacent enemy, if still possible
hunter_attack_weakest_adj_enemy(ai, unit)
if (not unit) or (not unit.valid) then return end
hunter_attack_weakest_adj_enemy(ai, hunter)
if (not hunter) or (not hunter.valid) then return end
-- If this is the last turn of resting, we also remove the status and turn variable
if (unit.hitpoints >= unit.max_hitpoints) and (hunter_vars.resting_until <= wesnoth.current.turn) then
if (hunter.hitpoints >= hunter.max_hitpoints) and (hunter_vars.resting_until <= wesnoth.current.turn) then
hunter_vars.hunting_status = nil
hunter_vars.resting_until = nil
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, hunter_vars)
MAIUV.set_mai_unit_variables(hunter, cfg.ai_id, hunter_vars)
if cfg.show_messages then
W.message { speaker = unit.id, message = 'I am done resting. It is time to go hunting again next turn.' }
W.message { speaker = hunter.id, message = 'I am done resting. It is time to go hunting again next turn.' }
end
end
return
end
-- In principle we should never get here, but just in case: reset variable, so that unit goes hunting on next turn
-- In principle we should never get here, but just in case: reset variable, so that hunter goes hunting on next turn
hunter_vars.hunting_status = nil
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, hunter_vars)
MAIUV.set_mai_unit_variables(hunter, cfg.ai_id, hunter_vars)
end
return ca_hunter

View file

@ -1,26 +1,23 @@
local LS = wesnoth.require "lua/location_set.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local function get_lurker(cfg)
local lurker = wesnoth.get_units { side = wesnoth.current.side,
{ "and", cfg.filter }, formula = '$this_unit.moves > 0'
}[1]
return lurker
end
local ca_lurkers = {}
function ca_lurkers:evaluation(ai, cfg)
-- If any lurker has moves left, we return score just above standard combat CA
local units = wesnoth.get_units { side = wesnoth.current.side,
{ "and", cfg.filter }, formula = '$this_unit.moves > 0'
}
if units[1] then return cfg.ca_score end
if get_lurker(cfg) then return cfg.ca_score end
return 0
end
function ca_lurkers:execution(ai, cfg)
-- We simply pick the first of the lurkers, they have no strategy
local me = wesnoth.get_units { side = wesnoth.current.side,
{ "and", cfg.filter }, formula = '$this_unit.moves > 0'
}[1]
--print("me at:" .. me.x .. "," .. me.y)
-- Potential targets
local lurker = get_lurker(cfg)
local targets = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
}
@ -29,11 +26,11 @@ function ca_lurkers:execution(ai, cfg)
--print("Number of potential targets:", #targets)
-- all reachable hexes
local reach = LS.of_pairs( wesnoth.find_reach(me.x, me.y) )
local reach = LS.of_pairs(wesnoth.find_reach(lurker.x, lurker.y))
-- all reachable attack hexes
local reachable_attack_terrain =
LS.of_pairs( wesnoth.get_locations {
{"and", {x = me.x, y = me.y, radius = me.moves} },
{"and", {x = lurker.x, y = lurker.y, radius = lurker.moves} },
{"and", cfg.filter_location}
} )
reachable_attack_terrain:inter(reach)
@ -41,7 +38,7 @@ function ca_lurkers:execution(ai, cfg)
-- need to restrict that to reachable and not occupied by an ally (except own position)
local reachable_attack_terrain = reachable_attack_terrain:filter(function(x, y, v)
local occ_hex = wesnoth.get_units { x = x, y = y, { "not", { x = me.x, y = me.y } } }[1]
local occ_hex = wesnoth.get_units { x = x, y = y, { "not", { x = lurker.x, y = lurker.y } } }[1]
return not occ_hex
end)
--print(" reach: " .. reach:size() .. " reach_attack no allies: " .. reachable_attack_terrain:size())
@ -62,14 +59,14 @@ function ca_lurkers:execution(ai, cfg)
-- Choose one of the possible attack locations at random
local rand = math.random(1, rattack_nt_target:size())
local dst = rattack_nt_target:to_stable_pairs()
AH.movefull_stopunit(ai, me, dst[rand])
if (not me) or (not me.valid) then return end
AH.checked_attack(ai, me, target)
AH.movefull_stopunit(ai, lurker, dst[rand])
if (not lurker) or (not lurker.valid) then return end
AH.checked_attack(ai, lurker, target)
attacked = true
break
end
end
if (not me) or (not me.valid) then return end
if (not lurker) or (not lurker.valid) then return end
-- If unit has moves left (that is, it didn't attack), go to random wander terrain hex
-- Check first that unit wasn't killed in the attack
@ -77,7 +74,7 @@ function ca_lurkers:execution(ai, cfg)
local reachable_wander_terrain =
LS.of_pairs( wesnoth.get_locations {
{"and", {x = me.x, y = me.y, radius = me.moves} },
{"and", {x = lurker.x, y = lurker.y, radius = lurker.moves} },
{"and", (cfg.filter_location_wander or cfg.filter_location)}
} )
reachable_wander_terrain:inter(reach)
@ -89,14 +86,14 @@ function ca_lurkers:execution(ai, cfg)
if dst[1] then
dst = dst[rand]
else
dst = { me.x, me.y }
dst = { lurker.x, lurker.y }
end
AH.movefull_stopunit(ai, me, dst)
AH.movefull_stopunit(ai, lurker, dst)
end
if (not me) or (not me.valid) then return end
if (not lurker) or (not lurker.valid) then return end
-- If the unit has moves or attacks left at this point, take them away
AH.checked_stopunit_all(ai, me)
AH.checked_stopunit_all(ai, lurker)
end
return ca_lurkers

View file

@ -5,19 +5,23 @@ local MAIUV = wesnoth.require "ai/micro_ais/micro_ai_unit_variables.lua"
local messenger_next_waypoint = wesnoth.require "ai/micro_ais/cas/ca_messenger_f_next_waypoint.lua"
local function get_escorts(cfg)
local escorts = wesnoth.get_units {
side = wesnoth.current.side,
formula = '$this_unit.moves > 0',
{ "and", cfg.filter_second }
}
return escorts
end
local ca_messenger_escort_move = {}
function ca_messenger_escort_move:evaluation(ai, cfg)
-- Move escort units close to messengers, and in between messengers and enemies
-- The messengers have moved at this time, so we don't need to exclude them,
-- but we check that there are messengers left
local my_units = wesnoth.get_units {
side = wesnoth.current.side,
formula = '$this_unit.moves > 0',
{ "and", cfg.filter_second }
}
if (not my_units[1]) then return 0 end
local escort = get_escorts(cfg)[1]
if (not escort) then return 0 end
local _, _, _, messengers = messenger_next_waypoint(cfg)
if (not messengers) or (not messengers[1]) then return 0 end
@ -26,12 +30,7 @@ function ca_messenger_escort_move:evaluation(ai, cfg)
end
function ca_messenger_escort_move:execution(ai, cfg)
local my_units = wesnoth.get_units {
side = wesnoth.current.side,
formula = '$this_unit.moves > 0',
{ "and", cfg.filter_second }
}
local escorts = get_escorts(cfg)
local _, _, _, messengers = messenger_next_waypoint(cfg)
local enemies = wesnoth.get_units {
@ -40,7 +39,7 @@ function ca_messenger_escort_move:execution(ai, cfg)
local base_rating_map = LS.create()
local max_rating, best_unit, best_hex = -9e99
for _,unit in ipairs(my_units) do
for _,unit in ipairs(escorts) do
-- Only considering hexes unoccupied by other units is good enough for this
local reach_map = AH.get_reachable_unocc(unit)

View file

@ -2,28 +2,25 @@ local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local MAIUV = wesnoth.require "ai/micro_ais/micro_ai_unit_variables.lua"
local ca_patrol = {}
function ca_patrol:evaluation(ai, cfg)
local function get_patrol(cfg)
local filter = cfg.filter or { id = cfg.id }
local patrol = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", filter },
formula = '$this_unit.moves > 0' }
)[1]
return patrol
end
if patrol then return cfg.ca_score end
local ca_patrol = {}
function ca_patrol:evaluation(ai, cfg)
if get_patrol(cfg) then return cfg.ca_score end
return 0
end
function ca_patrol:execution(ai, cfg)
local filter = cfg.filter or { id = cfg.id }
local patrol = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", filter },
formula = '$this_unit.moves > 0' }
)[1]
local patrol = get_patrol(cfg)
cfg.waypoint_x = AH.split(cfg.waypoint_x, ",")
cfg.waypoint_y = AH.split(cfg.waypoint_y, ",")

View file

@ -3,25 +3,24 @@ local LS = wesnoth.require "lua/location_set.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local BC = wesnoth.require "ai/lua/battle_calcs.lua"
local function get_protected_units(cfg)
local units = {}
for i,id in ipairs(cfg.id) do
table.insert(units, wesnoth.get_units { id = id, formula = '$this_unit.moves > 0' }[1])
end
return units
end
local ca_protect_unit_move = {}
function ca_protect_unit_move:evaluation(ai, cfg, self)
-- Always 94999 if one of the units can still move
local units = {}
for i,id in ipairs(cfg.id) do
table.insert(units, wesnoth.get_units{ id = id, formula = '$this_unit.moves > 0' }[1])
end
if units[1] then return 94999 end
if get_protected_units(cfg)[1] then return 94999 end
return 0
end
function ca_protect_unit_move:execution(ai, cfg, self)
-- Find and execute best (safest) move toward goal
local units = {}
for i,id in ipairs(cfg.id) do
table.insert(units, wesnoth.get_units{ id = id, formula = '$this_unit.moves > 0' }[1])
end
local units = get_protected_units(cfg)
-- Need to take the units off the map, as they don't count into the map scores
-- (as long as they can still move)

View file

@ -1,17 +1,21 @@
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local ca_return_guardian = {}
function ca_return_guardian:evaluation(ai, cfg)
local function get_guardian(cfg)
local filter = cfg.filter or { id = cfg.id }
local unit = wesnoth.get_units({
local guardian = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", filter },
formula = '$this_unit.moves > 0' }
)[1]
return guardian
end
if unit then
if ((unit.x ~= cfg.return_x) or (unit.y ~= cfg.return_y)) then
local ca_return_guardian = {}
function ca_return_guardian:evaluation(ai, cfg)
local guardian = get_guardian(cfg)
if guardian then
if ((guardian.x ~= cfg.return_x) or (guardian.y ~= cfg.return_y)) then
return cfg.ca_score
else
return cfg.ca_score - 20
@ -21,21 +25,16 @@ function ca_return_guardian:evaluation(ai, cfg)
end
function ca_return_guardian:execution(ai, cfg)
local filter = cfg.filter or { id = cfg.id }
local unit = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", filter },
formula = '$this_unit.moves > 0' }
)[1]
local guardian = get_guardian(cfg)
-- In case the return hex is occupied:
local x, y = cfg.return_x, cfg.return_y
if (unit.x ~= x) or (unit.y ~= y) then
x, y = wesnoth.find_vacant_tile(x, y, unit)
if (guardian.x ~= x) or (guardian.y ~= y) then
x, y = wesnoth.find_vacant_tile(x, y, guardian)
end
local nh = AH.next_hop(unit, x, y)
AH.movefull_stopunit(ai, unit, nh)
local nh = AH.next_hop(guardian, x, y)
AH.movefull_stopunit(ai, guardian, nh)
end
return ca_return_guardian

View file

@ -1,46 +1,44 @@
local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local function get_guardian(cfg)
local filter = cfg.filter or { id = cfg.id }
local guardian = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", filter },
formula = '$this_unit.moves > 0' }
)[1]
return guardian
end
local ca_stationed_guardian = {}
function ca_stationed_guardian:evaluation(ai, cfg)
local filter = cfg.filter or { id = cfg.id }
local unit = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", filter },
formula = '$this_unit.moves > 0' }
)[1]
if unit then return cfg.ca_score end
if get_guardian(cfg) then return cfg.ca_score end
return 0
end
function ca_stationed_guardian:execution(ai, cfg)
-- (s_x,s_y): coordinates where unit is stationed; tries to move here if there is nobody to attack
-- (g_x,g_y): location that the unit guards
-- (s_x,s_y): coordinates where guardian is stationed; tries to move here if there is nobody to attack
-- (g_x,g_y): location that the guardian guards
local filter = cfg.filter or { id = cfg.id }
local unit = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", filter },
formula = '$this_unit.moves > 0' }
)[1]
local guardian = get_guardian(cfg)
-- find if there are enemies within 'distance'
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_location", {x = unit.x, y = unit.y, radius = cfg.distance} }
{ "filter_location", {x = guardian.x, y = guardian.y, radius = cfg.distance} }
}
-- if no enemies are within 'distance': keep unit from doing anything and exit
-- if no enemies are within 'distance': keep guardian from doing anything and exit
if not enemies[1] then
--print("No enemies close -> sleeping:",unit.id)
AH.checked_stopunit_moves(ai, unit)
--print("No enemies close -> sleeping:",guardian.id)
AH.checked_stopunit_moves(ai, guardian)
return
end
-- Otherwise, unit will either attack or move toward station
--print("Guardian unit waking up",unit.id)
-- Otherwise, guardian will either attack or move toward station
--print("Guardian unit waking up",guardian.id)
-- enemies must be within 'distance' of guard, (s_x,s_y) *and* (g_x,g_y)
-- simultaneous for guard to attack
local target = {}
@ -57,22 +55,22 @@ function ca_stationed_guardian:execution(ai, cfg)
end
end
-- If a valid target was found, unit attacks this target, or moves toward it
-- If a valid target was found, guardian attacks this target, or moves toward it
if (min_dist ~= 9999) then
--print ("Go for enemy unit:", target.id)
-- Find tiles adjacent to the target, and save the one that our unit
-- Find tiles adjacent to the target, and save the one that our guardian
-- can reach with the highest defense rating
local best_defense, attack_loc = -9e99, {}
for x,y in H.adjacent_tiles(target.x, target.y) do
-- only consider unoccupied hexes
local occ_hex = wesnoth.get_units { x=x, y=y, { "not", { id = unit.id } } }[1]
local occ_hex = wesnoth.get_units { x=x, y=y, { "not", { id = guardian.id } } }[1]
if not occ_hex then
-- defense rating of the hex
local defense = 100 - wesnoth.unit_defense(unit, wesnoth.get_terrain(x, y))
local defense = 100 - wesnoth.unit_defense(guardian, wesnoth.get_terrain(x, y))
--print(x,y,defense)
local nh = AH.next_hop(unit, x, y)
-- if this is best defense rating and unit can reach it, save this location
local nh = AH.next_hop(guardian, x, y)
-- if this is best defense rating and guardian can reach it, save this location
if (nh[1] == x) and (nh[2] == y) and (defense > best_defense) then
best_defense, attack_loc = defense, {x, y}
end
@ -82,20 +80,20 @@ function ca_stationed_guardian:execution(ai, cfg)
-- If a valid hex was found: move there and attack
if (best_defense ~= -9e99) then
--print("Attack at:",attack_loc[1],attack_loc[2],best_defense)
AH.movefull_stopunit(ai, unit, attack_loc)
if (not unit) or (not unit.valid) then return end
AH.movefull_stopunit(ai, guardian, attack_loc)
if (not guardian) or (not guardian.valid) then return end
if (not target) or (not target.valid) then return end
AH.checked_attack(ai, unit, target)
AH.checked_attack(ai, guardian, target)
else -- otherwise move toward that enemy
--print("Cannot reach target, moving toward it")
local reach = wesnoth.find_reach(unit)
local reach = wesnoth.find_reach(guardian)
-- Go through all hexes the unit can reach, find closest to target
-- Go through all hexes the guardian can reach, find closest to target
local nh = {} -- cannot use next_hop here since target hex is occupied by enemy
local min_dist = 9999
for i,r in ipairs(reach) do
-- only consider unoccupied hexes
local occ_hex = wesnoth.get_units { x=r[1], y=r[2], { "not", { id = unit.id } } }[1]
local occ_hex = wesnoth.get_units { x=r[1], y=r[2], { "not", { id = guardian.id } } }[1]
if not occ_hex then
local d = H.distance_between(r[1], r[2], target.x, target.y)
if d < min_dist then
@ -106,20 +104,20 @@ function ca_stationed_guardian:execution(ai, cfg)
end
-- Finally, execute the move toward the target
AH.movefull_stopunit(ai, unit, nh)
AH.movefull_stopunit(ai, guardian, nh)
end
-- If no enemy within the target zone, move toward station position
else
--print "Move toward station"
local nh = AH.next_hop(unit, cfg.station_x, cfg.station_y)
AH.movefull_stopunit(ai, unit, nh)
local nh = AH.next_hop(guardian, cfg.station_x, cfg.station_y)
AH.movefull_stopunit(ai, guardian, nh)
end
if (not unit) or (not unit.valid) then return end
if (not guardian) or (not guardian.valid) then return end
AH.checked_stopunit_moves(ai, unit)
-- If there are attacks left and unit ended up next to an enemy, we'll leave this to RCA AI
AH.checked_stopunit_moves(ai, guardian)
-- If there are attacks left and guardian ended up next to an enemy, we'll leave this to RCA AI
end
return ca_stationed_guardian

View file

@ -1,46 +1,39 @@
local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local function get_enemies(cfg)
local scatter_distance = cfg.scatter_distance or 3
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_location",
{ radius = scatter_distance, { "filter", { side = wesnoth.current.side } } }
}
}
return enemies
end
local function get_swarm_units(cfg)
local units = {}
local all_units = wesnoth.get_units { side = wesnoth.current.side }
for i,u in ipairs(all_units) do
if (u.moves > 0) then table.insert(units, u) end
end
return units
end
local swarm_scatter = {}
function swarm_scatter:evaluation(ai, cfg)
local scatter_distance = cfg.scatter_distance or 3
-- Any enemy within "scatter_distance" hexes of a unit will cause swarm to scatter
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_location",
{ radius = scatter_distance, { "filter", { side = wesnoth.current.side } } }
}
}
if enemies[1] then -- don't use 'formula=' for moves, it's slow
local units = wesnoth.get_units { side = wesnoth.current.side }
for i,u in ipairs(units) do
if (u.moves > 0) then return cfg.ca_score end
end
end
return 0
if (not get_enemies(cfg)[1]) then return 0 end
if (not get_swarm_units(cfg)[1]) then return 0 end
return cfg.ca_score
end
function swarm_scatter:execution(ai, cfg)
local scatter_distance = cfg.scatter_distance or 3
local enemies = get_enemies(cfg)
local units = get_swarm_units(cfg)
local vision_distance = cfg.vision_distance or 12
-- Any enemy within "scatter_distance" hexes of a unit will cause swarm to scatter
local units = wesnoth.get_units { side = wesnoth.current.side }
for i = #units,1,-1 do
if (units[i].moves == 0) then table.remove(units, i) end
end
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_location",
{ radius = scatter_distance, { "filter", { side = wesnoth.current.side } } }
}
}
-- In this case we simply maximize the distance from all these close enemies
-- but only for units that are within 'vision_distance' of one of those enemies
for i,unit in ipairs(units) do

View file

@ -3,30 +3,34 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
local BC = wesnoth.require "ai/lua/battle_calcs.lua"
local LS = wesnoth.require "lua/location_set.lua"
local function get_wolves(cfg)
local wolves = wesnoth.get_units {
side = wesnoth.current.side,
{ "and", cfg.filter },
formula = '$this_unit.moves > 0'
}
return wolves
end
local function get_prey(cfg)
local prey = wesnoth.get_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
{ "and", cfg.filter_second }
}
return prey
end
local ca_wolves_move = {}
function ca_wolves_move:evaluation(ai, cfg)
local wolves = wesnoth.get_units { side = wesnoth.current.side,
formula = '$this_unit.moves > 0', { "and", cfg.filter }
}
local prey = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "and", cfg.filter_second }
}
if wolves[1] and prey[1] then return cfg.ca_score end
return 0
if (not get_wolves(cfg)[1]) then return 0 end
if (not get_prey(cfg)[1]) then return 0 end
return cfg.ca_score
end
function ca_wolves_move:execution(ai, cfg)
local wolves = wesnoth.get_units { side = wesnoth.current.side,
formula = '$this_unit.moves > 0', { "and", cfg.filter }
}
local prey = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "and", cfg.filter_second }
}
--print('#wolves, prey', #wolves, #prey)
local wolves = get_wolves(cfg)
local prey = get_prey(cfg)
-- When wandering (later) they avoid dogs, but not here
local avoid_units = wesnoth.get_units { type = cfg.avoid_type,

View file

@ -3,22 +3,23 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua"
local BC = wesnoth.require "ai/lua/battle_calcs.lua"
local LS = wesnoth.require "lua/location_set.lua"
local function get_wolves(cfg)
local wolves = wesnoth.get_units { side = wesnoth.current.side,
formula = '$this_unit.moves > 0', { "and", cfg.filter }
}
return wolves
end
local ca_wolves_wander = {}
function ca_wolves_wander:evaluation(ai, cfg)
-- When there's no prey left, the wolves wander and regroup
local wolves = wesnoth.get_units { side = wesnoth.current.side,
formula = '$this_unit.moves > 0', { "and", cfg.filter }
}
if wolves[1] then return cfg.ca_score end
if get_wolves(cfg)[1] then return cfg.ca_score end
return 0
end
function ca_wolves_wander:execution(ai, cfg)
local wolves = wesnoth.get_units { side = wesnoth.current.side,
formula = '$this_unit.moves > 0', { "and", cfg.filter }
}
local wolves = get_wolves(cfg)
-- Number of wolves that can reach each hex
local reach_map = LS.create()

View file

@ -2,29 +2,26 @@ 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 ca_zone_guardian = {}
function ca_zone_guardian:evaluation(ai, cfg)
local function get_guardian(cfg)
local filter = cfg.filter or { id = cfg.id }
local unit = wesnoth.get_units({
local guardian = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", filter },
formula = '$this_unit.moves > 0' }
)[1]
return guardian
end
if unit then return cfg.ca_score end
local ca_zone_guardian = {}
function ca_zone_guardian:evaluation(ai, cfg)
if get_guardian(cfg) then return cfg.ca_score end
return 0
end
function ca_zone_guardian:execution(ai, cfg)
local filter = cfg.filter or { id = cfg.id }
local unit = wesnoth.get_units({
side = wesnoth.current.side,
{ "and", filter },
formula = '$this_unit.moves > 0' }
)[1]
local reach = wesnoth.find_reach(unit)
local guardian = get_guardian(cfg)
local reach = wesnoth.find_reach(guardian)
local zone_enemy = cfg.filter_location_enemy or cfg.filter_location
-- enemy units within reach
@ -37,9 +34,9 @@ function ca_zone_guardian:execution(ai, cfg)
local target = {}
local min_dist = 9999
for i,e in ipairs(enemies) do
local dg = H.distance_between(unit.x, unit.y, e.x, e.y)
local dg = H.distance_between(guardian.x, guardian.y, e.x, e.y)
-- If valid target found, save the one with the shortest distance from unit
-- If valid target found, save the one with the shortest distance from guardian
if (dg < min_dist) then
--print("target:", e.id, ds, dg)
target = e
@ -47,22 +44,22 @@ function ca_zone_guardian:execution(ai, cfg)
end
end
-- If a valid target was found, unit attacks this target, or moves toward it
-- If a valid target was found, guardian attacks this target, or moves toward it
if (min_dist ~= 9999) then
--print ("Go for enemy unit:", target.id)
-- Find tiles adjacent to the target, and save the one that our unit
-- Find tiles adjacent to the target, and save the one that our guardian
-- can reach with the highest defense rating
local best_defense, attack_loc = -9e99, {}
for x,y in H.adjacent_tiles(target.x, target.y) do
-- only consider unoccupied hexes
local occ_hex = wesnoth.get_units { x=x, y=y, { "not", { id = unit.id } } }[1]
local occ_hex = wesnoth.get_units { x=x, y=y, { "not", { id = guardian.id } } }[1]
if not occ_hex then
-- defense rating of the hex
local defense = 100 - wesnoth.unit_defense(unit, wesnoth.get_terrain(x, y))
local defense = 100 - wesnoth.unit_defense(guardian, wesnoth.get_terrain(x, y))
--print(x,y,defense)
local nh = AH.next_hop(unit, x, y)
-- if this is best defense rating and unit can reach it, save this location
local nh = AH.next_hop(guardian, x, y)
-- if this is best defense rating and guardian can reach it, save this location
if nh then
if (nh[1] == x) and (nh[2] == y) and (defense > best_defense) then
best_defense, attack_loc = defense, {x, y}
@ -74,19 +71,19 @@ function ca_zone_guardian:execution(ai, cfg)
-- If a valid hex was found: move there and attack
if (best_defense ~= -9e99) then
--print("Attack at:",attack_loc[1],attack_loc[2],best_defense)
AH.movefull_stopunit(ai, unit, attack_loc)
if (not unit) or (not unit.valid) then return end
AH.checked_attack(ai, unit, target)
AH.movefull_stopunit(ai, guardian, attack_loc)
if (not guardian) or (not guardian.valid) then return end
AH.checked_attack(ai, guardian, target)
else -- otherwise move toward that enemy
--print("Cannot reach target, moving toward it")
local reach = wesnoth.find_reach(unit)
local reach = wesnoth.find_reach(guardian)
-- Go through all hexes the unit can reach, find closest to target
-- Go through all hexes the guardian can reach, find closest to target
local nh = {} -- cannot use next_hop here since target hex is occupied by enemy
local min_dist = 9999
for i,r in ipairs(reach) do
-- only consider unoccupied hexes
local occ_hex = wesnoth.get_units { x=r[1], y=r[2], { "not", { id = unit.id } } }[1]
local occ_hex = wesnoth.get_units { x=r[1], y=r[2], { "not", { id = guardian.id } } }[1]
if not occ_hex then
local d = H.distance_between(r[1], r[2], target.x, target.y)
if d < min_dist then
@ -97,7 +94,7 @@ function ca_zone_guardian:execution(ai, cfg)
end
-- Finally, execute the move toward the target
AH.movefull_stopunit(ai, unit, nh)
AH.movefull_stopunit(ai, guardian, nh)
end
end
@ -117,8 +114,8 @@ function ca_zone_guardian:execution(ai, cfg)
{ "and", cfg.filter_location }
})
-- Check out which of those hexes the unit can reach
local reach_map = LS.of_pairs(wesnoth.find_reach(unit))
-- Check out which of those hexes the guardian can reach
local reach_map = LS.of_pairs(wesnoth.find_reach(guardian))
reach_map:inter(locs_map)
-- If it can reach some hexes, use only reachable locations,
@ -129,26 +126,26 @@ function ca_zone_guardian:execution(ai, cfg)
local locs = locs_map:to_pairs()
-- If possible locations were found, move unit toward a random one,
-- otherwise the unit stays where it is
-- If possible locations were found, move guardian toward a random one,
-- otherwise the guardian stays where it is
if (#locs > 0) then
local newind = math.random(#locs)
newpos = { locs[newind][1], locs[newind][2] }
else
newpos = { unit.x, unit.y }
newpos = { guardian.x, guardian.y }
end
end
-- Next hop toward that position
local nh = AH.next_hop(unit, newpos[1], newpos[2])
local nh = AH.next_hop(guardian, newpos[1], newpos[2])
if nh then
AH.movefull_stopunit(ai, unit, nh)
AH.movefull_stopunit(ai, guardian, nh)
end
end
if (not unit) or (not unit.valid) then return end
if (not guardian) or (not guardian.valid) then return end
AH.checked_stopunit_moves(ai, unit)
-- If there are attacks left and unit ended up next to an enemy, we'll leave this to RCA AI
AH.checked_stopunit_moves(ai, guardian)
-- If there are attacks left and guardian ended up next to an enemy, we'll leave this to RCA AI
end
return ca_zone_guardian