1 /*
2    Copyright (C) 2009 - 2018 by Bartosz Waresiak <dragonking@o2.pl>
3    Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY.
11 
12    See the COPYING file for more details.
13 */
14 
15 /**
16  * @file
17  * Defines formula ai candidate actions
18  * */
19 
20 #include "ai/formula/ai.hpp"
21 #include "ai/formula/candidates.hpp"
22 #include "game_board.hpp"
23 #include "log.hpp"
24 #include "resources.hpp"
25 #include "units/unit.hpp"
26 
27 static lg::log_domain log_formula_ai("ai/engine/fai");
28 #define ERR_AI LOG_STREAM(err, log_formula_ai)
29 
30 
31 namespace wfl {
32 
base_candidate_action(const std::string & name,const std::string & type,const config & cfg,function_symbol_table * function_table)33 base_candidate_action::base_candidate_action(const std::string& name,
34 		const std::string& type, const config& cfg,
35 		function_symbol_table* function_table) :
36 	name_(name),
37 	type_(type),
38 	eval_(new formula(cfg["evaluation"], function_table)),
39 	action_(new formula(cfg["action"], function_table)),
40 	score_(0)
41 {}
42 
execute_formula(const const_formula_ptr & formula,const formula_callable & callable,const ai::formula_ai * ai)43 int base_candidate_action::execute_formula(const const_formula_ptr& formula,
44 			const formula_callable& callable, const ai::formula_ai* ai)
45 {
46 	int res = 0;
47 	try {
48 		res = (formula::evaluate(formula, callable)).as_int();
49 	} catch(const formula_error& e) {
50 		ai->handle_exception(e);
51 		res = 0;
52 	} catch(const type_error& e) {
53 		res = 0;
54 		ERR_AI << "formula type error while evaluating candidate action: " << e.message << std::endl;
55 	}
56 
57 	return res;
58 }
59 
candidate_action_with_filters(const std::string & name,const std::string & type,const config & cfg,function_symbol_table * function_table)60 candidate_action_with_filters::candidate_action_with_filters(
61 		const std::string& name, const std::string& type,
62 		const config& cfg, function_symbol_table* function_table)
63 	: base_candidate_action(name, type, cfg, function_table)
64 	, filter_map_()
65 {
66 	const config & filter_params = cfg.child("filter");
67 
68 	if( filter_params ) {
69 		for(const config::attribute filter_param : filter_params.attribute_range())
70 		{
71 			const_formula_ptr filter_formula(
72 					new formula(filter_param.second, function_table));
73 
74 			filter_map_[filter_param.first]=filter_formula;
75 		}
76 	}
77 }
78 
do_filtering(ai::formula_ai * ai,variant & input,const_formula_ptr formula)79 variant candidate_action_with_filters::do_filtering(ai::formula_ai* ai, variant& input, const_formula_ptr formula)
80 {
81 	map_formula_callable callable(ai->fake_ptr());
82 	callable.add("input", input);
83 
84 	return formula::evaluate(formula, callable);
85 
86 }
87 
move_candidate_action(const std::string & name,const std::string & type,const config & cfg,function_symbol_table * function_table)88 move_candidate_action::move_candidate_action(const std::string& name,
89 		const std::string& type, const config& cfg,
90 		function_symbol_table* function_table)
91 	: candidate_action_with_filters(name, type, cfg, function_table)
92 	, my_unit_()
93 {}
94 
evaluate(ai::formula_ai * ai,unit_map & units)95 void move_candidate_action::evaluate(ai::formula_ai* ai, unit_map& units)
96 {
97 	score_ = 0;
98 
99 	candidate_action_filters::const_iterator me_filter = filter_map_.find("me");
100 
101 	std::vector<variant> unit_vector;
102 
103 	for(unit_map::unit_iterator i = units.begin() ; i != units.end() ; ++i)
104 	{
105 		if (i->side() == ai->get_side() && i->movement_left() > 0) {
106 			unit_vector.emplace_back(std::make_shared<unit_callable>(*i));
107 		}
108 	}
109 
110 	variant my_units(unit_vector);
111 
112 	variant filtered_units;
113 	try {
114 		if(me_filter != filter_map_.end() )
115 			filtered_units = do_filtering(ai, my_units, me_filter->second);
116 		else
117 			filtered_units=my_units;
118 	}
119 	catch(const formula_error& e) {
120 		ai->handle_exception(e, "Error while executing filter formula for '" + get_name() + "' Candidate Action");
121 		return;
122 	}
123 
124 	for(variant_iterator i = filtered_units.begin() ; i != filtered_units.end() ; ++i)
125 	{
126 			map_formula_callable callable(ai->fake_ptr());
127 			callable.add("me", *i);
128 
129 			int res = execute_formula(eval_, callable, ai);
130 
131 			if(res > score_) {
132 				score_ = res;
133 				my_unit_ = *i;
134 			}
135 	}
136 }
137 
update_callable_map(map_formula_callable & callable)138 void move_candidate_action::update_callable_map(map_formula_callable& callable)
139 {
140 	callable.add("me", my_unit_);
141 }
142 
attack_candidate_action(const std::string & name,const std::string & type,const config & cfg,function_symbol_table * function_table)143 attack_candidate_action::attack_candidate_action(const std::string& name,
144 		const std::string& type, const config& cfg,
145 		function_symbol_table* function_table)
146 	: candidate_action_with_filters(name, type, cfg, function_table)
147 	, my_unit_()
148 	, enemy_unit_()
149 {}
150 
evaluate(ai::formula_ai * ai,unit_map & units)151 void attack_candidate_action::evaluate(ai::formula_ai* ai, unit_map& units)
152 {
153 	score_ = 0;
154 
155 	candidate_action_filters::const_iterator me_filter = filter_map_.find("me");
156 	candidate_action_filters::const_iterator target_filter = filter_map_.find("target");
157 
158 	std::vector<variant> my_res, enemy_res;
159 
160 	for(unit_map::unit_iterator i = units.begin() ; i != units.end() ; ++i)
161 	{
162 		if (i->side() == ai->get_side())
163 		{
164 			if (i->attacks_left()) {
165 				my_res.emplace_back(std::make_shared<unit_callable>(*i));
166 			}
167 		} else
168 		{
169 			if (ai->current_team().is_enemy(i->side()) && !i->incapacitated() && !i->invisible(i->get_location(), *resources::gameboard)) {
170 				enemy_res.emplace_back(std::make_shared<unit_callable>(*i));
171 			}
172 		}
173 	}
174 	variant my_units(my_res);
175 	variant enemy_units(enemy_res);
176 
177 	variant filtered_my_units, filtered_enemy_units;
178 	try {
179 		if(me_filter != filter_map_.end() )
180 			filtered_my_units = do_filtering(ai, my_units, me_filter->second);
181 		else
182 			filtered_my_units = my_units;
183 
184 		if(target_filter != filter_map_.end() )
185 			filtered_enemy_units = do_filtering(ai, enemy_units, target_filter->second);
186 		else
187 			filtered_enemy_units = enemy_units;
188 	}
189 	catch(const formula_error& e) {
190 		ai->handle_exception(e, "Error while executing filter formula for '" + get_name() + "' Candidate Action");
191 		return;
192 	}
193 
194 	try{
195 		if( !(filtered_enemy_units.num_elements() && filtered_my_units.num_elements() ) )
196 			return;
197 	}
198 	catch(const type_error& e) {
199 		ERR_AI << "Error while executing filter formulas for '" + get_name() + "' Candidate Action: " << e.message << std::endl;
200 		return;
201 	}
202 
203 	std::vector<variant> my_units_flt;
204 	std::vector<variant> enemy_units_flt;
205 
206 	for(variant_iterator i = filtered_my_units.begin() ; i != filtered_my_units.end() ; ++i) {
207 		auto u_callable = (*i).try_convert<const unit_callable>();
208 		if(!u_callable) {
209 			ERR_AI << "ERROR in "<< get_name() << "Candidate Action: Filter formula returned table that does not contain units" << std::endl;
210 			return;
211 		}
212 		my_units_flt.emplace_back(u_callable);
213 	}
214 
215 	for(variant_iterator i = filtered_enemy_units.begin() ; i != filtered_enemy_units.end() ; ++i) {
216 		auto u_callable = (*i).try_convert<const unit_callable>();
217 		if(!u_callable) {
218 			ERR_AI << "ERROR in "<< get_name() << "Candidate Action: Filter formula returned table that does not contain units" << std::endl;
219 			return;
220 		}
221 		enemy_units_flt.emplace_back(u_callable);
222 	}
223 
224 	for( size_t my_unit = 0 ; my_unit < my_units_flt.size() ; ++my_unit){
225 		auto my_unit_callable = my_units_flt[my_unit].convert_to<unit_callable>();
226 		for( size_t enemy_unit = 0 ; enemy_unit < enemy_units_flt.size() ; ++enemy_unit){
227 			auto enemy_unit_callable = enemy_units_flt[enemy_unit].convert_to<unit_callable>();
228 			if(ai->can_reach_unit(my_unit_callable->get_location(), enemy_unit_callable->get_location())) {
229 
230 				map_formula_callable callable(ai->fake_ptr());
231 				callable.add("me", filtered_my_units[my_unit]);
232 				callable.add("target", filtered_enemy_units[enemy_unit]);
233 
234 				int res = execute_formula(eval_, callable, ai);
235 
236 				if(res > score_) {
237 					score_ = res;
238 					my_unit_ = filtered_my_units[my_unit];
239 					enemy_unit_ = filtered_enemy_units[enemy_unit];
240 				}
241 			}
242 		}
243 	}
244 }
245 
update_callable_map(map_formula_callable & callable)246 void attack_candidate_action::update_callable_map(map_formula_callable& callable)
247 {
248 	callable.add("me", my_unit_);
249 	callable.add("target", enemy_unit_);
250 }
251 
252 }
253