wesnoth/data/ai/micro_ais/cas/ca_fast_move.lua
2014-05-04 14:18:14 -07:00

226 lines
8.3 KiB
Lua

local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "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 all_units_MP = AH.get_units_with_moves { side = wesnoth.current.side, canrecruit = 'no' }
local units = {}
for _,unit in ipairs(all_units_MP) do
if (not unit.status.guardian) then table.insert(units, unit) end
end
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()
-- 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
-- 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)
local villages_to_get = math.min(max_villages, #villages)
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
-- Now add enemy leaders to the goals
local enemy_leaders =
wesnoth.get_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
canrecruit = 'yes'
}
-- 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
-- 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
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
best_unit_info = unit_info
break
elseif (unit_info.cost < 1000) then
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
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
table.remove(goals, next_goal)
next_goal = next_goal - 1
end
else
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
end
return ca_fast_move