Skip to content

Commit

Permalink
Multi-pack Wolves MAI: code cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
mattsc committed Apr 15, 2014
1 parent 51d3119 commit 3a85688
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 196 deletions.
171 changes: 84 additions & 87 deletions data/ai/micro_ais/cas/ca_wolves_multipacks_attack.lua
@@ -1,164 +1,160 @@
local H = wesnoth.require "lua/helper.lua"
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 LS = wesnoth.require "lua/location_set.lua"
local WMPF = wesnoth.require "ai/micro_ais/cas/ca_wolves_multipacks_functions.lua"

local ca_wolves_multipacks_attack = {}

function ca_wolves_multipacks_attack:evaluation(ai, cfg)
local unit_type = cfg.type or "Wolf"
-- If wolves have attacks left, call this CA
-- It will generally be disabled by being black-listed, so as to avoid
-- having to do the full attack evaluation for every single move
-- It will be disabled by being black-listed, so as to avoid
-- having to do the full attack evaluation for every single move evaluation

local wolves = AH.get_units_with_attacks {
side = wesnoth.current.side,
type = unit_type
type = cfg.type or "Wolf"
}

if wolves[1] then return cfg.ca_score end
return 0
end

function ca_wolves_multipacks_attack:execution(ai, cfg)
-- First get all the packs
local packs = WMPF.assign_packs(cfg)

-- Attacks are dealt with on a pack by pack basis
-- and I want all wolves in a pack to move first, before going on to the next pack
-- which makes this slightly more complicated than it would be otherwise
-- and we want all wolves in a pack to move first, before going on to the next pack
for pack_number,pack in pairs(packs) do
local keep_attacking_this_pack = true -- whether there might be attacks left
local pack_attacked = false -- whether an attack by the pack has happened
local keep_attacking_this_pack = true
local pack_has_attacked = false

-- This repeats until all wolves in a pack have attacked, or none can attack any more
while keep_attacking_this_pack do
-- Get the wolves in the pack ...
local wolves, attacks = {}, {}
for i,p in ipairs(pack) do
for _,pack_wolf in ipairs(pack) do
-- Wolf might have moved in previous attack -> use id to identify it
local wolf = wesnoth.get_units { id = p.id }
-- Wolf could have died in previous attack
-- and only include wolves with attacks left to calc. possible attacks
if wolf[1] and (wolf[1].attacks_left > 0) then table.insert(wolves, wolf[1]) end
local wolf = wesnoth.get_units { id = pack_wolf.id }[1]
if wolf and (wolf.attacks_left > 0) then table.insert(wolves, wolf) end
end

