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