1 /*
2    Copyright (C) 2008 - 2018 by David White <dave@whitevine.net>
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 - headers
18  */
19 
20 #include "ai/formula/ai.hpp"
21 
22 #include "ai/formula/callable_objects.hpp"   // for unit_callable, etc
23 #include "chat_events.hpp"              // for chat_handler, etc
24 #include "display_chat_manager.hpp"
25 #include "formula/function.hpp"         // for formula_expression
26 #include "game_board.hpp"         // for game_board
27 #include "game_display.hpp"       // for game_display
28 #include "log.hpp"                // for LOG_STREAM, logger, etc
29 #include "map/map.hpp"                      // for gamemap
30 #include "pathfind/pathfind.hpp"  // for plain_route, etc
31 #include "pathfind/teleport.hpp"  // for get_teleport_locations, etc
32 #include "recall_list_manager.hpp"      // for recall_list_manager
33 #include "resources.hpp"          // for gameboard, teams, units, etc
34 #include "serialization/string_utils.hpp"  // for split
35 #include "team.hpp"                     // for team
36 #include "terrain/filter.hpp"     // for terrain_filter
37 #include "time_of_day.hpp"              // for time_of_day
38 #include "tod_manager.hpp"        // for tod_manager
39 #include "tstring.hpp"                  // for t_string, operator+
40 #include "units/unit.hpp"               // for unit
41 #include "units/formula_manager.hpp"  // for unit_formula_manager
42 #include "units/ptr.hpp"                 // for unit_ptr
43 #include "units/types.hpp"
44 #include "formula/formula.hpp"  // for formula_error, formula, etc
45 #include "map/location.hpp"  // for map_location, etc
46 #include "ai/actions.hpp"               // for recall_result, etc
47 #include "ai/manager.hpp"               // for manager
48 #include "ai/composite/contexts.hpp"
49 #include "ai/composite/stage.hpp"  // for stage
50 #include "ai/default/contexts.hpp"  // for attack_analysis
51 #include "ai/formula/function_table.hpp"           // for ai_function_symbol_table
52 #include "ai/game_info.hpp"  // for move_result_ptr, move_map, etc
53 #include "ai/formula/candidates.hpp"               // for base_candidate_action, etc
54 
55 #include <cassert>                     // for assert
56 #include <ctime>                       // for time
57 #include <map>                          // for multimap<>::const_iterator, etc
58 #include <sstream>                      // for operator<<, basic_ostream, etc
59 #include <stack>                        // for stack
60 #include <vector>                       // for vector, allocator, etc
61 
62 static lg::log_domain log_formula_ai("ai/engine/fai");
63 #define DBG_AI LOG_STREAM(debug, log_formula_ai)
64 #define LOG_AI LOG_STREAM(info, log_formula_ai)
65 #define WRN_AI LOG_STREAM(warn, log_formula_ai)
66 #define ERR_AI LOG_STREAM(err, log_formula_ai)
67 
68 
69 using namespace wfl;
70 
71 namespace ai {
72 
73 using ca_ptr = wfl::candidate_action_ptr;
74 
load_candidate_action_from_config(const config & rc_action)75 ca_ptr formula_ai::load_candidate_action_from_config(const config& rc_action)
76 {
77 	ca_ptr new_ca;
78 	const t_string &name = rc_action["name"];
79 	try {
80 		const t_string &type = rc_action["type"];
81 
82 		if( type == "movement") {
83 			new_ca = std::make_shared<move_candidate_action>(name, type, rc_action, &function_table_);
84 		} else if( type == "attack") {
85 			new_ca = std::make_shared<attack_candidate_action>(name, type, rc_action, &function_table_);
86 		} else {
87 			ERR_AI << "Unknown candidate action type: " << type << std::endl;
88 		}
89 	} catch(const formula_error& e) {
90 		handle_exception(e, "Error while registering candidate action '" + name + "'");
91 	}
92 	return new_ca;
93 }
94 
get_recursion_count() const95 int formula_ai::get_recursion_count() const{
96 	return recursion_counter_.get_count();
97 }
98 
99 
formula_ai(readonly_context & context,const config & cfg)100 formula_ai::formula_ai(readonly_context &context, const config &cfg)
101 	:
102 	readonly_context_proxy(),
103 	formula_callable(),
104 	ai_ptr_(nullptr),
105 	cfg_(cfg),
106 	recursion_counter_(context.get_recursion_count()),
107 	keeps_cache_(),
108 	attacks_callable(*this, resources::gameboard->units()),
109 //	infinite_loop_guardian_(),
110 	vars_(),
111 	function_table_(*this)
112 {
113 	init_readonly_context_proxy(context);
114 	LOG_AI << "creating new formula ai"<< std::endl;
115 }
116 
handle_exception(const formula_error & e) const117 void formula_ai::handle_exception(const formula_error& e) const
118 {
119 	handle_exception(e, "Error while parsing formula");
120 }
121 
handle_exception(const formula_error & e,const std::string & failed_operation) const122 void formula_ai::handle_exception(const formula_error& e, const std::string& failed_operation) const
123 {
124 	LOG_AI << failed_operation << ": " << e.formula << std::endl;
125 	display_message(failed_operation + ": " + e.formula);
126 	//if line number = 0, don't display info about filename and line number
127 	if (e.line != 0) {
128 		LOG_AI << e.type << " in " << e.filename << ":" << e.line << std::endl;
129 		display_message(e.type + " in " + e.filename + ":" + std::to_string(e.line));
130 	} else {
131 		LOG_AI << e.type << std::endl;
132 		display_message(e.type);
133 	}
134 }
135 
display_message(const std::string & msg) const136 void formula_ai::display_message(const std::string& msg) const
137 {
138 	game_display::get_singleton()->get_chat_manager().add_chat_message(time(nullptr), "wfl", get_side(), msg,
139 				events::chat_handler::MESSAGE_PUBLIC, false);
140 
141 }
142 
create_optional_formula(const std::string & formula_string) const143 formula_ptr formula_ai::create_optional_formula(const std::string& formula_string) const {
144 	try{
145 		return formula::create_optional_formula(formula_string, &function_table_);
146 	}
147 	catch(const formula_error& e) {
148 		handle_exception(e);
149 		return wfl::formula_ptr();
150 	}
151 }
152 
153 
set_ai_context(ai_context * context)154 void formula_ai::set_ai_context(ai_context *context)
155 {
156 	ai_ptr_ = context;
157 }
158 
159 
evaluate(const std::string & formula_str)160 std::string formula_ai::evaluate(const std::string& formula_str)
161 {
162 	try{
163 
164 		formula f(formula_str, &function_table_);
165 
166 		map_formula_callable callable(fake_ptr());
167 
168 		//formula_debugger fdb;
169 		const variant v = f.evaluate(callable,nullptr);
170 
171 		if (ai_ptr_) {
172 			variant var = variant(this->fake_ptr()).execute_variant(v);
173 
174 			if (  !var.is_empty() ) {
175 				return "Made move: " + var.to_debug_string();
176 			}
177 		}
178 
179 		return v.to_debug_string();
180 	}
181 	catch(formula_error& e) {
182 		e.line = 0;
183 		handle_exception(e);
184 		throw;
185 	}
186 }
187 
make_action(wfl::const_formula_ptr formula_,const wfl::formula_callable & variables)188 wfl::variant formula_ai::make_action(wfl::const_formula_ptr formula_, const wfl::formula_callable& variables)
189 {
190 	if (!formula_) {
191 		throw formula_error("null formula passed to make_action","","formula",0);
192 	}
193 	LOG_AI << "do move...\n";
194 	const variant var = formula_->evaluate(variables);///@todo 1.9 add formula_debugger
195 	variant res;
196 
197 	if (ai_ptr_) {
198 		res = variant(this->fake_ptr()).execute_variant(var);
199 	} else {
200 		ERR_AI << "skipped execution of action because ai context is not set correctly" << std::endl;
201 	}
202 
203 	return res;
204 }
205 
shortest_path_calculator(const map_location & src,const map_location & dst,unit_map::iterator & unit_it,pathfind::teleport_map & allowed_teleports) const206 pathfind::plain_route formula_ai::shortest_path_calculator(const map_location &src,
207 	const map_location &dst, unit_map::iterator &unit_it,
208 	pathfind::teleport_map& allowed_teleports) const
209 {
210     map_location destination = dst;
211 
212     unit_map &units_ = resources::gameboard->units();
213     pathfind::shortest_path_calculator calc(*unit_it, current_team(), resources::gameboard->teams(), resources::gameboard->map());
214 
215     unit_map::const_iterator dst_un = units_.find(destination);
216 
217     map_location res;
218 
219     if( dst_un != units_.end() ) {
220         //there is unit standing at dst, let's try to find free hex to move to
221         const map_location::DIRECTION preferred = destination.get_relative_dir(src);
222 
223         int best_rating = 100;//smaller is better
224         adjacent_loc_array_t adj;
225         get_adjacent_tiles(destination,adj.data());
226 
227         for(size_t n = 0; n < adj.size(); ++n) {
228                 if(resources::gameboard->map().on_board(adj[n]) == false) {
229                         continue;
230                 }
231 
232                 if(units_.find(adj[n]) != units_.end()) {
233                         continue;
234                 }
235 
236                 static const size_t NDIRECTIONS = map_location::NDIRECTIONS;
237                 unsigned int difference = std::abs(int(preferred - n));
238                 if(difference > NDIRECTIONS/2) {
239                         difference = NDIRECTIONS - difference;
240                 }
241 
242                 const int rating = difference * 2;
243                 if(rating < best_rating || res.valid() == false) {
244                        best_rating = rating;
245                        res = adj[n];
246                 }
247         }
248     }
249 
250     if( res != map_location() ) {
251         destination = res;
252     }
253 
254     pathfind::plain_route route = pathfind::a_star_search(src, destination, 1000.0, calc,
255             resources::gameboard->map().w(), resources::gameboard->map().h(), &allowed_teleports);
256 
257     return route;
258 }
259 
get_allowed_teleports(unit_map::iterator & unit_it) const260 pathfind::teleport_map formula_ai::get_allowed_teleports(unit_map::iterator& unit_it) const
261 {
262   return pathfind::get_teleport_locations(*unit_it, current_team(), true);
263 }
264 
add_formula_function(const std::string & name,const_formula_ptr formula,const_formula_ptr precondition,const std::vector<std::string> & args)265 void formula_ai::add_formula_function(const std::string& name, const_formula_ptr formula, const_formula_ptr precondition, const std::vector<std::string>& args)
266 {
267 	formula_function_ptr fcn(new user_formula_function(name,formula,precondition,args));
268 	function_table_.add_function(name, std::move(fcn));
269 }
270 
271 namespace {
272 template<typename Container>
villages_from_set(const Container & villages,const std::set<map_location> * exclude=nullptr)273 variant villages_from_set(const Container& villages,
274 				          const std::set<map_location>* exclude=nullptr) {
275 	std::vector<variant> vars;
276 	for(const map_location& loc : villages) {
277 		if(exclude && exclude->count(loc)) {
278 			continue;
279 		}
280 		vars.emplace_back(std::make_shared<location_callable>(loc));
281 	}
282 
283 	return variant(vars);
284 }
285 }
286 
get_value(const std::string & key) const287 variant formula_ai::get_value(const std::string& key) const
288 {
289 	const unit_map& units = resources::gameboard->units();
290 
291 	if(key == "aggression")
292 	{
293 		return variant(get_aggression()*1000,variant::DECIMAL_VARIANT);
294 
295 	} else if(key == "attack_depth")
296 	{
297 		return variant(get_attack_depth());
298 
299 	} else if(key == "avoid")
300 	{
301 		std::set<map_location> av_locs;
302 		get_avoid().get_locations(av_locs);
303 		return villages_from_set(av_locs);
304 
305 	} else if(key == "caution")
306 	{
307 		return variant(get_caution()*1000,variant::DECIMAL_VARIANT);
308 
309 	} else if(key == "grouping")
310 	{
311 		return variant(get_grouping());
312 
313 	} else if(key == "leader_aggression")
314 	{
315 		return variant(get_leader_aggression()*1000,variant::DECIMAL_VARIANT);
316 
317 	} else if(key == "leader_ignores_keep")
318 	{
319 		return variant(get_leader_ignores_keep());
320 
321 	} else if(key == "leader_value")
322 	{
323 		return variant(get_leader_value()*1000,variant::DECIMAL_VARIANT);
324 
325 	} else if(key == "passive_leader")
326 	{
327 		return variant(get_passive_leader());
328 
329 	} else if(key == "passive_leader_shares_keep")
330 	{
331 		return variant(get_passive_leader_shares_keep());
332 
333 	} else if(key == "recruitment_pattern")
334 	{
335 		const std::vector<std::string> &rp = get_recruitment_pattern();
336 		std::vector<variant> vars;
337 		for(const std::string &i : rp) {
338 			vars.emplace_back(i);
339 		}
340 		return variant(vars);
341 
342 	} else if(key == "scout_village_targeting")
343 	{
344 		return variant(get_scout_village_targeting()*1000,variant::DECIMAL_VARIANT);
345 
346 	} else if(key == "support_villages")
347 	{
348 		return variant(get_support_villages());
349 
350 	} else if(key == "village_value")
351 	{
352 		return variant(get_village_value()*1000,variant::DECIMAL_VARIANT);
353 
354 	} else if(key == "villages_per_scout")
355 	{
356 		return variant(get_villages_per_scout());
357 
358 	} else if(key == "attacks")
359 	{
360 		return get_attacks_as_variant();
361 
362 	} else if(key == "turn")
363 	{
364 		return variant(resources::tod_manager->turn());
365 
366 	} else if(key == "time_of_day")
367 	{
368 		return variant(resources::tod_manager->get_time_of_day().id);
369 
370 	} else if(key == "my_side")
371 	{
372 		return variant(std::make_shared<team_callable>(resources::gameboard->get_team(get_side())));
373 
374 	} else if(key == "my_side_number")
375 	{
376 		return variant(get_side()-1);
377 
378 	} else if(key == "teams")
379 	{
380 		std::vector<variant> vars;
381 		for(std::vector<team>::const_iterator i = resources::gameboard->teams().begin(); i != resources::gameboard->teams().end(); ++i) {
382 			vars.emplace_back(std::make_shared<team_callable>(*i));
383 		}
384 		return variant(vars);
385 
386 	} else if(key == "allies")
387 	{
388 		std::vector<variant> vars;
389 		for( size_t i = 0; i < resources::gameboard->teams().size(); ++i) {
390 			if ( !current_team().is_enemy( i+1 ) )
391 				vars.emplace_back(i);
392 		}
393 		return variant(vars);
394 
395 	} else if(key == "enemies")
396 	{
397 		std::vector<variant> vars;
398 		for( size_t i = 0; i < resources::gameboard->teams().size(); ++i) {
399 			if ( current_team().is_enemy( i+1 ) )
400 				vars.emplace_back(i);
401 		}
402 		return variant(vars);
403 
404 	} else if(key == "my_recruits")
405 	{
406 		std::vector<variant> vars;
407 
408 		unit_types.build_all(unit_type::FULL);
409 
410 		const std::set<std::string>& recruits = current_team().recruits();
411 		if(recruits.empty()) {
412 			return variant(vars);
413 		}
414 		for(std::set<std::string>::const_iterator i = recruits.begin(); i != recruits.end(); ++i)
415 		{
416 			const unit_type *ut = unit_types.find(*i);
417 			if (ut)
418 			{
419 				vars.emplace_back(std::make_shared<unit_type_callable>(*ut));
420 			}
421 		}
422 		return variant(vars);
423 
424 	} else if(key == "recruits_of_side")
425 	{
426 		std::vector<variant> vars;
427 		std::vector< std::vector< variant>> tmp;
428 
429 		unit_types.build_all(unit_type::FULL);
430 
431 		for( size_t i = 0; i<resources::gameboard->teams().size(); ++i)
432 		{
433 			std::vector<variant> v;
434 			tmp.push_back( v );
435 
436 			const std::set<std::string>& recruits = resources::gameboard->teams()[i].recruits();
437 			if(recruits.empty()) {
438 				continue;
439 			}
440 			for(std::set<std::string>::const_iterator str_it = recruits.begin(); str_it != recruits.end(); ++str_it)
441 			{
442 				const unit_type *ut = unit_types.find(*str_it);
443 				if (ut)
444 				{
445 					tmp[i].emplace_back(std::make_shared<unit_type_callable>(*ut));
446 				}
447 			}
448 		}
449 
450 		for( size_t i = 0; i<tmp.size(); ++i)
451 			vars.emplace_back(tmp[i]);
452 		return variant(vars);
453 
454 	} else if(key == "units")
455 	{
456 		std::vector<variant> vars;
457 		for(unit_map::const_iterator i = units.begin(); i != units.end(); ++i) {
458 			vars.emplace_back(std::make_shared<unit_callable>(*i));
459 		}
460 		return variant(vars);
461 
462 	} else if(key == "units_of_side")
463 	{
464 		std::vector<variant> vars;
465 		std::vector< std::vector< variant>> tmp;
466 		for( size_t i = 0; i<resources::gameboard->teams().size(); ++i)
467 		{
468 			std::vector<variant> v;
469 			tmp.push_back( v );
470 		}
471 		for(const unit &u : units) {
472 			tmp[u.side() - 1].emplace_back(std::make_shared<unit_callable>(u));
473 		}
474 		for( size_t i = 0; i<tmp.size(); ++i)
475 			vars.emplace_back(tmp[i]);
476 		return variant(vars);
477 
478 	} else if(key == "my_units")
479 	{
480 		std::vector<variant> vars;
481 		for(unit_map::const_iterator i = units.begin(); i != units.end(); ++i) {
482 			if (i->side() == get_side()) {
483 				vars.emplace_back(std::make_shared<unit_callable>(*i));
484 			}
485 		}
486 		return variant(vars);
487 
488 	} else if(key == "enemy_units")
489 	{
490 		std::vector<variant> vars;
491 		for(unit_map::const_iterator i = units.begin(); i != units.end(); ++i) {
492 			if (current_team().is_enemy(i->side())) {
493 				if (!i->incapacitated()) {
494 					vars.emplace_back(std::make_shared<unit_callable>(*i));
495 				}
496 			}
497 		}
498 		return variant(vars);
499 
500 	} else if(key == "my_moves")
501 	{
502 		return variant(std::make_shared<move_map_callable>(get_srcdst(), get_dstsrc(), units));
503 
504 	} else if(key == "my_attacks")
505 	{
506 		return variant(attacks_callable.fake_ptr());
507 	} else if(key == "enemy_moves")
508 	{
509 		return variant(std::make_shared<move_map_callable>(get_enemy_srcdst(), get_enemy_dstsrc(), units));
510 
511 	} else if(key == "my_leader")
512 	{
513 		unit_map::const_iterator i = units.find_leader(get_side());
514 		if(i == units.end()) {
515 			return variant();
516 		}
517 		return variant(std::make_shared<unit_callable>(*i));
518 
519 	} else if(key == "recall_list")
520 	{
521 		std::vector<variant> tmp;
522 
523 		for(std::vector<unit_ptr >::const_iterator i = current_team().recall_list().begin(); i != current_team().recall_list().end(); ++i) {
524 			tmp.emplace_back(std::make_shared<unit_callable>(**i));
525 		}
526 
527 		return variant(tmp);
528 
529 	} else if(key == "vars")
530 	{
531 		return variant(vars_.fake_ptr());
532 	} else if(key == "keeps")
533 	{
534 		return get_keeps();
535 	} else if(key == "map")
536 	{
537 		return variant(std::make_shared<gamemap_callable>(*resources::gameboard));
538 	} else if(key == "villages")
539 	{
540 		return villages_from_set(resources::gameboard->map().villages());
541 	} else if(key == "villages_of_side")
542 	{
543 		std::vector<variant> vars;
544 		for(size_t i = 0; i<resources::gameboard->teams().size(); ++i)
545 		{
546 			vars.emplace_back();
547 		}
548 		for(size_t i = 0; i<vars.size(); ++i)
549 		{
550 			vars[i] = villages_from_set(resources::gameboard->teams()[i].villages());
551 		}
552 		return variant(vars);
553 
554 	} else if(key == "my_villages")
555 	{
556 		return villages_from_set(current_team().villages());
557 
558 	} else if(key == "enemy_and_unowned_villages")
559 	{
560 		return villages_from_set(resources::gameboard->map().villages(), &current_team().villages());
561 	}
562 
563 	return variant();
564 }
565 
get_inputs(formula_input_vector & inputs) const566 void formula_ai::get_inputs(formula_input_vector& inputs) const
567 {
568 	add_input(inputs, "aggression");
569 	add_input(inputs, "leader_aggression");
570 	add_input(inputs, "caution");
571 	add_input(inputs, "attacks");
572 	add_input(inputs, "my_side");
573 	add_input(inputs, "teams");
574 	add_input(inputs, "turn");
575 	add_input(inputs, "time_of_day");
576 	add_input(inputs, "keeps");
577 	add_input(inputs, "vars");
578 	add_input(inputs, "allies");
579 	add_input(inputs, "enemies");
580 	add_input(inputs, "map");
581 	add_input(inputs, "my_attacks");
582 	add_input(inputs, "enemy_moves");
583 	add_input(inputs, "my_leader");
584 	add_input(inputs, "my_recruits");
585 	//add_input(inputs, "recall_list");
586 	add_input(inputs, "recruits_of_side");
587 	add_input(inputs, "units");
588 	add_input(inputs, "units_of_side");
589 	add_input(inputs, "my_units");
590 	add_input(inputs, "enemy_units");
591 	add_input(inputs, "villages");
592 	add_input(inputs, "my_villages");
593 	add_input(inputs, "villages_of_side");
594 	add_input(inputs, "enemy_and_unowned_villages");
595 }
596 
set_value(const std::string & key,const variant & value)597 void formula_ai::set_value(const std::string& key, const variant& value) {
598 	vars_.mutate_value(key, value);
599 }
600 
get_keeps() const601 variant formula_ai::get_keeps() const
602 {
603 	if(keeps_cache_.is_null()) {
604 		std::vector<variant> vars;
605 		for(size_t x = 0; x != size_t(resources::gameboard->map().w()); ++x) {
606 			for(size_t y = 0; y != size_t(resources::gameboard->map().h()); ++y) {
607 				const map_location loc(x,y);
608 				if(resources::gameboard->map().is_keep(loc)) {
609 					adjacent_loc_array_t adj;
610 					get_adjacent_tiles(loc,adj.data());
611 					for(size_t n = 0; n < adj.size(); ++n) {
612 						if(resources::gameboard->map().is_castle(adj[n])) {
613 							vars.emplace_back(std::make_shared<location_callable>(loc));
614 							break;
615 						}
616 					}
617 				}
618 			}
619 		}
620 		keeps_cache_ = variant(vars);
621 	}
622 
623 	return keeps_cache_;
624 }
625 
can_reach_unit(map_location unit_A,map_location unit_B) const626 bool formula_ai::can_reach_unit(map_location unit_A, map_location unit_B) const {
627 	if (tiles_adjacent(unit_A,unit_B)) {
628 		return true;
629 	}
630 	move_map::const_iterator i;
631 	std::pair<move_map::const_iterator,
632 			  move_map::const_iterator> unit_moves;
633 
634 	unit_moves = get_srcdst().equal_range(unit_A);
635 	for(i = unit_moves.first; i != unit_moves.second; ++i) {
636 		if (tiles_adjacent((*i).second,unit_B)) {
637 			return true;
638 		}
639 	}
640 	return false;
641 }
642 
on_create()643 void formula_ai::on_create(){
644 	//make sure we don't run out of refcount
645 
646 	for(const config &func : cfg_.child_range("function"))
647 	{
648 		const t_string &name = func["name"];
649 		const t_string &inputs = func["inputs"];
650 		const t_string &formula_str = func["formula"];
651 
652 		std::vector<std::string> args = utils::split(inputs);
653 		try {
654 			add_formula_function(name,
655 					     create_optional_formula(formula_str),
656 					     create_optional_formula(func["precondition"]),
657 					     args);
658 		}
659 		catch(const formula_error& e) {
660 			handle_exception(e, "Error while registering function '" + name + "'");
661 		}
662 	}
663 
664 
665 	vars_ = map_formula_callable();
666 	if (const config &ai_vars = cfg_.child("vars"))
667 	{
668 		variant var;
669 		for(const config::attribute &i : ai_vars.attribute_range()) {
670 			var.serialize_from_string(i.second);
671 			vars_.add(i.first, var);
672 		}
673 	}
674 
675 
676 }
677 
678 
evaluate_candidate_action(ca_ptr fai_ca)679 void formula_ai::evaluate_candidate_action(ca_ptr fai_ca)
680 {
681 	fai_ca->evaluate(this,resources::gameboard->units());
682 
683 }
684 
execute_candidate_action(ca_ptr fai_ca)685 bool formula_ai::execute_candidate_action(ca_ptr fai_ca)
686 {
687 	map_formula_callable callable(fake_ptr());
688 	fai_ca->update_callable_map( callable );
689 	const_formula_ptr move_formula(fai_ca->get_action());
690 	return !make_action(move_formula, callable).is_empty();
691 }
692 
693 #if 0
694 formula_ai::gamestate_change_observer::gamestate_change_observer() :
695 	set_var_counter_(), set_unit_var_counter_(), continue_counter_()
696 {
697 	ai::manager::get_singleton().add_gamestate_observer(this);
698 }
699 
700 formula_ai::gamestate_change_observer::~gamestate_change_observer() {
701 	ai::manager::get_singleton().remove_gamestate_observer(this);
702 }
703 
704 void formula_ai::gamestate_change_observer::handle_generic_event(const std::string& /*event_name*/) {
705 	set_var_counter_ = 0;
706 	set_unit_var_counter_ = 0;
707 	continue_counter_ = 0;
708 }
709 
710 //return false if number of calls exceeded MAX_CALLS
711 bool formula_ai::gamestate_change_observer::set_var_check() {
712 	if(set_var_counter_ >= MAX_CALLS)
713 	    return false;
714 
715 	set_var_counter_++;
716 	return true;
717 }
718 
719 bool formula_ai::gamestate_change_observer::set_unit_var_check() {
720 	if(set_unit_var_counter_ >= MAX_CALLS)
721 	    return false;
722 
723 	set_unit_var_counter_++;
724 	return true;
725 }
726 
727 bool formula_ai::gamestate_change_observer::continue_check() {
728 	if(continue_counter_ >= MAX_CALLS)
729 	    return false;
730 
731 	continue_counter_++;
732 	return true;
733 }
734 #endif
735 
to_config() const736 config formula_ai::to_config() const
737 {
738 	if (!cfg_)
739 	{
740 		return config();
741 	}
742 	DBG_AI << "formula_ai::to_config(): "<< cfg_<<std::endl;
743 	config cfg = cfg_;
744 
745 	//formula AI variables
746 	cfg.clear_children("vars");
747 	if (vars_.empty() == false) {
748 		config &ai_vars = cfg.add_child("vars");
749 
750 		std::string str;
751 		for(map_formula_callable::const_iterator i = vars_.begin(); i != vars_.end(); ++i)
752 		{
753 			try {
754 				str = i->second.serialize_to_string();
755 			} catch(const type_error&) {
756 				WRN_AI << "variable ["<< i->first <<"] is not serializable - it will not be persisted across savegames"<<std::endl;
757 				continue;
758 			}
759 				if (!str.empty())
760 				{
761 					ai_vars[i->first] = str;
762 					str.clear();
763 				}
764 		}
765 	}
766 
767 	return cfg;
768 }
769 
770 } // end of namespace ai
771