Micro AIs: prevent potential conflicts of stored data

It’s theoretically possible that different Micro AIs’ evaluation
functions store information in the same variable within self.data.
This would only happen if the CAs have the same score and while this
should generally be avoided when setting up a scenario, it is better to
ensure that it cannot cause conflicts (not in the stored data at least,
the MAIs might still interfere with each other in other respects).
This commit is contained in:
mattsc 2014-04-15 08:15:15 -07:00
parent 51251d48e1
commit a4ca7c164f
8 changed files with 94 additions and 94 deletions

View file

@ -58,28 +58,28 @@ function ca_bottleneck_attack:evaluation(ai, cfg, self)
if max_rating == 0 then
-- In this case we take attacks away from all units
-- This is done so that the RCA AI CAs can be kept in place
self.data.bottleneck_attacks_done = true
self.data.BD_bottleneck_attacks_done = true
else
self.data.bottleneck_attacks_done = false
self.data.attacker = best_att
self.data.target = best_tar
self.data.weapon = best_weapon
self.data.BD_bottleneck_attacks_done = false
self.data.BD_attacker = best_att
self.data.BD_target = best_tar
self.data.BD_weapon = best_weapon
end
return cfg.ca_score
end
function ca_bottleneck_attack:execution(ai, cfg, self)
if self.data.bottleneck_attacks_done then
if self.data.BD_bottleneck_attacks_done then
local units = AH.get_units_with_attacks { side = wesnoth.current.side }
for i,u in ipairs(units) do
AH.checked_stopunit_attacks(ai, u)
end
else
AH.checked_attack(ai, self.data.attacker, self.data.target, self.data.weapon)
AH.checked_attack(ai, self.data.BD_attacker, self.data.BD_target, self.data.BD_weapon)
end
self.data.attacker, self.data.target, self.data.weapon = nil, nil, nil
self.data.bottleneck_attacks_done = nil
self.data.BD_attacker, self.data.BD_target, self.data.BD_weapon = nil, nil, nil
self.data.BD_bottleneck_attacks_done = nil
end
return ca_bottleneck_attack

View file

