├── .gitignore
├── AI-demos
├── CHANGELOG
├── _main.cfg
├── ais
│ ├── ai_fred.cfg
│ └── ai_manual.cfg
├── lua
│ ├── ai_helper_local.lua
│ ├── ca_fred.lua
│ ├── ca_manual_move.lua
│ ├── ca_manual_stats.lua
│ ├── ca_move_unittype.lua
│ ├── ca_stats.lua
│ ├── compatibility.lua
│ ├── debug.lua
│ ├── eval_exec_CA.lua
│ ├── fred_advance.lua
│ ├── fred_attack.lua
│ ├── fred_attack_utils.lua
│ ├── fred_benefits_utilities.lua
│ ├── fred_config.lua
│ ├── fred_data_incremental.lua
│ ├── fred_data_move.lua
│ ├── fred_data_turn.lua
│ ├── fred_gamestate_map.lua
│ ├── fred_hold.lua
│ ├── fred_map_config.lua
│ ├── fred_map_utils.lua
│ ├── fred_move_leader_utils.lua
│ ├── fred_ops_analysis.lua
│ ├── fred_retreat.lua
│ ├── fred_scenario_events.lua
│ ├── fred_scenario_setup.lua
│ ├── fred_status.lua
│ ├── fred_utils.lua
│ ├── fred_village_utils.lua
│ ├── fred_virtual_state.lua
│ ├── generic_recruit_engine.lua
│ ├── manual_input.lua
│ └── test_lua_template.lua
├── maps
│ ├── Dark_Forecast.map
│ └── fai_demo.map
├── multiplayer
│ ├── _initial.cfg
│ ├── era.cfg
│ └── modification.cfg
├── scenarios
│ ├── Fred.cfg
│ ├── fai_demo.cfg
│ ├── luaai_demo.cfg
│ ├── manual_ai.cfg
│ ├── mp_ais.cfg
│ ├── switchboard.cfg
│ └── test.cfg
├── units
│ └── Invisible_Unit.cfg
├── version.lua
└── version.txt
├── COPYING.txt
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | test_lua.lua
3 | _server.pbl
4 | .idea
5 | *.pyc
6 | *~
7 | ml_utils/stats__release_*.log
8 | */.DS_Store
9 |
--------------------------------------------------------------------------------
/AI-demos/_main.cfg:
--------------------------------------------------------------------------------
1 | #textdomain wesnoth-AI-demos
2 | [textdomain]
3 | name="wesnoth-AI-demos"
4 | path="data/add-ons/AI-demos/translations"
5 | [/textdomain]
6 |
7 | # wmllint: general spellings RCA AIs Lua self.data Multiplayer_AI commandline dbms config
8 | # wmllint: general spellings Ron Fred Jabb Urudin Quank Freelands Google
9 |
10 | [campaign]
11 | id=AI-demos
12 | name=_"AI Modification Demos"
13 | abbrev=_"AI-demos"
14 |
15 | define=CAMPAIGN_AIDEMOS
16 | first_scenario=aid_switchboard
17 |
18 | icon=units/goblins/spearman.png~RC(magenta>green)~BLIT(halo/misc/leadership-flare-6.png~CROP(21,21,30,30)~CS(0,-50,0),30,8)
19 | image=portraits/goblins/spearman.png~CROP(1,65,500,335)
20 | rank=9999
21 | description=_"AI Development and Demonstration Campaign
22 |
23 | Home of a number of AI development projects, most notably two MP AIs dubbed 'Ron' and 'Fred' as well as several test and demo scenarios. This is also where the Micro AIs were developed. As the latter have now been moved into mainline, they are no longer part of this add-on.
24 |
25 | Version {./version.txt}" # wmllint: no spellcheck
26 |
27 | [about]
28 | title=_"Initial Campaign Design"
29 | [entry]
30 | name=_"mattsc"
31 | [/entry]
32 | [/about]
33 | [about]
34 | title=_"Coding"
35 | [entry]
36 | name=_"Alarantalara, AI0867, mattsc"
37 | [/entry]
38 | [/about]
39 | [about]
40 | title=_"2012 Google Code-in Students' Micro AI development"
41 | [entry]
42 | name=_"Samuel Kim, Robert Spencer, Martin Bede, gh0st, vitiv"
43 | [/entry]
44 | [/about]
45 | [about]
46 | title=_"Code and strategy contributions"
47 | [entry]
48 | name=_"nelson, Rigor and various others (see forum thread)"
49 | [/entry]
50 | [/about]
51 | [about]
52 | title=_"Home guard code"
53 | [entry]
54 | name=_"JaMiT"
55 | [/entry]
56 | [/about]
57 | [about]
58 | title=_"Stationed guardian and coward ideas"
59 | [entry]
60 | name=_"Simons Mith"
61 | [/entry]
62 | [/about]
63 | [about]
64 | title=_"Patrol scenario map, Konrad move idea (from 'The Earth Gut'), and dbms debug function"
65 | [entry]
66 | name=_"Anonymissimus"
67 | [/entry]
68 | [/about]
69 | [about]
70 | title=_"Goblin Jabb patrol code (from 'A Rough Life')"
71 | [entry]
72 | name=_"Elvish_Hunter"
73 | [/entry]
74 | [/about]
75 | [about]
76 | title=_"Urudin retreat code (from 'Legend of Wesmere')"
77 | [entry]
78 | name=_"nephro and Elvish_Hunter"
79 | [/entry]
80 | [/about]
81 | [about]
82 | title=_"Animal unit graphics and config files"
83 | [entry]
84 | name=_"Deer & Stag: Anna D. (dirtywhitellama); Rabbit: cedric; Other Animals: Different sources -- TBD: need to list those specifically!!!"
85 | [/entry]
86 | [/about]
87 | #[about]
88 | # title=_"Formula AI scenario and code"
89 | # [entry]
90 | # name=_"Wehrli Johan and Rinaldini Julien"
91 | # [/entry]
92 | #[/about]
93 | [/campaign]
94 |
95 | # This is needed for the CA debugging mechanism to work
96 | [lua]
97 | code = <<
98 | dummy_self = { data = {} }
99 | >>
100 | [/lua]
101 |
102 | #ifdef CAMPAIGN_AIDEMOS
103 | [binary_path]
104 | path=data/add-ons/AI-demos
105 | [/binary_path]
106 |
107 | [+units]
108 | {./units}
109 | [/units]
110 | {./scenarios}
111 | #endif
112 |
113 | #ifdef MULTIPLAYER
114 | [binary_path]
115 | path="data/add-ons/AI-demos"
116 | [/binary_path]
117 | {./multiplayer}
118 | #endif
119 |
120 | # Also enable use of test scenarios in scenarios/
121 | #ifdef TEST
122 | [binary_path]
123 | path=data/add-ons/AI-demos
124 | [/binary_path]
125 |
126 | [+units]
127 | {./units}
128 | [/units]
129 | {./scenarios}
130 | #endif
131 |
--------------------------------------------------------------------------------
/AI-demos/ais/ai_fred.cfg:
--------------------------------------------------------------------------------
1 | #textdomain wesnoth-AI-demos
2 |
3 | [ai]
4 | id=fred
5 | description=_"Multiplayer_AI^AI-demos: Fred — Freelands Custom AI"
6 | mp_rank=2000
7 | [stage]
8 | id=main_loop
9 | name=ai_default_rca::candidate_action_evaluation_loop
10 |
11 | [candidate_action]
12 | engine=lua
13 | name=stats
14 | max_score=999990
15 | location="~add-ons/AI-demos/lua/ca_stats.lua"
16 | [/candidate_action]
17 | [candidate_action]
18 | engine=lua
19 | name=fred
20 | max_score=350000
21 | location="~add-ons/AI-demos/lua/ca_fred.lua"
22 | [/candidate_action]
23 | [/stage]
24 | [/ai]
25 |
--------------------------------------------------------------------------------
/AI-demos/ais/ai_manual.cfg:
--------------------------------------------------------------------------------
1 | #textdomain wesnoth-AI-demos
2 |
3 | #ifndef AI_CA_GOTO
4 | {core/macros/ai_candidate_actions.cfg}
5 | #endif
6 |
7 | [ai]
8 | id=manual_ai
9 | description=_"Multiplayer_AI^AI-demos: Manual AI"
10 | mp_rank=2
11 | [stage]
12 | id=main_loop
13 | name=ai_default_rca::candidate_action_evaluation_loop
14 |
15 | [candidate_action]
16 | engine=lua
17 | name=stats
18 | max_score=999990
19 | location="~add-ons/AI-demos/lua/ca_manual_stats.lua"
20 | [/candidate_action]
21 | [candidate_action]
22 | engine=lua
23 | name=manual_move
24 | max_score=999980
25 | location="~add-ons/AI-demos/lua/ca_manual_move.lua"
26 | [/candidate_action]
27 | [/stage]
28 | [/ai]
29 |
--------------------------------------------------------------------------------
/AI-demos/lua/ai_helper_local.lua:
--------------------------------------------------------------------------------
1 | local AH = wesnoth.require "ai/lua/ai_helper.lua"
2 | local COMP = wesnoth.require "~/add-ons/AI-demos/lua/compatibility.lua"
3 |
4 | local ai_helper_local = {}
5 |
6 | function ai_helper_local.movepartial_outofway_stopunit(ai, unit, x, y, cfg)
7 | local viewing_side = cfg and cfg.viewing_side or unit.side
8 |
9 | if (type(x) ~= 'number') then
10 | if x[1] then
11 | x, y = x[1], x[2]
12 | else
13 | x, y = x.x, x.y
14 | end
15 | end
16 |
17 | -- Only move unit out of way if the main unit can get there
18 | local path, cost = AH.find_path_with_shroud(unit, x, y, cfg)
19 | if (cost <= unit.moves) then
20 | local unit_in_way_proxy = COMP.get_unit(x, y)
21 | if unit_in_way_proxy and (unit_in_way_proxy ~= unit)
22 | and AH.is_visible_unit(viewing_side, unit_in_way_proxy)
23 | then
24 | AH.move_unit_out_of_way(ai, unit_in_way_proxy, cfg)
25 | end
26 | end
27 |
28 | local next_hop = AH.next_hop(unit, x, y)
29 | if next_hop and ((next_hop[1] ~= unit.x) or (next_hop[2] ~= unit.y)) then
30 | AH.checked_move(ai, unit, next_hop[1], next_hop[2])
31 | else
32 | AH.checked_stopunit_moves(ai, unit)
33 | end
34 | end
35 |
36 | return ai_helper_local
37 |
--------------------------------------------------------------------------------
/AI-demos/lua/ca_manual_move.lua:
--------------------------------------------------------------------------------
1 | ----- CA: Manual move (max_score: 999980) -----
2 |
3 | local ca_manual_move = {}
4 |
5 | function ca_manual_move:evaluation()
6 | local start_time = wesnoth.get_time_stamp()
7 | local timeout_ms = 10000 -- milli-seconds
8 | --std_print('Start time:', start_time)
9 |
10 | local old_id = wml.variables.manual_ai_id
11 | local old_x = wml.variables.manual_ai_x
12 | local old_y = wml.variables.manual_ai_y
13 | local id, x, y = wesnoth.dofile("~/add-ons/AI-demos/lua/manual_input.lua")
14 |
15 | while (id == old_id) and (x == old_x) and (y == old_y) and (wesnoth.get_time_stamp() < start_time + timeout_ms) do
16 | id, x, y = wesnoth.dofile("~/add-ons/AI-demos/lua/manual_input.lua")
17 | end
18 |
19 | if (id == old_id) and (x == old_x) and (y == old_y) then
20 | std_print('manual move CA has timed out')
21 | return 0
22 | else
23 | return 999980
24 | end
25 | end
26 |
27 | function ca_manual_move:execution(cfg, data)
28 | local id, x, y = wesnoth.dofile("~/add-ons/AI-demos/lua/manual_input.lua")
29 | std_print('move ' .. id .. ' --> ' .. x .. ',' .. y)
30 |
31 | local unit = wesnoth.get_unit(id)
32 | ai.move(unit, x, y)
33 |
34 | wml.variables.manual_ai_id = id
35 | wml.variables.manual_ai_x = x
36 | wml.variables.manual_ai_y = y
37 | end
38 |
39 | return ca_manual_move
40 |
--------------------------------------------------------------------------------
/AI-demos/lua/ca_manual_stats.lua:
--------------------------------------------------------------------------------
1 | ----- CA: Stats at beginning of turn (max_score: 999990) -----
2 | -- This will be blacklisted after first execution each turn
3 |
4 | local ca_manual_stats = {}
5 |
6 | function ca_manual_stats:evaluation()
7 | return 999990
8 | end
9 |
10 | function ca_manual_stats:execution(cfg, data)
11 | local tod = wesnoth.get_time_of_day()
12 | std_print('\n**** Manual AI *********************************************************')
13 | std_print('Beginning of Turn ' .. wesnoth.current.turn .. ' (' .. tod.name ..') stats')
14 |
15 | local units = wesnoth.get_units()
16 | for _,unit in ipairs(units) do
17 | std_print(string.format('%-25s %2d,%2d %1d', unit.id, unit.x, unit.y, unit.side))
18 | end
19 |
20 | std_print('************************************************************************')
21 |
22 | local id, x, y = wesnoth.dofile("~/add-ons/AI-demos/lua/manual_input.lua")
23 | wml.variables.manual_ai_id = id
24 | wml.variables.manual_ai_x = x
25 | wml.variables.manual_ai_y = y
26 | end
27 |
28 | return ca_manual_stats
29 |
--------------------------------------------------------------------------------
/AI-demos/lua/ca_move_unittype.lua:
--------------------------------------------------------------------------------
1 | local AH = wesnoth.require "ai/lua/ai_helper.lua"
2 |
3 | local ca_move_unittype = {}
4 |
5 | function ca_move_unittype:evaluation(cfg)
6 | local units = wesnoth.get_units {
7 | side = wesnoth.current.side,
8 | type = cfg.type,
9 | formula = '$this_unit.moves > 0'
10 | }
11 |
12 | if units[1] then return cfg.score end
13 | return 0
14 | end
15 |
16 | function ca_move_unittype:execution(cfg)
17 | local unit = wesnoth.get_units {
18 | side = wesnoth.current.side,
19 | type = cfg.type,
20 | formula = '$this_unit.moves > 0'
21 | }[1]
22 |
23 | -- Find path toward the goal
24 | local path, cost = wesnoth.find_path(unit, cfg.goal_x, cfg.goal_y)
25 |
26 | -- If there's no path to goal, or all path hexes are occupied,
27 | -- use current position as default
28 | local next_hop = { unit.x, unit.y }
29 |
30 | -- Go through path to find farthest reachable, unoccupied hex
31 | -- Start at second index, as the first is just the unit position itself
32 | for i = 2,#path do
33 | local sub_path, sub_cost = wesnoth.find_path( unit, path[i][1], path[i][2])
34 |
35 | -- If we can get to that hex, check whether it is occupied
36 | -- (can only be own or allied units as enemy unit hexes are not reachable)
37 | if sub_cost <= unit.moves then
38 | local unit_in_way = wesnoth.get_unit(path[i][1], path[i][2])
39 | if not unit_in_way then
40 | next_hop = path[i]
41 | end
42 | else -- otherwise stop here; rest of path is outside movement range
43 | break
44 | end
45 | end
46 |
47 | --std_print('Moving:', unit.id, '-->', next_hop[1], next_hop[2])
48 | AH.checked_move_full(ai, unit, next_hop[1], next_hop[2])
49 | end
50 |
51 | return ca_move_unittype
52 |
--------------------------------------------------------------------------------
/AI-demos/lua/ca_stats.lua:
--------------------------------------------------------------------------------
1 | ----- CA: Stats at beginning of turn (max_score: 999990) -----
2 | -- This will be blacklisted after first execution each turn
3 |
4 | local DBG = wesnoth.dofile "~/add-ons/AI-demos/lua/debug.lua"
5 | local COMP = wesnoth.require "~/add-ons/AI-demos/lua/compatibility.lua"
6 |
7 | local ca_stats = {}
8 |
9 | function ca_stats:evaluation()
10 | return 999990
11 | end
12 |
13 | function ca_stats:execution()
14 | local tod = wesnoth.get_time_of_day()
15 | std_print('\n**** Fred Side ' .. wesnoth.current.side .. ' (version ' .. wesnoth.dofile('~/add-ons/AI-demos/version.lua') .. ') *******************************************************')
16 | DBG.print_ts('Beginning of Turn ' .. wesnoth.current.turn .. ' (' .. tod.name ..') stats')
17 |
18 | local sides = {}
19 | local leveled_units
20 | for _,unit_proxy in ipairs(COMP.get_units()) do
21 | local unit_side = unit_proxy.side
22 | if (not sides[unit_side]) then sides[unit_side] = {} end
23 |
24 | sides[unit_side].num_units = (sides[unit_side].num_units or 0) + 1
25 | sides[unit_side].hitpoints = (sides[unit_side].hitpoints or 0) + unit_proxy.hitpoints
26 |
27 | if unit_proxy.canrecruit then
28 | sides[unit_side].leader_type = unit_proxy.type
29 | sides[unit_side].leader_hp = unit_proxy.hitpoints
30 | sides[unit_side].leader_max_hp = unit_proxy.max_hitpoints
31 | else
32 | if (unit_side == wesnoth.current.side) and (unit_proxy.level > 1) then
33 | if (not leveled_units) then leveled_units = '' end
34 | leveled_units = leveled_units
35 | .. unit_proxy.type .. ' ('
36 | .. unit_proxy.hitpoints .. '/' .. unit_proxy.max_hitpoints .. ') '
37 | end
38 | end
39 | end
40 |
41 | local total_villages = 0
42 | for _,village in ipairs(wesnoth.get_villages()) do
43 | local owner = wesnoth.get_village_owner(village[1], village[2]) or 0
44 | if (owner > 0) then
45 | sides[owner].num_villages = (sides[owner].num_villages or 0) + 1
46 | end
47 |
48 | total_villages = total_villages + 1
49 | end
50 |
51 | for _,side_info in ipairs(wesnoth.sides) do
52 | local side = side_info.side
53 | local num_villages = sides[side].num_villages or 0
54 | std_print(' Side ' .. side .. ': '
55 | .. sides[side].num_units .. ' Units (' .. sides[side].hitpoints .. ' HP), '
56 | .. num_villages .. '/' .. total_villages .. ' villages ('
57 | .. sides[side].leader_type .. ', ' .. sides[side].leader_hp .. '/' .. sides[side].leader_max_hp .. ' HP, ' .. side_info.gold .. ' gold)'
58 | )
59 | end
60 |
61 | if leveled_units then std_print(' Leveled units: ' .. leveled_units) end
62 |
63 | std_print('************************************************************************************')
64 | end
65 |
66 | return ca_stats
67 |
--------------------------------------------------------------------------------
/AI-demos/lua/compatibility.lua:
--------------------------------------------------------------------------------
1 | -- Functions for simplifying complying with both 1.14 and 1.15 standards
2 | -- Many of these could, in principle, be combined into few functions with
3 | -- function names passed as arguments. I am doing it separately intentionally,
4 | -- so that it is clear which functions are affected.
5 |
6 | local AH = wesnoth.dofile "ai/lua/ai_helper.lua"
7 | local H = wesnoth.require "helper"
8 | local I = wesnoth.require "lua/wml/items.lua"
9 |
10 | local compatibility = {}
11 |
12 | function compatibility.change_max_moves(unit_proxy, max_moves)
13 | if wesnoth.compare_versions(wesnoth.game_config.version, '>=', '1.15.0') then
14 | unit_proxy.max_moves = max_moves
15 | else
16 | -- Note: this is very slow, calling it should be avoided as much as possible
17 | H.modify_unit(
18 | { id = unit_proxy.id} ,
19 | { max_moves = max_moves }
20 | )
21 | end
22 | end
23 |
24 | function compatibility.copy_unit(...)
25 | if wesnoth.compare_versions(wesnoth.game_config.version, '>=', '1.15.0') then
26 | return wesnoth.units.clone(...)
27 | else
28 | return wesnoth.copy_unit(...)
29 | end
30 | end
31 |
32 | function compatibility.create_unit(...)
33 | if wesnoth.compare_versions(wesnoth.game_config.version, '>=', '1.15.0') then
34 | return wesnoth.units.create(...)
35 | else
36 | return wesnoth.create_unit(...)
37 | end
38 | end
39 |
40 | function compatibility.debug_ai()
41 | if wesnoth.compare_versions(wesnoth.game_config.version, '>=', '1.15.0') then
42 | return wesnoth.sides.debug_ai(wesnoth.current.side).ai
43 | else
44 | return wesnoth.debug_ai(wesnoth.current.side).ai
45 | end
46 | end
47 |
48 | function compatibility.erase_unit(...)
49 | if wesnoth.compare_versions(wesnoth.game_config.version, '>=', '1.15.0') then
50 | return wesnoth.units.erase(...)
51 | else
52 | return wesnoth.erase_unit(...)
53 | end
54 | end
55 |
56 | function compatibility.extract_unit(...)
57 | if wesnoth.compare_versions(wesnoth.game_config.version, '>=', '1.15.0') then
58 | return wesnoth.units.extract(...)
59 | else
60 | return wesnoth.extract_unit(...)
61 | end
62 | end
63 |
64 | function compatibility.find_path_custom_cost(unit, x, y, cost_function)
65 | if wesnoth.compare_versions(wesnoth.game_config.version, '>=', '1.15.0') then
66 | return wesnoth.find_path(unit, x, y, { calculate = cost_function })
67 | else
68 | return wesnoth.find_path(unit, x, y, cost_function)
69 | end
70 | end
71 |
72 | function compatibility.get_closest_enemy(...)
73 | local distance, enemy = AH.get_closest_enemy(...)
74 | if wesnoth.compare_versions(wesnoth.game_config.version, '>=', '1.15.0') then
75 | local tmp = distance
76 | distance = enemy
77 | enemy = tmp
78 | end
79 | return distance, enemy
80 | end
81 |
82 | function compatibility.get_sides(side_filter)
83 | if wesnoth.compare_versions(wesnoth.game_config.version, '>=', '1.15.0') then
84 | return wesnoth.sides.find(side_filter)
85 | else
86 | return wesnoth.get_sides(side_filter)
87 | end
88 | end
89 |
90 | function compatibility.get_starting_location(side)
91 | if wesnoth.compare_versions(wesnoth.game_config.version, '>=', '1.15.0') then
92 | return wesnoth.sides[side].starting_location
93 | else
94 | return wesnoth.get_starting_location(side)
95 | end
96 | end
97 |
98 | function compatibility.get_unit(...)
99 | if wesnoth.compare_versions(wesnoth.game_config.version, '>=', '1.15.0') then
100 | return wesnoth.units.get(...)
101 | else
102 | return wesnoth.get_unit(...)
103 | end
104 | end
105 |
106 | function compatibility.get_units(...)
107 | if wesnoth.compare_versions(wesnoth.game_config.version, '>=', '1.15.0') then
108 | return wesnoth.units.find_on_map(...)
109 | else
110 | return wesnoth.get_units(...)
111 | end
112 | end
113 |
114 | function compatibility.is_enemy(...)
115 | if wesnoth.compare_versions(wesnoth.game_config.version, '>=', '1.15.0') then
116 | return wesnoth.sides.is_enemy(...)
117 | else
118 | return wesnoth.is_enemy(...)
119 | end
120 | end
121 |
122 | function compatibility.match_unit(unit, filter)
123 | if wesnoth.compare_versions(wesnoth.game_config.version, '>=', '1.15.0') then
124 | return unit:matches(filter)
125 | else
126 | return wesnoth.match_unit(unit, filter)
127 | end
128 | end
129 |
130 | function compatibility.place_halo(...)
131 | if wesnoth.compare_versions(wesnoth.game_config.version, '>=', '1.15.0') then
132 | return wesnoth.interface.add_item_halo(...)
133 | else
134 | return I.place_halo(...)
135 | end
136 | end
137 |
138 | function compatibility.put_unit(...)
139 | if wesnoth.compare_versions(wesnoth.game_config.version, '>=', '1.15.0') then
140 | return wesnoth.units.to_map(...)
141 | else
142 | return wesnoth.put_unit(...)
143 | end
144 | end
145 |
146 | function compatibility.remove(...)
147 | if wesnoth.compare_versions(wesnoth.game_config.version, '>=', '1.15.0') then
148 | return wesnoth.interface.remove_item(...)
149 | else
150 | return I.remove(...)
151 | end
152 | end
153 |
154 | function compatibility.unit_ability(unit, ability)
155 | if wesnoth.compare_versions(wesnoth.game_config.version, '>=', '1.15.0') then
156 | return unit:ability(ability)
157 | else
158 | return wesnoth.unit_ability(unit, ability)
159 | end
160 | end
161 |
162 | function compatibility.unit_defense(unit, terrain_type)
163 | if wesnoth.compare_versions(wesnoth.game_config.version, '>=', '1.15.0') then
164 | return 100 - unit:defense_on(terrain_type)
165 | else
166 | return wesnoth.unit_defense(unit, terrain_type)
167 | end
168 | end
169 |
170 | function compatibility.unit_movement_cost(unit, terrain_type)
171 | if wesnoth.compare_versions(wesnoth.game_config.version, '>=', '1.15.0') then
172 | return unit:movement_on(terrain_type)
173 | else
174 | return wesnoth.unit_movement_cost(unit, terrain_type)
175 | end
176 | end
177 |
178 | function compatibility.unit_resistance(unit, attack_type)
179 | if wesnoth.compare_versions(wesnoth.game_config.version, '>=', '1.15.0') then
180 | return 100 - unit:resistance_against(attack_type)
181 | else
182 | return wesnoth.unit_resistance(unit, attack_type)
183 | end
184 | end
185 |
186 | return compatibility
187 |
--------------------------------------------------------------------------------
/AI-demos/lua/eval_exec_CA.lua:
--------------------------------------------------------------------------------
1 | -- This is a collection of functions that can be used to evaluate and/or execute
2 | -- Fred's main CA from the right-click menu
3 | --
4 | -- See the github wiki page for a detailed description of how to use them:
5 | -- https://github.com/mattsc/Wesnoth-AI-Demos/wiki/CA-debugging
6 |
7 | local COMP = wesnoth.require "~/add-ons/AI-demos/lua/compatibility.lua"
8 | local DBG = wesnoth.dofile "~/add-ons/AI-demos/lua/debug.lua"
9 |
10 | local function is_CA_debugging_mode()
11 | -- Edit manually to indicate whether you want CA debugging mode enabled
12 | return false
13 | end
14 |
15 | local function is_wrong_side()
16 | local side_info = wesnoth.sides[wesnoth.current.side]
17 | local stage = wml.get_child(wml.get_child(side_info.__cfg, 'ai'), 'stage')
18 | for CA in wml.child_range(stage, 'candidate_action') do
19 | --std_print(CA.name)
20 | if (CA.name == 'fred') then
21 | return false
22 | end
23 | end
24 |
25 | wesnoth.message("!!!!! Error !!!!! You need to be in control of a side using the Fred AI CAs.")
26 | return true
27 | end
28 |
29 | local function CA_name()
30 | -- This function returns the name of the CA to be executed or evaluated
31 | -- It takes the value from WML variable 'debug_CA_name' if that variable exists,
32 | -- otherwise it defaults to 'recruit_orcs'
33 |
34 | local name = wml.variables.debug_CA_name
35 | return name or 'fred'
36 | end
37 |
38 | local function set_menus()
39 | -- Set the two menu items that have the selected CA name in them
40 | -- They need to be reset when that is changed, that's why this is done here
41 | -- Note: changing the CA name is currently not used, but we keep this for now
42 |
43 | wesnoth.wml_actions.set_menu_item {
44 | id = 'm01_eval',
45 | description = 'Evaluate Single Candidate Action: ' .. CA_name(),
46 | image = 'items/ring-red.png~CROP(26,26,20,20)',
47 | { 'command', {
48 | { 'lua', {
49 | code = 'wesnoth.dofile "~add-ons/AI-demos/lua/eval_exec_CA.lua".eval_exec_CA(false, "v")'
50 | } },
51 | } },
52 | { 'default_hotkey', { key = 'v' } }
53 | }
54 |
55 | wesnoth.wml_actions.set_menu_item {
56 | id = 'm02_exec',
57 | description = 'Evaluate and Execute Single Candidate Action: ' .. CA_name(),
58 | image = 'items/ring-gold.png~CROP(26,26,20,20)',
59 | { 'command', {
60 | { 'lua', {
61 | code = 'wesnoth.dofile "~add-ons/AI-demos/lua/eval_exec_CA.lua".eval_exec_CA(true, "x")'
62 | } },
63 | } },
64 | { 'default_hotkey', { key = 'x' } }
65 | }
66 |
67 | wesnoth.wml_actions.set_menu_item {
68 | id = 'm03_play_turn',
69 | description = "Play an entire AI turn",
70 | image = 'items/ring-white.png~CROP(26,26,20,20)',
71 | { 'command', {
72 | { 'lua', {
73 | code = 'wesnoth.dofile "~add-ons/AI-demos/lua/eval_exec_CA.lua".play_turn()'
74 | } },
75 | } },
76 | { 'default_hotkey', { key = 'a', shift = 'yes' } }
77 | }
78 |
79 | wesnoth.wml_actions.set_menu_item {
80 | id = 'm04_units_info',
81 | description = "Show/toggle Units Info",
82 | image = 'items/ring-silver.png~CROP(26,26,20,20)',
83 | { 'command', {
84 | { 'lua', {
85 | code = 'wesnoth.dofile "~add-ons/AI-demos/lua/eval_exec_CA.lua".units_info()'
86 | } },
87 | } },
88 | { 'default_hotkey', { key = 'i' } }
89 | }
90 | end
91 |
92 | local function init_CA(self)
93 | wesnoth.clear_messages()
94 | if is_wrong_side() then return end
95 |
96 | -- Get the AI table and the CA functions
97 | local ai = COMP.debug_ai()
98 | local ca = wesnoth.dofile("~add-ons/AI-demos/lua/ca_fred.lua")
99 |
100 | return ai, ca
101 | end
102 |
103 | return {
104 | activate_CA_debugging_mode = function()
105 | -- CA debugging mode is enabled only if 'is_CA_debugging_mode()' above
106 | -- returns true and if we're in debug mode
107 | if is_CA_debugging_mode() and wesnoth.game_config.debug then
108 | wesnoth.wml_actions.message {
109 | speaker = 'narrator',
110 | image = 'wesnoth-icon.png',
111 | caption = "Candidate Action Debugging Mode",
112 | message = "You are entering CA debugging mode. Check out the AI Demos github wiki about information on how to use this mode, or how to deactivate it."
113 | }
114 |
115 | wesnoth.sides[1].controller = 'human'
116 | wesnoth.sides[2].controller = 'human'
117 |
118 | set_menus()
119 |
120 | -- Also remove the menu items at the end of the scenario.
121 | -- This is only important when playing Fred from the switchboard
122 | -- scenario in AI-demos, and going back to switchboard afterward.
123 | wesnoth.add_event_handler {
124 | name = 'victory,defeat,time_over,enemies_defeated',
125 |
126 | { 'clear_menu_item', { id = 'm01_eval' } },
127 | { 'clear_menu_item', { id = 'm02_exec' } },
128 | { 'clear_menu_item', { id = 'm03_play_turn' } },
129 | { 'clear_menu_item', { id = 'm04_units_info' } }
130 | }
131 | end
132 | end,
133 |
134 | eval_exec_CA = function(exec_also, hotkey)
135 | if is_wrong_side() then return end
136 |
137 | std_print('\n********** Manual command : ' .. hotkey .. ' **********\n')
138 |
139 | local self = dummy_self
140 | local ai, ca = init_CA(self)
141 |
142 | local score, action = ca:evaluation(nil, self, ai)
143 | local action_str = ''
144 | if action then
145 | action_str = action.zone_id .. ' ' .. action.action_str .. ': '
146 | end
147 |
148 | wesnoth.message("Eval score for " .. CA_name() .. ' ' .. action_str .. score)
149 |
150 | if exec_also and (score > 0) then
151 | ca:execution(nil, self, ai)
152 | end
153 | dummy_self = self
154 | end,
155 |
156 | units_info = function(stdout_only)
157 | -- Shows some information for all units. Specifically, this links the
158 | -- unit id to its position, name etc. for easier identification
159 | local tmp_unit_proxies = COMP.get_units()
160 | local str = ''
161 | for _,u in ipairs(tmp_unit_proxies) do
162 | str = str .. string.format('%2d,%2d HP: %3d/%3d XP: %3d/%3d %s (%s)\n',
163 | u.x, u.y,
164 | u.hitpoints, u.max_hitpoints, u.experience, u.max_experience,
165 | u.id, tostring(u.name))
166 |
167 | if wml.variables.debug_unit_labels then
168 | wesnoth.label { x = u.x, y = u.y, text = '' }
169 | else
170 | wesnoth.label { x = u.x, y = u.y, text = u.id }
171 | end
172 | end
173 |
174 | if wml.variables.debug_unit_labels then
175 | wml.variables.debug_unit_labels = nil
176 | wesnoth.clear_messages()
177 | else
178 | wml.variables.debug_unit_labels = true
179 | std_print(str)
180 | if (not stdout_only) then wesnoth.message(str) end
181 | end
182 |
183 | end,
184 |
185 | play_turn = function(ai)
186 | -- Play through an entire AI turn (fred CA only)
187 | if is_wrong_side() then return end
188 |
189 | local self = dummy_self
190 |
191 | while 1 do
192 | local ai, ca = init_CA(self)
193 |
194 | local ca_name, score, action = 'fred', ca:evaluation(nil, self, ai)
195 |
196 | if (score > 0) then
197 | local action_str = action.zone_id .. ' ' .. action.action_str .. ': '
198 |
199 | if (not DBG.show_debug('timing')) then
200 | wesnoth.wml_actions.message {
201 | speaker = 'narrator',
202 | caption = "Executing " .. ca_name .. " CA",
203 | image = 'wesnoth-icon.png',
204 | message = "Score for " .. ca_name .. ' ' .. action_str .. score
205 | }
206 | end
207 |
208 | -- Need to evaluate the CA again first, so that 'self.data' gets set up
209 | wml.variables.debug_CA_name = ca_name
210 | ca:execution(nil, self, ai)
211 | else
212 | wesnoth.wml_actions.message {
213 | speaker = 'narrator',
214 | caption = "No more CAs with positive scores to execute",
215 | image = 'wesnoth-icon.png', message = "That's all."
216 | }
217 | break
218 | end
219 | end
220 |
221 | dummy_self = self
222 | end
223 | }
224 |
--------------------------------------------------------------------------------
/AI-demos/lua/fred_config.lua:
--------------------------------------------------------------------------------
1 | -- AI configuration parameters
2 |
3 | -- Note: Assigning this table is slow compared to accessing values in it. It is
4 | -- thus done outside the functions below, so that it is not done over and over
5 | -- for each function call.
6 | local cfg = {
7 | ----- Parameters in this section are meant to be adjustable for faction, scenario, ... -----
8 |
9 | aggression = 2, -- To first order, base ratio of acceptable damage: own / enemy
10 | min_aggression = 1.25,
11 | next_turn_influence_weight = 1, -- Fraction of ToD influence change of next turn to take into account
12 | pushfactor_min_midpoint = 1.4,
13 | milk_xp_die_chance_limit = 0, -- Enemy_leader die chance has to be greater than this to check for XP milking attack.
14 | -- Set to >=1 to disable. Engine uses 0 if this is set to negative values.
15 |
16 | action_base_ratings = { -- setting a rating to <= 0 disables the action
17 | protect_leader_eval = 32000, -- eval only
18 | attack_leader_threat = 31000,
19 | protect_leader_exec = 30000,
20 |
21 | fav_attack = 27000,
22 | advance_toward_leader = 26000,
23 | attack = 25000,
24 |
25 | protect = 22000,
26 | grab_villages = 21000,
27 | hold = 20000,
28 |
29 | recruit = 12000,
30 | retreat = 11000,
31 | advance = 10000,
32 | advance_all_map = 1000
33 | },
34 |
35 | leader_joins_action = false, -- set to true to make leader participate in zone actions
36 |
37 |
38 | ----- Do not change values below unless you know exactly what you are doing -----
39 |
40 | -- Unit value parameters
41 | xp_weight = 1.0,
42 | leader_weight = 1.5,
43 | leader_derating = 0.5,
44 |
45 | influence_falloff_floor = 0.5,
46 | influence_falloff_exp = 2,
47 |
48 | winning_ratio = 1.25,
49 | losing_ratio = 0.8,
50 |
51 | protect_others_ratio = 1.1,
52 | protect_min_value = 2,
53 |
54 | -- Leader rating parameters
55 | leader_unthreatened_hex_bonus = 500,
56 | leader_village_bonus = 10,
57 | leader_village_grab_bonus = 3,
58 | leader_moves_left_factor = 0.1,
59 | leader_unit_in_way_penalty = - 0.01,
60 | leader_unit_in_way_no_moves_penalty = - 1000,
61 | leader_eld_factor = - 0.01,
62 |
63 | -- Village grabbing parameters
64 | villages_per_unit = 2,
65 |
66 | -- Attack rating parameters
67 | terrain_defense_weight = 0.1,
68 | distance_leader_weight = 0.002,
69 | occupied_hex_penalty = 0.001,
70 | leader_max_die_chance = 0.02,
71 | leader_protect_weight = 1.0,
72 | unit_protect_weight = 0.5,
73 | village_protect_weight = 0.5,
74 | castle_protect_weight = 0.5,
75 |
76 | -- Hold rating parameters
77 | vuln_weight = 0.25,
78 | forward_weight = 0.02,
79 | hold_counter_weight = 0.1,
80 | protect_forward_weight = 1,
81 |
82 | -- Retreat rating parameters
83 | hp_inflection_base = 20,
84 |
85 | -- Delayed action scores
86 | score_grab_village = 20,
87 | score_leader_to_keep = 10,
88 | score_recruit = 9,
89 | score_leader_to_village = 8
90 | }
91 |
92 | local interaction_matrix = {
93 | abbrevs = {
94 | ALT = 'attack_leader_threat',
95 | PL = 'protect_leader',
96 | FA = 'favorable_attack',
97 | att = 'attack',
98 | pro = 'protect',
99 | GV = 'grab_village',
100 | adv = 'advance',
101 | rec = 'recruit',
102 | MLV = 'move_leader_to_village',
103 | MLK = 'move_leader_to_keep',
104 | ret = 'retreat'
105 | },
106 | -- A penalty multiplier of 0 is equivalent to not setting it in the first place.
107 | -- If one is set to 0 here, that is done to emphasize that it should be ignored.
108 | penalties = {
109 | -- 'ALT' and 'PL': use 'att'
110 | att = {
111 | units = { GV = 1, MLV = 1, MLK = 0, ret = 1 },
112 | hexes = { GV = 1, rec = 1, MLV = 1, MLK = 1, }
113 | },
114 | hold = {
115 | units = { GV = 1, MLV = 0, MLK = 0, ret = 1 },
116 | hexes = { GV = 1, rec = 1, MLV = 1, MLK = 1, }
117 | },
118 | GV = {
119 | units = { MLV = 1000, Ret = 0 }, -- retreaters may be used
120 | hexes = { MLV = 1000 }
121 | }
122 | }
123 | }
124 |
125 |
126 | local fred_config = {}
127 |
128 | function fred_config.get_cfg_parm(parm)
129 | return cfg[parm]
130 | end
131 |
132 | function fred_config.interaction_matrix()
133 | return interaction_matrix
134 | end
135 |
136 | return fred_config
137 |
--------------------------------------------------------------------------------
/AI-demos/lua/fred_data_incremental.lua:
--------------------------------------------------------------------------------
1 | -- Collection of functions to get information about units and the gamestate and
2 | -- collect them in tables for easy access. They are expensive, so this should
3 | -- be done as infrequently as possible, but it needs to be redone after each move.
4 | --
5 | -- Unlike fred_data_move.lua, these functions only acquire the information
6 | -- needed at the time and add it to a cache table as only a subset of all possible
7 | -- information is needed for any move evaluation.
8 |
9 | local FGM = wesnoth.require "~/add-ons/AI-demos/lua/fred_gamestate_map.lua"
10 | local COMP = wesnoth.require "~/add-ons/AI-demos/lua/compatibility.lua"
11 |
12 | local fred_data_incremental = {}
13 |
14 | function fred_data_incremental.get_unit_defense(unit_copy, x, y, defense_maps_cache)
15 | -- Get the terrain defense of a unit as a factor (that is, e.g. 0.40 rather than 40) and cache it
16 | --
17 | -- Inputs:
18 | -- @unit_copy: private copy of the unit (proxy table works too, but is slower)
19 | -- @x, @y: the location for which to calculate the unit's terrain defense
20 | -- @defense_maps_cache: table in which to cache the results. Note: this is NOT an optional input
21 |
22 | local unit_id = unit_copy.id
23 | local defense_map = defense_maps_cache[unit_id]
24 | if (not defense_map) then
25 | defense_maps_cache[unit_id] = {}
26 | defense_map = defense_maps_cache[unit_id]
27 | end
28 | local defense = FGM.get_value(defense_map, x, y, 'defense')
29 | if (not defense) then
30 | defense = (100. - COMP.unit_defense(unit_copy, wesnoth.get_terrain(x, y))) / 100.
31 | FGM.set_value(defense_maps_cache[unit_id], x, y, 'defense', defense)
32 | end
33 |
34 | return defense
35 | end
36 |
37 | function fred_data_incremental.get_unit_movecost(unit_copy, x, y, movecost_maps_cache)
38 | -- Get the movement cost of a unit and cache it
39 | --
40 | -- Inputs:
41 | -- @unit_copy: private copy of the unit (proxy table works too, but is slower)
42 | -- @x, @y: the location for which to calculate the unit's movement cost
43 | -- @movecost_maps_cache: table in which to cache the results. Note: this is NOT an optional input
44 |
45 | local unit_id = unit_copy.id
46 | local movecost_map = movecost_maps_cache[unit_id]
47 | if (not movecost_map) then
48 | movecost_maps_cache[unit_id] = {}
49 | movecost_map = movecost_maps_cache[unit_id]
50 | end
51 | local movecost = FGM.get_value(movecost_map, x, y, 'movecost')
52 | if (not movecost) then
53 | movecost = COMP.unit_movement_cost(unit_copy, wesnoth.get_terrain(x, y))
54 | FGM.set_value(movecost_maps_cache[unit_id], x, y, 'movecost', movecost)
55 | end
56 |
57 | return movecost
58 | end
59 |
60 | function fred_data_incremental.get_unit_type_attribute(unit_type, attribute_name, unit_types_cache)
61 | -- Access an attribute in the wesnoth.unit_types table and cache it
62 | --
63 | -- Inputs:
64 | -- @unit_type: string
65 | -- @attribute_name: string containing the key for the attribute to access
66 | -- @unit_types_cache: table in which to cache the results. Note: this is NOT an optional input
67 |
68 | local unit_type_table = unit_types_cache[unit_type]
69 | if (not unit_type_table) then
70 | unit_types_cache[unit_type] = {}
71 | unit_type_table = unit_types_cache[unit_type]
72 | end
73 | local attribute_value = unit_type_table[attribute_name]
74 | if (not attribute_value) then
75 | attribute_value = wesnoth.unit_types[unit_type][attribute_name]
76 | unit_type_table[attribute_name] = attribute_value
77 | end
78 |
79 | return attribute_value
80 | end
81 |
82 | return fred_data_incremental
83 |
--------------------------------------------------------------------------------
/AI-demos/lua/fred_data_move.lua:
--------------------------------------------------------------------------------
1 | local AH = wesnoth.dofile "ai/lua/ai_helper.lua"
2 | local H = wesnoth.require "helper"
3 | local FGM = wesnoth.require "~/add-ons/AI-demos/lua/fred_gamestate_map.lua"
4 | local FU = wesnoth.dofile "~/add-ons/AI-demos/lua/fred_utils.lua"
5 | local FMU = wesnoth.dofile "~/add-ons/AI-demos/lua/fred_map_utils.lua"
6 | local DBG = wesnoth.dofile "~/add-ons/AI-demos/lua/debug.lua"
7 | local COMP = wesnoth.require "~/add-ons/AI-demos/lua/compatibility.lua"
8 |
9 | -- Collect information about units and the gamestate and collect them in tables
10 | -- for easy access. This is expensive, so this should be done as infrequently as
11 | -- possible, but it needs to be redone after each move.
12 | --
13 | -- TODO: this does not account for allied sides at this time
14 | --
15 | -- Unit tables are of form { id = loc }, with loc = { x, y }
16 | -- except for leader tables, which also include the leader id
17 |
18 | local function show_timing_info(fred_data, text)
19 | -- Set to true or false manually, to enable timing info specifically for this
20 | -- function. The debug_utils timing flag still needs to be set also.
21 | if false then
22 | DBG.print_timing(fred_data, 1, text)
23 | end
24 | end
25 |
26 | local function find_connected_castles(keeps_map)
27 | local castle_map, new_hexes = {}, {}
28 | for x,y in FGM.iter(keeps_map) do
29 | FGM.set_value(castle_map, x, y, 'castle', true)
30 | if wesnoth.get_terrain_info(wesnoth.get_terrain(x, y)).keep then
31 | castle_map[x][y].keep = true
32 | end
33 | table.insert(new_hexes, { x, y })
34 | end
35 |
36 | while (#new_hexes > 0) do
37 | local old_hexes = AH.table_copy(new_hexes)
38 | new_hexes = {}
39 |
40 | for _,hex in ipairs(old_hexes) do
41 | for xa,ya in H.adjacent_tiles(hex[1], hex[2]) do
42 | local terrain_info = wesnoth.get_terrain_info(wesnoth.get_terrain(xa, ya))
43 | if (not FGM.get_value(castle_map, xa, ya, 'castle'))
44 | and terrain_info.castle
45 | then
46 | table.insert(new_hexes, { xa, ya })
47 | FGM.set_value(castle_map, xa, ya, 'castle', true)
48 | if terrain_info.keep then
49 | castle_map[xa][ya].keep = true
50 | end
51 | end
52 | end
53 | end
54 | end
55 |
56 | return castle_map
57 | end
58 |
59 |
60 | local fred_data_move = {}
61 |
62 | function fred_data_move.get_move_data(fred_data)
63 | -- Returns:
64 | -- - State of villages and units on the map (all in one variable: gamestate)
65 | -- - Reach maps for all the AI's units (in separate variable: reach_maps)
66 | -- - Private unit copies (in separate variable: unit_copies)
67 | -- These are returned separately in case only part of it is needed.
68 | -- They can also be retrieved all in one table using get_move_data()
69 | --
70 | -- See above for the information returned
71 |
72 | show_timing_info(fred_data, 'start fred_data_move.get_move_data()')
73 |
74 | local village_map = {}
75 | for _,village in ipairs(wesnoth.get_villages()) do
76 | FGM.set_value(village_map, village[1], village[2], 'owner',
77 | wesnoth.get_village_owner(village[1], village[2]) or 0
78 | )
79 | end
80 |
81 | local units, my_units, my_units_MP, my_units_noMP, enemies = {}, {}, {}, {}, {}
82 | local unit_infos, unit_copies = {}, {}
83 | local unit_map, my_unit_map, my_unit_map_MP, my_unit_map_noMP, enemy_map = {}, {}, {}, {}, {}
84 | local my_attack_map, my_move_map = {}, {}
85 | local attack_maps = { {}, {} }
86 | local reach_maps = {}
87 |
88 | local additional_turns = 1
89 | for i = 1,additional_turns+1 do
90 | my_attack_map[i] = {}
91 | my_move_map[i] = {}
92 | end
93 |
94 | local unit_proxies = COMP.get_units()
95 |
96 | local leaders = {} -- this is not added to move_data
97 | for _,unit_proxy in ipairs(unit_proxies) do
98 | local unit_info = FU.single_unit_info(unit_proxy, fred_data.caches.unit_types)
99 | local unit_copy = COMP.copy_unit(unit_proxy)
100 | local id, unit_x, unit_y = unit_info.id, unit_copy.x, unit_copy.y
101 | local current_power = unit_info.current_power
102 | local max_moves = unit_info.max_moves
103 |
104 | unit_infos[id] = unit_info
105 | unit_copies[id] = unit_copy
106 |
107 | FGM.set_value(unit_map, unit_x, unit_y, 'id', id)
108 |
109 | attack_maps[1][id] = {}
110 | attack_maps[2][id] = {}
111 |
112 | units[id] = { unit_x, unit_y }
113 |
114 | if unit_info.canrecruit then
115 | leaders[unit_info.side] = { unit_x, unit_y, id = id }
116 | end
117 |
118 | if (unit_info.side == wesnoth.current.side) then
119 | FGM.set_value(my_unit_map, unit_x, unit_y, 'id', id)
120 | my_units[id] = { unit_x, unit_y }
121 |
122 | -- Hexes the unit can reach in additional_turns+1 turns
123 | local reach = wesnoth.find_reach(unit_copy, { additional_turns = additional_turns })
124 |
125 | local n_reach_this_turn = 0
126 | reach_maps[id] = {}
127 | local attack_range, moves_left = {}, {}
128 |
129 | for _,loc in ipairs(reach) do
130 | -- reach_map:
131 | -- Only first-turn moves counts toward reach_maps
132 | if (loc[3] >= (max_moves * additional_turns)) then
133 | FGM.set_value(reach_maps[id], loc[1], loc[2], 'moves_left', loc[3] - (max_moves * additional_turns))
134 | n_reach_this_turn = n_reach_this_turn + 1
135 | end
136 |
137 | local turns = (additional_turns + 1) - loc[3] / max_moves
138 | local int_turns = math.ceil(turns)
139 | if (int_turns == 0) then int_turns = 1 end
140 |
141 | if (not my_move_map[int_turns][loc[1]]) then my_move_map[int_turns][loc[1]] = {} end
142 | if (not my_move_map[int_turns][loc[1]][loc[2]]) then my_move_map[int_turns][loc[1]][loc[2]] = {} end
143 | if (not my_move_map[int_turns][loc[1]][loc[2]].ids) then my_move_map[int_turns][loc[1]][loc[2]].ids = {} end
144 | table.insert(my_move_map[int_turns][loc[1]][loc[2]].ids, id)
145 |
146 | local moves_left_this_turn = loc[3] % max_moves
147 | if (loc[1] == unit_x) and (loc[2] == unit_y) then
148 | moves_left_this_turn = max_moves
149 | end
150 |
151 | -- attack_range: for attack_map
152 | if (not attack_range[loc[1]]) then
153 | attack_range[loc[1]] = {}
154 | moves_left[loc[1]] = {}
155 | end
156 | if (not attack_range[loc[1]][loc[2]]) or (attack_range[loc[1]][loc[2]] > int_turns) then
157 | attack_range[loc[1]][loc[2]] = int_turns
158 | moves_left[loc[1]][loc[2]] = moves_left_this_turn
159 | elseif (attack_range[loc[1]][loc[2]] == int_turns) and (moves_left[loc[1]][loc[2]] < moves_left_this_turn) then
160 | moves_left[loc[1]][loc[2]] = moves_left_this_turn
161 | end
162 |
163 | for xa,ya in H.adjacent_tiles(loc[1], loc[2]) do
164 | if (not attack_range[xa]) then
165 | attack_range[xa] = {}
166 | moves_left[xa] = {}
167 | end
168 |
169 | if (not attack_range[xa][ya]) or (attack_range[xa][ya] > int_turns) then
170 | attack_range[xa][ya] = int_turns
171 | moves_left[xa][ya] = moves_left_this_turn
172 | elseif (attack_range[xa][ya] == int_turns) and (moves_left[xa][ya] < moves_left_this_turn) then
173 | moves_left[xa][ya] = moves_left_this_turn
174 | end
175 | end
176 | end
177 |
178 | -- This is not a standard fgsmap
179 | for x,arr in pairs(attack_range) do
180 | for y,int_turns in pairs(arr) do
181 | if (not my_attack_map[int_turns][x]) then my_attack_map[int_turns][x] = {} end
182 | if (not my_attack_map[int_turns][x][y]) then my_attack_map[int_turns][x][y] = {} end
183 | if (not my_attack_map[int_turns][x][y].ids) then my_attack_map[int_turns][x][y].ids = {} end
184 | table.insert(my_attack_map[int_turns][x][y].ids, id)
185 |
186 | if (int_turns <= 2) then
187 | FGM.set_value(attack_maps[int_turns][id], x, y, 'current_power', current_power)
188 | attack_maps[int_turns][id][x][y].moves_left_this_turn = moves_left[x][y]
189 | end
190 | end
191 | end
192 |
193 | if (n_reach_this_turn > 1) then
194 | FGM.set_value(my_unit_map_MP, unit_x, unit_y, 'id', id)
195 | my_units_MP[id] = { unit_x, unit_y }
196 | else
197 | FGM.set_value(my_unit_map_noMP, unit_x, unit_y, 'id', id)
198 | my_units_noMP[id] = { unit_x, unit_y }
199 | end
200 | else
201 | if COMP.is_enemy(unit_info.side, wesnoth.current.side) then
202 | FGM.set_value(enemy_map, unit_x, unit_y, 'id', id)
203 | enemies[id] = { unit_x, unit_y }
204 | end
205 | end
206 | end
207 |
208 | show_timing_info(fred_data, ' reachmaps')
209 |
210 | -- reach_maps: eliminate hexes with other own units that cannot move out of the way
211 | -- Also, there might be hidden enemies on the hex
212 | for id,reach_map in pairs(reach_maps) do
213 | for id_noMP,loc in pairs(my_units_noMP) do
214 | if (id ~= id_noMP) then
215 | if reach_map[loc[1]] then reach_map[loc[1]][loc[2]] = nil end
216 | end
217 | end
218 | end
219 |
220 | -- Also mark units that can only get to hexes occupied by own units
221 | local my_units_can_move_away = {}
222 | for x,y,data in FGM.iter(my_unit_map_MP) do
223 | for x2,y2,_ in FGM.iter(reach_maps[data.id]) do
224 | if (not FGM.get_value(my_unit_map, x2, y2, 'id')) then
225 | my_units_can_move_away[data.id] = true
226 | break
227 | end
228 | end
229 | end
230 |
231 | show_timing_info(fred_data, ' keeps and castles')
232 |
233 | -- Reachable keeps: those the leader can actually move onto (AI side only)
234 | -- Reachable castles: connected to reachable keeps, independent of whether the leader
235 | -- can get there or not, but must be available for recruiting (no enemy or noMP units on it)
236 | -- Close keeps: those within one full move, not taking other units into account (all sides)
237 | -- Close castles: connected to close keeps, not taking other units into account
238 | local reachable_keeps_map, reachable_castles_map = {}, {}
239 | local close_keeps_map, close_castles_map = {}, {}
240 | local my_leader, enemy_leader
241 | for side,leader in ipairs(leaders) do
242 | if (side == wesnoth.current.side) then
243 | my_leader = leader
244 | else
245 | enemy_leader = leader
246 | end
247 |
248 | local leader_copy = unit_copies[leader.id]
249 | local old_moves = leader_copy.moves
250 | leader_copy.moves = leader_copy.max_moves
251 | local reach = wesnoth.find_reach(leader_copy, { ignore_units = true })
252 | leader_copy.moves = old_moves
253 |
254 | close_keeps_map[side], close_castles_map[side] = {}, {}
255 |
256 | for _,loc in ipairs(reach) do
257 | if wesnoth.get_terrain_info(wesnoth.get_terrain(loc[1], loc[2])).keep then
258 | FGM.set_value(close_keeps_map[side], loc[1], loc[2], 'moves_left', loc[3])
259 | if (side == wesnoth.current.side) then
260 | local ml = FGM.get_value(reach_maps[leader.id], loc[1], loc[2], 'moves_left')
261 | if ml then -- this excludes hexes with units without MP
262 | FGM.set_value(reachable_keeps_map, loc[1], loc[2], 'moves_left', ml)
263 | end
264 | end
265 | end
266 | end
267 |
268 | close_castles_map[side] = find_connected_castles(close_keeps_map[side])
269 | if (side == wesnoth.current.side) then
270 | reachable_castles_map = find_connected_castles(reachable_keeps_map)
271 | for x,y in FGM.iter(reachable_castles_map) do
272 | if FGM.get_value(enemy_map, x, y, 'id') or FGM.get_value(my_unit_map_noMP, x, y, 'id') then
273 | reachable_castles_map[x][y] = nil
274 | end
275 | end
276 | end
277 |
278 | if DBG.show_debug('ops_keep_maps') then
279 | DBG.show_fgm_with_message(close_keeps_map[side], 'moves_left', 'close_keeps_map: Side ' .. side, { x = leader[1], y = leader[2] })
280 | DBG.show_fgm_with_message(close_castles_map[side], 'castle', 'close_castles_map: Side ' .. side, { x = leader[1], y = leader[2] })
281 | if (side == wesnoth.current.side) then
282 | DBG.show_fgm_with_message(reachable_keeps_map, 'moves_left', 'reachable_keeps_map: Side ' .. side, { x = leader[1], y = leader[2] })
283 | DBG.show_fgm_with_message(reachable_castles_map, 'castle', 'reachable_castles_map: Side ' .. side, { x = leader[1], y = leader[2] })
284 | end
285 | end
286 | end
287 |
288 | show_timing_info(fred_data, ' enemy maps')
289 |
290 | -- Get enemy attack and reach maps
291 | -- These are for max MP of enemy units, and after taking all AI units with MP off the map
292 | -- This is almost the same as for my_units, but there are enough differences that this should be its own code block
293 | local enemy_attack_map, trapped_enemies = {}, {}
294 |
295 | for i = 1,additional_turns+1 do
296 | enemy_attack_map[i] = {}
297 | end
298 |
299 | -- Take all own units with MP left off the map (for enemy pathfinding)
300 | local extracted_units = {}
301 | for id,loc in pairs(my_units_MP) do
302 | local unit_proxy = COMP.get_unit(loc[1], loc[2])
303 | COMP.extract_unit(unit_proxy)
304 | table.insert(extracted_units, unit_proxy)
305 | end
306 |
307 | for enemy_id,_ in pairs(enemies) do
308 | local old_moves = unit_infos[enemy_id].moves
309 | unit_copies[enemy_id].moves = unit_copies[enemy_id].max_moves
310 | local unit_x, unit_y = unit_copies[enemy_id].x, unit_copies[enemy_id].y
311 |
312 | attack_maps[1][enemy_id] = {}
313 | attack_maps[2][enemy_id] = {}
314 |
315 | -- Hexes the enemy can reach in additional_turns+1 turns
316 | local reach = wesnoth.find_reach(unit_copies[enemy_id], { additional_turns = additional_turns })
317 |
318 | unit_copies[enemy_id].moves = old_moves
319 |
320 | reach_maps[enemy_id] = {}
321 | local is_trapped = true
322 | local attack_range, moves_left = {}, {}
323 | local max_moves = unit_infos[enemy_id].max_moves
324 | for _,loc in ipairs(reach) do
325 | -- reach_map:
326 | -- Only first-turn moves counts toward reach_maps
327 | if (loc[3] >= (max_moves * additional_turns)) then
328 | FGM.set_value(reach_maps[enemy_id], loc[1], loc[2], 'moves_left', loc[3] - (max_moves * additional_turns))
329 | end
330 |
331 | local turns = (additional_turns + 1) - loc[3] / max_moves
332 | local int_turns = math.ceil(turns)
333 | if (int_turns == 0) then int_turns = 1 end
334 |
335 | -- We count all enemies that cannot move more than 1 hex from their
336 | -- current location (for whatever reason) as trapped
337 | if is_trapped and (int_turns == 1) then
338 | if (wesnoth.map.distance_between(loc[1], loc[2], unit_copies[enemy_id].x, unit_copies[enemy_id].y) > 1) then
339 | is_trapped = nil
340 | end
341 | end
342 |
343 | local moves_left_this_turn = loc[3] % max_moves
344 | if (loc[1] == unit_x) and (loc[2] == unit_y) then
345 | moves_left_this_turn = max_moves
346 | end
347 |
348 | -- attack_range: for attack_map
349 | if (not attack_range[loc[1]]) then
350 | attack_range[loc[1]] = {}
351 | moves_left[loc[1]] = {}
352 | end
353 | if (not attack_range[loc[1]][loc[2]]) or (attack_range[loc[1]][loc[2]] > int_turns) then
354 | attack_range[loc[1]][loc[2]] = int_turns
355 | moves_left[loc[1]][loc[2]] = moves_left_this_turn
356 | elseif (attack_range[loc[1]][loc[2]] == int_turns) and (moves_left[loc[1]][loc[2]] < moves_left_this_turn) then
357 | moves_left[loc[1]][loc[2]] = moves_left_this_turn
358 | end
359 |
360 | for xa,ya in H.adjacent_tiles(loc[1], loc[2]) do
361 | if (not attack_range[xa]) then
362 | attack_range[xa] = {}
363 | moves_left[xa] = {}
364 | end
365 |
366 | if (not attack_range[xa][ya]) or (attack_range[xa][ya] > int_turns) then
367 | attack_range[xa][ya] = int_turns
368 | moves_left[xa][ya] = moves_left_this_turn
369 | elseif (attack_range[xa][ya] == int_turns) and (moves_left[xa][ya] < moves_left_this_turn) then
370 | moves_left[xa][ya] = moves_left_this_turn
371 | end
372 | end
373 | end
374 |
375 | if is_trapped then
376 | trapped_enemies[enemy_id] = true
377 | end
378 |
379 | -- This is not a standard fgsmap
380 | for x,arr in pairs(attack_range) do
381 | for y,int_turns in pairs(arr) do
382 | if (not enemy_attack_map[int_turns][x]) then enemy_attack_map[int_turns][x] = {} end
383 | if (not enemy_attack_map[int_turns][x][y]) then enemy_attack_map[int_turns][x][y] = {} end
384 | if (not enemy_attack_map[int_turns][x][y].ids) then enemy_attack_map[int_turns][x][y].ids = {} end
385 | table.insert(enemy_attack_map[int_turns][x][y].ids, enemy_id)
386 |
387 | if (int_turns <= 2) then
388 | FGM.set_value(attack_maps[int_turns][enemy_id], x, y, 'current_power', unit_infos[enemy_id].current_power)
389 | attack_maps[int_turns][enemy_id][x][y].moves_left_this_turn = moves_left[x][y]
390 | end
391 | end
392 | end
393 | end
394 |
395 | -- Put the own units with MP back out there
396 | for _,extracted_unit in ipairs(extracted_units) do COMP.put_unit(extracted_unit) end
397 |
398 |
399 | -- Keeping all of this in one place, as a "table of contents"
400 | fred_data.caches.attacks = {} -- unlike the other caches, we clear this every move, otherwise it gets too big
401 | fred_data.move_data = {
402 | units = units,
403 | my_units = my_units,
404 | my_units_MP = my_units_MP,
405 | my_units_can_move_away = my_units_can_move_away,
406 | my_units_noMP = my_units_noMP,
407 | enemies = enemies,
408 | my_leader = my_leader,
409 | enemy_leader = enemy_leader,
410 |
411 | reachable_keeps_map = reachable_keeps_map,
412 | reachable_castles_map = reachable_castles_map,
413 | close_keeps_map = close_keeps_map,
414 | close_castles_map = close_castles_map,
415 | village_map = village_map,
416 |
417 | -- can any of these be combined?
418 | unit_map = unit_map,
419 | attack_maps = attack_maps,
420 |
421 | my_unit_map = my_unit_map,
422 | my_unit_map_MP = my_unit_map_MP,
423 | my_unit_map_noMP = my_unit_map_noMP,
424 | my_move_map = my_move_map,
425 | my_attack_map = my_attack_map,
426 |
427 | enemy_map = enemy_map,
428 | enemy_attack_map = enemy_attack_map,
429 | trapped_enemies = trapped_enemies,
430 |
431 | reach_maps = reach_maps,
432 | unit_copies = unit_copies,
433 | unit_infos = unit_infos
434 | }
435 |
436 | show_timing_info(fred_data, ' influence maps')
437 |
438 | local influence_map, influence_maps = FMU.get_influence_maps(fred_data)
439 |
440 | fred_data.move_data.influence_map = influence_map
441 | fred_data.move_data.influence_maps = influence_maps
442 |
443 | show_timing_info(fred_data, 'end fred_data_move.get_move_data()')
444 | end
445 |
446 | return fred_data_move
447 |
--------------------------------------------------------------------------------
/AI-demos/lua/fred_data_turn.lua:
--------------------------------------------------------------------------------
1 | local FGM = wesnoth.require "~/add-ons/AI-demos/lua/fred_gamestate_map.lua"
2 | local DBG = wesnoth.dofile "~/add-ons/AI-demos/lua/debug.lua"
3 |
4 | local fred_data_turn = {}
5 |
6 | function fred_data_turn.set_turn_data(fred_data)
7 | -- The if statement below is so that debugging works when starting the evaluation in the
8 | -- middle of the turn. In normal gameplay, we can just use the existing enemy reach maps,
9 | -- so that we do not have to double-calculate them.
10 | local move_data = fred_data.move_data
11 | local enemy_initial_reach_maps = {}
12 | if (not next(move_data.my_units_noMP)) then
13 | --std_print('Using existing enemy move map')
14 | for enemy_id,_ in pairs(move_data.enemies) do
15 | enemy_initial_reach_maps[enemy_id] = {}
16 | for x,y,data in FGM.iter(move_data.reach_maps[enemy_id]) do
17 | FGM.set_value(enemy_initial_reach_maps[enemy_id], x, y, 'moves_left', data.moves_left)
18 | end
19 | end
20 | else
21 | --std_print('Need to create new enemy move map')
22 | for enemy_id,_ in pairs(move_data.enemies) do
23 | enemy_initial_reach_maps[enemy_id] = {}
24 |
25 | local old_moves = move_data.unit_copies[enemy_id].moves
26 | move_data.unit_copies[enemy_id].moves = move_data.unit_copies[enemy_id].max_moves
27 | local reach = wesnoth.find_reach(move_data.unit_copies[enemy_id], { ignore_units = true })
28 | move_data.unit_copies[enemy_id].moves = old_moves
29 |
30 | for _,loc in ipairs(reach) do
31 | FGM.set_value(enemy_initial_reach_maps[enemy_id], loc[1], loc[2], 'moves_left', loc[3])
32 | end
33 | end
34 | end
35 |
36 | if DBG.show_debug('ops_enemy_initial_reach_maps') then
37 | for enemy_id,_ in pairs(move_data.enemies) do
38 | DBG.show_fgm_with_message(enemy_initial_reach_maps[enemy_id], 'moves_left', 'enemy_initial_reach_maps', move_data.unit_copies[enemy_id])
39 | end
40 | end
41 |
42 | fred_data.turn_data = {
43 | turn_number = wesnoth.current.turn,
44 | side_number = wesnoth.current.side,
45 | enemy_initial_reach_maps = enemy_initial_reach_maps
46 | }
47 | end
48 |
49 | function fred_data_turn.update_turn_data(fred_data)
50 | -- In case an enemy has died
51 | for enemy_id,eirm in pairs(fred_data.turn_data.enemy_initial_reach_maps) do
52 | if (not fred_data.move_data.enemies[enemy_id]) then
53 | fred_data.turn_data.enemy_initial_reach_maps[enemy_id] = nil
54 | end
55 | end
56 | end
57 |
58 | return fred_data_turn
59 |
--------------------------------------------------------------------------------
/AI-demos/lua/fred_gamestate_map.lua:
--------------------------------------------------------------------------------
1 | local H = wesnoth.require "helper"
2 |
3 | local fred_gamestate_map = {}
4 |
5 | function fred_gamestate_map.get_value(map, x, y, key)
6 | if (not key) then error("Required parameter 'key' is missing in call to fred_gamestate_map.get_value()") end
7 | return (map[x] and map[x][y] and map[x][y][key])
8 | end
9 |
10 | function fred_gamestate_map.set_value(map, x, y, key, value)
11 | if (not map[x]) then map[x] = {} end
12 | if (not map[x][y]) then map[x][y] = {} end
13 | map[x][y][key] = value
14 | end
15 |
16 | function fred_gamestate_map.add(map, x, y, key, value)
17 | local old_value = fred_gamestate_map.get_value(map, x, y, key) or 0
18 | fred_gamestate_map.set_value(map, x, y, key, old_value + value)
19 | end
20 |
21 | function fred_gamestate_map.iter(map)
22 | function each_hex(state)
23 | while state.x ~= nil do
24 | local child = map[state.x]
25 | state.y = next(child, state.y)
26 | if state.y == nil then
27 | state.x = next(map, state.x)
28 | else
29 | return state.x, state.y, child[state.y]
30 | end
31 | end
32 | end
33 |
34 | return each_hex, { x = next(map) }
35 | end
36 |
37 | function fred_gamestate_map.normalize(map, key)
38 | local mx
39 | for _,_,data in fred_gamestate_map.iter(map) do
40 | if (not mx) or (data[key] > mx) then
41 | mx = data[key]
42 | end
43 | end
44 | for _,_,data in fred_gamestate_map.iter(map) do
45 | data[key] = data[key] / mx
46 | end
47 | end
48 |
49 | function fred_gamestate_map.blur(map, key)
50 | for x,y,data in fred_gamestate_map.iter(map) do
51 | local blurred_data = data[key]
52 | if blurred_data then
53 | local count = 1
54 | local adj_weight = 0.5
55 | for xa,ya in H.adjacent_tiles(x, y) do
56 | local value = fred_gamestate_map.get_value(map, xa, ya, key)
57 | if value then
58 | blurred_data = blurred_data + value * adj_weight
59 | count = count + adj_weight
60 | end
61 | end
62 | fred_gamestate_map.set_value(map, x, y, 'blurred_' .. key, blurred_data / count)
63 | end
64 | end
65 | end
66 |
67 | return fred_gamestate_map
68 |
--------------------------------------------------------------------------------
/AI-demos/lua/fred_map_config.lua:
--------------------------------------------------------------------------------
1 | -- These set all the scenario/map specific information
2 |
3 | local AH = wesnoth.require "ai/lua/ai_helper.lua"
4 |
5 | local fred_map_config = {}
6 |
7 | function fred_map_config.get_side_cfgs()
8 | -- These are one off from wesnoth.special_locations[2] on Freelands
9 | -- TODO: test whether that makes a difference
10 | local cfgs = {
11 | { start_hex = { 18, 4 } },
12 | { start_hex = { 20, 20 } }
13 | }
14 |
15 | return cfgs
16 | end
17 |
18 | function fred_map_config.get_attack_test_locs()
19 | -- TODO: this is really just a placeholder for now until I know whether this works
20 | -- It's easy to have this found automatically
21 | local locs = {
22 | attacker_loc = { 28, 13 },
23 | defender_loc = { 28, 14 }
24 | }
25 |
26 | return locs
27 | end
28 |
29 | function fred_map_config.get_raw_cfgs(zone_id)
30 | local cfg_all_map = {
31 | zone_id = 'all_map',
32 | ops_slf = { include_borders = 'no' },
33 | center_hexes = { { 20, 20 } }
34 | }
35 |
36 | local cfg_leader_threat = {
37 | zone_id = 'leader',
38 | ops_slf = { include_borders = 'no' },
39 | }
40 |
41 | local ops_slf_west = { x = '4-16,4-15,4-14,4-16,4-17', y = '1-6,7-12,13-15,16,17-23' }
42 | local center_hexes_west = { { 10, 13 } }
43 |
44 | local ops_slf_center = { x = '16-20,16-22,16-23,15-22,15-22,17-22,18-22', y = '7-8,9,10-12,13-14,15,16,17' }
45 | local center_hexes_center = { { 18, 12 }, { 20, 12 } }
46 |
47 | local ops_slf_east = { x = '21-34,23-34,24-34,23-34,22-34', y = '1-8,9,10-12,13-17,18-23' }
48 | local center_hexes_east = { { 28, 13 } }
49 |
50 | local cfg_right = {
51 | zone_id = 'right'
52 | }
53 |
54 | local cfg_center = {
55 | zone_id = 'center'
56 | }
57 |
58 | local cfg_left = {
59 | zone_id = 'left'
60 | }
61 |
62 | if (wesnoth.current.side == 1) then
63 | local enemy_leader_slf = { x = '17-22', y = '18-24' }
64 |
65 | cfg_right.ops_slf = ops_slf_west
66 | table.insert(cfg_right.ops_slf, { "or", enemy_leader_slf })
67 | cfg_right.center_hexes = center_hexes_west
68 |
69 | cfg_center.ops_slf = ops_slf_center
70 | table.insert(cfg_center.ops_slf, { "or", enemy_leader_slf })
71 | cfg_center.center_hexes = center_hexes_center
72 |
73 | cfg_left.ops_slf = ops_slf_east
74 | table.insert(cfg_left.ops_slf, { "or", enemy_leader_slf })
75 | cfg_left.center_hexes = center_hexes_east
76 | else
77 | local enemy_leader_slf = { x = '16-21', y = '1-7' }
78 |
79 | cfg_right.ops_slf = ops_slf_east
80 | table.insert(cfg_right.ops_slf, { "or", enemy_leader_slf })
81 | cfg_right.center_hexes = center_hexes_east
82 |
83 | cfg_center.ops_slf = ops_slf_center
84 | table.insert(cfg_center.ops_slf, { "or", enemy_leader_slf })
85 | cfg_center.center_hexes = center_hexes_center
86 |
87 | cfg_left.ops_slf = ops_slf_west
88 | table.insert(cfg_left.ops_slf, { "or", enemy_leader_slf })
89 | cfg_left.center_hexes = center_hexes_west
90 | end
91 |
92 |
93 | -- Replacement zones:
94 | local cfg_top = {
95 | zone_id = 'top',
96 | center_hexes = {}
97 | }
98 | for _,hex in ipairs(cfg_center.center_hexes) do table.insert(cfg_top.center_hexes, hex) end
99 | for _,hex in ipairs(cfg_left.center_hexes) do table.insert(cfg_top.center_hexes, hex) end
100 | cfg_top.ops_slf = AH.table_copy(cfg_center.ops_slf)
101 | table.insert(cfg_top.ops_slf, { "or", cfg_left.ops_slf })
102 |
103 | if (wesnoth.current.side == 1) then
104 | cfg_top.enemy_slf = { x = '17-34,21-34', y = '1-9,10' }
105 | else
106 | -- This one is much more complicated than for the other side, because of the uneven hex symmetry
107 | cfg_top.enemy_slf = { x = '4,6,8,10,12,14,16,4-18,20,4-21', y = '14,14,14,14,14,14,14,15,15,16-23' }
108 | end
109 |
110 | if (zone_id == 'leader_threat') then
111 | return cfg_leader_threat
112 | end
113 |
114 | if (not zone_id) then
115 | local zone_cfgs = {
116 | right = cfg_right,
117 | center = cfg_center,
118 | left = cfg_left
119 | }
120 | return zone_cfgs
121 | elseif (zone_id == 'all') then
122 | local all_cfgs = {
123 | leader_threat = cfg_leader_threat,
124 | right = cfg_right,
125 | center = cfg_center,
126 | left = cfg_left,
127 | top = cfg_top,
128 | all_map = cfg_all_map
129 | }
130 | return all_cfgs
131 | else
132 | local cfgs = {
133 | leader_threat = cfg_leader_threat,
134 | right = cfg_right,
135 | center = cfg_center,
136 | left = cfg_left,
137 | top = cfg_top,
138 | all_map = cfg_all_map
139 | }
140 |
141 | for _,cfg in pairs(cfgs) do
142 | if (cfg.zone_id == zone_id) then
143 | return cfg
144 | end
145 | end
146 | end
147 | end
148 |
149 | function fred_map_config.replace_zone_ids()
150 | local zone_ids = { {
151 | old = { 'center', 'left' },
152 | new = 'top'
153 | } }
154 |
155 | return zone_ids
156 | end
157 |
158 | return fred_map_config
159 |
--------------------------------------------------------------------------------
/AI-demos/lua/fred_scenario_events.lua:
--------------------------------------------------------------------------------
1 | -- Fred events (start/end messages, Easter eggs, etc.) are set up using Lua
2 | -- so that they can be inserted conditionally only into Fred games, and not
3 | -- into every game when AI-demos is active.
4 |
5 | -- TODO: the start and end messages will not show up any more, because the id
6 | -- of the side leader has been change to FredN, where N is the side number.
7 | -- That's okay, they were getting old anyway. Keep the events for now, but
8 | -- I'll probably remove them at some point altogether.
9 |
10 | ---------- Start message ----------
11 | wesnoth.add_event_handler {
12 | name = 'side 1 turn 1',
13 |
14 | { 'message', {
15 | id = 'Fred',
16 | caption = "Fred (Freelands AI v$AI_Demos_version)",
17 | message = "Good luck, have fun!\n"
18 | } }
19 | }
20 |
21 | ---------- End message ----------
22 | wesnoth.add_event_handler {
23 | name = 'last_breath',
24 | { 'filter', { canrecruit = 'yes' } },
25 |
26 | { 'message', {
27 | id = 'Fred',
28 | message = "Good game, thanks!",
29 | scroll = 'no'
30 | } }
31 | }
32 |
33 | ---------- Assassin exclaim ----------
34 | -- Assassin exclaims in frustration if he misses all 3 strikes (2 events)
35 | -- Assassin Event 1: The actual message. This is a separate event and without
36 | -- first_time_only=yes, so that it only fires once per scenario
37 | wesnoth.add_event_handler {
38 | name = 'assassin_message',
39 |
40 | { 'set_variable', {
41 | name = 'random',
42 | rand = "Oh come on!,I don't believe this!,What the …"
43 | } },
44 | { 'message', {
45 | id = '$unit.id',
46 | message = "$random"
47 | } },
48 | { 'clear_variable', { name = 'random' } }
49 | }
50 |
51 | -- Assassin Event 2: The trigger
52 | wesnoth.add_event_handler {
53 | name = 'attack end',
54 | first_time_only = 'no',
55 |
56 | -- Only happens for assassins on Fred's side
57 | { 'filter', {
58 | type = 'Orcish Assassin',
59 | { 'filter_side', {
60 | { 'has_unit', { id = 'Fred1,Fred2' } }
61 | } }
62 | } },
63 | -- Only if the defender is not poisoned after the attack. This also
64 | -- excludes attacks in which then enemy is already poisoned beforehand,
65 | -- but that does not matter, as it is not supposed to show every time anyway.
66 | -- Exclude unpoisonable units, as they may produce a false positive.
67 | { 'filter_second', {
68 | { 'filter_wml', {
69 | { 'not', {
70 | { 'status', { poisoned = 'yes' } }
71 | } },
72 | { 'not', {
73 | { 'status', { unpoisonable = 'yes' } }
74 | } }
75 | } }
76 | } },
77 | -- Only after the poison attack
78 | { 'filter_attack', { range = 'ranged' } },
79 |
80 | -- And even if all this is true, the message is only displayed with a 33% chance
81 | -- Also need to check that defender did not die, otherwise he might not count as
82 | -- poisoned (if this was the first hit)
83 | { 'set_variable', {
84 | name = 'random',
85 | rand = "1..3"
86 | } },
87 |
88 | { 'if', {
89 | { 'variable', {
90 | name = 'random',
91 | equals = 1
92 | } },
93 | { 'variable', {
94 | name = 'second_unit.hitpoints',
95 | greater_than = 0
96 | } },
97 |
98 | { 'then', {
99 | { 'fire_event', {
100 | name = 'assassin_message',
101 | { 'primary_unit' , { id = '$unit.id' } }
102 | } }
103 | } }
104 | } },
105 |
106 | { 'clear_variable', { name = 'random' } }
107 | }
108 |
109 | ---------- Goblin boasts ----------
110 | -- Goblin with 1HP boasts if he survives an attack (2 events)
111 | -- Goblin Event 1: The actual message. This is a separate event and without
112 | -- first_time_only=yes, so that it only fires once per scenario
113 | wesnoth.add_event_handler {
114 | name = 'goblin_boasts',
115 |
116 | { 'message', {
117 | id = '$unit.id',
118 | message = "Haw Haw!"
119 | } },
120 |
121 | { 'set_variable', {
122 | name = 'boast_goblin_id',
123 | value = "$unit.id"
124 | } },
125 |
126 | { 'event', {
127 | name = 'last breath',
128 | { 'filter', { id = '$boast_goblin_id' } },
129 |
130 | { 'message', {
131 | id = '$unit.id',
132 | message = "I guess that's what I get for being cocky."
133 | } },
134 |
135 | { 'clear_variable', { name = 'boast_goblin_id' } }
136 | } }
137 | }
138 |
139 | -- Goblin Event 2: The trigger
140 | wesnoth.add_event_handler {
141 | name = 'attack',
142 | first_time_only = 'no',
143 |
144 | -- Only happens for goblins on Fred's side that have 1 HP before the attack
145 | { 'filter_second', {
146 | race = 'goblin',
147 | { 'filter_side', {
148 | { 'has_unit', { id = 'Fred1,Fred2' } }
149 | } },
150 | { 'filter_wml', { hitpoints = 1 } }
151 | } },
152 |
153 | -- Attack end without filter, so it has to be the same attack
154 | { 'event', {
155 | name = 'attack end',
156 |
157 | -- 50% chance of the goblin boasting, if he still has 1 HP
158 | { 'set_variable', {
159 | name = 'random',
160 | rand = "1..2"
161 | } },
162 |
163 | { 'if', {
164 | { 'variable', {
165 | name = 'random',
166 | equals = 1
167 | } },
168 | { 'variable', {
169 | name = 'second_unit.hitpoints',
170 | greater_than = 0
171 | } },
172 |
173 | { 'then', {
174 | { 'fire_event', {
175 | name = 'goblin_boasts',
176 | { 'primary_unit' , { id = '$second_unit.id' } }
177 | } }
178 | } }
179 | } },
180 |
181 | { 'clear_variable', { name = 'random' } }
182 | } }
183 | }
184 |
185 | ---------- Lucky level-up message ----------
186 | -- Unit with fewer than 5 HP surviving and leveling up exclaims (2 events)
187 | -- Lucky Level-up Event 1: The actual message. This is a separate event and without
188 | -- first_time_only=yes, so that it only fires once per scenario
189 | wesnoth.add_event_handler {
190 | name = 'lucky_levelup',
191 |
192 | { 'message', {
193 | id = '$unit.id',
194 | message = "Ha! What does not kill you, makes you stronger."
195 | } }
196 | }
197 |
198 | -- Lucky Level-up Event 2: The trigger
199 | wesnoth.add_event_handler {
200 | name = 'attack',
201 | first_time_only = 'no',
202 |
203 | -- For any unit on Fred's side being attacked
204 | { 'filter_second', {
205 | { 'filter_side', {
206 | { 'has_unit', { id = 'Fred1,Fred2' } }
207 | } },
208 | } },
209 |
210 | -- If HP < 5, XP = max_XP-1 and attacker is at least level 1
211 | { 'if', {
212 | { 'variable', {
213 | name = 'unit.level',
214 | greater_than = 0
215 | } },
216 | { 'variable', {
217 | name = 'second_unit.hitpoints',
218 | less_than = 5
219 | } },
220 | { 'variable', {
221 | name = 'second_unit.experience',
222 | equals = '$($second_unit.max_experience-1)'
223 | } },
224 |
225 | { 'then', {
226 | { 'event', {
227 | name = 'attack end',
228 |
229 | -- 33% chance of the message being triggered, if the unit survived
230 | { 'set_variable', {
231 | name = 'random',
232 | rand = "1..3"
233 | } },
234 |
235 | { 'if', {
236 | { 'variable', {
237 | name = 'random',
238 | equals = 1
239 | } },
240 | { 'variable', {
241 | name = 'second_unit.hitpoints',
242 | greater_than = 0
243 | } },
244 |
245 | { 'then', {
246 | { 'fire_event', {
247 | name = 'lucky_levelup',
248 | { 'primary_unit' , { id = '$second_unit.id' } }
249 | } }
250 | } }
251 | } },
252 |
253 | { 'clear_variable', { name = 'random' } }
254 | } }
255 | } }
256 | } }
257 | }
258 |
259 | ---------- Fred angry message ----------
260 | -- Leveled-up unit with decent HP being killed causes Fred to exclaim (2 events)
261 | -- Fred Angry Event 1: The actual message. This is a separate event and without
262 | -- first_time_only=yes, so that it only fires once per scenario
263 | wesnoth.add_event_handler {
264 | name = 'Fred_angry',
265 |
266 | { 'message', {
267 | id = 'Fred1,Fred2',
268 | side = '$unit.side',
269 | message = "You'll pay for that!"
270 | } }
271 | }
272 |
273 | -- Fred Angry Event 2: The trigger
274 | wesnoth.add_event_handler {
275 | name = 'attack',
276 | first_time_only = 'no',
277 | -- For any unit on Fred's side (other than Fred himself) being attacked
278 | { 'filter_second', {
279 | canrecruit = 'no',
280 | { 'filter_side', {
281 | { 'has_unit', { id = 'Fred1,Fred2' } }
282 | } },
283 | } },
284 |
285 | -- If HP > 19 and level > 1
286 | { 'if', {
287 | { 'variable', {
288 | name = 'second_unit.hitpoints',
289 | greater_than = 19
290 | } },
291 | { 'variable', {
292 | name = 'second_unit.level',
293 | greater_than = 1
294 | } },
295 |
296 |
297 | { 'then', {
298 | { 'event', {
299 | name = 'attack end',
300 |
301 | -- 50% chance of the message being triggered, if the unit survived
302 | { 'set_variable', {
303 | name = 'random',
304 | rand = "1..2"
305 | } },
306 |
307 | { 'if', {
308 | { 'variable', {
309 | name = 'random',
310 | equals = 1
311 | } },
312 | { 'variable', {
313 | name = 'second_unit.hitpoints',
314 | less_than_equal_to = 0
315 | } },
316 |
317 | { 'then', {
318 | { 'fire_event', {
319 | name = 'Fred_angry',
320 | { 'primary_unit' , { id = '$second_unit.id' } }
321 | } }
322 | } }
323 | } },
324 |
325 | { 'clear_variable', { name = 'random' } }
326 | } }
327 | } }
328 | } }
329 | }
330 |
--------------------------------------------------------------------------------
/AI-demos/lua/fred_scenario_setup.lua:
--------------------------------------------------------------------------------
1 | local COMP = wesnoth.require "~/add-ons/AI-demos/lua/compatibility.lua"
2 |
3 | local function fred_sides()
4 | -- Check which sides are played by Fred
5 | -- We do this by testing whether the 'fred' CA exists
6 | -- Returns an array of the side numbers played by Fred
7 |
8 | local fred_sides = {}
9 | for _,side_info in ipairs(wesnoth.sides) do
10 | local stage = wml.get_child(wml.get_child(side_info.__cfg, 'ai'), 'stage')
11 | for CA in wml.child_range(stage, 'candidate_action') do
12 | --std_print(CA.name)
13 | if (CA.name == 'fred') then
14 | table.insert(fred_sides, side_info.side)
15 | break
16 | end
17 | end
18 | end
19 |
20 | return fred_sides
21 | end
22 |
23 | local fred_scenario_setup = {}
24 |
25 | function fred_scenario_setup.fred_scenario_setup()
26 | local fred_sides_table = fred_sides()
27 |
28 | if fred_sides_table[1] then
29 | -- First we set the names and ids of the leaders of all sides played by Fred
30 | -- This is done mostly for the messages
31 | for _,fred_side in pairs(fred_sides_table) do
32 | wesnoth.wml_actions.modify_unit {
33 | { 'filter', { side = fred_side, canrecruit = 'yes' } },
34 | id = 'Fred' .. fred_side,
35 | name = 'Fred Side ' .. fred_side
36 | }
37 | end
38 |
39 | -- We put this into a WML variable, so that it can easily be retrieved from replays
40 | wml.variables['AI_Demos_version'] = wesnoth.dofile('~/add-ons/AI-demos/version.lua')
41 |
42 | local version = wml.variables.AI_Demos_version
43 | version = version or '?.?.?'
44 |
45 | -- Freelands map is checked through the map size and the starting location of side 1
46 | local width, height = wesnoth.get_map_size()
47 | local start_loc = COMP.get_starting_location(1)
48 |
49 | if (width ~= 37) or (height ~= 24) or (start_loc[1] ~= 19) or ((start_loc[2] ~= 4) and (start_loc[2] ~= 20)) then
50 | wesnoth.wml_actions.message {
51 | id = 'Fred' .. fred_sides_table[1],
52 | caption = "Fred (Freelands AI v" .. version .. ")",
53 | message = "I currently only know how to play the Freelands map. Sorry!"
54 | }
55 | wesnoth.wml_actions.endlevel { result = 'victory' }
56 | return
57 | end
58 |
59 | -- Turn off fog and shroud if set
60 | local fog_set = false
61 | for _,side_info in ipairs(wesnoth.sides) do
62 | if side_info.fog or side_info.shroud then
63 | fog_set = true
64 | end
65 | end
66 | if fog_set then
67 | wesnoth.wml_actions.message {
68 | id = 'Fred' .. fred_sides_table[1],
69 | message = "I'm noticing that you have fog/shroud turned on. I'm turning it off in order to help with testing."
70 | }
71 | wesnoth.wml_actions.modify_side {
72 | side = '1,2',
73 | fog = false,
74 | shroud = false
75 | }
76 | end
77 |
78 | -- Set the message and easter egg events
79 | wesnoth.require "~/add-ons/AI-demos/lua/fred_scenario_events.lua"
80 |
81 | -- Set the behavior display menu options
82 | wesnoth.wml_actions.set_menu_item {
83 | id = 'm08_show_behavior',
84 | description = "Toggle Fred's behavior analysis display",
85 | image = 'items/ring-white.png~CROP(26,26,20,20)',
86 | { 'show_if', {
87 | { 'lua', {
88 | code = "return wesnoth.game_config.debug"
89 | } },
90 | } },
91 | { 'command', {
92 | { 'lua', {
93 | code = "local options = { 'off', 'start turn instructions only', 'move instructions (text)', 'move instructions (map)', 'move instructions (text & map)' }"
94 | .. " local fred_show_behavior = wml.variables.fred_show_behavior or 1"
95 | .. " fred_show_behavior = (fred_show_behavior % #options) + 1"
96 | .. " wml.variables.fred_show_behavior = fred_show_behavior"
97 | .. " local str = 'Show behavior now set to ' .. fred_show_behavior .. ': ' .. options[fred_show_behavior] .. ' (press shift-b to change)'"
98 | .. " wesnoth.message('Fred', str)"
99 | .. " std_print(str)"
100 | } },
101 | } },
102 | { 'default_hotkey', { key = 'b', shift = 'yes' } }
103 | }
104 |
105 | wesnoth.wml_actions.set_menu_item {
106 | id = 'm09_last_behavior',
107 | description = "Fred's most recent behavior instructions",
108 | image = 'items/ring-white.png~CROP(26,26,20,20)',
109 | { 'show_if', {
110 | { 'lua', {
111 | code = "return wesnoth.game_config.debug"
112 | } },
113 | } },
114 | { 'command', {
115 | { 'lua', {
116 | code = "local fred_behavior_str = wml.variables.fred_behavior_str or 'No behavior instructions yet'"
117 | .. " wesnoth.message('Fred', fred_behavior_str)"
118 | .. " std_print(fred_behavior_str)"
119 | } },
120 | } },
121 | { 'default_hotkey', { key = 'b' } }
122 | }
123 |
124 | -- Also remove the variables and menu items at the end of the scenario.
125 | -- This is only important when playing Fred from the switchboard
126 | -- scenario in AI-demos, and going back to switchboard afterward.
127 | -- The behavior variables also need to be deleted when loading.
128 | wesnoth.add_event_handler {
129 | name = 'victory,defeat,time_over,enemies_defeated,preload',
130 | first_time_only = 'no',
131 | { 'clear_variable', { name = 'fred_behavior_str' } },
132 | { 'clear_variable', { name = 'fred_show_behavior' } }
133 | }
134 | wesnoth.add_event_handler {
135 | name = 'victory,defeat,time_over,enemies_defeated',
136 | first_time_only = 'no',
137 | { 'clear_variable', { name = 'AI_Demos_version' } },
138 | { 'clear_menu_item', { id = 'm08_show_behavior' } },
139 | { 'clear_menu_item', { id = 'm09_last_behavior' } }
140 | }
141 |
142 | ---------- CA debugging mode ----------
143 | -- This needs to be activated here once, but this preload event is set
144 | -- to be removed after one use (because of all the other stuff in it),
145 | -- so we then also set up another preload event with first_time_only=no.
146 | -- That one will fire when saves/replays get loaded.
147 | wesnoth.dofile "~add-ons/AI-demos/lua/eval_exec_CA.lua".activate_CA_debugging_mode()
148 |
149 | wesnoth.add_event_handler {
150 | name = 'preload',
151 | first_time_only = 'no',
152 |
153 | { 'lua', {
154 | code = 'wesnoth.dofile "~add-ons/AI-demos/lua/eval_exec_CA.lua".activate_CA_debugging_mode()'
155 | } }
156 | }
157 | end
158 | end
159 |
160 | return fred_scenario_setup
161 |
--------------------------------------------------------------------------------
/AI-demos/lua/fred_status.lua:
--------------------------------------------------------------------------------
1 | -- Set up (and reset) hypothetical situations on the map that can be used for
2 | -- analyses of ZoC, counter attacks etc.
3 |
4 | local H = wesnoth.require "helper"
5 | local FGM = wesnoth.require "~/add-ons/AI-demos/lua/fred_gamestate_map.lua"
6 | local FU = wesnoth.dofile "~/add-ons/AI-demos/lua/fred_utils.lua"
7 | local FAU = wesnoth.dofile "~/add-ons/AI-demos/lua/fred_attack_utils.lua"
8 | local FVS = wesnoth.dofile "~/add-ons/AI-demos/lua/fred_virtual_state.lua"
9 | local DBG = wesnoth.dofile "~/add-ons/AI-demos/lua/debug.lua"
10 | local COMP = wesnoth.require "~/add-ons/AI-demos/lua/compatibility.lua"
11 |
12 | local fred_status = {}
13 |
14 | function fred_status.is_significant_threat(unit_info, av_total_loss, max_total_loss)
15 | -- Currently this is only set up for the leader.
16 | -- TODO: generalize?
17 | --
18 | -- We only consider these leader threats, if they either
19 | -- - maximum damage reduces current HP by more than 75%
20 | -- - average damage is more than 50% of max HP
21 | -- Otherwise we assume the leader can deal with them alone
22 |
23 | local significant_threat = false
24 | if (max_total_loss >= unit_info.hitpoints * 0.75)
25 | or (av_total_loss >= unit_info.max_hitpoints * 0.5)
26 | then
27 | significant_threat = true
28 | end
29 |
30 | return significant_threat
31 | end
32 |
33 | function fred_status.unit_exposure(pos_counter_rating, neg_counter_rating)
34 | -- @pos_counter_rating needs to be a positive number (that is, as returned by
35 | -- and from the point of view of the counter attack)
36 | -- Equivalently, @neg_counter_rating needs to be negative (also as returned by the counter attack eval)
37 | -- Just done to standardize this
38 | local exposure = pos_counter_rating + neg_counter_rating / 10
39 | if (exposure < 0) then exposure = 0 end
40 | return exposure
41 | end
42 |
43 | function fred_status.check_exposures(objectives, combo, virtual_reach_maps, cfg, fred_data)
44 | -- The virtual state needs to be set up for this, but virtual_reach_maps are
45 | -- calculated here unless passed as a parameter.
46 | -- Potential TODO: skip evaluation of units/hexes that have already been shown
47 | -- to be protected. However, no expensive analysis is done for these anyway,
48 | -- they are sorted out both for reach_maps and counter attacks, so the overhead
49 | -- is likely small. Eventually can do some timing tests on this.
50 | --
51 | -- @cfg: optional parameters
52 | -- zone_id: if set, use only protect units/villages in that zone
53 | -- exclude_leader: if true, leader exposure is not checked and set to zero; this is
54 | -- to be used when the leader is part of the action to be checked
55 |
56 | local move_data = fred_data.move_data
57 | local zone_id = cfg and cfg.zone_id
58 | local exclude_leader = cfg and cfg.exclude_leader
59 |
60 | local units = {}
61 | for zid,protect_objectives in pairs(objectives.protect.zones) do
62 | if (not zone_id) or (zone_id == zid) then
63 | for _,unit in ipairs(protect_objectives.units) do
64 | units[unit.id] = { unit.x, unit.y }
65 | end
66 | end
67 | end
68 | --DBG.dbms(units, false, 'units')
69 |
70 | local villages = {}
71 | for zid,protect_objectives in pairs(objectives.protect.zones) do
72 | if (not zone_id) or (zone_id == zid) then
73 | for _,village in ipairs(protect_objectives.villages or {}) do
74 | villages[village.x * 1000 + village.y] = {
75 | loc = { village.x, village.y },
76 | exposure = village.raw_benefit
77 | }
78 | end
79 | end
80 | end
81 | --DBG.dbms(villages, false, 'villages')
82 |
83 | if (not virtual_reach_maps) then
84 | local to_unit_locs, to_locs = {}, {}
85 | if (not exclude_leader) then
86 | table.insert(to_unit_locs, objectives.leader.final)
87 | end
88 | for x,y,_ in FGM.iter(move_data.close_castles_map[wesnoth.current.side]) do
89 | table.insert(to_locs, { x, y })
90 | end
91 | for id,loc in pairs(units) do
92 | table.insert(to_unit_locs, loc)
93 | end
94 | for xy,village in pairs(villages) do
95 | table.insert(to_locs, village.loc)
96 | end
97 | --DBG.dbms(to_locs, false, 'to_locs')
98 | --DBG.dbms(to_unit_locs, false, 'to_unit_locs')
99 |
100 | virtual_reach_maps = FVS.virtual_reach_maps(move_data.enemies, to_unit_locs, to_locs, move_data)
101 | --DBG.dbms(virtual_reach_maps, false, 'virtual_reach_maps')
102 | end
103 |
104 | local cfg_attack = { value_ratio = fred_data.ops_data.behavior.orders.value_ratio }
105 |
106 | local leader_exposure, enemy_power, is_protected, is_significant_threat = 0, 0, true, false
107 | if (not exclude_leader) then
108 | local leader = move_data.my_leader
109 | local leader_target = {}
110 | leader_target[leader.id] = objectives.leader.final
111 | local counter_outcomes, _, all_attackers = FAU.calc_counter_attack(
112 | leader_target, nil, nil, nil, virtual_reach_maps, cfg_attack, fred_data
113 | )
114 | --DBG.dbms(counter_outcomes)
115 | --DBG.dbms(all_attackers)
116 |
117 | local pos_rating, neg_rating = 0, 0
118 | if counter_outcomes then
119 | is_protected = false
120 | pos_rating = counter_outcomes.rating_table.pos_rating
121 | neg_rating = counter_outcomes.rating_table.neg_rating
122 | --std_print('pos_rating, neg_rating', pos_rating, neg_rating)
123 | local leader_info = move_data.unit_infos[leader.id]
124 | is_significant_threat = fred_status.is_significant_threat(
125 | leader_info,
126 | leader_info.hitpoints - counter_outcomes.def_outcome.average_hp,
127 | leader_info.hitpoints - counter_outcomes.def_outcome.min_hp
128 | )
129 | leader_exposure = fred_status.unit_exposure(pos_rating, neg_rating)
130 | --std_print('leader_exposure', leader_exposure)
131 | for enemy_id,_ in pairs(all_attackers) do
132 | -- TODO: not 100% sure yet whether we should use only the zone enemies,
133 | -- or all enemies, or both for different purposes
134 | --if (not zone_id)
135 | -- or (not fred_data.ops_data.assigned_enemies[zone_id])
136 | -- or (fred_data.ops_data.assigned_enemies[zone_id][enemy_id])
137 | --then
138 | enemy_power = enemy_power + move_data.unit_infos[enemy_id].current_power
139 | --end
140 | end
141 | end
142 | --std_print('ratings: ' .. pos_rating, neg_rating, is_significant_threat)
143 | end
144 |
145 | local status = { leader = {
146 | exposure = leader_exposure,
147 | is_protected = is_protected,
148 | is_significant_threat = is_significant_threat,
149 | enemy_power = enemy_power
150 | } }
151 |
152 |
153 | local n_castles_threatened, castle_hexes = 0, {}
154 | for x,y,_ in FGM.iter(move_data.close_castles_map[wesnoth.current.side]) do
155 | --std_print('castle: ', x .. ',' .. y)
156 | for enemy_id,_ in pairs(move_data.enemies) do
157 | --DBG.show_fgm_with_message(virtual_reach_maps[enemy_id], 'moves_left', 'virtual_reach_map', move_data.unit_copies[enemy_id])
158 | -- enemies with 0 HP are not in virtual_reach_maps
159 | if virtual_reach_maps[enemy_id] and FGM.get_value(virtual_reach_maps[enemy_id], x, y, 'moves_left') then
160 | n_castles_threatened = n_castles_threatened + 1
161 | table.insert(castle_hexes, { x, y })
162 | break
163 | end
164 | end
165 | end
166 | --std_print('n_castles_threatened: ' .. n_castles_threatened)
167 | status.castles = {
168 | n_threatened = n_castles_threatened ,
169 | exposure = 3 * math.sqrt(n_castles_threatened),
170 | locs = castle_hexes
171 | }
172 |
173 |
174 | status.villages = {}
175 | for xy,village in pairs(villages) do
176 | --std_print(' check protection of village: ' .. village.loc[1] .. ',' .. village.loc[2])
177 | local is_protected, exposure = true, 0
178 | for enemy_id,_ in pairs(move_data.enemies) do
179 | local moves_left = virtual_reach_maps[enemy_id] and FGM.get_value(virtual_reach_maps[enemy_id], village.loc[1], village.loc[2], 'moves_left')
180 | if moves_left then
181 | --std_print(' can reach this: ' .. enemy_id, moves_left)
182 | is_protected = false
183 | exposure = village.exposure
184 | break
185 | end
186 | end
187 | --std_print(' is_protected: ', is_protected)
188 | status.villages[xy] = {
189 | is_protected = is_protected,
190 | exposure = exposure
191 | }
192 | end
193 |
194 |
195 | status.units = {}
196 | for id,loc in pairs(units) do
197 | local target = {}
198 | target[id] = loc
199 | local counter_outcomes = FAU.calc_counter_attack(
200 | target, nil, nil, nil, virtual_reach_maps, cfg_attack, fred_data
201 | )
202 | local exposure, is_protected = 0, true
203 | if counter_outcomes then
204 | --DBG.dbms(counter_outcomes.rating_table, false, 'counter_outcomes.rating_table')
205 | is_protected = false
206 | exposure = fred_status.unit_exposure(counter_outcomes.rating_table.pos_rating, counter_outcomes.rating_table.neg_rating)
207 | end
208 | status.units[id] = {
209 | exposure = exposure,
210 | is_protected = is_protected
211 | }
212 | end
213 |
214 |
215 | -- Add a terrain bonus if the combo hexes have better terrain defense for the enemies
216 | -- than those adjacent to the combo hexes that the enemies can reach with the combo in place
217 | -- Potential TODOs:
218 | -- - The value of status.terrain does not mean anything. Currently it is only used to
219 | -- determine whether good terrain is taken away from the enemies, so it's essentially used as a boolean
220 | -- - We add up all terrain (dis)advantages for all enemy units on the best adjacent hexes they
221 | -- can get to, even if there are more enemies than hexes
222 | -- - We might want to add something more sophisticated, indicating how useful the protected
223 | -- terrain is or something. It's also only a single-hex (plus adjacents) analysis for now.
224 | -- Might want to consider all hexes not reachable for the enemies any more.
225 | local unit_values = {}
226 | status.terrain = 0
227 | if combo then
228 | for src,dst in pairs(combo) do
229 | local dst_x, dst_y = math.floor(dst / 1000), dst % 1000
230 | --std_print(dst_x, dst_y)
231 | for enemy_id,_ in pairs(move_data.enemies) do
232 | --std_print(enemy_id)
233 | if FGM.get_value(move_data.reach_maps[enemy_id], dst_x, dst_y, 'moves_left') then
234 | local defense = 100 - COMP.unit_defense(move_data.unit_copies[enemy_id], wesnoth.get_terrain(dst_x, dst_y))
235 | --std_print(' ' .. dst_x .. ',' .. dst_y .. ': ' .. enemy_id , defense)
236 |
237 | local max_adj_defense = - math.huge
238 | for xa,ya in H.adjacent_tiles(dst_x, dst_y) do
239 | if FGM.get_value(virtual_reach_maps[enemy_id], xa, ya, 'moves_left') then
240 | local adj_defense = 100 - COMP.unit_defense(move_data.unit_copies[enemy_id], wesnoth.get_terrain(xa, ya))
241 | --std_print(' can reach adj: ' .. xa .. ',' .. ya, adj_defense)
242 | if (adj_defense > max_adj_defense) then
243 | max_adj_defense = adj_defense
244 | end
245 | end
246 | end
247 | --std_print(' defense, adj_defense: ', defense, max_adj_defense)
248 |
249 | if (defense > max_adj_defense) then
250 | local unit_value = unit_values[enemy_id]
251 | if not unit_value then
252 | unit_value = move_data.unit_infos[enemy_id].unit_value
253 | --std_print(' value: ' .. enemy_id, unit_value)
254 | unit_values[enemy_id] = unit_value
255 | end
256 |
257 | local terrain_bonus = (defense - max_adj_defense) / 100 * unit_value
258 | status.terrain = status.terrain + terrain_bonus
259 | end
260 | end
261 | end
262 | end
263 | end
264 | --std_print('terrain advantage: ' .. status.terrain)
265 |
266 | return status
267 | end
268 |
269 | return fred_status
270 |
--------------------------------------------------------------------------------
/AI-demos/lua/fred_village_utils.lua:
--------------------------------------------------------------------------------
1 | local FU = wesnoth.dofile "~/add-ons/AI-demos/lua/fred_utils.lua"
2 | local FGM = wesnoth.require "~/add-ons/AI-demos/lua/fred_gamestate_map.lua"
3 | local FDI = wesnoth.require "~/add-ons/AI-demos/lua/fred_data_incremental.lua"
4 | local FCFG = wesnoth.dofile "~/add-ons/AI-demos/lua/fred_config.lua"
5 | local FAU = wesnoth.dofile "~/add-ons/AI-demos/lua/fred_attack_utils.lua"
6 | local FBU = wesnoth.dofile "~/add-ons/AI-demos/lua/fred_benefits_utilities.lua"
7 | local DBG = wesnoth.dofile "~/add-ons/AI-demos/lua/debug.lua"
8 | local COMP = wesnoth.require "~/add-ons/AI-demos/lua/compatibility.lua"
9 |
10 | local fred_village_utils = {}
11 |
12 | function fred_village_utils.village_objectives(zone_cfgs, side_cfgs, zone_maps, fred_data)
13 | local move_data = fred_data.move_data
14 |
15 | local village_objectives, villages_to_grab = { zones = {} }, {}
16 | for x,y,_ in FGM.iter(move_data.village_map) do
17 | local village_zone
18 | -- Note that we iterate over @zone_cfgs here, as @zone_maps can also contain maps for other zones
19 | for zone_id,_ in pairs(zone_cfgs) do
20 | if FGM.get_value(zone_maps[zone_id], x, y, 'in_zone') then
21 | village_zone = zone_id
22 | break
23 | end
24 | end
25 | if (not village_zone) then village_zone = 'other' end
26 |
27 | local eld_vill = wesnoth.map.distance_between(x, y, fred_data.move_data.enemy_leader[1], fred_data.move_data.enemy_leader[2])
28 |
29 | local my_infl = FGM.get_value(move_data.influence_map, x, y, 'my_influence') or 0
30 | local enemy_infl = FGM.get_value(move_data.influence_map, x, y, 'enemy_influence') or 0
31 | local infl_ratio = my_infl / (enemy_infl + 1e-6)
32 |
33 | local owner = FGM.get_value(move_data.village_map, x, y, 'owner')
34 |
35 | -- Only add village protect objective if AI units can actually protect it.
36 | -- Even if the influence is positive there might be an enemy unit on the village.
37 | if (not FGM.get_value(fred_data.move_data.enemy_map, x, y, 'id'))
38 | and (infl_ratio >= fred_data.ops_data.behavior.orders.value_ratio)
39 | then
40 | local is_threatened = false
41 | for enemy_id,enemy_loc in pairs(move_data.enemies) do
42 | if FGM.get_value(move_data.reach_maps[enemy_id], x, y, 'moves_left') then
43 | is_threatened = true
44 |
45 | if (not village_objectives.zones[village_zone]) then
46 | village_objectives.zones[village_zone] = { villages = {}, enemies = {} }
47 | end
48 |
49 | village_objectives.zones[village_zone].enemies[enemy_id] = enemy_loc[1] * 1000 + enemy_loc[2]
50 | end
51 | end
52 |
53 | if is_threatened then
54 | -- TODO: this is currently duplicated in the benefits functions
55 | local raw_benefit = 3
56 | if (owner ~= wesnoth.current.side) and (owner ~= 0) then
57 | raw_benefit = raw_benefit * 2
58 | end
59 | local v = {
60 | x = x, y = y,
61 | type = 'village',
62 | eld = eld_vill,
63 | raw_benefit = raw_benefit
64 | }
65 | table.insert(village_objectives.zones[village_zone].villages, v)
66 | end
67 | end
68 |
69 | if (owner ~= wesnoth.current.side) then
70 | table.insert(villages_to_grab, {
71 | x = x, y = y,
72 | owner = owner,
73 | zone_id = village_zone
74 | })
75 | end
76 | end
77 |
78 | for _,objectives in pairs(village_objectives.zones) do
79 | table.sort(objectives.villages, function(a, b) return a.eld < b.eld end)
80 | end
81 |
82 | return village_objectives, villages_to_grab
83 | end
84 |
85 |
86 | function fred_village_utils.village_grabs(villages_to_grab, reserved_actions, interactions, fred_data)
87 | local move_data = fred_data.move_data
88 | local value_ratio = fred_data.ops_data.behavior.orders.value_ratio
89 | local cfg_attack = { value_ratio = value_ratio }
90 |
91 | -- Units with MP need to be taken off the map, for counter attack calculation
92 | local extracted_units = {}
93 | for id,loc in pairs(move_data.my_units_MP) do
94 | local unit_proxy = COMP.get_unit(loc[1], loc[2])
95 | COMP.extract_unit(unit_proxy)
96 | table.insert(extracted_units, unit_proxy) -- Not a proxy unit any more at this point
97 | end
98 |
99 | local village_grabs = {}
100 | for _,village in pairs(villages_to_grab) do
101 | local x, y = village.x, village.y
102 | --std_print('village at ' .. x .. ',' .. y)
103 |
104 | -- Exclude reserved locations here (and units below) without checking for
105 | -- combined unit/hex availability, as we do not want to enter those units
106 | -- and villages into the pool for grabbable villages
107 | local penalty_loc = FBU.action_penalty({ { loc = { x, y } } }, reserved_actions, interactions, move_data)
108 | local ids = {}
109 | if (penalty_loc == 0) then
110 | ids = FGM.get_value(move_data.my_move_map[1], x, y, 'eff_reach_ids') or {}
111 | end
112 |
113 | local influence = FGM.get_value(move_data.influence_map, x, y, 'full_move_influence') or 0
114 | local my_number = FGM.get_value(move_data.influence_map, x, y, 'my_number') or 0
115 | local enemy_number = FGM.get_value(move_data.influence_map, x, y, 'enemy_number') or 0
116 | --std_print(' influence, numbers: ' .. influence, my_number, enemy_number)
117 |
118 | for _,id in pairs(ids) do
119 | local loc = move_data.my_units[id]
120 |
121 | local penalty_unit = FBU.action_penalty({ { id = id } }, reserved_actions, interactions, move_data)
122 | if (penalty_unit == 0) then
123 | local target = {}
124 | target[id] = { x, y }
125 |
126 | -- TODO: do we take prerecruits into account here already?
127 | local counter_outcomes = FAU.calc_counter_attack(
128 | target, { loc }, { { x, y } }, nil, nil, cfg_attack, fred_data
129 | )
130 | --if counter_outcomes then DBG.dbms(counter_outcomes.def_outcome, false, 'counter_outcomes.def_outcome') end
131 |
132 | local allow_grab = true
133 | local die_chance = 0
134 | local counter_rating
135 | if counter_outcomes then
136 | die_chance = counter_outcomes.def_outcome.hp_chance[0]
137 | counter_rating = counter_outcomes.rating_table.rating
138 | local value_loss = counter_outcomes.rating_table.pos_rating + counter_outcomes.rating_table.neg_rating
139 | --std_print(' ' .. id, die_chance, value_loss)
140 |
141 | if move_data.unit_infos[id].canrecruit then
142 | local min_hp = move_data.unit_infos[id].max_hitpoints
143 | for hp,chance in pairs(counter_outcomes.def_outcome.hp_chance) do
144 | --std_print(hp,chance)
145 | if (chance > 0) and (hp < min_hp) then
146 | min_hp = hp
147 | end
148 | end
149 | if (min_hp < move_data.unit_infos[id].max_hitpoints / 2) then
150 | allow_grab = false
151 | end
152 | --std_print(id, min_hp, move_data.unit_infos[id].max_hitpoints, allow_grab)
153 | else
154 | local xp_mult = FU.weight_s(move_data.unit_infos[id].experience / move_data.unit_infos[id].max_experience, 0.5)
155 | local level = move_data.unit_infos[id].level
156 | -- TODO: this equation needs to be tweaked
157 | local die_chance_threshold = (0.5 + 0.5 * (1 - value_ratio)) * (1 - xp_mult) / level^2
158 |
159 | local value_loss_threshold = math.huge
160 |
161 | if (my_number < 3) and (my_number < enemy_number) and (influence < 0) then
162 | --std_print(' *** dangerous village')
163 | die_chance_threshold = 0
164 | value_loss_threshold = 6
165 | end
166 |
167 | if (die_chance > die_chance_threshold) or (value_loss > value_loss_threshold) then
168 | allow_grab = false
169 | end
170 | --std_print(' ' .. id, value_ratio, xp_mult, level, die_chance_threshold, allow_grab)
171 | end
172 | end
173 |
174 | if allow_grab then
175 | local grabber = {
176 | x = x, y = y,
177 | id = id,
178 | die_chance = die_chance,
179 | counter_rating = counter_rating,
180 | zone_id = village.zone_id
181 | }
182 | table.insert(village_grabs, grabber)
183 | end
184 | end
185 | end
186 | end
187 |
188 | -- Put the units back on the map
189 | for _,extracted_unit in ipairs(extracted_units) do COMP.put_unit(extracted_unit) end
190 |
191 | return village_grabs
192 | end
193 |
194 |
195 | function fred_village_utils.assign_scouts(villages_to_grab, unused_units, assigned_units, move_data)
196 | -- Potential TODOs:
197 | -- - Add threat assessment for scout routes; it might in fact make sense to use
198 | -- injured units to scout in unthreatened areas
199 | -- - Actually assigning scouting actions
200 | -- Find how many units are needed in each zone for moving toward villages ('exploring')
201 | local units_needed_villages, zone_villages = {}, {}
202 | local villages_per_unit = FCFG.get_cfg_parm('villages_per_unit')
203 | for _,village in pairs(villages_to_grab) do
204 | if (not units_needed_villages[village.zone_id]) then
205 | units_needed_villages[village.zone_id] = 0
206 | zone_villages[village.zone_id] = {}
207 | end
208 | units_needed_villages[village.zone_id] = units_needed_villages[village.zone_id] + 1 / villages_per_unit
209 | table.insert(zone_villages[village.zone_id], { x = village.x, y = village.y })
210 | end
211 | --DBG.dbms(units_needed_villages, false, 'units_needed_villages')
212 | --DBG.dbms(zone_villages, false, 'zone_villages')
213 |
214 | local units_assigned_villages = {}
215 | local used_ids = {}
216 | for zone_id,units in pairs(assigned_units) do
217 | for id,_ in pairs(units) do
218 | units_assigned_villages[zone_id] = (units_assigned_villages[zone_id] or 0) + 1
219 | used_ids[id] = true
220 | end
221 | end
222 | --DBG.dbms(units_assigned_villages, false, 'units_assigned_villages')
223 |
224 |
225 | -- Check out what other units to send in this direction
226 | local scouts = {}
227 | for zone_id,units_needed in pairs(units_needed_villages) do
228 | if (units_needed > (units_assigned_villages[zone_id] or 0)) then
229 | --std_print(zone_id, units_needed)
230 | scouts[zone_id] = {}
231 | for _,village in ipairs(zone_villages[zone_id]) do
232 | -- TODO: 'grab_only' is currently not set and the 'if' below is always true.
233 | -- Keep for now anyway, as we might want to reactivate this
234 | if (not village.grab_only) then
235 | --std_print(' ' .. village.x, village.y)
236 | for id,_ in pairs(unused_units) do
237 | --std_print(' ' .. id)
238 | -- The leader is always excluded here, plus any unit that has already been assigned
239 | -- TODO: set up an array of unassigned units?
240 | if (not move_data.unit_infos[id].canrecruit) and (not used_ids[id]) then
241 | local _, cost = wesnoth.find_path(move_data.unit_copies[id], village.x, village.y)
242 | cost = cost + move_data.unit_infos[id].max_moves - move_data.unit_infos[id].moves
243 | --std_print(' ' .. id, cost)
244 | local _, cost_ign = wesnoth.find_path(move_data.unit_copies[id], village.x, village.y, { ignore_units = true })
245 | cost_ign = cost_ign + move_data.unit_infos[id].max_moves - move_data.unit_infos[id].moves
246 |
247 | local unit_rating = - cost / #zone_villages[zone_id] / move_data.unit_infos[id].max_moves
248 |
249 | --TODO: retreaters are assigned earlier now, but keep code for the time being, just in case
250 | --[[
251 | -- Scout utility to compare to retreat utility
252 | local int_turns = math.ceil(cost / move_data.unit_infos[id].max_moves)
253 | local int_turns_ign = math.ceil(cost_ign / move_data.unit_infos[id].max_moves)
254 | local scout_utility = math.sqrt(1 / math.max(1, int_turns - 1))
255 | scout_utility = scout_utility * int_turns_ign / int_turns
256 | --]]
257 |
258 | if scouts[zone_id][id] then
259 | unit_rating = unit_rating + scouts[zone_id][id].rating
260 | end
261 | scouts[zone_id][id] = { rating = unit_rating }
262 |
263 | --if (not scouts[zone_id][id].utility) or (scout_utiltiy > scouts[zone_id][id].utility) then
264 | -- scouts[zone_id][id].utility = scout_utility
265 | --end
266 | end
267 | end
268 | end
269 | end
270 | end
271 | end
272 | --DBG.dbms(scouts, false, 'scouts')
273 |
274 | local sorted_scouts = {}
275 | for zone_id,units in pairs(scouts) do
276 | for id,data in pairs(units) do
277 | --if (data.utility > retreat_utilities[id]) then
278 | if (not sorted_scouts[zone_id]) then
279 | sorted_scouts[zone_id] = {}
280 | end
281 |
282 | table.insert(sorted_scouts[zone_id], {
283 | id = id,
284 | rating = data.rating,
285 | org_rating = data.rating
286 | })
287 | --else
288 | --std_print('needs to retreat instead:', id)
289 | --end
290 | end
291 | if sorted_scouts[zone_id] then
292 | table.sort(sorted_scouts[zone_id], function(a, b) return a.rating > b.rating end)
293 | end
294 | end
295 | --DBG.dbms(sorted_scouts, false, 'sorted_scouts')
296 |
297 | local keep_trying = true
298 | local zone_id,units = next(sorted_scouts)
299 | if (not zone_id) or (#units == 0) then
300 | keep_trying = false
301 | end
302 |
303 | local scout_assignments = {}
304 | while keep_trying do
305 | keep_trying = false
306 |
307 | -- Set rating relative to the second highest rating in each zone
308 | -- This is
309 | --
310 | -- Notes:
311 | -- - If only one units is left, we use the original rating
312 | -- - This is unnecessary when only one zone is left, but it works then too,
313 | -- so we'll just keep it rather than adding yet another conditional
314 | for zone_id,units in pairs(sorted_scouts) do
315 | if (#units > 1) then
316 | local second_rating = units[2].rating
317 | for _,scout in pairs(units) do
318 | scout.rating = scout.rating - second_rating
319 | end
320 | else
321 | units[1].rating = units[1].org_rating
322 | end
323 | end
324 |
325 | local max_rating, best_id, best_zone = - math.huge
326 | for zone_id,units in pairs(sorted_scouts) do
327 | local rating = sorted_scouts[zone_id][1].rating
328 |
329 | if (rating > max_rating) then
330 | max_rating = rating
331 | best_id = sorted_scouts[zone_id][1].id
332 | best_zone = zone_id
333 | end
334 | end
335 | --std_print('best:', best_zone, best_id)
336 |
337 | for zone_id,units in pairs(sorted_scouts) do
338 | for i_u,data in ipairs(units) do
339 | if (data.id == best_id) then
340 | table.remove(units, i_u)
341 | break
342 | end
343 | end
344 | end
345 | for zone_id,units in pairs(sorted_scouts) do
346 | if (#units == 0) then
347 | sorted_scouts[zone_id] = nil
348 | end
349 | end
350 |
351 | scout_assignments[best_id] = 'scout:' .. best_zone
352 |
353 | units_assigned_villages[best_zone] = (units_assigned_villages[best_zone] or 0) + 1
354 |
355 | for zone_id,n_needed in pairs(units_needed_villages) do
356 | if (n_needed <= (units_assigned_villages[zone_id] or 0)) then
357 | sorted_scouts[zone_id] = nil
358 | end
359 | end
360 |
361 | -- Check whether we are done
362 | local zone_id,units = next(sorted_scouts)
363 | if zone_id and (#units > 0) then
364 | keep_trying = true
365 | end
366 | end
367 |
368 | return scout_assignments
369 | end
370 |
371 |
372 | return fred_village_utils
373 |
--------------------------------------------------------------------------------
/AI-demos/lua/fred_virtual_state.lua:
--------------------------------------------------------------------------------
1 | -- Set up (and reset) hypothetical situations on the map that can be used for
2 | -- analyses of ZoC, counter attacks etc.
3 |
4 | local H = wesnoth.require "helper"
5 | local FGM = wesnoth.require "~/add-ons/AI-demos/lua/fred_gamestate_map.lua"
6 | local FU = wesnoth.dofile "~/add-ons/AI-demos/lua/fred_utils.lua"
7 | local DBG = wesnoth.dofile "~/add-ons/AI-demos/lua/debug.lua"
8 | local COMP = wesnoth.require "~/add-ons/AI-demos/lua/compatibility.lua"
9 |
10 |
11 | -- Two arrays to be made available below via closure
12 | local FVS_stored_units, FVS_ids, FVS_add_units, FVS_extracted_units
13 |
14 |
15 | ----- Begin adjust_move_data_tables() -----
16 | local function adjust_move_data_tables(old_locs, new_locs, store_units_in_way, move_data, stored_data)
17 | -- Adjust all the move_data tables to the new position, and reset them again later.
18 | -- This is a local function as only the virtual state calculations should have to move units.
19 | --
20 | -- INPUTS:
21 | -- @old_locs, @new_locs as above
22 | -- @store_units_in_way (boolean): whether to store the locations of the units in the way. Needs to
23 | -- be set to 'true' when moving the units into their new locations, needs to be set to
24 | -- 'false' when moving them back, in which case the stored information will be used.
25 | --
26 | -- OPTIONAL INPUTS:
27 | -- @stored_data: table for temporary storage of units and ids. This is usually done automatically
28 | -- in the FVS_* variables via closure, but that does not work with nested uses of these functions.
29 | --
30 | -- Output:
31 | -- Returns nil if no counter attacks were found, otherwise a table with a
32 | -- variety of attack outcomes and ratings
33 |
34 | -- If any of the hexes marked in @new_locs is occupied, we
35 | -- need to store that information as it otherwise will be overwritten.
36 | -- This needs to be done for all locations before any unit is moved
37 | -- in order to avoid conflicts of the units to be moved among themselves
38 | if store_units_in_way then
39 | for i_l,old_loc in ipairs(old_locs) do
40 | local x1, y1 = old_loc[1], old_loc[2]
41 | local x2, y2 = new_locs[i_l][1], new_locs[i_l][2]
42 |
43 | -- Store the ids of the units to be put onto the map.
44 | -- This includes units with MP that attack from their current position,
45 | -- but not units without MP (as the latter are already on the map)
46 | -- Note: this array might have missing elements -> needs to be iterated using pairs()
47 | local id = FGM.get_value(move_data.my_unit_map_MP, x1, y1, 'id')
48 | if id then
49 | if stored_data then
50 | stored_data.FVS_ids[i_l] = id
51 | else
52 | FVS_ids[i_l] = id
53 | end
54 | end
55 |
56 | -- By contrast, we only need to store the information about units in the way,
57 | -- if a unit actually gets moved to the hex (independent of whether it has MP left or not)
58 | if (x1 ~= x2) or (y1 ~= y2) then
59 | -- If there is another unit at the new location, store it
60 | -- It does not matter for this whether this is a unit involved in the move or not
61 | local id2 = FGM.get_value(move_data.my_unit_map, x2, y2, 'id')
62 | if id2 then
63 | if stored_data then
64 | stored_data.FVS_stored_units[id2] = { x2, y2 }
65 | else
66 | FVS_stored_units[id2] = { x2, y2 }
67 | end
68 | end
69 | end
70 | end
71 | end
72 |
73 | -- Now adjust all the move_data tables
74 | for i_l,old_loc in ipairs(old_locs) do
75 | local x1, y1 = old_loc[1], old_loc[2]
76 | local x2, y2 = new_locs[i_l][1], new_locs[i_l][2]
77 | --std_print('Moving unit:', x1, y1, '-->', x2, y2)
78 |
79 | -- We only need to do this if the unit actually gets moved
80 | if (x1 ~= x2) or (y1 ~= y2) then
81 | local id = stored_data and stored_data.FVS_ids[i_l] or FVS_ids[i_l]
82 |
83 | -- Likely, not all of these tables have to be changed, but it takes
84 | -- very little time, so better safe than sorry
85 | move_data.unit_copies[id].x, move_data.unit_copies[id].y = x2, y2
86 |
87 | move_data.units[id] = { x2, y2 }
88 | move_data.my_units[id] = { x2, y2 }
89 | move_data.my_units_MP[id] = { x2, y2 }
90 |
91 | if move_data.unit_infos[id].canrecruit then
92 | end
93 |
94 | -- Note that the following might leave empty orphan table elements, but that doesn't matter
95 | move_data.my_unit_map[x1][y1] = nil
96 | FGM.set_value(move_data.my_unit_map, x2, y2, 'id', id)
97 | move_data.my_unit_map_MP[x1][y1] = nil
98 | FGM.set_value(move_data.my_unit_map_MP, x2, y2, 'id', id)
99 | end
100 | end
101 |
102 | -- Finally, if 'store_units_in_way' is not set (this is, when moving units back
103 | -- into place), restore the stored units into the maps again
104 | if (not store_units_in_way) then
105 | for id,loc in pairs(stored_data and stored_data.FVS_stored_units or FVS_stored_units) do
106 | FGM.set_value(move_data.my_unit_map, loc[1], loc[2], 'id', id)
107 | FGM.set_value(move_data.my_unit_map_MP, loc[1], loc[2], 'id', id)
108 | end
109 | end
110 | end
111 | ----- End adjust_move_data_tables() -----
112 |
113 |
114 | local fred_virtual_state = {}
115 |
116 | function fred_virtual_state.set_virtual_state(old_locs, new_locs, additional_units, do_extract_units, move_data, stored_data)
117 |
118 | if stored_data then
119 | stored_data.FVS_stored_units, stored_data.FVS_ids, stored_data.FVS_add_units, stored_data.extracted_units = {}, {}, {}, {}
120 | else
121 | FVS_stored_units, FVS_ids, FVS_add_units, FVS_extracted_units = {}, {}, {}, {}
122 | end
123 |
124 | if do_extract_units then
125 | for id,loc in pairs(move_data.my_units_MP) do
126 | local unit_proxy = COMP.get_unit(loc[1], loc[2])
127 | COMP.extract_unit(unit_proxy)
128 | table.insert(stored_data and stored_data.extracted_units or FVS_extracted_units, unit_proxy) -- Not a proxy unit any more at this point
129 | end
130 | end
131 |
132 | -- Mark the new positions of the units in the move_data tables
133 | adjust_move_data_tables(old_locs, new_locs, true, move_data, stored_data)
134 |
135 | -- Put all units in old_locs with MP onto the map (those without are already there)
136 | -- They need to be proxy units for the counter attack calculation.
137 | for _,id in pairs(stored_data and stored_data.FVS_ids or FVS_ids) do
138 | COMP.put_unit(move_data.unit_copies[id])
139 | end
140 |
141 | -- Also put the additonal units out there
142 | if additional_units then
143 | for _,add_unit in ipairs(additional_units) do
144 | local place_unit = true
145 | for _,new_loc in ipairs(new_locs) do
146 | if (add_unit[1] == new_loc[1]) and (add_unit[2] == new_loc[2]) then
147 | place_unit = false
148 | break
149 | end
150 | end
151 | if place_unit then
152 | if stored_data then
153 | table.insert(stored_data.FVS_add_units, add_unit)
154 | else
155 | table.insert(FVS_add_units, add_unit)
156 | end
157 |
158 | COMP.put_unit({
159 | type = add_unit.type,
160 | random_traits = false,
161 | name = "X",
162 | random_gender = false,
163 | moves = 0
164 | },
165 | add_unit[1], add_unit[2]
166 | )
167 | end
168 | end
169 | end
170 | end
171 |
172 | function fred_virtual_state.reset_state(old_locs, new_locs, do_extract_units, move_data, stored_data)
173 | -- Extract the units from the map
174 | for _,add_unit in ipairs(stored_data and stored_data.FVS_add_units or FVS_add_units) do
175 | COMP.erase_unit(add_unit[1], add_unit[2])
176 | end
177 | for _,id in pairs(stored_data and stored_data.FVS_ids or FVS_ids) do
178 | COMP.extract_unit(move_data.unit_copies[id])
179 | end
180 |
181 | -- And put them back into their original locations
182 | adjust_move_data_tables(new_locs, old_locs, false, move_data, stored_data)
183 |
184 | -- Put the extracted units back on the map
185 | if do_extract_units then
186 | for _,extracted_unit in ipairs(stored_data and stored_data.extracted_units or FVS_extracted_units) do
187 | COMP.put_unit(extracted_unit)
188 | end
189 | end
190 |
191 | if stored_data then
192 | stored_data.FVS_stored_units, stored_data.FVS_ids, stored_data.FVS_add_units, stored_data.extracted_units = nil, nil, nil, nil
193 | else
194 | FVS_stored_units, FVS_ids, FVS_add_units, FVS_extracted_units = nil, nil, nil, nil
195 | end
196 | end
197 |
198 | function fred_virtual_state.virtual_reach_maps(units, to_enemy_locs, to_locs, move_data)
199 | -- Find new reach_maps of @units in a (virtual) situation that has been set up on the map
200 | -- This is only done for units which could previously attack @to_enemies or move onto @to_hexes.
201 | -- The point of this is to test if placing AI units in certain positions changes where
202 | -- enemies can attack or reach.
203 | -- Note that this means that either @to_enemy_locs or @to_locs must be given,
204 | -- otherwise this function will always return empty reachmaps.
205 | --
206 | -- TODO: this could (should?) eventually also include placing the units in the new positions
207 |
208 | reach_maps = {}
209 | for unit_id,_ in pairs(units) do
210 | reach_maps[unit_id] = {}
211 |
212 | -- Only calculate reach if the unit could get there using its
213 | -- original reach. It cannot have gotten any better than this.
214 | local can_reach = false
215 | if to_enemy_locs then
216 | for _,enemy_loc in ipairs(to_enemy_locs) do
217 | for xa,ya in H.adjacent_tiles(enemy_loc[1], enemy_loc[2]) do
218 | if FGM.get_value(move_data.reach_maps[unit_id], xa, ya, 'moves_left') then
219 | can_reach = true
220 | break
221 | end
222 | end
223 | if can_reach then break end
224 | end
225 | end
226 | if (not can_reach) and to_locs then
227 | for _,loc in ipairs(to_locs) do
228 | if FGM.get_value(move_data.reach_maps[unit_id], loc[1], loc[2], 'moves_left') then
229 | can_reach = true
230 | break
231 | end
232 | end
233 | end
234 |
235 | if can_reach then
236 | -- For sides other than the current, we always use max_moves.
237 | -- For the current side, we always use current moves.
238 | local old_moves
239 | if (move_data.unit_infos[unit_id].side ~= wesnoth.current.side) then
240 | old_moves = move_data.unit_copies[unit_id].moves
241 | move_data.unit_copies[unit_id].moves = move_data.unit_copies[unit_id].max_moves
242 | end
243 |
244 | local reach = wesnoth.find_reach(move_data.unit_copies[unit_id])
245 |
246 | for _,r in ipairs(reach) do
247 | FGM.set_value(reach_maps[unit_id], r[1], r[2], 'moves_left', r[3])
248 | end
249 |
250 | if (move_data.unit_infos[unit_id].side ~= wesnoth.current.side) then
251 | move_data.unit_copies[unit_id].moves = old_moves
252 | end
253 | end
254 | end
255 |
256 | -- Eliminate hexes with other units that cannot move out of the way
257 | for id,reach_map in pairs(reach_maps) do
258 | for id_noMP,loc in pairs(move_data.my_units_noMP) do
259 | if (id ~= id_noMP) then
260 | if reach_map[loc[1]] then reach_map[loc[1]][loc[2]] = nil end
261 | end
262 | end
263 | end
264 |
265 | return reach_maps
266 | end
267 |
268 | return fred_virtual_state
269 |
--------------------------------------------------------------------------------
/AI-demos/lua/manual_input.lua:
--------------------------------------------------------------------------------
1 | -- Edit the information below to make the AI move the unit
2 | return 'challenger', 19, 21
3 |
--------------------------------------------------------------------------------
/AI-demos/lua/test_lua_template.lua:
--------------------------------------------------------------------------------
1 | -- This is the manual 'AI handler'
2 | -- Want this in a separate file, so that it can be changed on the fly
3 | -- It loads the AI from another file which can then be executed by right-click menu
4 |
5 | -- Start test scenario from command line with
6 | -- /Applications/Wesnoth-1.11.app/Contents/MacOS/Wesnoth -d -l AI-demos-test.gz
7 |
8 | -- Include all these, just in case (mostly not needed)
9 | local H = wesnoth.require "helper"
10 | local AH = wesnoth.require "ai/lua/ai_helper.lua"
11 | local MAIH = wesnoth.require "ai/micro_ais/micro_ai_helper.lua"
12 | local MAIUV = wesnoth.require "ai/micro_ais/micro_ai_unit_variables.lua"
13 | local MAISD = wesnoth.require "ai/micro_ais/micro_ai_self_data.lua"
14 | local BC = wesnoth.require "ai/lua/battle_calcs.lua"
15 | local FGU = wesnoth.dofile "~/add-ons/AI-demos/lua/fred_gamestate_utils.lua"
16 | local FGUI = wesnoth.dofile "~/add-ons/AI-demos/lua/fred_gamestate_utils_incremental.lua"
17 | local FU = wesnoth.dofile "~/add-ons/AI-demos/lua/fred_utils.lua"
18 | local FAU = wesnoth.dofile "~/add-ons/AI-demos/lua/fred_attack_utils.lua"
19 | local LS = wesnoth.require "location_set"
20 | local DBG = wesnoth.dofile "~/add-ons/AI-demos/lua/debug.lua"
21 |
22 | -- Clean up the screen
23 | wesnoth.clear_messages()
24 | AH.clear_labels()
25 | std_print('\n---- Side ', wesnoth.current.side, '------------')
26 |
27 | -- Check for debug mode and quit if it is not activated
28 | if (not wesnoth.game_config.debug) then
29 | wesnoth.message("***** This option requires debug mode. Activate by typing ':debug' *****")
30 | return
31 | end
32 |
33 | -- Add shortcut to debug ai table
34 | local ai = wesnoth.debug_ai(wesnoth.current.side).ai
35 | --DBG.dbms(ai, false, 'ai')
36 |
37 | local fn_fred = "~add-ons/AI-demos/lua/fred.lua"
38 | local fn = "ai/micro_ais/cas/ca_fast_move.lua"
39 | local self = { data = {} }
40 | local cfg = {}
41 |
42 | -----------------------------------------------------------------
43 |
44 | local test_CA, exec_also, exec_loop = true, false, false
45 |
46 | if test_CA then -- Test a specific CA ...
47 | --local cfg = { ca_score = 300000 }
48 | local cfg = { ca_score = 300000, target_id='Bad Orc', { "filter", { type = 'Orcish Grunt' } } }
49 |
50 | eval = 1
51 |
52 | if (wesnoth.current.side == 1) then
53 | while (eval > 0) do
54 | local start_time = wesnoth.get_time_stamp() / 1000.
55 | wesnoth.message('Start time:', start_time)
56 | eval = wesnoth.dofile(fn):evaluation(ai, cfg, self)
57 | wesnoth.message('Eval score:', eval)
58 | wesnoth.message('Time after eval:', wesnoth.get_time_stamp() / 1000., wesnoth.get_time_stamp() / 1000. - start_time)
59 | if (eval > 0) and (exec_also or exec_loop) then
60 | wesnoth.dofile(fn):execution(ai, cfg, self)
61 | wesnoth.message('Time after exec:', wesnoth.get_time_stamp() / 1000., wesnoth.get_time_stamp() / 1000. - start_time)
62 | end
63 |
64 | -- Only do another iteration if ca_exec_loop is set.
65 | -- The loop is also broken if evaluation resulted in a score of 0.
66 | if (not exec_loop) then
67 | eval = 0
68 | end
69 | end
70 | else
71 | wesnoth.message("This only works when you're in control of Side 1")
72 | end
73 |
74 | else -- ... or do manual testing
75 | -- Set up Fred's variables
76 | local move_data = FGU.get_move_data()
77 | local move_cache = {}
78 | --for k,_ in pairs(move_data) do
79 | -- std_print(k)
80 | --end
81 |
82 |
83 | local leader = wesnoth.get_units { side = 1, canrecruit = 'yes' }[1]
84 | local units = wesnoth.get_units { side = 1, canrecruit = 'no' }
85 | local enemies = wesnoth.get_units { side = 2, canrecruit = 'no' }
86 | std_print('#units, #enemies', #units, #enemies)
87 | local unit = units[1]
88 | local enemy = enemies[1]
89 |
90 | local start_time = wesnoth.get_time_stamp()
91 | wesnoth.message('Start time:', start_time)
92 | std_print('Start time:', start_time)
93 |
94 | ------- Begin: Do something with the units here -------
95 |
96 |
97 |
98 | local end_time = wesnoth.get_time_stamp()
99 | wesnoth.message('Finish time:', end_time .. ' ' .. tostring(end_time - start_time))
100 | std_print('Finish time:', end_time .. ' ' .. tostring(end_time - start_time))
101 | end
102 |
--------------------------------------------------------------------------------
/AI-demos/maps/Dark_Forecast.map:
--------------------------------------------------------------------------------
1 | border_size=1
2 | usage=map
3 |
4 | _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr
5 | _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr
6 | _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr
7 | _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr
8 | _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr
9 | _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr
10 | _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , Ai , _off^_usr
11 | _off^_usr , Xv , Xv , Xv , Xv , Xv , Xv , Xv , Xv , Xv , Xv , Xv , Xv , Xv , Xv , Xv , Xv , _off^_usr , Ai , _off^_usr , Ai , _off^_usr
12 | _off^_usr , Ww^Vm , Ww , Gs^Fp , Mm , Hh , Gs^Fp , Re , Gs^Fp , Ss , Ss , Ss , Ss , Ds , Ds , Ss , Mm^Xm , Xu , Xu , Ai , Ai , _off^_usr
13 | _off^_usr , Ww , Ww , Mm , Mm , Mm , Re , Re , Gg , Ss^Vhs , Gs^Fp , Gs^Fp , Ds , Ds , Re^Uf , Ss , Mm^Xm , Xu , Cud , Xu , Ai , _off^_usr
14 | _off^_usr , Mm , Gs^Fp , Ww , Ww , Gg , Gg , Re , Gg , Hh , Gs^Fp , Hh , Gg , Gg^Vh , Gg , Re^Uf , Mm^Xm , Xu , Cud , Xu , Ai , _off^_usr
15 | _off^_usr , Hh , Gs , Gs^Ft , Gs , Wwf , Wwf , Gg , Gg , Hh , Gg , Gg , Gg , Gg , Re , Re , Mm^Xm , Xu , Cud , Xu , Ai , _off^_usr
16 | _off^_usr , Gs^Vht , Re , Gs , Gs^Ft , Gs , Ss^Vhs , Ds , Hh , Gg , Gg , Gg , Re , Re , Ch , Ch , Mm^Xm , Xu , Cud , Xu , Ai , _off^_usr
17 | _off^_usr , Re , Re , Re , Gs^Ft , Ds , Ss , Wwf , Gg , Gg , Re , Re , Ch , Ch , Re^Gvs , Ch , Mm^Xm , Xu , Cud , Xu , Ai , _off^_usr
18 | _off^_usr , Gg , Hh , Re , Re , Hh , Gs^Fp , Wwf , Gg , Gg , Re , Ch , Gg^Efm , Re^Gvs , Gg , 4 Kh , Mm^Xm , Xu , Cud , Xu , Ai , _off^_usr
19 | _off^_usr , Gg , Gg^Vh , Re^Uf , Re , Gg , Gg , Gg , Ww , Gg , Re , Gg^Ve , Re^Gvs , Gg , Re^Gvs , Gg^Vh , Mm^Xm , Xu , Cud , Xu , Ai , _off^_usr
20 | _off^_usr , Gg , Gs^Fp , Hh , Re , Re , Gg , Mm , Ww , Gg , Re , Ch , Ch , Re^Gvs , Gg^Efm , 3 Kh , Mm^Xm , Xu , Cud , Xu , Ai , _off^_usr
21 | _off^_usr , Gg , Gg , Gg , Mm , Re , Gg , Ds , Ww , Gs^Fp , Gg , Re , Re , Ch , Ch , Ch , Mm^Xm , Xu , Cud , Xu , Ai , _off^_usr
22 | _off^_usr , Gg , Gg , Gs^Fp , Hh , Re , Gg^Ve , Gs^Fp , Mm , Ww , Gg , Gg , Gs^Fp , Re , Re , Ch , Mm^Xm , Xu , Cud , Xu , Ai , _off^_usr
23 | _off^_usr , Gg , Gs^Ft , Gg , Gg , Re , Re , Gg , Gs^Fp , Ww , Gg , Gs^Fp , Gg , Gg , Gg , Re , Mm^Xm , Xu , Cud , Xu , Ai , _off^_usr
24 | _off^_usr , Gs^Ft , Hd , Dd , Dd , Gg , Gg , Re , Ss^Bw\ , Ss , Gg , Gg , Hh , Gg , Gg , Hh , Mm^Xm , Xu , Cud , Xu , Ai , _off^_usr
25 | _off^_usr , Dd , Md , Dd^Do , Dd , Dd , Dd , Gs^Ft , Ss , Re , Gg , Mm , Ww , Wwf , Gg , Hh , Mm^Xm , Xu , Xu , Xu , Ai , _off^_usr
26 | _off^_usr , Md , Dd^Dc , Hd , Dd , Dd^Vda , Gs^Ft , Hh , Ss , Re , Re , Mm , Ww , Gg^Ve , Gs^Fp , Gg^Fet , Mm^Xm , Xu , Ai , Ai , Ai , _off^_usr
27 | _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr , _off^_usr
28 |
--------------------------------------------------------------------------------
/AI-demos/maps/fai_demo.map:
--------------------------------------------------------------------------------
1 | border_size=1
2 | usage=map
3 |
4 | Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gs^Fds , Gg , Gs^Fds , Gg , Gg
5 | Gg , Ch , Kh , Ch , Gg^Ve , Gg , Gs^Fds , Gg , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds
6 | Gg , Ch , Ch , Ch , Gg , Gg , Gg , Gs^Fds , Gs^Fds , Gs^Fds , Gg^Ve , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds
7 | Gs^Fds , Gg^Ve , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds , Gg
8 | Gs^Fds , Gs^Fds , Gs^Fds , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg^Ve , Gs^Fds , Gg , Gg , Gg , Gg , Gg , Gg , Gg
9 | Gs^Fds , Gs^Fds , Gg^Ve , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg
10 | Gs^Fds , Gs^Fds , Gs^Fds , Gg , Gg , Gg , Gg , Gg , Gg^Ve , Gg , Gg , Gg , Gg , Gg , Gg , Gg^Ve , Gg , Gg , Gg , Gg , Gs^Fds
11 | Ms , Ms , Ms , Ms , Ms , Gs^Fds , Gs^Fds , Gg , Gg , Gg , Gg , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds , Ms , Gs^Fds , Ms , Ms , Ms
12 | Ms , Ms , Gs^Fds , Ms , Gs^Fds , Ms , Gs^Fds , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gs^Fds , Gs^Fds , Ms , Gs^Fds , Ms , Ms
13 | Gg , Gs^Fds , Gg , Gg , Gg^Ve , Gs^Fds , Gg , Gg , Wo , Gg , Wo , Gg , Gg , Gg , Gg , Gg , Gg , Gs^Fds , Gs^Fds , Ms , Gs^Fds
14 | Gg , Gs^Fds , Gg^Ve , Gg , Gg , Gg , Gg , Wo , Wo , Wo , Wot , Wo , Gg , Gg , Gg , Gg , Gg , Gg^Ve , Gg , Gg , Gg
15 | Gg , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds , Gg , Wo , Wot , Wot , Wot , Wo , Wo , Gg , Gg , Gg , Gg , Gs^Fds , Gg , Gg , Gg
16 | Gg , Gg , Gs^Fds , Gs^Fds , Gg , Gs^Fds , Gg , Wo , Wo , Wot , Wot , Wot , Wo , Gg^Ve , Gg , Gg , Gs^Fds , Gs^Fds , Gs^Fds , Gg , Gg
17 | Gg , Gs^Fds , Gs^Fds , Gg , Gs^Fds , Gg , Gg , Gs^Fds , Gg , Wo , Wo , Wo , Gg , Gg , Gg , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds , Gg , Gg
18 | Gg , Gs^Fds , Gg , Gs^Fds , Gg , Gs^Fds , Gg , Gg^Ve , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gs^Fds , Gs^Fds , Gs^Fds , Gg , Gg , Gg
19 | Gg , Gs^Fds , Gg , Gs^Fds , Gg , Gg , Gg , Gg , Gg , Gg , Gg^Ve , Gg , Gg , Gg , Gg , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds , Gg^Ve , Gg
20 | Gg , Gg , Gs^Fds , Gs^Fds , Gs^Fds , Gs^Fds , Ms , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gs^Fds , Gg , Gg , Gg
21 | Gg , Gg , Gs^Fds , Gg , Gg , Ms , Ms , Ms , Ms , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Cea , Gg , Gg
22 | Gg , Gg , Gg , Gg , Ms , Ms , Ms , Ms , Ms , Gg , Gg , Gg , Gg^Ve , Gg , Gg , Gg , Gg , Cea , Ke , Cea , Gg
23 | Gg , Gg , Gg , Ms , Ms , Ms , Ms , Ms , Ms , Ms , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Cea , Gg , Cea , Gg
24 | Gg , Gg , Gg , Gg , Gg , Ms , Gg , Ms , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg , Gg
25 |
--------------------------------------------------------------------------------
/AI-demos/multiplayer/_initial.cfg:
--------------------------------------------------------------------------------
1 | #textdomain wesnoth-AI-demos
2 |
3 | #define DEMO_MULTIPLAYER_AIS
4 | {~add-ons/AI-demos/ais/ai_fred.cfg}
5 |
6 | [options]
7 | [slider]
8 | id=fred_aggression_multiplier
9 | name=_"Aggression multiplier (x10)"
10 | description=_"Set Fred's overall aggression level relative to default (default: 10)"
11 | default=10
12 | min=1
13 | max=20
14 | [/slider]
15 | [/options]
16 |
17 | [event]
18 | # This needs to be a preload event so that CA debugging mode works with replays
19 | name=preload
20 |
21 | [lua]
22 | code = <<
23 | local FSS = wesnoth.require "~/add-ons/AI-demos/lua/fred_scenario_setup.lua"
24 | FSS.fred_scenario_setup()
25 | >>
26 | [/lua]
27 | [/event]
28 | #enddef
29 |
--------------------------------------------------------------------------------
/AI-demos/multiplayer/era.cfg:
--------------------------------------------------------------------------------
1 | #textdomain wesnoth-AI-demos
2 |
3 | [era]
4 | id=AI_Demos
5 | name= _ "AI-demos Experimental AIs"
6 |
7 | {RANDOM_SIDE}
8 | {multiplayer/factions/loyalists-default.cfg}
9 | {multiplayer/factions/rebels-default.cfg}
10 | {multiplayer/factions/northerners-default.cfg}
11 | {multiplayer/factions/undead-default.cfg}
12 | {multiplayer/factions/knalgans-default.cfg}
13 | {multiplayer/factions/drakes-default.cfg}
14 | {QUICK_4MP_LEADERS}
15 |
16 | {DEMO_MULTIPLAYER_AIS}
17 | [/era]
18 |
--------------------------------------------------------------------------------
/AI-demos/multiplayer/modification.cfg:
--------------------------------------------------------------------------------
1 | #textdomain wesnoth-AI-demos
2 |
3 | [modification]
4 | id=AI_Demos
5 | name= _ "AI-demos Experimental AIs"
6 | description= _ "Some experimental AIs"
7 |
8 | disallow_era="AI_Demos"
9 |
10 | {DEMO_MULTIPLAYER_AIS}
11 | [/modification]
12 |
--------------------------------------------------------------------------------
/AI-demos/scenarios/Fred.cfg:
--------------------------------------------------------------------------------
1 | #textdomain wesnoth-AI-demos
2 |
3 | #ifdef TEST
4 | [test]
5 | #else
6 | # wmlindent: start ignoring
7 | [scenario]
8 | # wmlindent: stop ignoring
9 | #endif
10 | id=fred
11 | name=_"Fred"
12 | next_scenario=aid_switchboard
13 |
14 | map_data="{multiplayer/maps/2p_The_Freelands.map}"
15 |
16 | {DEFAULT_SCHEDULE}
17 | random_start_time=yes
18 | turns=50
19 | victory_when_enemies_defeated=yes
20 |
21 | [side]
22 | side=1
23 | controller=ai
24 | id=Fred1
25 | name=Fred
26 | type=Orcish Warrior
27 | persistent=no
28 | facing=sw
29 |
30 | team_name=Fred
31 | user_team_name=_"Fred"
32 |
33 | recruit=Goblin Spearman, Naga Fighter, Orcish Archer, Orcish Assassin, Orcish Grunt, Troll Whelp, Wolf Rider
34 | gold=100
35 | village_gold=2
36 |
37 | {~add-ons/AI-demos/ais/ai_fred.cfg}
38 | [/side]
39 |
40 | [side]
41 | side=2
42 | controller=ai
43 | type=Invisible Unit
44 | id=challenger
45 | name=_"Fred's Challenger"
46 | persistent=no
47 |
48 | team_name=challenger
49 | user_team_name=_"Fred's Challenger"
50 | save_id="Fred's Challenger"
51 |
52 | gold=0
53 | village_gold=2
54 | [village]
55 | x=26
56 | y=22
57 | [/village]
58 |
59 | {~add-ons/AI-demos/ais/ai_fred.cfg}
60 | [/side]
61 |
62 | [event]
63 | # This needs to be a preload event so that CA debugging mode works with replays
64 | name=preload
65 | [lua]
66 | code = <<
67 | local FSS = wesnoth.require "~/add-ons/AI-demos/lua/fred_scenario_setup.lua"
68 | FSS.fred_scenario_setup()
69 | >>
70 | [/lua]
71 | [/event]
72 |
73 | [event]
74 | name=prestart
75 |
76 | {VARIABLE scenario_name fred}
77 |
78 | [set_variable]
79 | name=fred_type
80 | rand=Orcish Warrior,Troll,Troll Rocklobber,Orcish Crossbowman,Orcish Slayer
81 | [/set_variable]
82 |
83 | [kill]
84 | id=Fred1
85 | [/kill]
86 |
87 | [unit]
88 | side=1
89 | id=Fred1
90 | name=Fred Side 1
91 | type=$fred_type
92 | canrecruit=yes
93 |
94 | x,y=19,4
95 | facing=sw
96 | [/unit]
97 |
98 | {CLEAR_VARIABLE fred_type}
99 | [/event]
100 |
101 | # Put this into a new turn as the AI_Demos_version variable is not set until the start event
102 | [event]
103 | name=new turn
104 |
105 | [objectives]
106 | [objective]
107 | description=_"Death of Fred"
108 | condition=win
109 | [/objective]
110 | [objective]
111 | description=_"Death of your leader"
112 | condition=lose
113 | [/objective]
114 | {TURNS_RUN_OUT}
115 | [note]
116 | description=_"This is Fred v$AI_Demos_version" # wmllint: no spellcheck
117 | [/note]
118 | [note]
119 | description=_"This scenario can be started directly from the commandline using '-tfred' or by using the test scenario hotkey in the title screen." # wmllint: no spellcheck
120 | [/note]
121 | [/objectives]
122 |
123 | [message]
124 | message=_"Which faction should Fred's challenger (side 2) play? Fred (side 1) always plays Northerners in this scenario."
125 | speaker=narrator
126 | image=wesnoth-icon.png
127 |
128 | [option]
129 | label=_"Northerners"
130 | [command]
131 | {VARIABLE ai_leader (Orcish Warrior,Troll,Troll Rocklobber,Orcish Crossbowman,Orcish Slayer)}
132 | {VARIABLE recruit_types (Orcish Grunt,Troll Whelp,Wolf Rider,Orcish Archer,Orcish Assassin,Naga Fighter,Goblin Spearman)}
133 | [/command]
134 | [/option]
135 |
136 | [option]
137 | label=_"Rebels"
138 | [command]
139 | {VARIABLE ai_leader (Elvish Captain,Elvish Hero,Elvish Ranger,Elvish Marksman,Elvish Druid,Elvish Sorceress,White Mage,Red Mage)}
140 | {VARIABLE recruit_types (Elvish Fighter,Elvish Archer,Mage,Elvish Shaman,Elvish Scout,Wose,Merman Hunter)}
141 | [/command]
142 | [/option]
143 |
144 | [option]
145 | label=_"Undead"
146 | [command]
147 | {VARIABLE ai_leader (Dark Sorcerer,Revenant,Deathblade,Bone Shooter)}
148 | {VARIABLE recruit_types (Skeleton,Skeleton Archer,Walking Corpse,Ghost,Vampire Bat,Dark Adept,Ghoul)}
149 | [/command]
150 | [/option]
151 |
152 | [option]
153 | label=_"Knalgan Alliance"
154 | [command]
155 | {VARIABLE ai_leader (Dwarvish Steelclad,Dwarvish Thunderguard,Dwarvish Stalwart,Rogue,Trapper)}
156 | {VARIABLE recruit_types (Dwarvish Guardsman,Dwarvish Fighter,Dwarvish Ulfserker,Dwarvish Thunderer,Thief,Poacher,Footpad,Gryphon Rider)}
157 | [/command]
158 | [/option]
159 |
160 | [option]
161 | label=_"Drakes"
162 | [command]
163 | {VARIABLE ai_leader (Drake Flare,Fire Drake,Drake Arbiter,Drake Thrasher,Drake Warrior)}
164 | {VARIABLE recruit_types (Drake Burner,Drake Clasher,Drake Glider,Drake Fighter,Saurian Skirmisher,Saurian Augur)}
165 | [/command]
166 | [/option]
167 |
168 | [option]
169 | label=_"Loyalists"
170 | [command]
171 | {VARIABLE ai_leader (Lieutenant,Swordsman,Pikeman,Javelineer,Longbowman,White Mage,Red Mage)}
172 | {VARIABLE recruit_types (Cavalryman,Horseman,Spearman,Fencer,Heavy Infantryman,Bowman,Mage,Merman Fighter)}
173 | [/command]
174 | [/option]
175 | [/message]
176 |
177 | [set_variable]
178 | name=leader_type
179 | rand=$ai_leader
180 | [/set_variable]
181 |
182 | [kill]
183 | side=2
184 | canrecruit=yes
185 | [/kill]
186 |
187 | [unit]
188 | side=2
189 | type=$leader_type
190 | canrecruit=yes
191 |
192 | x,y=19,21
193 | [/unit]
194 |
195 | [allow_recruit]
196 | side=2
197 | type=$recruit_types
198 | [/allow_recruit]
199 |
200 | {CLEAR_VARIABLE ai_leader,recruit_types,leader_type}
201 |
202 | [message]
203 | message=_"Who should play Fred's challenger?"
204 | speaker=narrator
205 | image=wesnoth-icon.png
206 |
207 | [option]
208 | label=_"Let the Fred AI also play side 2."
209 | [command]
210 | [modify_unit]
211 | [filter]
212 | side=2
213 | canrecruit=yes
214 | [/filter]
215 | id=Fred2
216 | name=Fred Side 2
217 | [/modify_unit]
218 | [/command]
219 | [/option]
220 |
221 | [option]
222 | label=_"I want to do it."
223 | [command]
224 | [modify_side]
225 | side=2
226 | controller=human
227 | [/modify_side]
228 | [/command]
229 | [/option]
230 | [/message]
231 |
232 | [message]
233 | message=_"How much gold should Fred's challenger (side 2) have? Fred (side 1) always has 100."
234 | speaker=narrator
235 | image=wesnoth-icon.png
236 |
237 | [option]
238 | label=_"100"
239 | [command]
240 | [gold]
241 | side=2
242 | amount=100
243 | [/gold]
244 | [/command]
245 | [/option]
246 |
247 | [option]
248 | label=_"125"
249 | [command]
250 | [gold]
251 | side=2
252 | amount=125
253 | [/gold]
254 | [/command]
255 | [/option]
256 |
257 | [option]
258 | label=_"150"
259 | [command]
260 | [gold]
261 | side=2
262 | amount=150
263 | [/gold]
264 | [/command]
265 | [/option]
266 |
267 | [option]
268 | label=_"175"
269 | [command]
270 | [gold]
271 | side=2
272 | amount=175
273 | [/gold]
274 | [/command]
275 | [/option]
276 |
277 | [option]
278 | label=_"200"
279 | [command]
280 | [gold]
281 | side=2
282 | amount=200
283 | [/gold]
284 | [/command]
285 | [/option]
286 |
287 | [option]
288 | label=_"75"
289 | [command]
290 | [gold]
291 | side=2
292 | amount=75
293 | [/gold]
294 | [/command]
295 | [/option]
296 | [/message]
297 | [/event]
298 | #ifndef TEST
299 | [/scenario]
300 | #else
301 | # wmlindent: start ignoring
302 | [/test]
303 | # wmlindent: stop ignoring
304 | #endif
305 |
--------------------------------------------------------------------------------
/AI-demos/scenarios/fai_demo.cfg:
--------------------------------------------------------------------------------
1 | #textdomain wesnoth-AI-demos
2 |
3 | #wmllint: local spelling Wehrli Johan Rinaldini Julien Zerg Starcraft
4 |
5 | [scenario]
6 | id="fai_demo"
7 | name=_"Formula AI Demo"
8 | next_scenario= aid_switchboard
9 |
10 | map_data="{~add-ons/AI-demos/maps/fai_demo.map}"
11 |
12 | {DEFAULT_SCHEDULE}
13 | turns=-1
14 | victory_when_enemies_defeated=yes
15 |
16 | [side]
17 | side=1
18 | x,y=2,1
19 | type=Elvish Avenger
20 | id=elf1
21 | canrecruit=yes
22 | persistent=no
23 |
24 | user_team_name=_"Formula AI"
25 | recruit=Elvish Archer,Elvish Fighter,Elvish Scout,Elvish Shaman,Mage
26 |
27 | gold=200
28 |
29 | [ai]
30 | formula="
31 | if(turn = 1,
32 | [ recruit('Elvish Scout'),
33 | recruit('Elvish Scout'),
34 | recruit('Elvish Scout') ]
35 | ,[])
36 | "
37 | [/ai]
38 |
39 | [ai]
40 | version=10710
41 | [stage]
42 | engine=fai
43 | name=unit_formulas
44 | [/stage]
45 | [stage]
46 | engine=fai
47 | name=side_formulas
48 | move="
49 | move(units[0].loc,nearest_loc(units[0].loc,self.enemy_and_unowned_villages))
50 | where units=filter(my_units,(self.type='Elvish Scout') and (self.movement_left > 0))
51 | "
52 | [/stage]
53 | [stage]
54 | engine=fai
55 | name=side_formulas
56 | move="
57 | attack(units[0].loc, head(shortest_path(nearest_loc(units[0].loc,map(enemy_units,self.loc)), units[0].loc)), nearest_loc(units[0].loc,map(enemy_units,self.loc)))
58 | where units=filter(my_units, (self.leader!=1) and (self.movement_left > 0))
59 | "
60 | [/stage]
61 | [stage]
62 | engine=fai
63 | name=side_formulas
64 | move="
65 | if(size(units) < 2 and size(filter(my_units,type='Elvish Archer' or type='Elvish Fighter')) != 0 and my_side.gold >= 18,
66 | recruit('Elvish Scout'),
67 | [if(my_side.gold >= 17,recruit('Elvish Archer'),my_side.gold >= 14,recruit('Elvish Fighter'),
68 | [])]
69 | )
70 | where units=filter(my_units,type='Elvish Scout')
71 | "
72 | [/stage]
73 | [/ai]
74 | [/side]
75 |
76 | [side]
77 | side=2
78 | controller=ai
79 | type=Orcish Warrior
80 | x,y=18,18
81 | persistent=no
82 |
83 | user_team_name=_"Default AI"
84 |
85 | recruit=Goblin Spearman,Orcish Archer,Orcish Assassin,Orcish Grunt,Wolf Rider
86 | gold=100
87 | [/side]
88 |
89 | [side] # This side is only here because we need one persistent side for the game to go on
90 | side=3
91 | controller=null
92 | persistent=yes
93 | save_id=Grnk
94 | type=Goblin Spearman
95 | hidden=yes
96 | [/side]
97 |
98 | [event]
99 | name=start
100 |
101 | {VARIABLE scenario_name fai_demo}
102 |
103 | # wmllint: display on
104 | [message]
105 | id=elf1
106 | message=_"This scenario demonstrates a simple but complete AI written entirely in Formula AI by Wehrli Johan and Rinaldini Julien. Its point is to defeat the enemy leader. Until that is done, it has the same behavior as the 'Zerg rush' (Starcraft). The AI spawns an Elvish Archer and Elvish Fighter on every turn (if it has the gold), or an Elvish Scout if there are less than 2 Elvish Scouts. The Elvish Scouts' goal is to capture the maximum number of villages. If there are no more unowned villages, the scouts support the other units in attacking the enemy. Other units, such as fighters and archers, only attack enemy units.
107 | This results in a very aggressive AI."
108 | [/message]
109 | # wmllint: display off
110 | [/event]
111 |
112 | # Stop if this was the last death on this side
113 | [event]
114 | name=die
115 | first_time_only=no
116 |
117 | [if]
118 | [not]
119 | [have_unit]
120 | side=$unit.side
121 | [/have_unit]
122 | [/not]
123 | [then]
124 | [message]
125 | id=$unit.id
126 | message=_"We lost ..."
127 | [/message]
128 |
129 | # So that game goes on to next scenario
130 | [modify_side]
131 | side=3
132 | controller=human
133 | [/modify_side]
134 |
135 | [endlevel]
136 | result=victory
137 | bonus=no
138 | carryover_percentage=0
139 | carryover_report=no
140 | linger_mode=no
141 | [/endlevel]
142 | [/then]
143 | [/if]
144 | [/event]
145 | [/scenario]
146 |
--------------------------------------------------------------------------------
/AI-demos/scenarios/luaai_demo.cfg:
--------------------------------------------------------------------------------
1 | #textdomain wesnoth-AI-demos
2 |
3 | #wmllint: local spelling SW
4 |
5 | [scenario]
6 | id=luaai-demo
7 | name=_"LuaAI"
8 | next_scenario=aid_switchboard
9 |
10 | map_data="{multiplayer/maps/2p_The_Freelands.map}"
11 |
12 | {DEFAULT_SCHEDULE}
13 | turns=-1
14 | victory_when_enemies_defeated=yes
15 |
16 | [side]
17 | side=1
18 | controller=ai
19 | id=Lua AI
20 | type=Lieutenant
21 | persistent=no
22 |
23 | team_name=north
24 | user_team_name=_"Northern Army"
25 | recruit=Cavalryman,Horseman,Spearman,Fencer,Heavy Infantryman,Bowman,Mage
26 |
27 | gold=200
28 |
29 | [ai]
30 | {MODIFY_AI_ADD_CANDIDATE_ACTION 1 main_loop (
31 | [candidate_action]
32 | engine=lua
33 | name=move_spearmen
34 | id=move_spearmen
35 | max_score=100010
36 | location="~add-ons/AI-demos/lua/ca_move_unittype.lua"
37 | [args]
38 | type=Spearman
39 | score=100010
40 | goal_x=4
41 | goal_y=20
42 | [/args]
43 | [/candidate_action]
44 | )}
45 | {MODIFY_AI_ADD_CANDIDATE_ACTION 1 main_loop (
46 | [candidate_action]
47 | engine=lua
48 | name=move_bowmen
49 | id=move_bowmen
50 | max_score=99990
51 | location="~add-ons/AI-demos/lua/ca_move_unittype.lua"
52 | [args]
53 | type=Bowman
54 | score=99990
55 | goal_x=34
56 | goal_y=20
57 | [/args]
58 | [/candidate_action]
59 | )}
60 | [/ai]
61 | [/side]
62 | # We want to make sure there are some spearmen and bowmen
63 | {LIMIT_CONTEMPORANEOUS_RECRUITS 1 "Mage" 1}
64 | {LIMIT_CONTEMPORANEOUS_RECRUITS 1 "Cavalryman" 1}
65 | {LIMIT_CONTEMPORANEOUS_RECRUITS 1 "Horseman" 1}
66 | {LIMIT_CONTEMPORANEOUS_RECRUITS 1 "Heavy Infantryman" 1}
67 | {LIMIT_CONTEMPORANEOUS_RECRUITS 1 "Fencer" 1}
68 |
69 | [side]
70 | side=2
71 | controller=human
72 | id=Player
73 | type=Lieutenant
74 | persistent=no
75 |
76 | team_name=south
77 | user_team_name=_"Southern Army"
78 | recruit=Cavalryman,Horseman,Spearman,Fencer,Heavy Infantryman,Bowman,Mage,Merman Fighter
79 |
80 | gold=200
81 | [/side]
82 |
83 | [side] # This side is only here because we need one persistent side for the game to go on
84 | side=3
85 | controller=null
86 | persistent=yes
87 | save_id=Grnk
88 | type=Goblin Spearman
89 | hidden=yes
90 | [/side]
91 |
92 | # Prestart actions
93 | [event]
94 | name=prestart
95 |
96 | {VARIABLE scenario_name luaai-demo}
97 |
98 | # Goal signposts for AI
99 | {PLACE_IMAGE "scenery/signpost.png" 4 20}
100 | {SET_LABEL 4 20 _"Spearmen go here"}
101 | {PLACE_IMAGE "scenery/signpost.png" 34 20}
102 | {SET_LABEL 34 20 _"Bowmen go here"}
103 | [/event]
104 |
105 | [event]
106 | name=start
107 |
108 | # wmllint: display on
109 | [message]
110 | speaker=narrator
111 | image="wesnoth-icon.png"
112 | caption=_"Note"
113 | message=_"This scenario is meant as a simple demonstration of setting up a Lua AI engine and using it in a scenario. Its purpose is not to provide an AI that plays well or does anything particularly useful, but to serve as code template for your own AI work. The behavior is as follows:
114 |
115 | - All spearmen are sent toward the signpost in the SW.
116 | - All bowmen are sent toward the signpost in the SE.
117 | - Spearmen ignore all enemies and try to circumvent them, while bowmen are distractible and veer off their path to attack enemy units.
118 |
119 | Note that the differences in behavior (unit types, signpost locations and whether units attack or not) are determined by parameters that are passed to the AI functions rather than by different code. In particular, whether units attack or not is determined simply by the candidate action evaluation score."
120 | [/message]
121 | # wmllint: display off
122 |
123 | [objectives]
124 | side=2
125 | [objective]
126 | description=_"Defeat the enemy leader"
127 | condition=win
128 | [/objective]
129 | [objective]
130 | description=_"Death of your leader"
131 | condition=lose
132 | [/objective]
133 | [objective]
134 | description=_"A Side 1 spearman or bowman makes it to a signpost"
135 | condition=lose
136 | [/objective]
137 | [/objectives]
138 | [/event]
139 |
140 | # IF we beat the enemy leader
141 | [event]
142 | name=victory
143 |
144 | # So that game goes on to next scenario
145 | [modify_side]
146 | side=3
147 | controller=human
148 | [/modify_side]
149 | [/event]
150 |
151 | [event]
152 | name=moveto
153 | [filter]
154 | type=Spearman,Bowman
155 | x=4,34
156 | y=20,20
157 | [/filter]
158 |
159 | [message]
160 | id=$unit.id
161 | message=_"I made it. You lost!"
162 | [/message]
163 |
164 | [endlevel]
165 | result=defeat
166 | [/endlevel]
167 | [/event]
168 | [/scenario]
169 |
--------------------------------------------------------------------------------
/AI-demos/scenarios/manual_ai.cfg:
--------------------------------------------------------------------------------
1 | #textdomain wesnoth-AI-demos
2 |
3 | #ifdef TEST
4 | [test]
5 | #else
6 | # wmlindent: start ignoring
7 | [scenario]
8 | # wmlindent: stop ignoring
9 | #endif
10 | id=manual_ai
11 | name=_"Manual AI"
12 | next_scenario=aid_switchboard
13 |
14 | map_data="{multiplayer/maps/2p_The_Freelands.map}"
15 |
16 | {DEFAULT_SCHEDULE}
17 | random_start_time=yes
18 | turns=50
19 | victory_when_enemies_defeated=yes
20 |
21 | [side]
22 | side=1
23 | controller=human
24 | id=Fred
25 | name=Fred
26 | type=Orcish Warrior
27 | persistent=no
28 | facing=sw
29 |
30 | team_name=Fred
31 | user_team_name=_"Fred"
32 |
33 | gold=100
34 | village_gold=2
35 | [/side]
36 |
37 | [side]
38 | side=2
39 | controller=ai
40 | id=challenger
41 | type=Orcish Warrior
42 | persistent=no
43 |
44 | team_name=challenger
45 | user_team_name=_"Fred's Challenger"
46 | save_id="Fred's Challenger"
47 |
48 | recruit=Goblin Spearman, Naga Fighter, Orcish Archer, Orcish Assassin, Orcish Grunt, Troll Whelp, Wolf Rider
49 | gold=100
50 | village_gold=2
51 | [village]
52 | x=26
53 | y=22
54 | [/village]
55 |
56 | {~add-ons/AI-demos/ais/ai_manual.cfg}
57 | [/side]
58 |
59 | [event]
60 | name=start
61 |
62 | [message]
63 | id=Fred
64 | caption=_"Manual External AI Control Testing"
65 | message=_"This is a proof-of-concept scenario demonstrating how one can control units on an AI side with input external to Wesnoth (via a text editor in this case). This method can be used, for example, by a machine learning framework.
66 |
67 | End the side 1 turn and move the side 2 leader (id = challenger) by editing (and saving!) file '~/add-ons/AI-demos/lua/manual_input.lua'. Note that there are no checks built in whether the move is actually meaningful or possible, this is really just a bare-bones proof of concept.
68 |
69 | This scenario can also be started from the commandline as a test scenario with 'wesnoth -d -tmanual_ai' or by using the test scenario hotkey in the title screen." # wmllint: no spellcheck
70 | [/message]
71 |
72 | [objectives]
73 | summary=_"Testing of manual interface to AI"
74 | [/objectives]
75 | [/event]
76 |
77 | #ifndef TEST
78 | [/scenario]
79 | #else
80 | # wmlindent: start ignoring
81 | [/test]
82 | # wmlindent: stop ignoring
83 | #endif
84 |
--------------------------------------------------------------------------------
/AI-demos/scenarios/mp_ais.cfg:
--------------------------------------------------------------------------------
1 | #textdomain wesnoth-AI-demos
2 |
3 | [scenario]
4 | id=MP_AIs
5 | name=_"Experimental MP AIs"
6 | next_scenario=aid_switchboard
7 |
8 | map_data="{multiplayer/maps/2p_The_Freelands.map}"
9 |
10 | {DEFAULT_SCHEDULE}
11 | turns=-1
12 | victory_when_enemies_defeated=no
13 |
14 | # wmllint: recognize grunt
15 | [side]
16 | side=1
17 | controller=human
18 | type=Goblin Spearman
19 | id=Grnk
20 |
21 | persistent=yes
22 | save_id=Grnk
23 |
24 | gold=0
25 | income=-2 # No income whatsoever
26 | [/side]
27 |
28 | [event]
29 | name=prestart
30 |
31 | {VARIABLE scenario_name MP_AIs}
32 |
33 | {UNIT 1 (Orcish Grunt) 15 4 id,facing=grunt,se}
34 |
35 | [modify_unit]
36 | [filter]
37 | id=Grnk
38 | [/filter]
39 |
40 | facing=sw
41 | [/modify_unit]
42 | [/event]
43 |
44 | [event]
45 | name=start
46 |
47 | # wmllint: display on
48 | [message]
49 | id=grunt
50 | message=_"They there. We them get.
51 |
52 | Grunt rush many players."
53 | [/message]
54 | # wmllint: display off
55 | [message]
56 | id=Grnk
57 | message=_"It's multiplayer, not many players. Maybe you better let me explain."
58 | [/message]
59 | [message]
60 | id=grunt
61 | message=_"Little goblin smart."
62 | [/message]
63 | # wmllint: display on
64 | [message]
65 | id=Grnk
66 | message=_"Yeah, well, fortunately you don't need smarts to execute a good grunt rush. Anyway...
67 |
68 | This campaign includes several experimental AIs (not just grunt rushes) that can be used in multiplayer mode. To play one of these AIs:
69 | - Go into the MP lobby (either local game or on the server)
70 | - Select either era or modification 'AI-demos Experimental AIs'
71 | - Select a map and options
72 | - Select 'Computer Player' for the AI side(s)
73 | - Select one of the new AIs
74 |
75 | Note:
76 | - The AI called 'AI-demos: Ron — Generic Rush AI' has by now been added into mainline and is available there as 'Experimental AI'. The mainline and AI-Demos versions are not necessarily exactly the same as changes are first tested in AI-Demos and implemented in mainline later."
77 | [/message]
78 | # wmllint: display off
79 | [message]
80 | id=Grnk
81 | message=_"And now back to the start ..."
82 | [/message]
83 |
84 | [endlevel]
85 | result=victory
86 | bonus=no
87 | carryover_percentage=0
88 | carryover_report=no
89 | linger_mode=no
90 | [/endlevel]
91 | [/event]
92 | [/scenario]
93 |
--------------------------------------------------------------------------------
/AI-demos/scenarios/switchboard.cfg:
--------------------------------------------------------------------------------
1 | #textdomain wesnoth-AI-demos
2 |
3 | [scenario]
4 | id=aid_switchboard
5 | name= _ "Switchboard"
6 | next_scenario=null
7 |
8 | map_data="{~add-ons/AI-demos/maps/Dark_Forecast.map}"
9 |
10 | {DEFAULT_SCHEDULE}
11 | turns=-1
12 | victory_when_enemies_defeated=no
13 |
14 | [story]
15 | [if]
16 | [variable]
17 | name=story_shown
18 | not_equals=yes
19 | [/variable]
20 | [then]
21 | [part]
22 | [background_layer]
23 | image=misc/blank.png
24 | base_layer=yes
25 | [/background_layer]
26 | [background_layer]
27 | image=portraits/goblins/spearman.png
28 | scale=no
29 | [/background_layer]
30 |
31 | story= _ "Hi, I'm Grnk the Frail. You may remember me from such instructional campaigns as 'Grnk the Mighty' and 'Why You Shouldn't Attack Peasants When There's A Mad Goblin Around.'
32 |
33 | This campaign is the home of a number of AI development projects, most notably two MP AIs dubbed 'Ron' and 'Fred' as well as several test and demo scenarios. This is also where the Micro AIs were developed. As the latter have now been moved into mainline, they are no longer part of this add-on.
34 |
35 | If you have comments, problems, suggestions or requests for additional AI behaviors, please visit our thread on the Wesnoth forums at http://tinyurl.com/AI-mods" # wmllint: no spellcheck
36 | [/part]
37 | [/then]
38 | [/if]
39 | [/story]
40 |
41 | [side]
42 | side=1
43 | controller=human
44 | id=Grnk
45 | name= _ "Grnk the Frail"
46 | gender=male
47 | unrenamable=yes
48 | type=Goblin Spearman
49 | max_moves=99
50 | x,y=13,15
51 |
52 | team_name=Grnk
53 | user_team_name= _ "team_name^Grnk"
54 | persistent=yes
55 | save_id=Grnk
56 |
57 | [modifications]
58 | {TRAIT_QUICK}
59 | [/modifications]
60 |
61 | village_gold=0
62 | {GOLD 24 22 19}
63 | income=-2 # No income whatsoever
64 | [/side]
65 |
66 | # The labels and signposts to go on to the next scenario
67 | [event]
68 | name=prestart
69 |
70 | [modify_unit]
71 | [filter]
72 | id=Grnk
73 | [/filter]
74 |
75 | facing=sw
76 | [/modify_unit]
77 |
78 | {PLACE_IMAGE "scenery/signpost.png~GS()" 9 12}
79 | {SET_LABEL 9 12 _"Manual AIs"}
80 |
81 | {PLACE_IMAGE "scenery/signpost.png~GS()" 9 13}
82 | {SET_LABEL 9 13 _"MP AIs"}
83 |
84 | {PLACE_IMAGE "scenery/signpost.png~GS()" 9 14}
85 | {SET_LABEL 9 14 _"Test Scenario"}
86 |
87 | {PLACE_IMAGE "scenery/signpost.png~GS()" 9 15}
88 | {SET_LABEL 9 15 _"Simple Lua AI Demo"}
89 |
90 | {PLACE_IMAGE "scenery/signpost.png~GS()" 9 16}
91 | {SET_LABEL 9 16 _"Fred"}
92 |
93 | #{PLACE_IMAGE "scenery/signpost.png~GS()" 1 10}
94 | #{SET_LABEL 4 10 _"Formula AI Demo"}
95 |
96 | {VARIABLE scenario_name aid_switchboard}
97 | {VARIABLE story_shown yes}
98 |
99 | [set_menu_item]
100 | id=m95_manual
101 | description= _ "Manual AI"
102 | image=units/orcs/grunt.png~CROP(23,15,24,24)
103 | [filter_location]
104 | x,y=9,12
105 | [/filter_location]
106 | [show_if]
107 | {VARIABLE_CONDITIONAL scenario_name equals aid_switchboard}
108 | [/show_if]
109 | [command]
110 | # wmllint: display on
111 | [message]
112 | id=Grnk
113 | caption=_"Manual External AI Control Demo Scenario"
114 | message=_"This is a proof-of-concept scenario demonstrating how one can control units on an AI side with input external to Wesnoth (via a text editor in this case). This method can be used, for example, by a machine learning framework.
115 |
116 | This scenario can also be started from the commandline as a test scenario with 'wesnoth -d -tmanual_ai' or by using the test scenario hotkey in the title screen." # wmllint: no spellcheck
117 | [/message]
118 | # wmllint: display off
119 | [/command]
120 | [/set_menu_item]
121 | [set_menu_item]
122 | id=m91_MP
123 | description= _ "Experimental MP AIs (Lua AI)"
124 | image=units/orcs/grunt.png~CROP(23,15,24,24)
125 | [filter_location]
126 | x,y=9,13
127 | [/filter_location]
128 | [show_if]
129 | {VARIABLE_CONDITIONAL scenario_name equals aid_switchboard}
130 | [/show_if]
131 | [command]
132 | [message]
133 | id=Grnk
134 | caption=_"Experimental MP AIs"
135 | message=_"Story only scenario that explains how to use the experimental multiplayer AIs."
136 | [/message]
137 | [/command]
138 | [/set_menu_item]
139 |
140 | [set_menu_item]
141 | id=m92_test
142 | description= _ "Lua AI Test Scenario"
143 | image=units/orcs/grunt.png~CROP(23,15,24,24)
144 | [filter_location]
145 | x,y=9,14
146 | [/filter_location]
147 | [show_if]
148 | {VARIABLE_CONDITIONAL scenario_name equals aid_switchboard}
149 | [/show_if]
150 | [command]
151 | # wmllint: display on
152 | [message]
153 | id=Grnk
154 | caption=_"Lua AI Test Scenario"
155 | message=_"This is a hard-core test scenario that is here for manual testing of AI functions or candidate actions. It provides a right-click menu option for doing so without having to reload.
156 |
157 | It can also be started from the commandline as a test scenario with 'wesnoth -d -taid_test' or by using the test scenario hotkey in the title screen." # wmllint: no spellcheck
158 | [/message]
159 | # wmllint: display off
160 | [/command]
161 | [/set_menu_item]
162 |
163 | [set_menu_item]
164 | id=m80_Lua_demo
165 | description= _ "Simple Lua AI Demo"
166 | image=units/orcs/grunt.png~CROP(23,15,24,24)
167 | [filter_location]
168 | x,y=9,15
169 | [/filter_location]
170 | [show_if]
171 | {VARIABLE_CONDITIONAL scenario_name equals aid_switchboard}
172 | [/show_if]
173 | [command]
174 | [message]
175 | id=Grnk
176 | caption=_"Simple Lua AI Demo"
177 | message=_"This scenario provides a simple template for setting up a Lua AI engine and scenario. The AI behavior itself is trivial and not particularly useful (some of the AI units simply move toward two map locations, with priority either higher or lower than attacks on enemies), it is only meant as a demonstration how to code this."
178 | [/message]
179 | [/command]
180 | [/set_menu_item]
181 |
182 | [set_menu_item]
183 | id=m93_fred
184 | description= _ "Fred — Freelands MP AI"
185 | image=units/orcs/grunt.png~CROP(23,15,24,24)
186 | [filter_location]
187 | x,y=9,16
188 | [/filter_location]
189 | [show_if]
190 | {VARIABLE_CONDITIONAL scenario_name equals aid_switchboard}
191 | [/show_if]
192 | [command]
193 | [message]
194 | id=Grnk
195 | caption=_"Fred — Freelands MP AI Test Scenario"
196 | message=_"That's what it is."
197 | [/message]
198 | [/command]
199 | [/set_menu_item]
200 |
201 | [set_menu_item]
202 | id=m94_fai
203 | description= _ "Formula AI demo" # Not included yet
204 | image=units/elves-wood/avenger.png~CROP(25,11,24,24)
205 | [filter_location]
206 | x,y=0,0
207 | [/filter_location]
208 | [show_if]
209 | {VARIABLE_CONDITIONAL scenario_name equals aid_switchboard}
210 | [/show_if]
211 | [command]
212 | [message]
213 | id=Grnk
214 | caption=_"Formula AI Demo"
215 | message=_"This scenario contains a simple demonstration of setting up a full AI using Formula AI."
216 | [/message]
217 | [/command]
218 | [/set_menu_item]
219 | [/event]
220 |
221 | [event]
222 | name=start
223 |
224 | {SCROLL_TO 13 18}
225 |
226 | [message]
227 | id=Grnk
228 | message=_"Move me to any of the signposts to go to an AI demonstration. Information about each demonstration can be accessed by right-clicking on the respective signpost."
229 | [/message]
230 |
231 | [objectives]
232 | [objective]
233 | description= _ "Move Grnk to one of the signposts"
234 | condition=win
235 | [/objective]
236 | [note]
237 | description= _ "Right-click on a signpost to get information about the scenario"
238 | [/note]
239 | [/objectives]
240 | [/event]
241 |
242 | [event]
243 | name=moveto
244 | [filter]
245 | x,y=9,12
246 | [/filter]
247 |
248 | [endlevel]
249 | result=victory
250 | next_scenario=manual_ai
251 | bonus=no
252 | carryover_percentage=0
253 | carryover_report=no
254 | linger_mode=no
255 | replay_save=no
256 | [/endlevel]
257 | [/event]
258 |
259 | [event]
260 | name=moveto
261 | [filter]
262 | x,y=9,13
263 | [/filter]
264 |
265 | [endlevel]
266 | result=victory
267 | next_scenario=MP_AIs
268 | bonus=no
269 | carryover_percentage=0
270 | carryover_report=no
271 | linger_mode=no
272 | replay_save=no
273 | [/endlevel]
274 | [/event]
275 |
276 | [event]
277 | name=moveto
278 | [filter]
279 | x,y=9,14
280 | [/filter]
281 |
282 | [endlevel]
283 | result=victory
284 | next_scenario=aid_test
285 | bonus=no
286 | carryover_percentage=0
287 | carryover_report=no
288 | linger_mode=no
289 | replay_save=no
290 | [/endlevel]
291 | [/event]
292 |
293 | [event]
294 | name=moveto
295 | [filter]
296 | x,y=9,15
297 | [/filter]
298 |
299 | [endlevel]
300 | result=victory
301 | next_scenario=luaai-demo
302 | bonus=no
303 | carryover_percentage=0
304 | carryover_report=no
305 | linger_mode=no
306 | replay_save=no
307 | [/endlevel]
308 | [/event]
309 |
310 | [event]
311 | name=moveto
312 | [filter]
313 | x,y=9,16
314 | [/filter]
315 |
316 | [endlevel]
317 | result=victory
318 | next_scenario=fred
319 | bonus=no
320 | carryover_percentage=0
321 | carryover_report=no
322 | linger_mode=no
323 | replay_save=no
324 | [/endlevel]
325 | [/event]
326 |
327 | [event]
328 | name=moveto2
329 | [filter]
330 | x,y=0,0
331 | [/filter]
332 |
333 | [endlevel]
334 | result=victory
335 | next_scenario=fai_demo
336 | bonus=no
337 | carryover_percentage=0
338 | carryover_report=no
339 | linger_mode=no
340 | replay_save=no
341 | [/endlevel]
342 | [/event]
343 | [/scenario]
344 |
--------------------------------------------------------------------------------
/AI-demos/scenarios/test.cfg:
--------------------------------------------------------------------------------
1 | #textdomain wesnoth-AI-demos
2 |
3 | #ifdef TEST
4 | [test]
5 | #else
6 | # wmlindent: start ignoring
7 | [scenario]
8 | # wmlindent: stop ignoring
9 | #endif
10 | id=aid_test
11 | name=_"Test"
12 | next_scenario=aid_switchboard
13 |
14 | map_data="{multiplayer/maps/2p_The_Freelands.map}"
15 | #map_data="{~add-ons/Grnk/part1/maps/11_Escape.map}"
16 |
17 | {DEFAULT_SCHEDULE}
18 | turns=50
19 | victory_when_enemies_defeated=no
20 |
21 | [side]
22 | side=1
23 | controller=human
24 | id=Vanak
25 | name=Vanak
26 | type=Orcish Ruler
27 | persistent=yes
28 |
29 | team_name=Vanak
30 | user_team_name=_"Vanak"
31 |
32 | recruit=Orcish Grunt,Orcish Archer,Orcish Assassin,Wolf Rider,Troll Whelp
33 | gold=200
34 |
35 | {ai/aliases/stable_singleplayer.cfg}
36 | [ai]
37 | id=luaAI_test
38 | description=_"Lua AI Test"
39 | [engine]
40 | name="lua"
41 | code= <<
42 | --local ai_stdlib = wesnoth.require('ai/lua/stdlib.lua');
43 | --ai_stdlib.init(ai, true)
44 | return {} -- Just a dummy return
45 | >>
46 | [/engine]
47 | [/ai]
48 | ################## Or use external CA ##########################
49 | #[ai]
50 | # version=10710
51 | # [stage]
52 | # id=main_loop
53 | # name=ai_default_rca::candidate_action_evaluation_loop
54 | # [candidate_action]
55 | # engine=lua
56 | # name=external_example
57 | # location="~/add-ons/LuaAI_tests/ext_test.lua"
58 | # [/candidate_action]
59 | # [/stage]
60 | #[/ai]
61 | [/side]
62 |
63 | [side]
64 | side=2
65 | controller=human
66 | id=Bad Orc
67 | type=Orcish Ruler
68 | persistent=no
69 |
70 | team_name=Orcs
71 | user_team_name=_"Orcs"
72 |
73 | recruit=Orcish Grunt,Orcish Archer,Orcish Assassin,Wolf Rider,Troll Whelp
74 | gold=0
75 | [ai]
76 | aggression=1
77 | #{MODIFY_AI_DELETE_CANDIDATE_ACTION 2 main_loop combat}
78 | {MODIFY_AI_ADD_CANDIDATE_ACTION 2 main_loop (
79 | [candidate_action]
80 | engine=lua
81 | name=attack_highca
82 | id=attack_highca
83 | max_score=100010
84 | location="~add-ons/AI-demos/lua/ca_test.lua"
85 | [/candidate_action]
86 | )}
87 | [/ai]
88 | [/side]
89 |
90 | [event]
91 | name=prestart
92 |
93 | {VARIABLE scenario_name aid_test}
94 |
95 | [set_menu_item]
96 | id = m01
97 | description=_"Reload Lua code"
98 | image=items/ring-red.png~CROP(26,26,20,20)
99 | [command]
100 | [lua]
101 | code=<>
102 | [/lua]
103 | [/command]
104 | [default_hotkey]
105 | key=x
106 | [/default_hotkey]
107 | [/set_menu_item]
108 |
109 | [objectives]
110 | summary = _ "This scenario can be started directly from the commandline with '-taid_test' or by using the test scenario hotkey in the title screen." # wmllint: no spellcheck
111 | [/objectives]
112 | [/event]
113 | #ifndef TEST
114 | [/scenario]
115 | #else
116 | # wmlindent: start ignoring
117 | [/test]
118 | # wmlindent: stop ignoring
119 | #endif
120 |
--------------------------------------------------------------------------------
/AI-demos/units/Invisible_Unit.cfg:
--------------------------------------------------------------------------------
1 | #textdomain wesnoth-Grnk
2 |
3 | # This is just the Fog Clearer code, but with team color set
4 | [unit_type]
5 | id=Invisible Unit
6 | name=_ "dummy_unit^Invisible Unit"
7 | race=monster
8 | image="misc/blank-hex.png"
9 | ellipse=none
10 | hitpoints=100
11 | movement_type=fly
12 | movement=9
13 | experience=100
14 | level=2
15 | alignment=neutral
16 | advances_to=null
17 | cost=50
18 | usage=scout
19 |
20 | # This unit hides everywhere. It can therefore be used if one wants
21 | # a unit whose ellipse and status bars never show up, by using it on
22 | # an enemy side. If desired, it can be made visible by assigning it
23 | # role=IU_visible.
24 | [abilities]
25 | [hides]
26 | id=hides_everywhere
27 | affect_self=yes
28 | [filter]
29 | [not]
30 | role=IU_visible
31 | [/not]
32 | [/filter]
33 | [/hides]
34 | [/abilities]
35 |
36 | # Use attacks from the Javelineer
37 | [attack]
38 | name=spear
39 | description=_"spear"
40 | type=pierce
41 | range=melee
42 | damage=8
43 | number=3
44 | [specials]
45 | {WEAPON_SPECIAL_FIRSTSTRIKE}
46 | [/specials]
47 | [/attack]
48 | [attack]
49 | name=javelin
50 | description=_"javelin"
51 | icon=attacks/javelin-human.png
52 | type=pierce
53 | range=ranged
54 | damage=11
55 | number=2
56 | [/attack]
57 | [/unit_type]
58 |
--------------------------------------------------------------------------------
/AI-demos/version.lua:
--------------------------------------------------------------------------------
1 | return '0.14.15+dev'
--------------------------------------------------------------------------------
/AI-demos/version.txt:
--------------------------------------------------------------------------------
1 | 0.14.15+dev
--------------------------------------------------------------------------------
/COPYING.txt:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.
5 | 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Library General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 |
294 | Copyright (C)
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License
307 | along with this program; if not, write to the Free Software
308 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
309 |
310 |
311 | Also add information on how to contact you by electronic and paper mail.
312 |
313 | If the program is interactive, make it output a short notice like this
314 | when it starts in an interactive mode:
315 |
316 | Gnomovision version 69, Copyright (C) year name of author
317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
318 | This is free software, and you are welcome to redistribute it
319 | under certain conditions; type `show c' for details.
320 |
321 | The hypothetical commands `show w' and `show c' should show the appropriate
322 | parts of the General Public License. Of course, the commands you use may
323 | be called something other than `show w' and `show c'; they could even be
324 | mouse-clicks or menu items--whatever suits your program.
325 |
326 | You should also get your employer (if you work as a programmer) or your
327 | school, if any, to sign a "copyright disclaimer" for the program, if
328 | necessary. Here is a sample; alter the names:
329 |
330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
331 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
332 |
333 | , 1 April 1989
334 | Ty Coon, President of Vice
335 |
336 | This General Public License does not permit incorporating your program into
337 | proprietary programs. If your program is a subroutine library, you may
338 | consider it more useful to permit linking proprietary applications with the
339 | library. If this is what you want to do, use the GNU Library General
340 | Public License instead of this License.
341 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Wesnoth-AI-Demos
2 | ================
3 |
4 | Wesnoth AI Modification Demonstration Scenarios
5 |
6 | NOTES:
7 | * This add-on requires Wesnoth version 1.12.0 or later, but using the most recent stable version is recommended.
8 | * If done manually, the add-on needs to be installed into ~add-ons/AI-demos/ or just download it from the Wesnoth add-ons server.
9 |
10 | Home of the formidable Freelands AI, "Fred", and his little brother, the generic rushing AI "Ron"!
11 |
12 | Many other Micro AIs were also developed in this add-on, but have now mostly been moved into mainline, such as:
13 | * Animals
14 | * Bottleneck Defense
15 | * Goto
16 | * Guardians
17 | * Hang Out
18 | * Healer Support
19 | * Lurkers
20 | * Messenger Escort
21 | * Patrol
22 | * Protect Unit
23 | * Recruiting
24 | * Simple Attack
25 |
26 | For a complete and current list of the Micro AIs, see: http://wiki.wesnoth.org/Micro_AIs
27 |
28 | For more information, check out the thread on the official Wesnoth forums:
29 | http://forum.wesnoth.org/viewtopic.php?f=10&t=34976
30 |
31 | There is also a feedback thread specifically for the Micro AIs:
32 | http://forums.wesnoth.org/viewtopic.php?f=10&t=39456
33 |
34 | And of course get the game itself at http://wesnoth.org/
35 |
36 | All of our code is licensed under the GPLv2+, just like the rest of Wesnoth.
37 |
--------------------------------------------------------------------------------