The Battle for Wesnoth  1.17.21+dev
aspect_attacks.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2009 - 2023
3  by Yurii Chernyi <terraninfo@terraninfo.net>
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 /**
17  * Stage: fallback to other AI
18  * @file
19  */
20 
22 
23 #include "actions/attack.hpp"
24 #include "ai/manager.hpp"
25 #include "game_board.hpp"
26 #include "log.hpp"
27 #include "lua/lauxlib.h"
28 #include "map/map.hpp"
29 #include "pathfind/pathfind.hpp"
30 #include "resources.hpp"
31 #include "scripting/lua_unit.hpp"
32 #include "team.hpp"
33 #include "tod_manager.hpp"
34 #include "units/filter.hpp"
35 #include "units/unit.hpp"
36 
37 namespace ai
38 {
39 namespace ai_default_rca
40 {
41 static lg::log_domain log_ai_testing_aspect_attacks("ai/aspect/attacks");
42 #define DBG_AI LOG_STREAM(debug, log_ai_testing_aspect_attacks)
43 #define LOG_AI LOG_STREAM(info, log_ai_testing_aspect_attacks)
44 #define ERR_AI LOG_STREAM(err, log_ai_testing_aspect_attacks)
45 
46 aspect_attacks_base::aspect_attacks_base(readonly_context& context, const config& cfg, const std::string& id)
47  : typesafe_aspect<attacks_vector>(context, cfg, id)
48 {
49 }
50 
51 aspect_attacks::aspect_attacks(readonly_context& context, const config& cfg, const std::string& id)
52  : aspect_attacks_base(context, cfg, id)
53  , filter_own_()
54  , filter_enemy_()
55 {
56  if(auto filter_own = cfg.optional_child("filter_own")) {
57  vconfig vcfg(*filter_own);
58  vcfg.make_safe();
59  filter_own_.reset(new unit_filter(vcfg));
60  }
61 
62  if(auto filter_enemy = cfg.optional_child("filter_enemy")) {
63  vconfig vcfg(*filter_enemy);
64  vcfg.make_safe();
65  filter_enemy_.reset(new unit_filter(vcfg));
66  }
67 }
68 
70 {
71  this->value_ = analyze_targets();
72  this->valid_ = true;
73 }
74 
75 std::shared_ptr<attacks_vector> aspect_attacks_base::analyze_targets() const
76 {
77  const move_map& srcdst = get_srcdst();
78  const move_map& dstsrc = get_dstsrc();
79  const move_map& enemy_srcdst = get_enemy_srcdst();
80  const move_map& enemy_dstsrc = get_enemy_dstsrc();
81 
82  auto res = std::make_shared<attacks_vector>();
83  const unit_map& units_ = resources::gameboard->units();
84 
85  std::vector<map_location> unit_locs;
86  for(const unit& u : units_) {
87  if(u.side() == get_side() && u.attacks_left() && !(u.can_recruit() && is_passive_leader(u.id()))) {
88  if(!is_allowed_attacker(u)) {
89  continue;
90  }
91 
92  unit_locs.push_back(u.get_location());
93  }
94  }
95 
96  std::array<bool, 6> used_locations;
97  used_locations.fill(false);
98 
99  moves_map dummy_moves;
100  move_map fullmove_srcdst, fullmove_dstsrc;
101  calculate_possible_moves(dummy_moves, fullmove_srcdst, fullmove_dstsrc, false, true);
102 
103  unit_stats_cache().clear();
104 
105  for(const unit& u : units_) {
106  // Attack anyone who is on the enemy side,
107  // and who is not invisible or petrified.
108  if(current_team().is_enemy(u.side()) && !u.incapacitated() && !u.invisible(u.get_location())) {
109  if(!is_allowed_enemy(u)) {
110  continue;
111  }
112 
113  const auto adjacent = get_adjacent_tiles(u.get_location());
114  attack_analysis analysis;
115  analysis.target = u.get_location();
116  analysis.vulnerability = 0.0;
117  analysis.support = 0.0;
118 
119  do_attack_analysis(u.get_location(), srcdst, dstsrc, fullmove_srcdst, fullmove_dstsrc, enemy_srcdst,
120  enemy_dstsrc, adjacent, used_locations, unit_locs, *res, analysis, current_team());
121  }
122  }
123  return res;
124 }
125 
127  const move_map& srcdst,
128  const move_map& dstsrc,
129  const move_map& fullmove_srcdst,
130  const move_map& fullmove_dstsrc,
131  const move_map& enemy_srcdst,
132  const move_map& enemy_dstsrc,
133  const std::array<map_location, 6>& tiles,
134  std::array<bool, 6>& used_locations,
135  std::vector<map_location>& units,
136  std::vector<attack_analysis>& result,
137  attack_analysis& cur_analysis,
138  const team& current_team) const
139 {
140  // This function is called fairly frequently, so interact with the user here.
141 
143  const int max_attack_depth = 5;
144  if(cur_analysis.movements.size() >= std::size_t(max_attack_depth)) {
145  return;
146  }
147 
148  const gamemap& map_ = resources::gameboard->map();
149  unit_map& units_ = resources::gameboard->units();
150  const std::vector<team>& teams_ = resources::gameboard->teams();
151 
152  const std::size_t max_positions = 1000;
153  if(result.size() > max_positions && !cur_analysis.movements.empty()) {
154  LOG_AI << "cut analysis short with number of positions";
155  return;
156  }
157 
158  for(std::size_t i = 0; i != units.size(); ++i) {
159  const map_location current_unit = units[i];
160 
161  unit_map::iterator unit_itor = units_.find(current_unit);
162  assert(unit_itor != units_.end());
163 
164  // See if the unit has the backstab ability.
165  // Units with backstab will want to try to have a
166  // friendly unit opposite the position they move to.
167  //
168  // See if the unit has the slow ability -- units with slow only attack first.
169  bool backstab = false, slow = false;
170  for(const attack_type& a : unit_itor->attacks()) {
171  // For speed, just assume these specials will be active if they are present.
172  if(a.has_special("backstab", true)) {
173  backstab = true;
174  }
175 
176  if(a.has_special("slow", true)) {
177  slow = true;
178  }
179  }
180 
181  if(slow && cur_analysis.movements.empty() == false) {
182  continue;
183  }
184 
185  // Check if the friendly unit is surrounded,
186  // A unit is surrounded if it is flanked by enemy units
187  // and at least one other enemy unit is nearby
188  // or if the unit is totally surrounded by enemies
189  // with max. one tile to escape.
190  bool is_surrounded = false;
191  bool is_flanked = false;
192  int enemy_units_around = 0;
193  int accessible_tiles = 0;
194  const auto adj = get_adjacent_tiles(current_unit);
195 
196  for(std::size_t tile = 0; tile != 3; ++tile) {
197  const unit_map::const_iterator tmp_unit = units_.find(adj[tile]);
198  bool possible_flanked = false;
199 
200  if(map_.on_board(adj[tile])) {
201  ++accessible_tiles;
202  if(tmp_unit != units_.end() && current_team.is_enemy(tmp_unit->side())) {
203  ++enemy_units_around;
204  possible_flanked = true;
205  }
206  }
207 
208  const unit_map::const_iterator tmp_opposite_unit = units_.find(adj[tile + 3]);
209  if(map_.on_board(adj[tile + 3])) {
210  ++accessible_tiles;
211  if(tmp_opposite_unit != units_.end() && current_team.is_enemy(tmp_opposite_unit->side())) {
212  ++enemy_units_around;
213  if(possible_flanked) {
214  is_flanked = true;
215  }
216  }
217  }
218  }
219 
220  if((is_flanked && enemy_units_around > 2) || enemy_units_around >= accessible_tiles - 1) {
221  is_surrounded = true;
222  }
223 
224  double best_vulnerability = 0.0, best_support = 0.0;
225  int best_rating = 0;
226  int cur_position = -1;
227 
228  // Iterate over positions adjacent to the unit, finding the best rated one.
229  for(unsigned j = 0; j < tiles.size(); ++j) {
230  // If in this planned attack, a unit is already in this location.
231  if(used_locations[j]) {
232  continue;
233  }
234 
235  // See if the current unit can reach that position.
236  if(tiles[j] != current_unit) {
237  auto its = dstsrc.equal_range(tiles[j]);
238  while(its.first != its.second) {
239  if(its.first->second == current_unit) {
240  break;
241  }
242 
243  ++its.first;
244  }
245 
246  // If the unit can't move to this location.
247  if(its.first == its.second || units_.find(tiles[j]) != units_.end()) {
248  continue;
249  }
250  }
251 
252  int best_leadership_bonus = under_leadership(*unit_itor, tiles[j]);
253  double leadership_bonus = static_cast<double>(best_leadership_bonus + 100) / 100.0;
254  if(leadership_bonus > 1.1) {
255  LOG_AI << unit_itor->name() << " is getting leadership " << leadership_bonus;
256  }
257 
258  // Check to see whether this move would be a backstab.
259  int backstab_bonus = 1;
260  double surround_bonus = 1.0;
261 
262  if(tiles[(j + 3) % 6] != current_unit) {
263  const unit_map::const_iterator itor = units_.find(tiles[(j + 3) % 6]);
264 
265  // Note that we *could* also check if a unit plans to move there
266  // before we're at this stage, but we don't because, since the
267  // attack calculations don't actually take backstab into account (too complicated),
268  // this could actually make our analysis look *worse* instead of better.
269  // So we only check for 'concrete' backstab opportunities.
270  // That would also break backstab_check, since it assumes
271  // the defender is in place.
272  if(itor != units_.end() && backstab_check(tiles[j], loc, units_, teams_)) {
273  if(backstab) {
274  backstab_bonus = 2;
275  }
276 
277  // No surround bonus if target is skirmisher
278  if(!itor->get_ability_bool("skirmisher")) {
279  surround_bonus = 1.2;
280  }
281  }
282  }
283 
284  // See if this position is the best rated we've seen so far.
285  int rating = static_cast<int>(rate_terrain(*unit_itor, tiles[j]) * backstab_bonus * leadership_bonus);
286  if(cur_position >= 0 && rating < best_rating) {
287  continue;
288  }
289 
290  // Find out how vulnerable we are to attack from enemy units in this hex.
291  // FIXME: suokko's r29531 multiplied this by a constant 1.5. ?
292  const double vulnerability = power_projection(tiles[j], enemy_dstsrc); //?
293 
294  // Calculate how much support we have on this hex from allies.
295  const double support = power_projection(tiles[j], fullmove_dstsrc); //?
296 
297  // If this is a position with equal defense to another position,
298  // but more vulnerability then we don't want to use it.
299  if(cur_position >= 0 && rating == best_rating
300  && vulnerability / surround_bonus - support * surround_bonus >= best_vulnerability - best_support) {
301  continue;
302  }
303 
304  cur_position = j;
305  best_rating = rating;
306  best_vulnerability = vulnerability / surround_bonus;
307  best_support = support * surround_bonus;
308  }
309 
310  if(cur_position != -1) {
311  units.erase(units.begin() + i);
312 
313  cur_analysis.movements.emplace_back(current_unit, tiles[cur_position]);
314  cur_analysis.vulnerability += best_vulnerability;
315  cur_analysis.support += best_support;
316  cur_analysis.is_surrounded = is_surrounded;
317  cur_analysis.analyze(map_, units_, *this, dstsrc, srcdst, enemy_dstsrc, get_aggression());
318  result.push_back(cur_analysis);
319 
320  used_locations[cur_position] = true;
321 
322  do_attack_analysis(loc, srcdst, dstsrc, fullmove_srcdst, fullmove_dstsrc, enemy_srcdst, enemy_dstsrc, tiles,
323  used_locations, units, result, cur_analysis, current_team);
324 
325  used_locations[cur_position] = false;
326 
327  cur_analysis.vulnerability -= best_vulnerability;
328  cur_analysis.support -= best_support;
329  cur_analysis.movements.pop_back();
330 
331  units.insert(units.begin() + i, current_unit);
332  }
333  }
334 }
335 
337 {
338  const gamemap& map_ = resources::gameboard->map();
339  const t_translation::terrain_code terrain = map_.get_terrain(loc);
340  const int defense = u.defense_modifier(terrain);
341  int rating = 100 - defense;
342 
343  const int healing_value = 10;
344  const int friendly_village_value = 5;
345  const int neutral_village_value = 10;
346  const int enemy_village_value = 15;
347 
348  if(map_.gives_healing(terrain) && u.get_ability_bool("regenerate", loc) == false) {
349  rating += healing_value;
350  }
351 
352  if(map_.is_village(terrain)) {
353  int owner = resources::gameboard->village_owner(loc);
354 
355  if(owner == u.side()) {
356  rating += friendly_village_value;
357  } else if(owner == 0) {
358  rating += neutral_village_value;
359  } else {
360  rating += enemy_village_value;
361  }
362  }
363 
364  return rating;
365 }
366 
368 {
370  if(filter_own_ && !filter_own_->empty()) {
371  cfg.add_child("filter_own", filter_own_->to_config());
372  }
373 
374  if(filter_enemy_ && !filter_enemy_->empty()) {
375  cfg.add_child("filter_enemy", filter_enemy_->to_config());
376  }
377 
378  return cfg;
379 }
380 
382 {
383  if(u.side() != get_side()) {
384  return false;
385  }
386 
387  if(filter_own_) {
388  return (*filter_own_)(u);
389  }
390 
391  return true;
392 }
393 
395 {
396  const team& my_team = resources::gameboard->get_team(get_side());
397  if(!my_team.is_enemy(u.side())) {
398  return false;
399  }
400 
401  if(filter_enemy_) {
402  return (*filter_enemy_)(u);
403  }
404 
405  return true;
406 }
407 
408 } // namespace ai_default_rca
409 
411  readonly_context& context, const config& cfg, const std::string& id, std::shared_ptr<lua_ai_context>& l_ctx)
412  : aspect_attacks_base(context, cfg, id)
413  , handler_()
414  , code_()
415  , params_(cfg.child_or_empty("args"))
416 {
417  this->name_ = "lua_aspect";
418  if(cfg.has_attribute("code")) {
419  code_ = cfg["code"].str();
420  } else if(cfg.has_attribute("value")) {
421  code_ = "return " + cfg["value"].apply_visitor(lua_aspect_visitor());
422  } else {
423  // error
424  return;
425  }
426 
427  handler_.reset(resources::lua_kernel->create_lua_ai_action_handler(code_.c_str(), *l_ctx));
428 }
429 
431 {
433  const config empty_cfg;
434  handler_->handle(params_, empty_cfg, true, obj_);
435 
436  aspect_attacks_lua_filter filt = *obj_->get();
437  aspect_attacks_base::recalculate();
438 
439  if(filt.lua) {
440  if(filt.ref_own_ != -1) {
441  luaL_unref(filt.lua, LUA_REGISTRYINDEX, filt.ref_own_);
442  }
443  if(filt.ref_enemy_ != -1) {
444  luaL_unref(filt.lua, LUA_REGISTRYINDEX, filt.ref_enemy_);
445  }
446  }
447 
448  obj_.reset();
449 }
450 
452 {
453  config cfg = aspect::to_config();
454  cfg["code"] = code_;
455  if(!params_.empty()) {
456  cfg.add_child("args", params_);
457  }
458 
459  return cfg;
460 }
461 
462 static bool call_lua_filter_fcn(lua_State* L, const unit& u, int idx)
463 {
464  lua_rawgeti(L, LUA_REGISTRYINDEX, idx);
466  luaW_pcall(L, 1, 1);
467  bool result = luaW_toboolean(L, -1);
468  lua_pop(L, 1);
469  return result;
470 }
471 
473 {
474  const aspect_attacks_lua_filter& filt = *obj_->get();
475  if(filt.lua && filt.ref_own_ != -1) {
476  return call_lua_filter_fcn(filt.lua, u, filt.ref_own_);
477  } else if(filt.filter_own_) {
478  return (*filt.filter_own_)(u);
479  } else {
480  return true;
481  }
482 }
483 
485 {
486  const aspect_attacks_lua_filter& filt = *obj_->get();
487  if(filt.lua && filt.ref_enemy_ != -1) {
488  return call_lua_filter_fcn(filt.lua, u, filt.ref_enemy_);
489  } else if(filt.filter_enemy_) {
490  return (*filt.filter_enemy_)(u);
491  } else {
492  return true;
493  }
494 }
495 
496 } // end of namespace ai
int under_leadership(const unit &u, const map_location &loc, const_attack_ptr weapon, const_attack_ptr opp_weapon)
Tests if the unit at loc is currently affected by leadership.
Definition: attack.cpp:1579
bool backstab_check(const map_location &attacker_loc, const map_location &defender_loc, const unit_map &units, const std::vector< team > &teams)
Function to check if an attack will satisfy the requirements for backstab.
Definition: attack.cpp:1634
Various functions that implement attacks and attack calculations.
Managing the AIs lifecycle - headers TODO: Refactor history handling and internal commands.
#define LOG_AI
Aspect: attacks.
std::shared_ptr< attacks_vector > analyze_targets() const
virtual bool is_allowed_enemy(const unit &u) const =0
void do_attack_analysis(const map_location &loc, const move_map &srcdst, const move_map &dstsrc, const move_map &fullmove_srcdst, const move_map &fullmove_dstsrc, const move_map &enemy_srcdst, const move_map &enemy_dstsrc, const std::array< map_location, 6 > &tiles, std::array< bool, 6 > &used_locations, std::vector< map_location > &units, std::vector< attack_analysis > &result, attack_analysis &cur_analysis, const team &current_team) const
aspect_attacks_base(readonly_context &context, const config &cfg, const std::string &id)
static int rate_terrain(const unit &u, const map_location &loc)
virtual bool is_allowed_attacker(const unit &u) const =0
std::shared_ptr< unit_filter > filter_own_
virtual bool is_allowed_attacker(const unit &u) const
virtual bool is_allowed_enemy(const unit &u) const
std::shared_ptr< unit_filter > filter_enemy_
aspect_attacks(readonly_context &context, const config &cfg, const std::string &id)
virtual config to_config() const
virtual bool is_allowed_enemy(const unit &u) const
std::shared_ptr< lua_object< aspect_attacks_lua_filter > > obj_
virtual bool is_allowed_attacker(const unit &u) const
virtual void recalculate() const
aspect_attacks_lua(readonly_context &context, const config &cfg, const std::string &id, std::shared_ptr< lua_ai_context > &l_ctx)
std::shared_ptr< lua_ai_action_handler > handler_
virtual config to_config() const
Definition: aspect.cpp:106
std::string name_
Definition: aspect.hpp:95
bool valid_
Definition: aspect.hpp:86
std::vector< std::pair< map_location, map_location > > movements
Definition: contexts.hpp:75
void analyze(const gamemap &map, unit_map &units, const readonly_context &ai_obj, const move_map &dstsrc, const move_map &srcdst, const move_map &enemy_dstsrc, double aggression)
Definition: attack.cpp:46
map_location target
Definition: contexts.hpp:74
double vulnerability
The vulnerability is the power projection of enemy units onto the hex we're standing on.
Definition: contexts.hpp:111
bool is_surrounded
Is true if the units involved in this attack sequence are surrounded.
Definition: contexts.hpp:120
void raise_user_interact()
Notifies all observers of 'ai_user_interact' event.
Definition: manager.cpp:421
static manager & get_singleton()
Definition: manager.hpp:145
virtual const team & current_team() const override
Definition: contexts.hpp:457
virtual const move_map & get_enemy_srcdst() const override
Definition: contexts.hpp:618
virtual const move_map & get_dstsrc() const override
Definition: contexts.hpp:603
virtual const move_map & get_srcdst() const override
Definition: contexts.hpp:723
virtual void calculate_possible_moves(std::map< map_location, pathfind::paths > &possible_moves, move_map &srcdst, move_map &dstsrc, bool enemy, bool assume_full_movement=false, const terrain_filter *remove_destinations=nullptr) const override
Definition: contexts.hpp:504
virtual bool is_passive_leader(const std::string &id) const override
Definition: contexts.hpp:773
virtual double power_projection(const map_location &loc, const move_map &dstsrc) const override
Function which finds how much 'power' a side can attack a certain location with.
Definition: contexts.hpp:688
virtual unit_stats_cache_t & unit_stats_cache() const override
Definition: contexts.hpp:873
virtual double get_aggression() const override
Definition: contexts.hpp:553
virtual const move_map & get_enemy_dstsrc() const override
Definition: contexts.hpp:608
virtual side_number get_side() const override
Get the side number.
Definition: contexts.hpp:403
std::shared_ptr< attacks_vector > value_
Definition: aspect.hpp:178
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:161
bool has_attribute(config_key_type key) const
Definition: config.cpp:159
bool empty() const
Definition: config.cpp:856
optional_config_impl< config > optional_child(config_key_type key, int n=0)
Euivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:389
config & add_child(config_key_type key)
Definition: config.cpp:445
int village_owner(const map_location &loc) const
Given the location of a village, will return the 1-based number of the team that currently owns it,...
virtual const std::vector< team > & teams() const override
Definition: game_board.hpp:86
team & get_team(int i)
Definition: game_board.hpp:98
virtual const unit_map & units() const override
Definition: game_board.hpp:113
virtual const gamemap & map() const override
Definition: game_board.hpp:103
terrain_code get_terrain(const map_location &loc) const
Looks up terrain at a particular location.
Definition: map.cpp:302
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:385
Encapsulates the map of the game.
Definition: map.hpp:172
bool is_village(const map_location &loc) const
Definition: map.cpp:66
int gives_healing(const map_location &loc) const
Definition: map.cpp:68
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:76
bool is_enemy(int n) const
Definition: team.hpp:231
Container associating units to locations.
Definition: map.hpp:99
unit_iterator end()
Definition: map.hpp:429
unit_iterator find(std::size_t id)
Definition: map.cpp:301
This class represents a single unit of a specific type.
Definition: unit.hpp:134
A variable-expanding proxy for the config class.
Definition: variable.hpp:45
const vconfig & make_safe() const
instruct the vconfig to make a private copy of its underlying data.
Definition: variable.cpp:163
std::size_t i
Definition: function.cpp:968
bool get_ability_bool(const std::string &tag_name, const map_location &loc) const
Checks whether this unit currently possesses or is affected by a given ability.
Definition: abilities.cpp:182
int side() const
The side this unit belongs to.
Definition: unit.hpp:344
std::size_t underlying_id() const
This unit's unique internal ID.
Definition: unit.hpp:393
int defense_modifier(const t_translation::terrain_code &terrain) const
The unit's defense on a given terrain.
Definition: unit.cpp:1743
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:215
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
Standard logging facilities (interface).
bool luaW_toboolean(lua_State *L, int n)
Definition: lua_common.cpp:991
bool luaW_pcall(lua_State *L, int nArgs, int nRets, bool allow_wml_error)
Calls a Lua function stored below its nArgs arguments at the top of the stack.
lua_unit * luaW_pushunit(lua_State *L, Args... args)
Definition: lua_unit.hpp:116
static lg::log_domain log_ai_testing_aspect_attacks("ai/aspect/attacks")
A small explanation about what's going on here: Each action has access to two game_info objects First...
Definition: actions.cpp:61
std::vector< attack_analysis > attacks_vector
Definition: game_info.hpp:51
std::multimap< map_location, map_location > move_map
The standard way in which a map of possible moves is recorded.
Definition: game_info.hpp:43
static bool call_lua_filter_fcn(lua_State *L, const unit &u, int idx)
std::map< map_location, pathfind::paths > moves_map
The standard way in which a map of possible movement routes to location is recorded.
Definition: game_info.hpp:46
static std::unique_ptr< class sdl_event_handler > handler_
Definition: handler.cpp:64
game_board * gameboard
Definition: resources.cpp:21
game_lua_kernel * lua_kernel
Definition: resources.cpp:26
This module contains various pathfinding functions and utilities.
std::shared_ptr< unit_filter > filter_own_
std::shared_ptr< unit_filter > filter_enemy_
Encapsulates the map of the game.
Definition: location.hpp:38
A terrain string which is converted to a terrain is a string with 1 or 2 layers the layers are separa...
Definition: translation.hpp:49
#define a