@ -88,11 +88,11 @@ local function bottleneck_create_positioning_map(max_value, self)
-- This might include hexes on the line itself, but
-- only store those that are not in enemy territory
local map = LS.create()
self.data.def_map:iter( function(x, y, v)
self.data.BD_def_map:iter( function(x, y, v)
for xa, ya in H.adjacent_tiles(x, y) do
if self.data.is_my_territory:get(xa, ya) then
if self.data.BD_is_my_territory:get(xa, ya) then
-- This rating adds up the scores of all the adjacent def_map hexes
local rating = self.data.def_map:get(x, y) or 0
local rating = self.data.BD_def_map:get(x, y) or 0
rating = rating + (map:get(xa, ya) or 0)
map:insert(xa, ya, rating)
end
@ -107,7 +107,7 @@ local function bottleneck_create_positioning_map(max_value, self)
-- Finally, we merge the defense map into this, as healers/leaders (by default)
-- can take position on the front line
map:union_merge(self.data.def_map,
map:union_merge(self.data.BD_def_map,
function(x, y, v1, v2) return v1 or v2 end
)
@ -123,18 +123,18 @@ local function bottleneck_get_rating(unit, x, y, has_leadership, is_healer, self
-- Defense positioning rating
-- We exclude healers/leaders here, as we don't necessarily want them on the front line
if (not is_healer) and (not has_leadership) then
rating = self.data.def_map:get(x, y) or 0
rating = self.data.BD_def_map:get(x, y) or 0
end
-- Healer positioning rating
if is_healer then
local healer_rating = self.data.healer_map:get(x, y) or 0
local healer_rating = self.data.BD_healer_map:get(x, y) or 0
if (healer_rating > rating) then rating = healer_rating end
end
-- Leadership unit positioning rating
if has_leadership then
local leadership_rating = self.data.leadership_map:get(x, y) or 0
local leadership_rating = self.data.BD_leadership_map:get(x, y) or 0
-- If leadership unit is injured -> prefer hexes next to healers
if (unit.hitpoints < unit.max_hitpoints) then
@ -153,18 +153,18 @@ local function bottleneck_get_rating(unit, x, y, has_leadership, is_healer, self
-- Injured unit positioning
if (unit.hitpoints < unit.max_hitpoints) then
local healing_rating = self.data.healing_map:get(x, y) or 0
local healing_rating = self.data.BD_healing_map:get(x, y) or 0
if (healing_rating > rating) then rating = healing_rating end
end
-- If this did not produce a positive rating, we add a
-- distance-based rating, to get units to the bottleneck in the first place
if (rating <= 0) and self.data.is_my_territory:get(x, y) then
if (rating <= 0) and self.data.BD_is_my_territory:get(x, y) then
local combined_dist = 0
self.data.def_map:iter(function(x_def, y_def, v)
self.data.BD_def_map:iter(function(x_def, y_def, v)
combined_dist = combined_dist + H.distance_between(x, y, x_def, y_def)
end)
combined_dist = combined_dist / self.data.def_map:size()
combined_dist = combined_dist / self.data.BD_def_map:size()
rating = 1000 - combined_dist * 10.
end
@ -197,7 +197,7 @@ local function bottleneck_move_out_of_way(unit, self)
-- find the closest unoccupied reachable hex in the east
local best_reach, best_hex = -1, {}
for i,r in ipairs(reach) do
if self.data.is_my_territory:get(r[1], r[2]) and (not occ_hexes:get(r[1], r[2])) then
if self.data.BD_is_my_territory:get(r[1], r[2]) and (not occ_hexes:get(r[1], r[2])) then
-- Best hex to move out of way to:
-- (r[3] > best_reach) : move shorter than previous best move
if (r[3] > best_reach) then
@ -245,68 +245,68 @@ function ca_bottleneck_move:evaluation(ai, cfg, self)
-- Set up the arrays that tell the AI where to defend the bottleneck
-- Get the x and y coordinates (this assumes that cfg.x and cfg.y have the same length)
self.data.def_map = bottleneck_triple_from_keys(cfg.x, cfg.y, 10000)
--AH.put_labels(self.data.def_map)
self.data.BD_def_map = bottleneck_triple_from_keys(cfg.x, cfg.y, 10000)
--AH.put_labels(self.data.BD_def_map)
-- Get the territory map, describing which hex is on AI's side of the bottleneck
-- This one is a bit expensive, esp. on large maps -> don't delete every move and reuse
-- However, after a reload, self.data.is_my_territory is an empty string
-- However, after a reload, self.data.BD_is_my_territory is an empty string
-- -> need to recalculate in that case also (the reason is that is_my_territory is not a WML table)
if (not self.data.is_my_territory) or (type(self.data.is_my_territory) == 'string') then
if (not self.data.BD_is_my_territory) or (type(self.data.BD_is_my_territory) == 'string') then
local enemy_map = bottleneck_triple_from_keys(cfg.enemy_x, cfg.enemy_y, 10000)
self.data.is_my_territory = bottleneck_is_my_territory(self.data.def_map, enemy_map)
self.data.BD_is_my_territory = bottleneck_is_my_territory(self.data.BD_def_map, enemy_map)
end
--AH.put_labels(self.data.is_my_territory)
--AH.put_labels(self.data.BD_is_my_territory)
-- Setting up healer positioning map
if cfg.healer_x and cfg.healer_y then
-- If healer_x,healer_y are given, extract locs from there
self.data.healer_map = bottleneck_triple_from_keys(cfg.healer_x, cfg.healer_y, 5000)
self.data.BD_healer_map = bottleneck_triple_from_keys(cfg.healer_x, cfg.healer_y, 5000)
else
-- Otherwise create the map here
self.data.healer_map = bottleneck_create_positioning_map(5000, self)
self.data.BD_healer_map = bottleneck_create_positioning_map(5000, self)
end
-- Use def_map values for any healer hexes that are defined in def_map as well
self.data.healer_map:inter_merge(self.data.def_map,
self.data.BD_healer_map:inter_merge(self.data.BD_def_map,
function(x, y, v1, v2) return v2 or v1 end
)
--AH.put_labels(self.data.healer_map)
--AH.put_labels(self.data.BD_healer_map)
-- Setting up leadership position map
-- If leadership_x, leadership_y are not given, we create the leadership positioning array
if cfg.leadership_x and cfg.leadership_y then
-- If leadership_x,leadership_y are given, extract locs from there
self.data.leadership_map = bottleneck_triple_from_keys(cfg.leadership_x, cfg.leadership_y, 4000)
self.data.BD_leadership_map = bottleneck_triple_from_keys(cfg.leadership_x, cfg.leadership_y, 4000)
else
-- Otherwise create the map here
self.data.leadership_map = bottleneck_create_positioning_map(4000, self)
self.data.BD_leadership_map = bottleneck_create_positioning_map(4000, self)
end
-- Use def_map values for any leadership hexes that are defined in def_map as well
self.data.leadership_map:inter_merge(self.data.def_map,
self.data.BD_leadership_map:inter_merge(self.data.BD_def_map,
function(x, y, v1, v2) return v2 or v1 end
)
--AH.put_labels(self.data.leadership_map)
--AH.put_labels(self.data.BD_leadership_map)
-- healing map: positions next to healers, needs to be calculated each move
-- Healers get moved with higher priority, so don't need to check their MP
local healers = wesnoth.get_units { side = wesnoth.current.side, ability = "healing" }
self.data.healing_map = LS.create()
self.data.BD_healing_map = LS.create()
for i,h in ipairs(healers) do
for x, y in H.adjacent_tiles(h.x, h.y) do
-- Cannot be on the line, and needs to be in own territory
if self.data.is_my_territory:get(x, y) then
if self.data.BD_is_my_territory:get(x, y) then
local min_dist = 9e99
self.data.def_map:iter( function(xd, yd, vd)
self.data.BD_def_map:iter( function(xd, yd, vd)
local dist_line = H.distance_between(x, y, xd, yd)
if (dist_line < min_dist) then min_dist = dist_line end
end)
if (min_dist > 0) then
self.data.healing_map:insert(x, y, 3000 + min_dist) -- farther away from enemy is good
self.data.BD_healing_map:insert(x, y, 3000 + min_dist) -- farther away from enemy is good
end
end
end
end
--AH.put_labels(self.data.healing_map)
--AH.put_labels(self.data.BD_healing_map)
-- Now on to evaluating possible moves:
-- First, get the rating of all units in their current positions
@ -341,7 +341,7 @@ function ca_bottleneck_move:evaluation(ai, cfg, self)
local attacks = {}
for i,e in ipairs(enemies) do
for x,y in H.adjacent_tiles(e.x, e.y) do
if self.data.is_my_territory:get(x,y) then
if self.data.BD_is_my_territory:get(x,y) then
local unit_in_way = wesnoth.get_unit(x, y)
local data = { x = x, y = y,
defender = e,
@ -438,8 +438,8 @@ function ca_bottleneck_move:evaluation(ai, cfg, self)
-- The following are also needed in this case
-- We don't have to worry about unsetting them, as LU attacks
-- always have higher priority than any other move
self.data.lu_defender = a.defender
self.data.lu_weapon = n_weapon
self.data.BD_lu_defender = a.defender
self.data.BD_lu_weapon = n_weapon
end
end
end
@ -462,25 +462,25 @@ function ca_bottleneck_move:evaluation(ai, cfg, self)
-- Also need to delete the level-up attack fields
-- They will be reset on the next turn
self.data.lu_defender = nil
self.data.lu_weapon = nil
self.data.BD_lu_defender = nil
self.data.BD_lu_weapon = nil
end
-- Set the variables for the exec() function
if max_rating == 0 then
-- In this case we take MP away from all units
-- This is done so that the RCA AI CAs can be kept in place
self.data.bottleneck_moves_done = true
self.data.BD_bottleneck_moves_done = true
else
self.data.bottleneck_moves_done = false
self.data.unit = best_unit
self.data.hex = best_hex
self.data.BD_bottleneck_moves_done = false
self.data.BD_unit = best_unit
self.data.BD_hex = best_hex
end
return cfg.ca_score
end
function ca_bottleneck_move:execution(ai, cfg, self)
if self.data.bottleneck_moves_done then
if self.data.BD_bottleneck_moves_done then
local units = {}
if MAISD.get_mai_self_data(self.data, cfg.ai_id, "side_leader_activated") then
units = AH.get_units_with_moves { side = wesnoth.current.side }
@ -491,27 +491,27 @@ function ca_bottleneck_move:execution(ai, cfg, self)
AH.checked_stopunit_moves(ai, u)
end
else
--print("Moving unit:",self.data.unit.id, self.data.unit.x, self.data.unit.y, " ->", best_hex[1], best_hex[2], " -- turn:", wesnoth.current.turn)
--print("Moving unit:",self.data.BD_unit.id, self.data.BD_unit.x, self.data.BD_unit.y, " ->", best_hex[1], best_hex[2], " -- turn:", wesnoth.current.turn)
if (self.data.unit.x ~= self.data.hex[1]) or (self.data.unit.y ~= self.data.hex[2]) then -- test needed for level-up move
AH.checked_move(ai, self.data.unit, self.data.hex[1], self.data.hex[2]) -- don't want full move, as this might be stepping out of the way
if (self.data.BD_unit.x ~= self.data.BD_hex[1]) or (self.data.BD_unit.y ~= self.data.BD_hex[2]) then -- test needed for level-up move
AH.checked_move(ai, self.data.BD_unit, self.data.BD_hex[1], self.data.BD_hex[2]) -- don't want full move, as this might be stepping out of the way
end
if (not self.data.unit) or (not self.data.unit.valid) then return end
if (not self.data.BD_unit) or (not self.data.BD_unit.valid) then return end
-- If this is a move for a level-up attack, do the attack also
if self.data.lu_defender then
--print("Level-up attack",self.data.unit.id, self.data.lu_defender.id, self.data.lu_weapon)
if self.data.BD_lu_defender then
--print("Level-up attack",self.data.BD_unit.id, self.data.BD_lu_defender.id, self.data.BD_lu_weapon)
AH.checked_attack(ai, self.data.unit, self.data.lu_defender, self.data.lu_weapon)
AH.checked_attack(ai, self.data.BD_unit, self.data.BD_lu_defender, self.data.BD_lu_weapon)
end
end
-- Now delete almost everything
-- Keep: self.data.is_my_territory, [micro_ai]side_leader_activated=
self.data.unit, self.data.hex = nil, nil
self.data.lu_defender, self.data.lu_weapon = nil, nil
self.data.bottleneck_moves_done = nil
self.data.def_map, self.data.healer_map, self.data.leadership_map, self.data.healing_map = nil, nil, nil, nil
-- Keep: self.data.BD_is_my_territory, [micro_ai]side_leader_activated=
self.data.BD_unit, self.data.BD_hex = nil, nil
self.data.BD_lu_defender, self.data.BD_lu_weapon = nil, nil
self.data.BD_bottleneck_moves_done = nil
self.data.BD_def_map, self.data.BD_healer_map, self.data.BD_leadership_map, self.data.BD_healing_map = nil, nil, nil, nil
end
return ca_bottleneck_move

View file

@ -41,11 +41,11 @@ function ca_goto:evaluation(ai, cfg, self)
local locs = {}
if cfg.unique_goals then
-- First, some cleanup of previous turn data
local str = 'goals_taken_' .. (wesnoth.current.turn - 1)
local str = 'GO_goals_taken_' .. (wesnoth.current.turn - 1)
self.data[str] = nil
-- Now on to the current turn
local str = 'goals_taken_' .. wesnoth.current.turn
local str = 'GO_goals_taken_' .. wesnoth.current.turn
for _, loc in ipairs(all_locs) do
if (not self.data[str]) or (not self.data[str]:get(loc[1], loc[2])) then
table.insert(locs, loc)
@ -76,13 +76,13 @@ function ca_goto:evaluation(ai, cfg, self)
if (not units[1]) then return 0 end
-- Now store units and locs in self.data, so that we don't need to duplicate this in the exec function
self.data.units, self.data.locs = units, locs
self.data.GO_units, self.data.GO_locs = units, locs
return cfg.ca_score
end
function ca_goto:execution(ai, cfg, self)
local units, locs = self.data.units, self.data.locs -- simply for convenience
local units, locs = self.data.GO_units, self.data.GO_locs -- simply for convenience
-- Need the enemy map and enemy attack map if avoid_enemies is set
local enemy_map, enemy_attack_map
@ -174,7 +174,7 @@ function ca_goto:execution(ai, cfg, self)
-- If 'unique_goals' is set, mark this location as being taken
if cfg.unique_goals then
local str = 'goals_taken_' .. wesnoth.current.turn
local str = 'GO_goals_taken_' .. wesnoth.current.turn
if (not self.data[str]) then self.data[str] = LS.create() end
self.data[str]:insert(closest_hex[1], closest_hex[2])
end
@ -239,7 +239,7 @@ function ca_goto:execution(ai, cfg, self)
end
-- And some cleanup
self.data.units, self.data.locs = nil, nil
self.data.GO_units, self.data.GO_locs = nil, nil
end
return ca_goto

View file

@ -135,7 +135,7 @@ function ca_messenger_attack:evaluation(ai, cfg, self)
local attack = messenger_find_clearing_attack(messenger, x, y, cfg)
if attack then
self.data.best_attack = attack
self.data.ME_best_attack = attack
return cfg.ca_score
end
@ -143,15 +143,15 @@ function ca_messenger_attack:evaluation(ai, cfg, self)
end
function ca_messenger_attack:execution(ai, cfg, self)
local attacker = wesnoth.get_unit(self.data.best_attack.src.x, self.data.best_attack.src.y)
local defender = wesnoth.get_unit(self.data.best_attack.target.x, self.data.best_attack.target.y)
local attacker = wesnoth.get_unit(self.data.ME_best_attack.src.x, self.data.ME_best_attack.src.y)
local defender = wesnoth.get_unit(self.data.ME_best_attack.target.x, self.data.ME_best_attack.target.y)
AH.movefull_stopunit(ai, attacker, self.data.best_attack.dst.x, self.data.best_attack.dst.y)
AH.movefull_stopunit(ai, attacker, self.data.ME_best_attack.dst.x, self.data.ME_best_attack.dst.y)
if (not attacker) or (not attacker.valid) then return end
if (not defender) or (not defender.valid) then return end
AH.checked_attack(ai, attacker, defender)
self.data.best_attack = nil
self.data.ME_best_attack = nil
end
return ca_messenger_attack

View file

@ -116,22 +116,22 @@ function ca_protect_unit_attack:evaluation(ai, cfg, self)
--print('Max_rating:', max_rating)
if (max_rating > -9e99) then
self.data.best_attack = best_attack
self.data.PU_best_attack = best_attack
return 95000
end
return 0
end
function ca_protect_unit_attack:execution(ai, cfg, self)
local attacker = wesnoth.get_unit(self.data.best_attack.src.x, self.data.best_attack.src.y)
local defender = wesnoth.get_unit(self.data.best_attack.target.x, self.data.best_attack.target.y)
local attacker = wesnoth.get_unit(self.data.PU_best_attack.src.x, self.data.PU_best_attack.src.y)
local defender = wesnoth.get_unit(self.data.PU_best_attack.target.x, self.data.PU_best_attack.target.y)
AH.movefull_stopunit(ai, attacker, self.data.best_attack.dst.x, self.data.best_attack.dst.y)
AH.movefull_stopunit(ai, attacker, self.data.PU_best_attack.dst.x, self.data.PU_best_attack.dst.y)
if (not attacker) or (not attacker.valid) then return end
if (not defender) or (not defender.valid) then return end
AH.checked_attack(ai, attacker, defender)
self.data.best_attack = nil
self.data.PU_best_attack = nil
end
return ca_protect_unit_attack

View file

@ -9,8 +9,8 @@ function ca_protect_unit_finish:evaluation(ai, cfg, self)
if unit then
local path, cost = wesnoth.find_path(unit, cfg.goal_x[i], cfg.goal_y[i])
if (cost <= unit.moves) and ((unit.x ~= cfg.goal_x[i]) or (unit.y ~= cfg.goal_y[i])) then
self.data.unit = unit
self.data.goal = { cfg.goal_x[i], cfg.goal_y[i] }
self.data.PU_unit = unit
self.data.PU_goal = { cfg.goal_x[i], cfg.goal_y[i] }
return 300000
end
end
@ -19,9 +19,9 @@ function ca_protect_unit_finish:evaluation(ai, cfg, self)
end
function ca_protect_unit_finish:execution(ai, cfg, self)
AH.movefull_stopunit(ai, self.data.unit, self.data.goal)
self.data.unit = nil
self.data.goal = nil
AH.movefull_stopunit(ai, self.data.PU_unit, self.data.PU_goal)
self.data.PU_unit = nil
self.data.PU_goal = nil
end
return ca_protect_unit_finish

View file

@ -80,11 +80,11 @@ function ca_protect_unit_move:execution(ai, cfg, self)
--AH.put_labels(GDM)
-- Configuration parameters (no option to change these enabled at the moment)
local enemy_weight = self.data.enemy_weight or 100.
local my_unit_weight = self.data.my_unit_weight or 1.
local distance_weight = self.data.distance_weight or 3.
local terrain_weight = self.data.terrain_weight or 0.1
local bearing = self.data.bearing or 1
local enemy_weight = self.data.PU_enemy_weight or 100.
local my_unit_weight = self.data.PU_my_unit_weight or 1.
local distance_weight = self.data.PU_distance_weight or 3.
local terrain_weight = self.data.PU_terrain_weight or 0.1
local bearing = self.data.PU_bearing or 1
-- If there are no enemies left, only distance to goal matters
-- This is to avoid rare situations where moving toward goal is canceled by moving away from own units

View file

@ -51,7 +51,7 @@ function ca_simple_attack:evaluation(ai, cfg, self)
end
if (max_rating > -9e99) then
self.data.attack = best_attack
self.data.SA_attack = best_attack
return cfg.ca_score
end
@ -59,15 +59,15 @@ function ca_simple_attack:evaluation(ai, cfg, self)
end
function ca_simple_attack:execution(ai, cfg, self)
local attacker = wesnoth.get_unit(self.data.attack.src.x, self.data.attack.src.y)
local defender = wesnoth.get_unit(self.data.attack.target.x, self.data.attack.target.y)
local attacker = wesnoth.get_unit(self.data.SA_attack.src.x, self.data.SA_attack.src.y)
local defender = wesnoth.get_unit(self.data.SA_attack.target.x, self.data.SA_attack.target.y)
AH.movefull_outofway_stopunit(ai, attacker, self.data.attack.dst.x, self.data.attack.dst.y)
AH.movefull_outofway_stopunit(ai, attacker, self.data.SA_attack.dst.x, self.data.SA_attack.dst.y)
if (not attacker) or (not attacker.valid) then return end
if (not defender) or (not defender.valid) then return end
AH.checked_attack(ai, attacker, defender, (cfg.weapon or -1))
self.data.attack = nil
self.data.SA_attack = nil
end
return ca_simple_attack
return ca_simple_attack