The Battle for Wesnoth  1.19.12+dev
location.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2025
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 
175 {
176  switch(dir) {
177  case direction::north:
178  return _("North");
180  return _("North East");
182  return _("North West");
183  case direction::south:
184  return _("South");
186  return _("South East");
188  return _("South West");
189  default:
190  return std::string();
191  }
192 }
193 
194 map_location::map_location(const config& cfg, const variable_set* variables)
195  : x(-1000)
196  , y(-1000)
197 {
198  std::string xs = cfg["x"], ys = cfg["y"];
199  if (variables)
200  {
201  xs = utils::interpolate_variables_into_string( xs, *variables);
202  ys = utils::interpolate_variables_into_string( ys, *variables);
203  }
204  // The co-ordinates in config files will be 1-based,
205  // while we want them as 0-based.
206  if(xs.empty() == false && xs != "recall") {
207  try {
208  x = std::stoi(xs) - 1;
209  } catch(const std::invalid_argument&) {
210  ERR_CF << "Invalid map coordinate: " << xs;
211  }
212  }
213 
214  if(ys.empty() == false && ys != "recall") {\
215  try {
216  y = std::stoi(ys) - 1;
217  } catch(const std::invalid_argument&) {
218  ERR_CF << "Invalid map coordinate: " << ys;
219  }
220  }
221 }
222 
223 void map_location::write(config& cfg) const
224 {
225  cfg["x"] = x + 1;
226  cfg["y"] = y + 1;
227 }
228 
229 static bool is_vertically_higher_than (const map_location& m1, const map_location& m2) {
230  return (is_odd(m1.wml_x()) && is_even(m2.wml_x())) ? (m1.wml_y() <= m2.wml_y()) : (m1.wml_y() < m2.wml_y());
231 }
232 
234 {
236 }
237 
239 {
240  if (opt == map_location::DEFAULT) {
242 
243  int dx = loc.x - x;
244  int dy = loc.y - y;
245  if (loc.x%2==0 && x%2==1) dy--;
246 
247  if (dx==0 && dy==0) return direction::indeterminate;
248 
249  int dist = std::abs(dx); // Distance from north-south line
250  int dist_diag_SW_NE = std::abs(dy + (dx + (dy>0?0:1) )/2); // Distance from diagonal line SW-NE
251  int dist_diag_SE_NW = std::abs(dy - (dx - (dy>0?0:1) )/2); // Distance from diagonal line SE-NW
252 
253  if (dy > 0) dir = direction::south;
254  else dir = direction::north;
255 
256  if (dist_diag_SE_NW < dist) {
257  if (dx>0) dir = direction::south_east;
258  else dir = direction::north_west;
259  dist = dist_diag_SE_NW;
260  }
261  if (dist_diag_SW_NE < dist) {
262  if (dx>0) dir = direction::north_east;
263  else dir = direction::south_west;
264  }
265  return dir;
266  } else {
267  map_location temp(loc);
268 
269  if (is_vertically_higher_than(temp,*this)) {
270  temp = temp.rotate_right_around_center(*this,1u);
271  if (!is_vertically_higher_than(temp,*this)) {
273  }
274  temp = temp.rotate_right_around_center(*this,1u);
275  if (!is_vertically_higher_than(temp,*this)) {
277  }
279  } else if (is_vertically_higher_than(*this,temp)) {
280  temp = temp.rotate_right_around_center(*this,1u);
281  if (!is_vertically_higher_than(*this,temp)) {
283  }
284  temp = temp.rotate_right_around_center(*this,1u);
285  if (!is_vertically_higher_than(*this,temp)) {
287  }
289  } else if (temp.x > x) {
291  } else if (temp.x < x) {
293  } else {
295  }
296  }
297 }
298 
300  auto me_as_cube = to_cubic(), c_as_cube = center.to_cubic();
301  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};
302  // These represent the 6 possible rotation matrices on the hex grid.
303  // These are orthogonal 3x3 matrices containing only 0, 1, and -1.
304  // Each element represents one row of the matrix.
305  // The absolute value indicates which (1-based) column is non-zero.
306  // The sign indicates whether that cell contains -1 or 1.
307  static const int rotations[6][3] = {{1,2,3}, {-2,-3,-1}, {3,1,2}, {-1,-2,-3}, {2,3,1}, {-3,-1,-2}};
308  int vec_temp[3] = {vec.q, vec.r, vec.s}, vec_temp2[3];
309  int i = ((k % 6) + 6) % 6; // modulo-clamp rotation count to the range [0,6)
310  assert(i >= 0 && i < 6);
311  #define sgn(x) ((x) < 0 ? -1 : 1) // Not quite right, but we know we won't be passing in a 0
312  for(int j = 0; j < 3; j++) vec_temp2[j] = sgn(rotations[i][j]) * vec_temp[abs(rotations[i][j])-1];
313  #undef sgn
314  vec.q = vec_temp2[0] + c_as_cube.q;
315  vec.r = vec_temp2[1] + c_as_cube.r;
316  vec.s = vec_temp2[2] + c_as_cube.s;
317  return from_cubic(vec);
318 }
319 
320 bool map_location::matches_range(const std::string& xloc, const std::string& yloc) const
321 {
322  const auto xlocs = utils::split(xloc);
323  const auto ylocs = utils::split(yloc);
324 
325  if(xlocs.size() == 0 && ylocs.size() == 0) {
326  return true;
327  }
328 
329  // Warn if both x and y were given, but they have different numbers of commas;
330  // the missing entries will be assumed to be 1-infinity.
331  //
332  // No warning if only x or only y was given, as matching only that coordinate seems sane.
333  if(xlocs.size() != ylocs.size() && xlocs.size() && ylocs.size()) {
334  ERR_CF << "Different size lists when pairing coordinate ranges: " << xloc << " vs " << yloc;
335  }
336 
337  std::size_t i = 0;
338  for(; i < xlocs.size() && i < ylocs.size(); ++i) {
339  const auto xr = utils::parse_range(xlocs[i]);
340  const auto yr = utils::parse_range(ylocs[i]);
341  // The ranges are 1-based, but the coordinates are 0-based. Thus the +1 s.
342  if(xr.first <= x+1 && x+1 <= xr.second
343  && yr.first <= y+1 && y+1 <= yr.second) {
344  return true;
345  }
346  }
347  for(; i < xlocs.size(); ++i) {
348  const auto xr = utils::parse_range(xlocs[i]);
349  if(xr.first <= x+1 && x+1 <= xr.second) {
350  return true;
351  }
352  }
353  for(; i < ylocs.size(); ++i) {
354  const auto yr = utils::parse_range(ylocs[i]);
355  if(yr.first <= y+1 && y+1 <= yr.second) {
356  return true;
357  }
358  }
359  return false;
360 }
361 
363 {
366  }
367 
368  if (dir == direction::north) {
369  return map_location(x,y-n);
370  }
371 
372  if (dir == direction::south) {
373  return map_location(x,y+n);
374  }
375 
376  int x_factor = (static_cast<unsigned int> (dir) <= 2u) ? 1 : -1; //whether we go east + or west -
377 
378  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
379  int y_factor = (tmp_y <= 2u) ? 1 : -1; //whether we go south + or north -
380 
381  if (tmp_y <= 2u) {
382  return map_location(x + x_factor * n, y + y_factor * ((n + ((x & 1) == 1)) / 2));
383  } else {
384  return map_location(x + x_factor * n, y + y_factor * ((n + ((x & 1) == 0)) / 2));
385  }
386 
387 /*
388  switch(dir) {
389  case direction::north: return map_location(x, y - n);
390  case direction::south: return map_location(x, y + n);
391  case direction::south_east: return map_location(x + n, y + (n+is_odd(x))/2 );
392  case direction::south_west: return map_location(x - n, y + (n+is_odd(x))/2 );
393  case direction::north_east: return map_location(x + n, y - (n+is_even(x))/2 );
394  case direction::north_west: return map_location(x - n, y - (n+is_even(x))/2 );
395  default:
396  assert(false);
397  return map_location::null_location();
398  }*/
399 }
400 
401 std::vector<map_location> map_location::get_ring(int min, int max) const
402 {
403  std::vector<map_location> tiles;
404 
405  // Convert this location to cubic coordinates
406  cubic_location cubic_center = this->to_cubic();
407 
408  // Enumerate range in cubic coordinates
409  for (int dx = -max; dx <= max; ++dx) {
410  for (int dy = std::max(-max, -dx-max); dy <= std::min(max, -dx+max); ++dy) {
411  int dz = -dx - dy;
412 
413  // Calculate Manhattan distance in cubic coordinates
414  int distance = (std::abs(dx) + std::abs(dy) + std::abs(dz)) / 2;
415 
416  // Skip positions outside our min/max range
417  if (distance < min || distance > max) {
418  continue;
419  }
420 
421  // Create new cubic location
422  cubic_location neighbor{
423  cubic_center.q + dx,
424  cubic_center.r + dy,
425  cubic_center.s + dz
426  };
427 
428  // Convert back to map coordinates and add to tiles
429  tiles.push_back(from_cubic(neighbor));
430  }
431  }
432 
433  return tiles;
434 }
435 
436 void write_location_range(const std::set<map_location>& locs, config& cfg)
437 {
438  if(locs.empty()){
439  cfg["x"] = "";
440  cfg["y"] = "";
441  return;
442  }
443 
444  // need that operator< uses x first
445  assert(map_location(0,1) < map_location(1,0));
446 
447  std::stringstream x, y;
448  std::set<map_location>::const_iterator
449  i = locs.begin(),
450  first = i,
451  last = i;
452 
453  x << (i->wml_x());
454  y << (i->wml_y());
455 
456  for(++i; i != locs.end(); ++i) {
457  if(i->wml_x() != first->wml_x() || i->wml_y() - 1 != last->wml_y()) {
458  if (last->wml_y() != first->wml_y()) {
459  y << "-" << (last->wml_y());
460  }
461  x << "," << (i->wml_x());
462  y << "," << (i->wml_y());
463  first = i;
464  }
465  last = i;
466  }
467  // finish last range
468  if(last->wml_y() != first->wml_y())
469  y << "-" << (last->wml_y());
470 
471  cfg["x"] = x.str();
472  cfg["y"] = y.str();
473 }
474 
475 static map_location read_locations_helper(const std::string& xi, const std::string& yi)
476 {
477  return map_location(std::stoi(xi)-1, std::stoi(yi)-1);
478 }
479 
480 void read_locations(const config& cfg, std::vector<map_location>& locs)
481 {
482  const std::vector<std::string> xvals = utils::split(cfg["x"]);
483  const std::vector<std::string> yvals = utils::split(cfg["y"]);
484 
485  if (xvals.size() != yvals.size()) {
486  throw std::invalid_argument("Number of x and y coordinates do not match.");
487  }
488 
489  std::transform(xvals.begin(), xvals.end(), yvals.begin(), std::back_inserter(locs), &read_locations_helper);
490 }
491 
492 void write_locations(const std::vector<map_location>& locs, config& cfg)
493 {
494  std::stringstream x, y;
495 
496  std::vector<map_location>::const_iterator i = locs.begin(),
497  end = locs.end();
498 
499  for(; i != end; ++i) {
500  x << (i->wml_x());
501  y << (i->wml_y());
502  if(i+1 != end){
503  x << ",";
504  y << ",";
505  }
506  }
507 
508  cfg["x"] = x.str();
509  cfg["y"] = y.str();
510 }
511 
513 {
514  res->x = a.x;
515  res->y = a.y - 1;
516  ++res;
517  res->x = a.x + 1;
518  res->y = a.y - (((a.x & 1) == 0) ? 1 : 0);
519  ++res;
520  res->x = a.x + 1;
521  res->y = a.y + (((a.x & 1) == 1) ? 1 : 0);
522  ++res;
523  res->x = a.x;
524  res->y = a.y + 1;
525  ++res;
526  res->x = a.x - 1;
527  res->y = a.y + (((a.x & 1) == 1) ? 1 : 0);
528  ++res;
529  res->x = a.x - 1;
530  res->y = a.y - (((a.x & 1) == 0) ? 1 : 0);
531 }
532 
533 std::array<map_location, 6> get_adjacent_tiles(const map_location& center)
534 {
535  std::array<map_location, 6> res;
536  get_adjacent_tiles(center, res.data());
537  return res;
538 }
539 
541 {
542  // Two tiles are adjacent:
543  // if y is different by 1, and x by 0,
544  // or if x is different by 1 and y by 0,
545  // or if x and y are each different by 1,
546  // and the x value of the hex with the greater y value is even.
547 
548  switch (a.y - b.y) {
549  case 1 :
550  switch (a.x - b.x) {
551  case 1:
552  case -1:
553  return (a.x & 1) == 0;
554  case 0:
555  return true;
556  default:
557  return false;
558  }
559  case -1 :
560  switch (a.x - b.x) {
561  case 1:
562  case -1:
563  return (b.x & 1) == 0;
564  case 0:
565  return true;
566  default:
567  return false;
568  }
569  case 0 :
570  return ((a.x - b.x) == 1) || ((a.x - b.x) == - 1);
571  default:
572  return false;
573  }
574 
575  /*
576  const int xdiff = std::abs(a.x - b.x);
577  const int ydiff = std::abs(a.y - b.y);
578  return (ydiff == 1 && a.x == b.x) || (xdiff == 1 && a.y == b.y) ||
579  (xdiff == 1 && ydiff == 1 && (a.y > b.y ? is_even(a.x) : is_even(b.x)));
580  */
581 }
582 
583 std::size_t distance_between(const map_location& a, const map_location& b)
584 {
585  const std::size_t hdistance = std::abs(a.x - b.x);
586 
587  const std::size_t vpenalty = ( (((a.x & 1)==0) && ((b.x & 1)==1) && (a.y < b.y))
588  || (((b.x & 1)==0) && ((a.x & 1)==1) && (b.y < a.y)) ) ? 1 : 0;
589 
590 /* Don't want to include util.hpp in this header
591  const std::size_t vpenalty = ( (is_even(a.x) && is_odd(b.x) && (a.y < b.y))
592  || (is_even(b.x) && is_odd(a.x) && (b.y < a.y)) ) ? 1 : 0;
593 */
594  // For any non-negative integer i, i - i/2 - i%2 == i/2
595  // previously returned (hdistance + vdistance - vsavings)
596  // = hdistance + vdistance - minimum(vdistance,hdistance/2+hdistance%2)
597  // = maximum(hdistance, vdistance+hdistance-hdistance/2-hdistance%2)
598  // = maximum(hdistance,std::abs(a.y-b.y)+vpenalty+hdistance/2)
599 
600  return std::max<int>(hdistance, std::abs(a.y - b.y) + vpenalty + hdistance/2);
601 }
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:158
Definitions for the interface to Wesnoth Markup Language (WML).
std::size_t i
Definition: function.cpp:1030
static std::string _(const char *str)
Definition: gettext.hpp:97
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:512
static bool is_vertically_higher_than(const map_location &m1, const map_location &m2)
Definition: location.cpp:229
static map_location read_locations_helper(const std::string &xi, const std::string &yi)
Definition: location.cpp:475
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:583
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:492
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:480
bool tiles_adjacent(const map_location &a, const map_location &b)
Function which tells if two locations are adjacent.
Definition: location.cpp:540
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:436
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.
constexpr auto transform
Definition: ranges.hpp:41
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:362
map_location rotate_right_around_center(const map_location &center, int k) const
Definition: location.cpp:299
int wml_y() const
Definition: location.hpp:186
std::vector< map_location > get_ring(int min, int max) const
Definition: location.cpp:401
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:238
static const map_location & null_location()
Definition: location.hpp:102
int wml_x() const
Definition: location.hpp:185
static direction parse_direction(const std::string &str)
Definition: location.cpp:79
static std::string write_translated_direction(direction dir)
Definition: location.cpp:174
void write(config &cfg) const
Definition: location.cpp:223
bool matches_range(const std::string &xloc, const std::string &yloc) const
Definition: location.cpp:320
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