The Battle for Wesnoth  1.15.12+dev
callable_objects.cpp
Go to the documentation of this file.
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 #include "ai/formula/ai.hpp"
16 #include "attack_prediction.hpp"
17 #include "game_board.hpp"
20 #include "resources.hpp"
21 #include "map/map.hpp"
22 #include "ai/game_info.hpp"
23 #include "ai/actions.hpp"
25 #include "units/unit.hpp"
26 #include "log.hpp"
27 #include "menu_events.hpp" // for fallback_ai_to_human_exception
28 
29 static lg::log_domain log_formula_ai("ai/engine/fai");
30 #define DBG_AI LOG_STREAM(debug, log_formula_ai)
31 #define LOG_AI LOG_STREAM(info, log_formula_ai)
32 #define WRN_AI LOG_STREAM(warn, log_formula_ai)
33 #define ERR_AI LOG_STREAM(err, log_formula_ai)
34 
35 namespace ai {
36 
38  auto fai = std::dynamic_pointer_cast<const formula_ai>(for_fai);
39  assert(fai != nullptr);
40  return *std::const_pointer_cast<formula_ai>(fai)->ai_ptr_;
41 }
42 
43 }
44 
45 namespace wfl {
46  using namespace ai;
47 
48 variant move_map_callable::get_value(const std::string& key) const
49 {
50  if(key == "moves") {
51  std::vector<variant> vars;
52  for(move_map::const_iterator i = srcdst_.begin(); i != srcdst_.end(); ++i) {
53  if( i->first == i->second || units_.count(i->second) == 0) {
54  auto item = std::make_shared<move_callable>(i->first, i->second);
55  vars.emplace_back(item);
56  }
57  }
58 
59  return variant(vars);
60  } else if(key == "has_moves") {
61  return variant(!srcdst_.empty());
62  } else {
63  return variant();
64  }
65 }
66 
67 void move_map_callable::get_inputs(formula_input_vector& inputs) const
68 {
69  add_input(inputs, "moves");
70 }
71 
72 int move_callable::do_compare(const formula_callable* callable) const
73 {
74  const move_callable* mv_callable = dynamic_cast<const move_callable*>(callable);
75  if(mv_callable == nullptr) {
76  return formula_callable::do_compare(callable);
77  }
78 
79  const map_location& other_src = mv_callable->src_;
80  const map_location& other_dst = mv_callable->dst_;
81 
82  if (int cmp = src_.do_compare(other_src)) {
83  return cmp;
84  }
85 
86  return dst_.do_compare(other_dst);
87 }
88 
89 variant move_callable::execute_self(variant ctxt) {
91  move_result_ptr move_result = ai.execute_move_action(src_, dst_, true);
92 
93  if(!move_result->is_ok()) {
94  LOG_AI << "ERROR #" << move_result->get_status() << " while executing 'move' formula function\n" << std::endl;
95  return variant(std::make_shared<safe_call_result>(fake_ptr(), move_result->get_status(), move_result->get_unit_location()));
96  }
97 
98  return variant(move_result->is_gamestate_changed());
99 }
100 
101 int move_partial_callable::do_compare(const formula_callable* callable) const
102 {
103  const move_partial_callable* mv_callable = dynamic_cast<const move_partial_callable*>(callable);
104  if(mv_callable == nullptr) {
105  return formula_callable::do_compare(callable);
106  }
107 
108  const map_location& other_src = mv_callable->src_;
109  const map_location& other_dst = mv_callable->dst_;
110 
111  if (int cmp = src_.do_compare(other_src)) {
112  return cmp;
113  }
114 
115  return dst_.do_compare(other_dst);
116 }
117 
118 variant move_partial_callable::execute_self(variant ctxt) {
120  move_result_ptr move_result = ai.execute_move_action(src_, dst_, false);
121 
122  if(!move_result->is_ok()) {
123  LOG_AI << "ERROR #" << move_result->get_status() << " while executing 'move_partial' formula function\n" << std::endl;
124  return variant(std::make_shared<safe_call_result>(fake_ptr(), move_result->get_status(), move_result->get_unit_location()));
125  }
126 
127  return variant(move_result->is_gamestate_changed());
128 }
129 
130 variant position_callable::get_value(const std::string& key) const {
131  if(key == "chance") {
132  return variant(chance_);
133  } else {
134  return variant();
135  }
136 }
137 
138 void position_callable::get_inputs(formula_input_vector& inputs) const {
139  add_input(inputs, "chance");
140 }
141 
142 variant outcome_callable::get_value(const std::string& key) const {
143  if(key == "hitpoints_left") {
144  return variant(hitLeft_);
145  } else if(key == "probability") {
146  return variant(prob_);
147  } else if(key == "possible_status") {
148  return variant(status_);
149  } else {
150  return variant();
151  }
152 }
153 
154 void outcome_callable::get_inputs(formula_input_vector& inputs) const {
155  add_input(inputs, "hitpoints_left");
156  add_input(inputs, "probability");
157  add_input(inputs, "possible_status");
158 }
159 
160 attack_callable::attack_callable(const map_location& move_from,
161  const map_location& src, const map_location& dst, int weapon)
162  : move_from_(move_from), src_(src), dst_(dst),
163  bc_(resources::gameboard->units(), src, dst, weapon, -1, 1.0, nullptr,
164  resources::gameboard->units().find(move_from).get_shared_ptr())
165 {
166  type_ = ATTACK_C;
167 }
168 
169 variant attack_callable::get_value(const std::string& key) const {
170  if(key == "attack_from") {
171  return variant(std::make_shared<location_callable>(src_));
172  } else if(key == "defender") {
173  return variant(std::make_shared<location_callable>(dst_));
174  } else if(key == "move_from") {
175  return variant(std::make_shared<location_callable>(move_from_));
176  } else {
177  return variant();
178  }
179 }
180 
182  add_input(inputs, "attack_from");
183  add_input(inputs, "defender");
184  add_input(inputs, "move_from");
185 }
186 
188  const {
189  const attack_callable* a_callable = dynamic_cast<const attack_callable*>(callable);
190  if(a_callable == nullptr) {
191  return formula_callable::do_compare(callable);
192  }
193 
194  const map_location& other_from = a_callable->move_from();
195 
196  if (int cmp = move_from_.do_compare(other_from)) {
197  return cmp;
198  }
199  const map_location& other_src = a_callable->src();
200  if (int cmp = src_.do_compare(other_src)) {
201  return cmp;
202  }
203  const map_location& other_dst = a_callable->dst();
204  if (int cmp = dst_.do_compare(other_dst)) {
205  return cmp;
206  }
207  const int other_weapon = a_callable->weapon();
208  if (int cmp = (this->weapon() - other_weapon)) {
209  return cmp;
210  }
211  const int other_def_weapon = a_callable->defender_weapon();
212  return this->defender_weapon() - other_def_weapon;
213 }
214 
217  bool gamestate_changed = false;
219 
220  if(move_from_ != src_) {
221  move_result = ai.execute_move_action(move_from_, src_, false);
222  gamestate_changed |= move_result->is_gamestate_changed();
223 
224  if(!move_result->is_ok()) {
225  //move part failed
226  LOG_AI << "ERROR #" << move_result->get_status() << " while executing 'attack' formula function\n" << std::endl;
227  return variant(std::make_shared<safe_call_result>(fake_ptr(), move_result->get_status(), move_result->get_unit_location()));
228  }
229  }
230 
231  if(!move_result || move_result->is_ok()) {
232  //if move wasn't done at all or was done successfully
234  gamestate_changed |= attack_result->is_gamestate_changed();
235  if(!attack_result->is_ok()) {
236  //attack failed
237  LOG_AI << "ERROR #" << attack_result->get_status() << " while executing 'attack' formula function\n" << std::endl;
238  return variant(std::make_shared<safe_call_result>(fake_ptr(), attack_result->get_status()));
239  }
240  }
241 
242  return variant(gamestate_changed);
243 }
244 
245 variant attack_map_callable::get_value(const std::string& key) const {
246  if(key == "attacks") {
247  std::vector<variant> vars;
248  for(move_map::const_iterator i = ai_.get_srcdst().begin(); i != ai_.get_srcdst().end(); ++i) {
249  /* for each possible move check all adjacent tiles for enemies */
250  if(units_.count(i->second) == 0) {
251  collect_possible_attacks(vars, i->first, i->second);
252  }
253  }
254  /* special case, when unit moved toward enemy and can only attack */
255  for(unit_map::const_iterator i = resources::gameboard->units().begin(); i != resources::gameboard->units().end(); ++i) {
256  if (i->side() == ai_.get_side() && i->attacks_left() > 0) {
257  collect_possible_attacks(vars, i->get_location(), i->get_location());
258  }
259  }
260  return variant(vars);
261  } else {
262  return variant();
263  }
264 }
265 
267  add_input(inputs, "attacks");
268 }
269 
270 /* add to vars all attacks on enemy units around <attack_position> tile. attacker_location is tile where unit is currently standing. It's moved to attack_position first and then performs attack.*/
271 void attack_map_callable::collect_possible_attacks(std::vector<variant>& vars, map_location attacker_location, map_location attack_position) const {
272  for(const map_location& adj : get_adjacent_tiles(attack_position)) {
273  /* if adjacent tile is outside the board */
274  if (! resources::gameboard->map().on_board(adj))
275  continue;
276  unit_map::const_iterator unit = units_.find(adj);
277  /* if tile is empty */
278  if (unit == units_.end())
279  continue;
280  /* if tile is occupied by friendly or petrified/invisible unit */
281  if (!ai_.current_team().is_enemy(unit->side()) ||
282  unit->incapacitated() ||
283  unit->invisible(unit->get_location()))
284  continue;
285  /* add attacks with default weapon */
286  auto item = std::make_shared<attack_callable>(attacker_location, attack_position, adj, -1);
287  vars.emplace_back(item);
288  }
289 }
290 
291 variant recall_callable::get_value(const std::string& key) const {
292  if( key == "id")
293  return variant(id_);
294  if( key == "loc")
295  return variant(std::make_shared<location_callable>(loc_));
296  return variant();
297 }
298 
300  add_input(inputs, "id");
301  add_input(inputs, "loc");
302 }
303 
307 
308  if(recall_result->is_ok()) {
309  recall_result->execute();
310  } else {
311  LOG_AI << "ERROR #" << recall_result->get_status() << " while executing 'recall' formula function\n" << std::endl;
312  return variant(std::make_shared<safe_call_result>(fake_ptr(), recall_result->get_status()));
313  }
314 
315  return variant(recall_result->is_gamestate_changed());
316 }
317 
318 variant recruit_callable::get_value(const std::string& key) const {
319  if( key == "unit_type")
320  return variant(type_);
321  if( key == "recruit_loc")
322  return variant(std::make_shared<location_callable>(loc_));
323  return variant();
324 }
325 
327  add_input(inputs, "unit_type");
328  add_input(inputs, "recruit_loc");
329 }
330 
334 
335  //is_ok()==true means that the action is successful (eg. no unexpected events)
336  //is_ok() must be checked or the code will complain :)
337  if(recruit_result->is_ok()) {
338  recruit_result->execute();
339  } else {
340  LOG_AI << "ERROR #" << recruit_result->get_status() << " while executing 'recruit' formula function\n" << std::endl;
341  return variant(std::make_shared<safe_call_result>(fake_ptr(), recruit_result->get_status()));
342  }
343 
344  //is_gamestate_changed()==true means that the game state was somehow changed by action.
345  //it is believed that during a turn, a game state can change only a finite number of times
346  return variant(recruit_result->is_gamestate_changed());
347 }
348 
349 variant set_unit_var_callable::get_value(const std::string& key) const {
350  if(key == "loc")
351  return variant(std::make_shared<location_callable>(loc_));
352 
353  if(key == "key")
354  return variant(key_);
355 
356  if(key == "value")
357  return value_;
358 
359  return variant();
360 }
361 
363  add_input(inputs, "loc");
364  add_input(inputs, "key");
365  add_input(inputs, "value");
366 }
367 
369  int status = 0;
371  unit_map& units = resources::gameboard->units();
372 
373 /* if(!infinite_loop_guardian_.set_unit_var_check()) {
374  status = 5001; //exceeded nmber of calls in a row - possible infinite loop
375  } else*/ if((unit = units.find(loc_)) == units.end()) {
376  status = 5002; //unit not found
377  } else if(unit->side() != get_ai_context(ctxt.as_callable()).get_side()) {
378  status = 5003;//unit does not belong to our side
379  }
380 
381  if(status == 0) {
382  LOG_AI << "Setting unit variable: " << key_ << " -> " << value_.to_debug_string() << "\n";
383  unit->formula_manager().add_formula_var(key_, value_);
384  return variant(true);
385  }
386 
387  ERR_AI << "ERROR #" << status << " while executing 'set_unit_var' formula function" << std::endl;
388  return variant(std::make_shared<safe_call_result>(fake_ptr(), status));
389 }
390 
392  // We want give control of the side to human for the rest of this turn
394 }
395 
396 }
Defines formula ai.
int do_compare(const formula_callable *callable) const override
Compare two attacks in deterministic way or compare pointers (nondeterministic in consequent game run...
variant execute_self(variant ctxt) override
variant get_value(const std::string &key) const override
unit_iterator end()
Definition: map.hpp:428
void get_adjacent_tiles(const map_location &a, map_location *res)
Function which, given a location, will place all adjacent locations in res.
Definition: location.cpp:474
virtual const unit_map & units() const override
Definition: game_board.hpp:111
This class represents a single unit of a specific type.
Definition: unit.hpp:120
virtual recall_result_ptr check_recall_action(const std::string &id, const map_location &where=map_location::null_location(), const map_location &from=map_location::null_location())=0
const map_location & src() const
Managing the AI-Game interaction - AI actions and their results.
#define LOG_AI
void get_inputs(formula_input_vector &inputs) const override
virtual const move_map & get_srcdst() const override
Definition: contexts.hpp:715
int do_compare(const map_location &a) const
three-way comparator
Definition: location.hpp:104
std::shared_ptr< move_result > move_result_ptr
Definition: game_info.hpp:84
std::vector< formula_input > formula_input_vector
std::shared_ptr< recruit_result > recruit_result_ptr
Definition: game_info.hpp:83
std::shared_ptr< attack_result > attack_result_ptr
Definition: game_info.hpp:81
ai_context & get_ai_context(wfl::const_formula_callable_ptr for_fai)
formula_ai & ai_
map_location loc_
A small explanation about what&#39;s going on here: Each action has access to two game_info objects First...
Definition: actions.cpp:59
std::shared_ptr< recall_result > recall_result_ptr
Definition: game_info.hpp:82
void get_inputs(formula_input_vector &inputs) const override
void get_inputs(formula_input_vector &inputs) const override
game_board * gameboard
Definition: resources.cpp:20
bool is_enemy(int n) const
Definition: team.hpp:251
variant execute_self(variant ctxt) override
variant execute_self(variant ctxt) override
variant get_value(const std::string &key) const override
virtual attack_result_ptr execute_attack_action(const map_location &attacker_loc, const map_location &defender_loc, int attacker_weapon)=0
Encapsulates the map of the game.
Definition: location.hpp:37
const_formula_callable_ptr as_callable() const
Definition: variant.hpp:82
unit_iterator find(std::size_t id)
Definition: map.cpp:309
static lg::log_domain log_formula_ai("ai/engine/fai")
void get_inputs(formula_input_vector &inputs) const override
std::size_t i
Definition: function.cpp:940
formula_callable_ptr fake_ptr()
Definition: callable.hpp:41
virtual const team & current_team() const override
Definition: contexts.hpp:454
Game information for the AI.
#define ERR_AI
variant get_value(const std::string &key) const override
void get_inputs(formula_input_vector &inputs) const override
variant execute_self(variant ctxt) override
variant execute_self(variant ctxt) override
virtual side_number get_side() const override
Get the side number.
Definition: contexts.hpp:400
virtual recruit_result_ptr check_recruit_action(const std::string &unit_name, const map_location &where=map_location::null_location(), const map_location &from=map_location::null_location())=0
std::shared_ptr< const formula_callable > const_formula_callable_ptr
const map_location & move_from() const
formula_input_vector inputs() const
Definition: callable.hpp:62
variant get_value(const std::string &key) const override
Definition: contexts.hpp:43
void collect_possible_attacks(std::vector< variant > &vars, map_location attacker_location, map_location attack_position) const
Composite AI contexts.
Standard logging facilities (interface).
Container associating units to locations.
Definition: map.hpp:97
const map_location & dst() const
static void add_input(formula_input_vector &inputs, const std::string &key, FORMULA_ACCESS_TYPE access_type=FORMULA_READ_ONLY)
Definition: callable.hpp:135
virtual int do_compare(const formula_callable *callable) const
Definition: callable.hpp:145
variant get_value(const std::string &key) const override
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:409
virtual move_result_ptr execute_move_action(const map_location &from, const map_location &to, bool remove_movement=true, bool unreach_is_ok=false)=0