The Battle for Wesnoth  1.19.7+dev
location.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2024
3  by David White <dave@whitevine.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  * @file
18  * Routines related to game-maps, terrain, locations, directions. etc.
19  */
20 
21 #include <cassert>
22 
23 #include "map/location.hpp"
24 
25 #include "config.hpp"
26 #include "formula/string_utils.hpp"
27 #include "gettext.hpp"
28 #include "log.hpp"
30 #include "utils/math.hpp"
31 
32 
33 static lg::log_domain log_config("config");
34 #define ERR_CF LOG_STREAM(err, log_config)
35 
36 std::ostream &operator<<(std::ostream &s, const map_location& l) {
37  s << (l.wml_x()) << ',' << (l.wml_y());
38  return s;
39 }
40 
41 std::ostream &operator<<(std::ostream &s, const std::vector<map_location>& v) {
42  std::vector<map_location>::const_iterator i = v.begin();
43  for(; i!= v.end(); ++i) {
44  s << "(" << *i << ") ";
45  }
46  return s;
47 }
48 
49 /** Print a direction to stream. */
50 std::ostream& operator<<(std::ostream& s, map_location::direction dir)
51 {
53  return s;
54 }
55 
57  : map_location(x.to_int(), y.to_int(), wml_loc{})
58 {
59 }
60 
61 auto map_location::all_directions() -> std::vector<direction>
62 {
63  return {
70  };
71 }
72 
73 std::size_t hash_value(const map_location& a){
74  std::hash<std::size_t> h;
75  return h( (static_cast<uint32_t>(a.x) << 16) ^ static_cast<uint32_t>(a.y) );
76 }
77 
78 
80 {
81  if(str.empty()) {
83  }
84 
85  // Syntax: [-] (n|ne|se|s|sw|nw) [:cw|:ccw]
86  // - means "take opposite direction" and has higher precedence
87  // :cw and :ccw mean "one step (counter-)clockwise"
88  // Parentheses can be used for grouping or to apply an operator more than once
89 
90  const std::size_t open = str.find_first_of('('), close = str.find_last_of(')');
91  if (open != std::string::npos && close != std::string::npos) {
92  std::string sub = str.substr(open + 1, close - open - 1);
94  sub = str;
95  sub.replace(open, close - open + 1, write_direction(dir));
96  return parse_direction(sub);
97  }
98 
99  const std::size_t start = str[0] == '-' ? 1 : 0;
100  const std::size_t end = str.find_first_of(':');
101  const std::string& main_dir = str.substr(start, end - start);
103 
104  if (main_dir == "n") {
105  dir = direction::north;
106  } else if (main_dir == "ne") {
107  dir = direction::north_east;
108  } else if (main_dir == "se") {
109  dir = direction::south_east;
110  } else if (main_dir == "s") {
111  dir = direction::south;
112  } else if (main_dir == "sw") {
113  dir = direction::south_west;
114  } else if (main_dir == "nw") {
115  dir = direction::north_west;
116  } else {
118  }
119 
120  if (start == 1) {
121  dir = get_opposite_direction(dir);
122  }
123 
124  if (end != std::string::npos) {
125  const std::string rel_dir = str.substr(end + 1);
126  if (rel_dir == "cw") {
127  dir = rotate_direction(dir, 1);
128  } else if (rel_dir == "ccw") {
129  dir = rotate_direction(dir, -1);
130  } else {
132  }
133  }
134 
135  return dir;
136 }
137 
138 std::vector<map_location::direction> map_location::parse_directions(const std::string& str)
139 {
141  std::vector<map_location::direction> to_return;
142  std::vector<std::string> dir_strs = utils::split(str);
143  std::vector<std::string>::const_iterator i, i_end=dir_strs.end();
144  for(i = dir_strs.begin(); i != i_end; ++i) {
146  // Filter out any invalid directions
147  if(temp != direction::indeterminate) {
148  to_return.push_back(temp);
149  }
150  }
151  return to_return;
152 }
153 
155 {
156  switch(dir) {
157  case direction::north:
158  return std::string("n");
160  return std::string("ne");
162  return std::string("nw");
163  case direction::south:
164  return std::string("s");
166  return std::string("se");
168  return std::string("sw");
169  default:
170  return std::string();
171 
172  }
173 }
174 
176 {
177  switch(dir) {
178  case direction::north:
179  return _("North");
181  return _("North East");
183  return _("North West");
184  case direction::south:
185  return _("South");
187  return _("South East");
189  return _("South West");
190  default:
191  return std::string();
192 
193  }
194 }
195 
196 map_location::map_location(const config& cfg, const variable_set *variables) :
197  x(-1000),
198  y(-1000)
199 {
200  std::string xs = cfg["x"], ys = cfg["y"];
201  if (variables)
202  {
203  xs = utils::interpolate_variables_into_string( xs, *variables);
204  ys = utils::interpolate_variables_into_string( ys, *variables);
205  }
206  // The co-ordinates in config files will be 1-based,
207  // while we want them as 0-based.
208  if(xs.empty() == false && xs != "recall") {
209  try {
210  x = std::stoi(xs) - 1;
211  } catch(const std::invalid_argument&) {
212  ERR_CF << "Invalid map coordinate: " << xs;
213  }
214  }
215 
216  if(ys.empty() == false && ys != "recall") {\
217  try {
218  y = std::stoi(ys) - 1;
219  } catch(const std::invalid_argument&) {
220  ERR_CF << "Invalid map coordinate: " << ys;
221  }
222  }
223 }
224 
225 void map_location::write(config& cfg) const
226 {
227  cfg["x"] = x + 1;
228  cfg["y"] = y + 1;
229 }
230 
231 static bool is_vertically_higher_than ( const map_location & m1, const map_location & m2 ) {
232  return (is_odd(m1.wml_x()) && is_even(m2.wml_x())) ? (m1.wml_y() <= m2.wml_y()) : (m1.wml_y() < m2.wml_y());
233 }
234 
236 {
238 }
239 
241 {
242  if (opt == map_location::DEFAULT) {
244 
245  int dx = loc.x - x;
246  int dy = loc.y - y;
247  if (loc.x%2==0 && x%2==1) dy--;
248 
249  if (dx==0 && dy==0) return direction::indeterminate;
250 
251  int dist = std::abs(dx); // Distance from north-south line
252  int dist_diag_SW_NE = std::abs(dy + (dx + (dy>0?0:1) )/2); // Distance from diagonal line SW-NE
253  int dist_diag_SE_NW = std::abs(dy - (dx - (dy>0?0:1) )/2); // Distance from diagonal line SE-NW
254 
255  if (dy > 0) dir = direction::south;
256  else dir = direction::north;
257 
258  if (dist_diag_SE_NW < dist) {
259  if (dx>0) dir = direction::south_east;
260  else dir = direction::north_west;
261  dist = dist_diag_SE_NW;
262  }
263  if (dist_diag_SW_NE < dist) {
264  if (dx>0) dir = direction::north_east;
265  else dir = direction::south_west;
266  }
267  return dir;
268  } else {
269  map_location temp(loc);
270 
271  if (is_vertically_higher_than(temp,*this)) {
272  temp = temp.rotate_right_around_center(*this,1u);
273  if (!is_vertically_higher_than(temp,*this)) {
275  }
276  temp = temp.rotate_right_around_center(*this,1u);
277  if (!is_vertically_higher_than(temp,*this)) {
279  }
281  } else if (is_vertically_higher_than(*this,temp)) {
282  temp = temp.rotate_right_around_center(*this,1u);
283  if (!is_vertically_higher_than(*this,temp)) {
285  }
286  temp = temp.rotate_right_around_center(*this,1u);
287  if (!is_vertically_higher_than(*this,temp)) {
289  }
291  } else if (temp.x > x) {
293  } else if (temp.x < x) {
295  } else {
297  }
298  }
299 }
300 
302  auto me_as_cube = to_cubic(), c_as_cube = center.to_cubic();
303  auto vec = cubic_location{me_as_cube.q - c_as_cube.q, me_as_cube.r - c_as_cube.r, me_as_cube.s - c_as_cube.s};
304  // These represent the 6 possible rotation matrices on the hex grid.
305  // These are orthogonal 3x3 matrices containing only 0, 1, and -1.
306  // Each element represents one row of the matrix.
307  // The absolute value indicates which (1-based) column is non-zero.
308  // The sign indicates whether that cell contains -1 or 1.
309  static const int rotations[6][3] = {{1,2,3}, {-2,-3,-1}, {3,1,2}, {-1,-2,-3}, {2,3,1}, {-3,-1,-2}};
310  int vec_temp[3] = {vec.q, vec.r, vec.s}, vec_temp2[3];
311  int i = ((k % 6) + 6) % 6; // modulo-clamp rotation count to the range [0,6)
312  assert(i >= 0 && i < 6);
313  #define sgn(x) ((x) < 0 ? -1 : 1) // Not quite right, but we know we won't be passing in a 0
314  for(int j = 0; j < 3; j++) vec_temp2[j] = sgn(rotations[i][j]) * vec_temp[abs(rotations[i][j])-1];
315  #undef sgn
316  vec.q = vec_temp2[0] + c_as_cube.q;
317  vec.r = vec_temp2[1] + c_as_cube.r;
318  vec.s = vec_temp2[2] + c_as_cube.s;
319  return from_cubic(vec);
320 }
321 
322 bool map_location::matches_range(const std::string& xloc, const std::string &yloc) const
323 {
324  const auto xlocs = utils::split(xloc);
325  const auto ylocs = utils::split(yloc);
326 
327  if(xlocs.size() == 0 && ylocs.size() == 0) {
328  return true;
329  }
330 
331  // Warn if both x and y were given, but they have different numbers of commas;
332  // the missing entries will be assumed to be 1-infinity.
333  //
334  // No warning if only x or only y was given, as matching only that coordinate seems sane.
335  if(xlocs.size() != ylocs.size() && xlocs.size() && ylocs.size()) {
336  ERR_CF << "Different size lists when pairing coordinate ranges: " << xloc << " vs " << yloc;
337  }
338 
339  std::size_t i = 0;
340  for(; i < xlocs.size() && i < ylocs.size(); ++i) {
341  const auto xr = utils::parse_range(xlocs[i]);
342  const auto yr = utils::parse_range(ylocs[i]);
343  // The ranges are 1-based, but the coordinates are 0-based. Thus the +1 s.
344  if(xr.first <= x+1 && x+1 <= xr.second
345  && yr.first <= y+1 && y+1 <= yr.second) {
346  return true;
347  }
348  }
349  for(; i < xlocs.size(); ++i) {
350  const auto xr = utils::parse_range(xlocs[i]);
351  if(xr.first <= x+1 && x+1 <= xr.second) {
352  return true;
353  }
354  }
355  for(; i < ylocs.size(); ++i) {
356  const auto yr = utils::parse_range(ylocs[i]);
357  if(yr.first <= y+1 && y+1 <= yr.second) {
358  return true;
359  }
360  }
361  return false;
362 }
363 
365 {
368  }
369 
370  if (dir == direction::north) {
371  return map_location(x,y-n);
372  }
373 
374  if (dir == direction::south) {
375  return map_location(x,y+n);
376  }
377 
378  int x_factor = (static_cast<unsigned int> (dir) <= 2u) ? 1 : -1; //whether we go east + or west -
379 
380  unsigned int tmp_y = static_cast<unsigned int> (dir) - 2; //South East => 0, South => 1, South West => 2, North West => 3, North => INT_MAX, North East => INT_MAX - 1
381  int y_factor = (tmp_y <= 2u) ? 1 : -1; //whether we go south + or north -
382 
383  if (tmp_y <= 2u) {
384  return map_location(x + x_factor * n, y + y_factor * ((n + ((x & 1) == 1)) / 2));
385  } else {
386  return map_location(x + x_factor * n, y + y_factor * ((n + ((x & 1) == 0)) / 2));
387  }
388 
389 /*
390  switch(dir) {
391  case direction::north: return map_location(x, y - n);
392  case direction::south: return map_location(x, y + n);
393  case direction::south_east: return map_location(x + n, y + (n+is_odd(x))/2 );
394  case direction::south_west: return map_location(x - n, y + (n+is_odd(x))/2 );
395  case direction::north_east: return map_location(x + n, y - (n+is_even(x))/2 );
396  case direction::north_west: return map_location(x - n, y - (n+is_even(x))/2 );
397  default:
398  assert(false);
399  return map_location::null_location();
400  }*/
401 }
402 
403 void write_location_range(const std::set<map_location>& locs, config& cfg)
404 {
405  if(locs.empty()){
406  cfg["x"] = "";
407  cfg["y"] = "";
408  return;
409  }
410 
411  // need that operator< uses x first
412  assert(map_location(0,1) < map_location(1,0));
413 
414  std::stringstream x, y;
415  std::set<map_location>::const_iterator
416  i = locs.begin(),
417  first = i,
418  last = i;
419 
420  x << (i->wml_x());
421  y << (i->wml_y());
422 
423  for(++i; i != locs.end(); ++i) {
424  if(i->wml_x() != first->wml_x() || i->wml_y() - 1 != last->wml_y()) {
425  if (last->wml_y() != first->wml_y()) {
426  y << "-" << (last->wml_y());
427  }
428  x << "," << (i->wml_x());
429  y << "," << (i->wml_y());
430  first = i;
431  }
432  last = i;
433  }
434  // finish last range
435  if(last->wml_y() != first->wml_y())
436  y << "-" << (last->wml_y());
437 
438  cfg["x"] = x.str();
439  cfg["y"] = y.str();
440 }
441 
442 static map_location read_locations_helper(const std::string & xi, const std::string & yi)
443 {
444  return map_location(std::stoi(xi)-1, std::stoi(yi)-1);
445 }
446 
447 void read_locations(const config& cfg, std::vector<map_location>& locs)
448 {
449  const std::vector<std::string> xvals = utils::split(cfg["x"]);
450  const std::vector<std::string> yvals = utils::split(cfg["y"]);
451 
452  if (xvals.size() != yvals.size()) {
453  throw std::invalid_argument("Number of x and y coordinates do not match.");
454  }
455 
456  std::transform(xvals.begin(), xvals.end(), yvals.begin(), std::back_inserter(locs), &read_locations_helper);
457 }
458 
459 void write_locations(const std::vector<map_location>& locs, config& cfg)
460 {
461  std::stringstream x, y;
462 
463  std::vector<map_location>::const_iterator i = locs.begin(),
464  end = locs.end();
465 
466  for(; i != end; ++i) {
467  x << (i->wml_x());
468  y << (i->wml_y());
469  if(i+1 != end){
470  x << ",";
471  y << ",";
472  }
473  }
474 
475  cfg["x"] = x.str();
476  cfg["y"] = y.str();
477 }
478 
480 {
481  res->x = a.x;
482  res->y = a.y - 1;
483  ++res;
484  res->x = a.x + 1;
485  res->y = a.y - (((a.x & 1) == 0) ? 1 : 0);
486  ++res;
487  res->x = a.x + 1;
488  res->y = a.y + (((a.x & 1) == 1) ? 1 : 0);
489  ++res;
490  res->x = a.x;
491  res->y = a.y + 1;
492  ++res;
493  res->x = a.x - 1;
494  res->y = a.y + (((a.x & 1) == 1) ? 1 : 0);
495  ++res;
496  res->x = a.x - 1;
497  res->y = a.y - (((a.x & 1) == 0) ? 1 : 0);
498 }
499 
500 std::array<map_location, 6> get_adjacent_tiles(const map_location& center)
501 {
502  std::array<map_location, 6> res;
503  get_adjacent_tiles(center, res.data());
504  return res;
505 }
506 
508 {
509  // Two tiles are adjacent:
510  // if y is different by 1, and x by 0,
511  // or if x is different by 1 and y by 0,
512  // or if x and y are each different by 1,
513  // and the x value of the hex with the greater y value is even.
514 
515  switch (a.y - b.y) {
516  case 1 :
517  switch (a.x - b.x) {
518  case 1:
519  case -1:
520  return (a.x & 1) == 0;
521  case 0:
522  return true;
523  default:
524  return false;
525  }
526  case -1 :
527  switch (a.x - b.x) {
528  case 1:
529  case -1:
530  return (b.x & 1) == 0;
531  case 0:
532  return true;
533  default:
534  return false;
535  }
536  case 0 :
537  return ((a.x - b.x) == 1) || ((a.x - b.x) == - 1);
538  default:
539  return false;
540  }
541 
542  /*
543  const int xdiff = std::abs(a.x - b.x);
544  const int ydiff = std::abs(a.y - b.y);
545  return (ydiff == 1 && a.x == b.x) || (xdiff == 1 && a.y == b.y) ||
546  (xdiff == 1 && ydiff == 1 && (a.y > b.y ? is_even(a.x) : is_even(b.x)));
547  */
548 }
549 
550 std::size_t distance_between(const map_location& a, const map_location& b)
551 {
552  const std::size_t hdistance = std::abs(a.x - b.x);
553 
554  const std::size_t vpenalty = ( (((a.x & 1)==0) && ((b.x & 1)==1) && (a.y < b.y))
555  || (((b.x & 1)==0) && ((a.x & 1)==1) && (b.y < a.y)) ) ? 1 : 0;
556 
557 /* Don't want to include util.hpp in this header
558  const std::size_t vpenalty = ( (is_even(a.x) && is_odd(b.x) && (a.y < b.y))
559  || (is_even(b.x) && is_odd(a.x) && (b.y < a.y)) ) ? 1 : 0;
560 */
561  // For any non-negative integer i, i - i/2 - i%2 == i/2
562  // previously returned (hdistance + vdistance - vsavings)
563  // = hdistance + vdistance - minimum(vdistance,hdistance/2+hdistance%2)
564  // = maximum(hdistance, vdistance+hdistance-hdistance/2-hdistance%2)
565  // = maximum(hdistance,std::abs(a.y-b.y)+vpenalty+hdistance/2)
566 
567  return std::max<int>(hdistance, std::abs(a.y - b.y) + vpenalty + hdistance/2);
568 }
map_location loc
Definition: move.cpp:172
Variant for storing WML attributes.
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:172
Definitions for the interface to Wesnoth Markup Language (WML).
std::size_t i
Definition: function.cpp:1029
static std::string _(const char *str)
Definition: gettext.hpp:93
std::size_t hash_value(const map_location &a)
Definition: location.cpp:73
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
static bool is_vertically_higher_than(const map_location &m1, const map_location &m2)
Definition: location.cpp:231
static map_location read_locations_helper(const std::string &xi, const std::string &yi)
Definition: location.cpp:442
std::size_t distance_between(const map_location &a, const map_location &b)
Function which gives the number of hexes between two tiles (i.e.
Definition: location.cpp:550
void write_locations(const std::vector< map_location > &locs, config &cfg)
Write a vector of locations into a config adding keys x=x1,x2,..,xn and y=y1,y2,.....
Definition: location.cpp:459
std::ostream & operator<<(std::ostream &s, const map_location &l)
Dumps a position on a stream, for debug purposes.
Definition: location.cpp:36
#define ERR_CF
Definition: location.cpp:34
#define sgn(x)
void read_locations(const config &cfg, std::vector< map_location > &locs)
Parse x,y keys of a config into a vector of locations.
Definition: location.cpp:447
bool tiles_adjacent(const map_location &a, const map_location &b)
Function which tells if two locations are adjacent.
Definition: location.cpp:507
void write_location_range(const std::set< map_location > &locs, config &cfg)
Write a set of locations into a config using ranges, adding keys x=x1,..,xn and y=y1a-y1b,...
Definition: location.cpp:403
static lg::log_domain log_config("config")
Standard logging facilities (interface).
General math utility functions.
constexpr bool is_even(T num)
Definition: math.hpp:33
constexpr bool is_odd(T num)
Definition: math.hpp:36
EXIT_STATUS start(bool clear_id, const std::string &filename, bool take_screenshot, const std::string &screenshot_filename)
Main interface for launching the editor from the title screen.
std::string interpolate_variables_into_string(const std::string &str, const string_map *const symbols)
Function which will interpolate variables, starting with '$' in the string 'str' with the equivalent ...
int stoi(std::string_view str)
Same interface as std::stoi and meant as a drop in replacement, except:
Definition: charconv.hpp:154
std::pair< int, int > parse_range(std::string_view str)
Recognises the following patterns, and returns a {min, max} pair.
std::vector< std::string > split(const config_attribute_value &val)
Represents a map location in cubic hexagonal coordinates.
Definition: location.hpp:33
Encapsulates the map of the game.
Definition: location.hpp:45
static map_location from_cubic(cubic_location h)
Definition: location.hpp:172
static std::string write_direction(direction dir)
Definition: location.cpp:154
static std::vector< direction > parse_directions(const std::string &str)
Parse_directions takes a comma-separated list, and filters out any invalid directions.
Definition: location.cpp:138
static std::vector< direction > all_directions()
Definition: location.cpp:61
map_location get_direction(direction dir, unsigned int n=1u) const
Definition: location.cpp:364
map_location rotate_right_around_center(const map_location &center, int k) const
Definition: location.cpp:301
int wml_y() const
Definition: location.hpp:184
static constexpr direction get_opposite_direction(direction d)
Definition: location.hpp:75
cubic_location to_cubic() const
Definition: location.hpp:166
direction
Valid directions which can be moved in our hexagonal world.
Definition: location.hpp:47
direction get_relative_dir(const map_location &loc, map_location::RELATIVE_DIR_MODE mode) const
Definition: location.cpp:240
static const map_location & null_location()
Definition: location.hpp:102
int wml_x() const
Definition: location.hpp:183
static direction parse_direction(const std::string &str)
Definition: location.cpp:79
static std::string write_translated_direction(direction dir)
Definition: location.cpp:175
void write(config &cfg) const
Definition: location.cpp:225
bool matches_range(const std::string &xloc, const std::string &yloc) const
Definition: location.cpp:322
static constexpr direction rotate_direction(direction d, int steps=1)
Returns the direction one would face having taken steps clockwise around an undefined center.
Definition: location.hpp:60
static map_location::direction n
static map_location::direction s
#define h
#define b