wesnoth/data/ai/micro_ais/cas/ca_fast_move.lua
mattsc feabdfb4ea New Micro AI: Fast AI
This AI is meant as a fall-back AI for scenarios where there are so
many units that attacks and moves done by the default AI are slow with
long delays before each move. It replaces the default combat and
move-to-targets candidate actions.
See http://wiki.wesnoth.org/Micro_AIs
2014-05-03 16:52:58 -07:00

235 lines
9.2 KiB
Lua

local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.dofile "ai/lua/ai_helper.lua"
local ca_fast_move = {}
function ca_fast_move:evaluation(ai, cfg, self)
local unit = AH.get_units_with_moves { side = wesnoth.current.side, canrecruit = 'no' }[1]
if unit then return 20000 end
return 0
end
function ca_fast_move:execution(ai, cfg, self)
local move_cost_factor = cfg.move_cost_factor or 2.0
if (move_cost_factor < 1.1) then move_cost_factor = 1.1 end
local units = AH.get_units_with_moves { side = wesnoth.current.side, canrecruit = 'no' }
local leader = wesnoth.get_units { side = wesnoth.current.side, canrecruit = 'yes' }[1]
local goals = {}
-- Villages get added first, so that (hopefully, scouts and faster units will go for them first)
local village_value = ai.get_village_value()
if (village_value > 0) then
local villages = wesnoth.get_villages()
--print('#villages', #villages)
-- Eliminate villages owned by a side that is not an enemy
for i = #villages,1,-1 do
local owner = wesnoth.get_village_owner(villages[i][1], villages[i][2])
if owner and (not wesnoth.is_enemy(owner, wesnoth.current.side)) then
table.remove(villages, i)
end
end
--print('#villages up for grabs', #villages)
-- Add rating that is sum of inverse distances from all other villages
for i = 1,#villages-1 do
local v1 = villages[i]
for j = i+1,#villages do
local v2 = villages[j]
local dist = H.distance_between(v1[1], v1[2], v2[1], v2[2])
dist = math.ceil(dist / 5.) -- In discrete steps of 5 hexes
v1.rating = (v1.rating or 0) + 1. / dist
v2.rating = (v2.rating or 0) + 1. / dist
end
end
-- Multiply by distance from side leader
for _,village in ipairs(villages) do
local dist = 1 -- Just in case there is no leader
dist = math.ceil(dist / 5.) -- In discrete steps of 5 hexes
if leader then
dist = H.distance_between(village[1], village[2], leader.x, leader.y)
end
village.rating = (village.rating or 1.) * 1. / dist
end
-- Now we figure out how many villages we want to go for
local max_villages = math.floor(#units / 4. * village_value)
--print('max_villages', max_villages)
local villages_to_get = math.min(max_villages, #villages)
--print('villages_to_get', villages_to_get)
for _ = 1,villages_to_get do
-- Sort villages by rating (highest rating last, because that makes table.remove faster)
table.sort(villages, function(a, b) return (a.rating < b.rating) end)
local x,y = villages[#villages][1], villages[#villages][2]
table.insert(goals, { x = x, y = y, is_village = true })
table.remove(villages)
-- Now re-rate the villages, want those farthest from the last selected one first
-- Need to check whether this was the last village
if (#villages > 1) then
local base_rating = villages[#villages].rating
for _,village in ipairs(villages) do
village.rating = village.rating / base_rating
* H.distance_between(x, y, village[1], village[2])
end
end
end
end
--print('Time after setting up village goals:', wesnoth.get_time_stamp() - start_time)
-- Now add enemy leaders to the goals
local enemy_leaders =
wesnoth.get_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
canrecruit = 'yes'
}
--print('#enemy_leaders', #enemy_leaders)
-- Sort enemy leaders by distance to AI leader
if leader then
table.sort(enemy_leaders, function(a, b)
local dist_a = H.distance_between(leader.x, leader.y, a.x, a.y)
local dist_b = H.distance_between(leader.x, leader.y, b.x, b.y)
return (dist_a < dist_b)
end)
end
for i_el,enemy_leader in ipairs(enemy_leaders) do
local goal = { x = enemy_leader.x, y = enemy_leader.y }
table.insert(goals, goal)
end
--print('Time after setting up enemy leader goals:', wesnoth.get_time_stamp() - start_time)
-- Putting information about all the units into the goals
-- This is a one-time expense up front, but generally not too bad
for _,goal in ipairs(goals) do
-- Insert information about the units
for i_u,unit in ipairs(units) do
local dist = H.distance_between(unit.x, unit.y, goal.x, goal.y)
goal[i_u] = { dist = dist / unit.max_moves, i_unit = i_u }
end
table.sort(goal, function(a, b) return (a.dist < b.dist) end)
end
--print('Time after adding unit info into goals:', wesnoth.get_time_stamp() - start_time)
local keep_moving, next_goal = true, 0
while keep_moving do
keep_moving = false
next_goal = next_goal + 1
if (next_goal > #goals) then next_goal = 1 end
local goal = goals[next_goal]
local max_rating, best_unit_info = -9e99
for _,unit_info in ipairs(goal) do
if (not unit_info.cost) then
local _,cost =
wesnoth.find_path(
units[unit_info.i_unit].x, units[unit_info.i_unit].y,
goal.x, goal.y,
{ ignore_units = true }
)
cost = cost / units[unit_info.i_unit].max_moves
unit_info.cost = cost
end
if (unit_info.cost < unit_info.dist * move_cost_factor) then
--print('Efficient move found:', units[unit_info.i_unit].x, units[unit_info.i_unit].y, goal.x, goal.y)
best_unit_info = unit_info
break
elseif (unit_info.cost < 1000) then
--print('No efficient move found:', units[unit_info.i_unit].x, units[unit_info.i_unit].y, goal.x, goal.y)
local rating = - unit_info.cost
if (rating > max_rating) then
max_rating, best_unit_info = rating, unit_info
end
end
end
if best_unit_info then
local unit = units[best_unit_info.i_unit]
-- Next hop if there weren't any other units around
local next_hop = AH.next_hop(unit, goal.x, goal.y, { ignore_units = true })
-- Finally find the best move for this unit
local reach = wesnoth.find_reach(unit)
local max_rating, best_hex = -9e99
for _,loc in ipairs(reach) do
local rating = - H.distance_between(loc[1], loc[2], next_hop[1], next_hop[2])
rating = rating - H.distance_between(loc[1], loc[2], goal.x, goal.y) / 2.
local unit_in_way
if (rating > max_rating) then
unit_in_way = wesnoth.get_unit(loc[1], loc[2])
if (unit_in_way == unit) then unit_in_way = nil end
if unit_in_way and (unit_in_way.side == unit.side) then
local reach = AH.get_reachable_unocc(unit_in_way)
if (reach:size() > 1) then
unit_in_way = nil
rating = rating - 0.01
end
end
end
if (rating > max_rating) and (not unit_in_way) then
max_rating, best_hex = rating, { loc[1], loc[2] }
end
end
if best_hex then
--print('Doing move:', unit.x, unit.y, best_hex[1], best_hex[2], goal.x, goal.y)
local dx, dy = goal.x - best_hex[1], goal.y - best_hex[2]
local r = math.sqrt(dx * dx + dy * dy)
dx, dy = dx / r, dy / r
AH.movefull_outofway_stopunit(ai, unit, best_hex[1], best_hex[2], { dx = dx, dy = dy })
end
-- Also remove this unit from all the tables; using table.remove is fine here
for _,tmp_goal in ipairs(goals) do
for i_info,tmp_info in ipairs(tmp_goal) do
if (tmp_info.i_unit == best_unit_info.i_unit) then
table.remove(tmp_goal, i_info)
break
end
end
end
-- Finally, if this was a village, remove the goal (we only do one unit per village
if goal.is_village then
--print('Removing goal (only one unit per village):', goal.x, goal.y)
table.remove(goals, next_goal)
next_goal = next_goal - 1
end
else
--print('Removing goal (no more unit can get there):', goal.x, goal.y)
table.remove(goals, next_goal)
next_goal = next_goal - 1
end
for _,goal in ipairs(goals) do
if goal[1] then
keep_moving = true
break
end
end
end
--print('Overall time:', wesnoth.get_time_stamp() - start_time)
end
return ca_fast_move