The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
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"
27#include "gettext.hpp"
28#include "log.hpp"
30#include "utils/math.hpp"
31
32
33static lg::log_domain log_config("config");
34#define ERR_CF LOG_STREAM(err, log_config)
35
36std::ostream& operator<<(std::ostream& s, const map_location& l) {
37 s << (l.wml_x()) << ',' << (l.wml_y());
38 return s;
39}
40
41std::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. */
50std::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
72
73std::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") {
108 } else if (main_dir == "se") {
110 } else if (main_dir == "s") {
111 dir = direction::south;
112 } else if (main_dir == "sw") {
114 } else if (main_dir == "nw") {
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
138std::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
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
224{
225 cfg["x"] = x + 1;
226 cfg["y"] = y + 1;
227}
228
229static 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
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
320bool 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
401std::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
436void 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
475std::vector<map_location> read_locations(const config& cfg)
476{
477 const std::vector<std::string> xvals = utils::split(cfg["x"]);
478 const std::vector<std::string> yvals = utils::split(cfg["y"]);
479
480 if (xvals.size() != yvals.size()) {
481 throw std::invalid_argument("Number of x and y coordinates do not match.");
482 }
483
484 std::vector<map_location> locs;
485 std::transform(xvals.begin(), xvals.end(), yvals.begin(), std::back_inserter(locs),
486 [](const std::string& xi, const std::string& yi) -> map_location {
487 return {std::stoi(xi), std::stoi(yi), wml_loc{}};
488 });
489
490 return locs;
491}
492
493void write_locations(const std::vector<map_location>& locs, config& cfg)
494{
495 std::stringstream x, y;
496
497 std::vector<map_location>::const_iterator i = locs.begin(),
498 end = locs.end();
499
500 for(; i != end; ++i) {
501 x << (i->wml_x());
502 y << (i->wml_y());
503 if(i+1 != end){
504 x << ",";
505 y << ",";
506 }
507 }
508
509 cfg["x"] = x.str();
510 cfg["y"] = y.str();
511}
512
514{
515 res[0].x = a.x;
516 res[0].y = a.y - 1;
517
518 res[1].x = a.x + 1;
519 res[1].y = a.y - (((a.x & 1) == 0) ? 1 : 0);
520
521 res[2].x = a.x + 1;
522 res[2].y = a.y + (((a.x & 1) == 1) ? 1 : 0);
523
524 res[3].x = a.x;
525 res[3].y = a.y + 1;
526
527 res[4].x = a.x - 1;
528 res[4].y = a.y + (((a.x & 1) == 1) ? 1 : 0);
529
530 res[5].x = a.x - 1;
531 res[5].y = a.y - (((a.x & 1) == 0) ? 1 : 0);
532}
533
534std::array<map_location, 6> get_adjacent_tiles(const map_location& center)
535{
536 std::array<map_location, 6> res;
537 get_adjacent_tiles(center, res);
538 return res;
539}
540
542{
543 // Two tiles are adjacent:
544 // if y is different by 1, and x by 0,
545 // or if x is different by 1 and y by 0,
546 // or if x and y are each different by 1,
547 // and the x value of the hex with the greater y value is even.
548
549 switch (a.y - b.y) {
550 case 1 :
551 switch (a.x - b.x) {
552 case 1:
553 case -1:
554 return (a.x & 1) == 0;
555 case 0:
556 return true;
557 default:
558 return false;
559 }
560 case -1 :
561 switch (a.x - b.x) {
562 case 1:
563 case -1:
564 return (b.x & 1) == 0;
565 case 0:
566 return true;
567 default:
568 return false;
569 }
570 case 0 :
571 return ((a.x - b.x) == 1) || ((a.x - b.x) == - 1);
572 default:
573 return false;
574 }
575
576 /*
577 const int xdiff = std::abs(a.x - b.x);
578 const int ydiff = std::abs(a.y - b.y);
579 return (ydiff == 1 && a.x == b.x) || (xdiff == 1 && a.y == b.y) ||
580 (xdiff == 1 && ydiff == 1 && (a.y > b.y ? is_even(a.x) : is_even(b.x)));
581 */
582}
583
584std::size_t distance_between(const map_location& a, const map_location& b)
585{
586 const std::size_t hdistance = std::abs(a.x - b.x);
587
588 const std::size_t vpenalty = ( (((a.x & 1)==0) && ((b.x & 1)==1) && (a.y < b.y))
589 || (((b.x & 1)==0) && ((a.x & 1)==1) && (b.y < a.y)) ) ? 1 : 0;
590
591/* Don't want to include util.hpp in this header
592 const std::size_t vpenalty = ( (is_even(a.x) && is_odd(b.x) && (a.y < b.y))
593 || (is_even(b.x) && is_odd(a.x) && (b.y < a.y)) ) ? 1 : 0;
594*/
595 // For any non-negative integer i, i - i/2 - i%2 == i/2
596 // previously returned (hdistance + vdistance - vsavings)
597 // = hdistance + vdistance - minimum(vdistance,hdistance/2+hdistance%2)
598 // = maximum(hdistance, vdistance+hdistance-hdistance/2-hdistance%2)
599 // = maximum(hdistance,std::abs(a.y-b.y)+vpenalty+hdistance/2)
600
601 return std::max<int>(hdistance, std::abs(a.y - b.y) + vpenalty + hdistance/2);
602}
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:157
#define ERR_CF
Definition config.cpp:37
Definitions for the interface to Wesnoth Markup Language (WML).
const config * cfg
static std::string _(const char *str)
Definition gettext.hpp:100
std::size_t hash_value(const map_location &a)
Definition location.cpp:73
std::ostream & operator<<(std::ostream &s, const map_location &l)
Dumps a position on a stream, for debug purposes.
Definition location.cpp:36
static bool is_vertically_higher_than(const map_location &m1, const map_location &m2)
Definition location.cpp:229
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:584
void get_adjacent_tiles(const map_location &a, utils::span< map_location, 6 > res)
Function which, given a location, will place all adjacent locations in res.
Definition location.cpp:513
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:493
std::vector< map_location > read_locations(const config &cfg)
Parse x,y keys of a config into a vector of locations.
Definition location.cpp:475
#define sgn(x)
bool tiles_adjacent(const map_location &a, const map_location &b)
Function which tells if two locations are adjacent.
Definition location.cpp:541
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:31
constexpr bool is_odd(T num)
Definition math.hpp:34
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 ...
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:34
Encapsulates the map of the game.
Definition location.hpp:46
static map_location from_cubic(cubic_location h)
Definition location.hpp:173
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:187
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:76
cubic_location to_cubic() const
Definition location.hpp:167
direction
Valid directions which can be moved in our hexagonal world.
Definition location.hpp:48
direction get_relative_dir(const map_location &loc, map_location::RELATIVE_DIR_MODE mode) const
Definition location.cpp:238
int wml_x() const
Definition location.hpp:186
static const map_location & null_location()
Definition location.hpp:103
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:61
static map_location::direction n
static map_location::direction s
#define i
#define h
#define b