New Hang Out Micro AI and test scenario

This commit is contained in:
mattsc 2013-07-03 17:10:33 -07:00
parent 0ed4441b09
commit bce61017f1
5 changed files with 376 additions and 1 deletions

View file

@ -0,0 +1,126 @@
return {
init = function(ai, existing_engine)
local engine = existing_engine or {}
local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local LS = wesnoth.require "lua/location_set.lua"
function engine:mai_hang_out_eval(cfg)
cfg = cfg or {}
-- Return 0 if the mobilize condition has previously been met
for mobilze in H.child_range(self.data, "hangout_mobilize_units") do
if (mobilze.id == cfg.ca_id) then
return 0
end
end
-- Otherwise check if any of the mobilize conditions are now met
if (cfg.mobilize_condition and wesnoth.eval_conditional(cfg.mobilize_condition))
or (cfg.mobilize_on_gold_less_than and (wesnoth.sides[wesnoth.current.side].gold < cfg.mobilize_on_gold_less_than))
then
table.insert(self.data, { "hangout_mobilize_units" , { id = cfg.ca_id } } )
-- Need to unmark all units also
local units = wesnoth.get_units { side = wesnoth.current.side, { "and", cfg.filter } }
for i,u in ipairs(units) do
u.variables.mai_hangout_moved = nil
end
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 or 170000
end
return 0
end
function engine:mai_hang_out_exec(cfg)
cfg = cfg or {}
local units = wesnoth.get_units { side = wesnoth.current.side,
{ "and", cfg.filter }, formula = '$this_unit.moves > 0'
}
--print('#unit', #units)
-- Get the locations close to which the units should hang out
-- cfg.filter_location defaults to the location of the side leader(s)
local filter_location = cfg.filter_location or {
{ "filter", { side = wesnoth.current.side, canrecruit = "yes" } }
}
local width, height = wesnoth.get_map_size()
local locs = wesnoth.get_locations {
x = '1-' .. width,
y = '1-' .. height,
{ "and", filter_location }
}
--print('#locs', #locs)
-- Get map for locations to be avoided (defaults to all castle terrain)
local avoid = cfg.avoid or { terrain = 'C*,C*^*,*^C*' }
local avoid_map = LS.of_pairs(wesnoth.get_locations(avoid))
local best_hex, best_unit, max_rating = {}, {}, -9e99
for i,u in ipairs(units) do
-- Only consider units that have not been marked yet
if (not u.variables.mai_hangout_moved) then
local best_hex_unit, max_rating_unit = {}, -9e99
-- Check out all unoccupied hexes the unit can reach
local reach_map = AH.get_reachable_unocc(u)
reach_map:iter( function(x, y, v)
if (not avoid_map:get(x, y)) then
for k,l in ipairs(locs) do
-- Main rating is the distance from any of the goal hexes
local rating = -H.distance_between(x, y, l[1], l[2])
-- Fastest unit moves first
rating = rating + u.max_moves / 100.
-- Minor penalty for distance from current position of unit
-- so that there's not too much shuffling around
local rating = rating - H.distance_between(x, y, u.x, u.y) / 1000.
if (rating > max_rating_unit) then
max_rating_unit = rating
best_hex_unit = {x, y}
end
end
end
end)
-- Only consider a unit if the best hex found for it is not its current location
if (best_hex_unit[1] ~= u.x) or (best_hex_unit[2] ~= u.y) then
if (max_rating_unit > max_rating) then
max_rating = max_rating_unit
best_hex, best_unit = best_hex_unit, u
end
end
end
end
--print(best_unit.id, best_unit.x, best_unit.y, best_hex[1], best_hex[2], max_rating)
-- If no valid locations/units were found or all units are in their
-- respective best locations already, we take moves away from all units
if (max_rating == -9e99) then
for i,u in ipairs(units) do
ai.stopunit_moves(u)
-- Also remove the markers
u.variables.mai_hangout_moved = nil
end
else
-- Otherwise move unit and mark as having been used
ai.move(best_unit, best_hex[1], best_hex[2])
best_unit.variables.mai_hangout_moved = true
end
end
return engine
end
}

View file

@ -592,12 +592,22 @@ function wesnoth.wml_actions.micro_ai(cfg)
required_keys = { "filter", "filter_location" }
optional_keys = { "release_all_units_at_goal", "release_unit_at_goal", "unique_goals", "use_straight_line" }
CA_parms = {
{ -- Note: do not define max_score
{
ca_id = 'goto', eval_name = 'mai_goto_eval', exec_name = 'mai_goto_exec',
max_score = cfg.ca_score or 300000
}
}
--------- Hang Out Micro AI - side-wide AI ------------------------------------
elseif (cfg.ai_type == 'hang_out') then
optional_keys = { "filter", "filter_location", "avoid", "mobilize_condition", "mobilize_on_gold_less_than" }
CA_parms = {
{
ca_id = 'hang_out', eval_name = 'mai_hang_out_eval', exec_name = 'mai_hang_out_exec',
max_score = cfg.ca_score or 170000
}
}
-- If we got here, none of the valid ai_types was specified
else
H.wml_error("unknown value for ai_type= in [micro_ai]")

View file

@ -0,0 +1,184 @@
#textdomain wesnoth-ai
[test]
id=hang-out
name= _ "Hang Out"
next_scenario=micro_ai_test
map_data="{multiplayer/maps/4p_Castle_Hopping_Isle.map}"
{DEFAULT_SCHEDULE}
turns=-1
victory_when_enemies_defeated=no
[side]
side=1
controller=ai
id=Bad Outlaw
type=Outlaw
x,y=2,19
persistent=no
team_name=Outlaw
user_team_name= _ "team_name^Bad Outlaw"
recruit=Footpad
gold=200
[ai]
version=10710
[engine]
name="lua"
code= <<
local ai = ...
local engine = {}
engine = wesnoth.require("ai/micro_ais/ais/mai_hang_out_engine.lua").init(ai, engine)
engine = wesnoth.require("ai/micro_ais/ais/mai_messenger_escort_engine.lua").init(ai, engine)
return engine
>>
[/engine]
{RCA_STAGE}
[/ai]
[/side]
[side]
side=2
controller=human
id=Good Bandit
type=Bandit
x,y=16,2
persistent=no
team_name=Bandit
user_team_name= _ "team_name^Good Bandit"
recruit=Thief
gold=200
[/side]
[side] # This side is only here because we need one persistent side for the game to go on
side=3
controller=null
persistent=yes
save_id=Grnk
hidden=yes
[/side]
# Prestart actions
[event]
name=prestart
{VARIABLE scenario_name hang-out}
# Goal signpost for Rossauba
{PLACE_IMAGE "scenery/signpost.png" 36 19}
{SET_LABEL 36 19 _"Outlaw moves here"}
# Change some of the terrain for this demonstration
[terrain]
x=4,15,23
y=19,20,20
terrain=Aa
[/terrain]
[terrain]
x=0,1
y=20-22,21-22
terrain=Mm
[/terrain]
[terrain]
x=18,20
y=19,19
terrain=Wwf
[/terrain]
[terrain]
x,y=21-22,16-17
terrain=Ww
[/terrain]
[micro_ai]
side=1
ai_type=hang_out
action=add
[avoid]
terrain=C*,H*,M*,A*,S*,*^F*
[/avoid]
[mobilize_condition]
[have_unit]
side=1
count=7-99
[/have_unit]
[/mobilize_condition]
[/micro_ai]
[micro_ai]
side=1
ai_type=messenger_escort
action=add
id=Bad Outlaw
ca_score=165000
waypoint_x=16,22,36
waypoint_y=19,19,19
[/micro_ai]
[/event]
[event]
name=start
{MESSAGE (Good Bandit) "" "" _"That outlaw over there is going to run for the keep in the southeast. He's only going to recruit for three rounds before he'll start moving and he and his footpads are much faster than we are. Let's make haste or we'll never catch him.
Note: This scenario uses a combination of two Micro AIs, the Hang Out Micro AI which makes the Side 2 units remain around the keep for two turns (while moving off castle tiles to allow for recruiting) and the Messenger Escort AI which takes over after that. A Micro AI can be added and adapted to the need of a scenario easily using only WML and the [micro_ai] tag. Check out the <span color='#00A000'>Micro AI wiki page</span> at http://wiki.wesnoth.org/Micro_AIs for more information."}
[objectives]
summary= _ "Get into the outlaw's way before he can make it to the south-eastern keep"
[objective]
description= _ "Death of Bad Outlaw"
condition=win
[/objective]
[objective]
description= _ "Death of Good Bandit"
condition=lose
[/objective]
[objective]
description= _ "Bad Outlaw makes it to the signpost"
condition=lose
[/objective]
[/objectives]
[/event]
[event]
name=die
[filter]
id=Bad Outlaw
[/filter]
{MESSAGE (Good Bandit) "" "" _"We got him! Now whatever it is we are fighting for is safe."}
# So that game goes on to next scenario
[modify_side]
side=3
controller=human
[/modify_side]
[endlevel]
result=victory
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
[/endlevel]
[/event]
[event]
name=moveto
[filter]
id=Bad Outlaw
x,y=36,19
[/filter]
{MESSAGE (Bad Outlaw) "" "" _"I made it! Now we can keep fighting for whatever it is that we are fighting for."}
[endlevel]
result=defeat
[/endlevel]
[/event][/test]

View file

@ -82,6 +82,9 @@
{PLACE_IMAGE "scenery/signpost.png" 12 18}
{SET_LABEL 12 18 _"Goto"}
{PLACE_IMAGE "scenery/signpost.png" 13 19}
{SET_LABEL 13 19 _"Hang Out and Messenger"}
{VARIABLE scenario_name micro_ai_test}
# Menu items explaining the different scenarios
@ -294,6 +297,21 @@
{MESSAGE Grnk "" _"Goto Micro AI demo" _"This scenario contains several example usages of the Goto Micro AI, which is a highly configurable method of sending a unit (or units) to a location or set of locations. The units to be moved are defined using a Standard Unit Filter, while the goto locations are given in a Standard Location Filter."}
[/command]
[/set_menu_item]
[set_menu_item]
id=m14_hangout
description= _ "Hang Out Micro AI demo"
image=units/human-outlaws/bandit.png~CROP(22,14,24,24)
[filter_location]
x,y=13,19
[/filter_location]
[show_if]
{VARIABLE_CONDITIONAL scenario_name equals micro_ai_test}
[/show_if]
[command]
{MESSAGE Grnk "" _"Combined Hang Out and Messenger Escort Micro AI demo" _"This scenario is a demonstration of the Hang Out Micro AI which keeps units around a (customizable) location until a (customizable) condition is met. After that the units are released to follow other AI behavior. The scenario also shows how to combine two Micro AIs on the same side by having the Messenger Escort Micro AI take over at that point."}
[/command]
[/set_menu_item]
[/event]
[event]
@ -554,4 +572,21 @@ Information about each demonstration can be accessed by right-clicking on the re
replay_save=no
[/endlevel]
[/event]
[event]
name=moveto
[filter]
x,y=13,19
[/filter]
[endlevel]
result=victory
next_scenario=hang-out
bonus=no
carryover_percentage=0
carryover_report=no
linger_mode=no
replay_save=no
[/endlevel]
[/event]
[/test]

View file

@ -217,3 +217,23 @@
{RCA_STAGE}
[/ai]
#enddef
#define MICRO_AI_HANG_OUT
# Sets up the Hang Out Micro AI for a side
# Include this macro in the side definition
# Needs to be in [side], does not work in [modify_side] in BfW 1.10
[ai]
id=hang_out
description=_"Hang Out Micro AI"
version=10710
[engine]
name="lua"
code= <<
local ai = ...
return wesnoth.require("ai/micro_ais/ais/mai_hang_out_engine.lua").init(ai)
>>
[/engine]
{RCA_STAGE}
[/ai]
#enddef