-- ... and check if any targets are in reach
local attacks = {}
local all_attacks = {}
if wolves[1] then all_attacks = AH.get_attacks(wolves, { simulate_combat = true }) end
--print('pack, wolves, attacks:', pack_number, #wolves, #all_attacks)

-- Eliminate targets that would split up the wolves by more than 3 hexes
-- This also takes care of wolves joining as a pack rather than attacking individually
local attacks = {}
for _, attack in ipairs(all_attacks) do
for j,w in ipairs(wolves) do
local nh = AH.next_hop(w, attack.dst.x, attack.dst.y)
local d = H.distance_between(nh[1], nh[2], attack.dst.x, attack.dst.y)
if d <= 3 then
table.insert(attacks, attack)
for _,attack in ipairs(all_attacks) do
local attack_splits_pack = false
for _,wolf in ipairs(wolves) do
local nh = AH.next_hop(wolf, attack.dst.x, attack.dst.y)
local dist = H.distance_between(nh[1], nh[2], attack.dst.x, attack.dst.y)
if (dist > 3) then
attack_splits_pack = true
break
end
end
if (not attack_splits_pack) then
table.insert(attacks, attack)
end
end
--print('-> pack, wolves, attacks:', pack_number, #wolves, #attacks)

-- If valid attacks were found for this pack
if attacks[1] then
-- Figure out how many different wolves can reach each target, and on how many hexes
-- The target with the largest value for the smaller of these two numbers is chosen
-- This is not an exact method, but good enough in most cases
local diff_wolves, diff_hexes = {}, {}
for i,a in ipairs(attacks) do
-- This is not an exact method, but good enough and much faster than simulating combat
local attack_map_wolves, attack_map_hexes = {}, {}
for _,attack in ipairs(attacks) do
-- Number different wolves
local att_xy = a.src.x + a.src.y * 1000
local def_xy = a.target.x + a.target.y * 1000
if (not diff_wolves[def_xy]) then diff_wolves[def_xy] = {} end
diff_wolves[def_xy][att_xy] = 1
local att_xy = attack.src.x + attack.src.y * 1000
local def_xy = attack.target.x + attack.target.y * 1000
if (not attack_map_wolves[def_xy]) then attack_map_wolves[def_xy] = {} end
attack_map_wolves[def_xy][att_xy] = 1

-- Number different hexes
if (not diff_hexes[def_xy]) then diff_hexes[def_xy] = {} end
diff_hexes[def_xy][a.dst.x + a.dst.y * 1000] = 1
if (not attack_map_hexes[def_xy]) then attack_map_hexes[def_xy] = {} end
attack_map_hexes[def_xy][attack.dst.x + attack.dst.y * 1000] = 1
end

-- Find which target can be attacked by the most units, from the most hexes; and rate by fewest HP if equal
local max_rating, best_target = -9e99, {}
for k,t in pairs(diff_wolves) do
local n_w, n_h = 0, 0
for k1,w in pairs(t) do n_w = n_w + 1 end
for k2,h in pairs(diff_hexes[k]) do n_h = n_h + 1 end
local rating = math.min(n_w, n_h)

local target = wesnoth.get_unit( k % 1000, math.floor(k / 1000))
local max_rating, best_target = -9e99
for attack_ind,attack in pairs(attack_map_wolves) do
local number_wolves, number_hexes = 0, 0
for _,w in pairs(attack) do number_wolves = number_wolves + 1 end
for _,h in pairs(attack_map_hexes[attack_ind]) do number_hexes = number_hexes + 1 end
local rating = math.min(number_wolves, number_hexes)

local target = wesnoth.get_unit(attack_ind % 1000, math.floor(attack_ind / 1000))
rating = rating - target.hitpoints / 100.

-- Also, any target sitting next to a wolf of the same pack that has
-- no attacks left is priority targeted (in order to stick with
-- the same target for all wolves of the pack)
for x, y in H.adjacent_tiles(target.x, target.y) do
local adj_unit = wesnoth.get_unit(x, y)
for xa,ya in H.adjacent_tiles(target.x, target.y) do
local adj_unit = wesnoth.get_unit(xa, ya)
if adj_unit then
local pack = MAIUV.get_mai_unit_variables(adj_unit, cfg.ai_id, "pack")
if (pack == pack_number) and (adj_unit.side == wesnoth.current.side)
local unit_pack_number = MAIUV.get_mai_unit_variables(adj_unit, cfg.ai_id, "pack_number")
if (unit_pack_number == pack_number)
and (adj_unit.side == wesnoth.current.side)
and (adj_unit.attacks_left == 0)
then
rating = rating + 10 -- very strongly favors this target
rating = rating + 10
end
end
end

--print(k, n_w, n_h, rating)
if rating > max_rating then
max_rating, best_target = rating, target
end
end
--print('Best target:', best_target.id, best_target.x, best_target.y)

-- Now we know what the best target is, we need to attack now
-- Now we know the best target and need to attack
-- This is done on a wolf-by-wolf basis, the outside while loop taking care of
-- the next wolf in the pack on subsequent iterations
local max_rating, best_attack = -9e99, {}
for i,a in ipairs(attacks) do
if (a.target.x == best_target.x) and (a.target.y == best_target.y) then
-- HP outcome is rating, twice as important for target as for attacker
local rating = a.att_stats.average_hp / 2. - a.def_stats.average_hp
local max_rating, best_attack = -9e99
for _,attack in ipairs(attacks) do
if (attack.target.x == best_target.x) and (attack.target.y == best_target.y) then
local rating = attack.att_stats.average_hp / 2. - attack.def_stats.average_hp
if (rating > max_rating) then
max_rating, best_attack = rating, a
max_rating, best_attack = rating, attack
end
end
end

local attacker = wesnoth.get_unit(best_attack.src.x, best_attack.src.y)
local defender = wesnoth.get_unit(best_attack.target.x, best_attack.target.y)
if cfg.show_pack_number then
W.label { x = attacker.x, y = attacker.y, text = "" }
end

if cfg.show_pack_number then WMPF.clear_label(attacker.x, attacker.y) end

AH.movefull_stopunit(ai, attacker, best_attack.dst.x, best_attack.dst.y)
if cfg.show_pack_number then
WMPF.color_label(attacker.x, attacker.y, pack_number)

if attacker and attacker.valid then
if cfg.show_pack_number then WMPF.put_label(attacker.x, attacker.y, pack_number) end
end

local a_x, a_y, d_x, d_y = attacker.x, attacker.y, defender.x, defender.y
AH.checked_attack(ai, attacker, defender)
-- Remove the labels, if one of the units died
if cfg.show_pack_number then
if (not attacker.valid) then W.label { x = a_x, y = a_y, text = "" } end
if (not defender.valid) then W.label { x = d_x, y = d_y, text = "" } end
if attacker and attacker.valid and defender and defender.valid then
-- In case one of the units dies in the attack or is removed in an event, we need these:
local ax, ay, dx, dy = attacker.x, attacker.y, defender.x, defender.y

AH.checked_attack(ai, attacker, defender)

-- Remove the labels, if one of the units isgone
if cfg.show_pack_number then
if (not attacker) or (not attacker.valid) then WMPF.clear_label(ax, ay) end
if (not defender) or (not defender.valid) then WMPF.clear_label(dx, dy) end
end
end

pack_attacked = true -- This pack has done an attack
pack_has_attacked = true
else
keep_attacking_this_pack = false -- no more valid attacks found
keep_attacking_this_pack = false
end
end

-- Finally, if any of the wolves in this pack did attack, move the rest of the pack in close
if pack_attacked then
if pack_has_attacked then
local wolves_moves, wolves_no_moves = {}, {}
for i,p in ipairs(pack) do
for _,pack_wolf in ipairs(pack) do
-- Wolf might have moved in previous attack -> use id to identify it
local wolf = wesnoth.get_unit(p.x, p.y)
-- Wolf could have died in previous attack
local wolf = wesnoth.get_units { id = pack_wolf.id }[1]
if wolf then
if (wolf.moves > 0) then
table.insert(wolves_moves, wolf)
Expand All @@ -167,26 +163,27 @@ function ca_wolves_multipacks_attack:execution(ai, cfg)
end
end
end
--print('#wolves_moves, #wolves_no_moves', #wolves_moves, #wolves_no_moves)

-- If we have both wolves that have moved and those that have not moved,
-- move the latter toward the former
if wolves_moves[1] and wolves_no_moves[1] then
--print('Collecting stragglers')
for i,w in ipairs(wolves_moves) do
local best_hex = AH.find_best_move(w, function(x, y)
for _,wolf_moves in ipairs(wolves_moves) do
local best_hex = AH.find_best_move(wolf_moves, function(x, y)
local rating = 0
for j,w_nm in ipairs(wolves_no_moves) do
rating = rating - H.distance_between(x, y, w_nm.x, w_nm.y)
for _,wolf_no_moves in ipairs(wolves_no_moves) do
rating = rating - H.distance_between(x, y, wolf_no_moves.x, wolf_no_moves.y)
end
return rating
end)

if cfg.show_pack_number then
W.label { x = w.x, y = w.y, text = "" }
WMPF.clear_label(wolf_moves.x, wolf_moves.y)
end
AH.movefull_stopunit(ai, w, best_hex)
if cfg.show_pack_number and w and w.valid then
WMPF.color_label(w.x, w.y, pack_number)

AH.movefull_stopunit(ai, wolf_moves, best_hex)

if cfg.show_pack_number and wolf_moves and wolf_moves.valid then
WMPF.put_label(wolf_moves.x, wolf_moves.y, pack_number)
end
end
end
Expand Down

0 comments on commit 3a85688

Please sign in to comment.