The Battle for Wesnoth  1.17.0-dev
callable_objects.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2009 - 2021
3  by Bartosz Waresiak <dragonking@o2.pl>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #include "ai/formula/ai.hpp"
17 #include "attack_prediction.hpp"
18 #include "game_board.hpp"
21 #include "resources.hpp"
22 #include "map/map.hpp"
23 #include "ai/game_info.hpp"
24 #include "ai/actions.hpp"
26 #include "units/unit.hpp"
27 #include "log.hpp"
28 #include "menu_events.hpp" // for fallback_ai_to_human_exception
29 
30 static lg::log_domain log_formula_ai("ai/engine/fai");
31 #define DBG_AI LOG_STREAM(debug, log_formula_ai)
32 #define LOG_AI LOG_STREAM(info, log_formula_ai)
33 #define WRN_AI LOG_STREAM(warn, log_formula_ai)
34 #define ERR_AI LOG_STREAM(err, log_formula_ai)
35 
36 namespace ai {
37 
39  auto fai = std::dynamic_pointer_cast<const formula_ai>(for_fai);
40  assert(fai != nullptr);
41  return *std::const_pointer_cast<formula_ai>(fai)->ai_ptr_;
42 }
43 
44 }
45 
46 namespace wfl {
47  using namespace ai;
48 
49 variant move_map_callable::get_value(const std::string& key) const
50 {
51  if(key == "moves") {
52  std::vector<variant> vars;
53  for(move_map::const_iterator i = srcdst_.begin(); i != srcdst_.end(); ++i) {
54  if( i->first == i->second || units_.count(i->second) == 0) {
55  auto item = std::make_shared<move_callable>(i->first, i->second);
56  vars.emplace_back(item);
57  }
58  }
59 
60  return variant(vars);
61  } else if(key == "has_moves") {
62  return variant(!srcdst_.empty());
63  } else {
64  return variant();
65  }
66 }
67 
68 void move_map_callable::get_inputs(formula_input_vector& inputs) const
69 {
70  add_input(inputs, "moves");
71 }
72 
73 int move_callable::do_compare(const formula_callable* callable) const
74 {
75  const move_callable* mv_callable = dynamic_cast<const move_callable*>(callable);
76  if(mv_callable == nullptr) {
77  return formula_callable::do_compare(callable);
78  }
79 
80  const map_location& other_src = mv_callable->src_;
81  const map_location& other_dst = mv_callable->dst_;
82 
83  if (int cmp = src_.do_compare(other_src)) {
84  return cmp;
85  }
86 
87  return dst_.do_compare(other_dst);
88 }
89 
90 variant move_callable::execute_self(variant ctxt) {
92  move_result_ptr move_result = ai.execute_move_action(src_, dst_, true);
93 
94  if(!move_result->is_ok()) {
95  LOG_AI << "ERROR #" << move_result->get_status() << " while executing 'move' formula function\n" << std::endl;
96  return variant(std::make_shared<safe_call_result>(fake_ptr(), move_result->get_status(), move_result->get_unit_location()));
97  }
98 
99  return variant(move_result->is_gamestate_changed());
100 }
101 
102 int move_partial_callable::do_compare(const formula_callable* callable) const
103 {
104  const move_partial_callable* mv_callable = dynamic_cast<const move_partial_callable*>(callable);
105  if(mv_callable == nullptr) {
106  return formula_callable::do_compare(callable);
107  }
108 
109  const map_location& other_src = mv_callable->src_;
110  const map_location& other_dst = mv_callable->dst_;
111 
112  if (int cmp = src_.do_compare(other_src)) {
113  return cmp;
114  }
115 
116  return dst_.do_compare(other_dst);
117 }
118 
119 variant move_partial_callable::execute_self(variant ctxt) {
121  move_result_ptr move_result = ai.execute_move_action(src_, dst_, false);
122 
123  if(!move_result->is_ok()) {
124  LOG_AI << "ERROR #" << move_result->get_status() << " while executing 'move_partial' formula function\n" << std::endl;
125  return variant(std::make_shared<safe_call_result>(fake_ptr(), move_result->get_status(), move_result->get_unit_location()));
126  }
127 
128  return variant(move_result->is_gamestate_changed());
129 }
130 
131 variant position_callable::get_value(const std::string& key) const {
132  if(key == "chance") {
133  return variant(chance_);
134  } else {
135  return variant();
136  }
137 }
138 
139 void position_callable::get_inputs(formula_input_vector& inputs) const {
140  add_input(inputs, "chance");
141 }
142 
143 variant outcome_callable::get_value(const std::string& key) const {
144  if(key == "hitpoints_left") {
145  return variant(hitLeft_);
146  } else if(key == "probability") {
147  return variant(prob_);
148  } else if(key == "possible_status") {
149  return variant(status_);
150  } else {
151  return variant();
152  }
153 }
154 
155 void outcome_callable::get_inputs(formula_input_vector& inputs) const {
156  add_input(inputs, "hitpoints_left");
157  add_input(inputs, "probability");
158  add_input(inputs, "possible_status");
159 }
160 
161 attack_callable::attack_callable(const map_location& move_from,
162  const map_location& src, const map_location& dst, int weapon)
163  : move_from_(move_from), src_(src), dst_(dst),
164  bc_(resources::gameboard->units(), src, dst, weapon, -1, 1.0, nullptr,
165  resources::gameboard->units().find(move_from).get_shared_ptr())
166 {
167  type_ = ATTACK_C;
168 }
169 
170 variant attack_callable::get_value(const std::string& key) const {
171  if(key == "attack_from") {
172  return variant(std::make_shared<location_callable>(src_));
173  } else if(key == "defender") {
174  return variant(std::make_shared<location_callable>(dst_));
175  } else if(key == "move_from") {
176  return variant(std::make_shared<location_callable>(move_from_));
177  } else {
178  return variant();
179  }
180 }
181 
183  add_input(inputs, "attack_from");
184  add_input(inputs, "defender");
185  add_input(inputs, "move_from");
186 }
187 
189  const {
190  const attack_callable* a_callable = dynamic_cast<const attack_callable*>(callable);
191  if(a_callable == nullptr) {
192  return formula_callable::do_compare(callable);
193  }
194 
195  const map_location& other_from = a_callable->move_from();
196 
197  if (int cmp = move_from_.do_compare(other_from)) {
198  return cmp;
199  }
200  const map_location& other_src = a_callable->src();
201  if (int cmp = src_.do_compare(other_src)) {
202  return cmp;
203  }
204  const map_location& other_dst = a_callable->dst();
205  if (int cmp = dst_.do_compare(other_dst)) {
206  return cmp;
207  }
208  const int other_weapon = a_callable->weapon();
209  if (int cmp = (this->weapon() - other_weapon)) {
210  return cmp;
211  }
212  const int other_def_weapon = a_callable->defender_weapon();
213  return this->defender_weapon() - other_def_weapon;
214 }
215 
218  bool gamestate_changed = false;
220 
221  if(move_from_ != src_) {
222  move_result = ai.execute_move_action(move_from_, src_, false);
223  gamestate_changed |= move_result->is_gamestate_changed();
224 
225  if(!move_result->is_ok()) {
226  //move part failed
227  LOG_AI << "ERROR #" << move_result->get_status() << " while executing 'attack' formula function\n" << std::endl;
228  return variant(std::make_shared<safe_call_result>(fake_ptr(), move_result->get_status(), move_result->get_unit_location()));
229  }
230  }
231 
232  if(!move_result || move_result->is_ok()) {
233  //if move wasn't done at all or was done successfully
235  gamestate_changed |= attack_result->is_gamestate_changed();
236  if(!attack_result->is_ok()) {
237  //attack failed
238  LOG_AI << "ERROR #" << attack_result->get_status() << " while executing 'attack' formula function\n" << std::endl;
239  return variant(std::make_shared<safe_call_result>(fake_ptr(), attack_result->get_status()));
240  }
241  }
242 
243  return variant(gamestate_changed);
244 }
245 
246 variant attack_map_callable::get_value(const std::string& key) const {
247  if(key == "attacks") {
248  std::vector<variant> vars;
249  for(move_map::const_iterator i = ai_.get_srcdst().begin(); i != ai_.get_srcdst().end(); ++i) {
250  /* for each possible move check all adjacent tiles for enemies */
251  if(units_.count(i->second) == 0) {
252  collect_possible_attacks(vars, i->first, i->second);
253  }
254  }
255  /* special case, when unit moved toward enemy and can only attack */
256  for(unit_map::const_iterator i = resources::gameboard->units().begin(); i != resources::gameboard->units().end(); ++i) {
257  if (i->side() == ai_.get_side() && i->attacks_left() > 0) {
258  collect_possible_attacks(vars, i->get_location(), i->get_location());
259  }
260  }
261  return variant(vars);
262  } else {
263  return variant();
264  }
265 }
266 
268  add_input(inputs, "attacks");
269 }
270 
271 /* 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.*/
272 void attack_map_callable::collect_possible_attacks(std::vector<variant>& vars, map_location attacker_location, map_location attack_position) const {
273  for(const map_location& adj : get_adjacent_tiles(attack_position)) {
274  /* if adjacent tile is outside the board */
275  if (! resources::gameboard->map().on_board(adj))
276  continue;
277  unit_map::const_iterator unit = units_.find(adj);
278  /* if tile is empty */
279  if (unit == units_.end())
280  continue;
281  /* if tile is occupied by friendly or petrified/invisible unit */
282  if (!ai_.current_team().is_enemy(unit->side()) ||
283  unit->incapacitated() ||
284  unit->invisible(unit->get_location()))
285  continue;
286  /* add attacks with default weapon */
287  auto item = std::make_shared<attack_callable>(attacker_location, attack_position, adj, -1);
288  vars.emplace_back(item);
289  }
290 }
291 
292 variant recall_callable::get_value(const std::string& key) const {
293  if( key == "id")
294  return variant(id_);
295  if( key == "loc")
296  return variant(std::make_shared<location_callable>(loc_));
297  return variant();
298 }
299 
301  add_input(inputs, "id");
302  add_input(inputs, "loc");
303 }
304 
308 
309  if(recall_result->is_ok()) {
310  recall_result->execute();
311  } else {
312  LOG_AI << "ERROR #" << recall_result->get_status() << " while executing 'recall' formula function\n" << std::endl;
313  return variant(std::make_shared<safe_call_result>(fake_ptr(), recall_result->get_status()));
314  }
315 
316  return variant(recall_result->is_gamestate_changed());
317 }
318 
319 variant recruit_callable::get_value(const std::string& key) const {
320  if( key == "unit_type")
321  return variant(type_);
322  if( key == "recruit_loc")
323  return variant(std::make_shared<location_callable>(loc_));
324  return variant();
325 }
326 
328  add_input(inputs, "unit_type");
329  add_input(inputs, "recruit_loc");
330 }
331 
335 
336  //is_ok()==true means that the action is successful (eg. no unexpected events)
337  //is_ok() must be checked or the code will complain :)
338  if(recruit_result->is_ok()) {
339  recruit_result->execute();
340  } else {
341  LOG_AI << "ERROR #" << recruit_result->get_status() << " while executing 'recruit' formula function\n" << std::endl;
342  return variant(std::make_shared<safe_call_result>(fake_ptr(), recruit_result->get_status()));
343  }
344 
345  //is_gamestate_changed()==true means that the game state was somehow changed by action.
346  //it is believed that during a turn, a game state can change only a finite number of times
347  return variant(recruit_result->is_gamestate_changed());
348 }
349 
350 variant set_unit_var_callable::get_value(const std::string& key) const {
351  if(key == "loc")
352  return variant(std::make_shared<location_callable>(loc_));
353 
354  if(key == "key")
355  return variant(key_);
356 
357  if(key == "value")
358  return value_;
359 
360  return variant();
361 }
362 
364  add_input(inputs, "loc");
365  add_input(inputs, "key");
366  add_input(inputs, "value");
367 }
368 
370  int status = 0;
372  unit_map& units = resources::gameboard->units();
373 
374 /* if(!infinite_loop_guardian_.set_unit_var_check()) {
375  status = 5001; //exceeded nmber of calls in a row - possible infinite loop
376  } else*/ if((unit = units.find(loc_)) == units.end()) {
377  status = 5002; //unit not found
378  } else if(unit->side() != get_ai_context(ctxt.as_callable()).get_side()) {
379  status = 5003;//unit does not belong to our side
380  }
381 
382  if(status == 0) {
383  LOG_AI << "Setting unit variable: " << key_ << " -> " << value_.to_debug_string() << "\n";
384  unit->formula_manager().add_formula_var(key_, value_);
385  return variant(true);
386  }
387 
388  ERR_AI << "ERROR #" << status << " while executing 'set_unit_var' formula function" << std::endl;
389  return variant(std::make_shared<safe_call_result>(fake_ptr(), status));
390 }
391 
393  // We want give control of the side to human for the rest of this turn
395 }
396 
397 }
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:429
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:475
virtual const unit_map & units() const override
Definition: game_board.hpp:112
This class represents a single unit of a specific type.
Definition: unit.hpp:121
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:716
int do_compare(const map_location &a) const
three-way comparator
Definition: location.hpp:105
std::shared_ptr< move_result > move_result_ptr
Definition: game_info.hpp:85
std::vector< formula_input > formula_input_vector
std::shared_ptr< recruit_result > recruit_result_ptr
Definition: game_info.hpp:84
std::shared_ptr< attack_result > attack_result_ptr
Definition: game_info.hpp:82
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:61
std::shared_ptr< recall_result > recall_result_ptr
Definition: game_info.hpp:83
void get_inputs(formula_input_vector &inputs) const override
void get_inputs(formula_input_vector &inputs) const override
game_board * gameboard
Definition: resources.cpp:21
bool is_enemy(int n) const
Definition: team.hpp:255
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:38
const_formula_callable_ptr as_callable() const
Definition: variant.hpp:83
unit_iterator find(std::size_t id)
Definition: map.cpp:310
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:967
formula_callable_ptr fake_ptr()
Definition: callable.hpp:42
virtual const team & current_team() const override
Definition: contexts.hpp:455
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:401
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:63
variant get_value(const std::string &key) const override
Definition: contexts.hpp:44
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:98
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:136
virtual int do_compare(const formula_callable *callable) const
Definition: callable.hpp:146
variant get_value(const std::string &key) const override
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:410
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