The Battle for Wesnoth  1.19.5+dev
pathutils.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  * Various pathfinding functions and utilities.
19  */
20 
21 #include "pathutils.hpp"
22 #include "pathutils_impl.hpp"
23 
24 #include "map/map.hpp"
25 
26 
27 /**
28  * Function that will add to @a result all locations exactly @a radius tiles
29  * from @a center (or nothing if @a radius is not positive). @a result must be
30  * a std::vector of locations.
31  */
32 void get_tile_ring(const map_location& center, const int radius,
33  std::vector<map_location>& result)
34 {
35  if ( radius <= 0 ) {
36  return;
37  }
38 
40 
41  for(int n = 0; n != 6; ++n) {
42  const map_location::direction dir{ n };
43  for(int i = 0; i != radius; ++i) {
44  result.push_back(loc);
45  loc = loc.get_direction(dir, 1);
46  }
47  }
48 }
49 
50 
51 /**
52  * Function that will add to @a result all locations within @a radius tiles
53  * of @a center (excluding @a center itself). @a result must be a std::vector
54  * of locations.
55  */
56 void get_tiles_in_radius(const map_location& center, const int radius,
57  std::vector<map_location>& result)
58 {
59  for(int n = 1; n <= radius; ++n) {
60  get_tile_ring(center, n, result);
61  }
62 }
63 
64 
65 /**
66  * Function that will add to @a result all locations within @a radius tiles
67  * of @a center (including @a center itself). @a result must be a std::set
68  * of locations.
69  */
70 void get_tiles_radius(const map_location& center, std::size_t radius,
71  std::set<map_location>& result)
72 {
73  // Re-use some logic.
74  std::vector<map_location> internal_result(1, center);
75  get_tiles_in_radius(center, static_cast<int>(radius), internal_result);
76 
77  // Convert to a set.
78  result.insert(internal_result.begin(), internal_result.end());
79 }
80 
81 
82 namespace { // Helpers for get_tiles_radius() without a radius filter.
83 
84  // Ranges of rows are stored as pairs of a row number and a number of rows.
85  typedef std::pair<int, std::size_t> row_range;
86  // This is a map from column numbers to sets of ranges of rows.
87  typedef std::map<int, std::set<row_range>> column_ranges;
88 
89 
90  /**
91  * Function that will collect all locations within @a radius tiles of an
92  * element of @a locs, subject to the restriction col_begin <= x < col_end.
93  */
94  // Complexity: O(nr lg(nr)), where n = locs.size() and r = radius.
95  // In this formula, r is bound by col_end-col_begin (but that is
96  // probably a rare event).
97  void get_column_ranges(column_ranges & collected_tiles,
98  const std::vector<map_location>& locs,
99  const std::size_t radius,
100  const int col_begin, const int col_end)
101  {
102 #ifdef __cpp_using_enum // c++20
103  using enum map_location::direction;
104 #else
105  // Shorter names for the directions we'll use.
109 #endif
110 
111  // Perform this conversion once.
112  const int radius_i = static_cast<int>(radius);
113 
114  for (const map_location &loc : locs)
115  if ( loc != map_location::null_location() )
116  {
117  // Calculate the circle of hexes around this one.
118  std::size_t height = radius;
119  map_location top = loc.get_direction(north_west, radius_i);
120  // Don't start off the map edge.
121  if ( top.x < col_begin ) {
122  const int col_shift = std::min(col_begin, loc.x) - top.x;
123  top = top.get_direction(north_east, col_shift);
124  height += col_shift;
125  }
126  // The left side.
127  const int end_l = std::min(loc.x, col_end);
128  for ( ; top.x < end_l; top = top.get_direction(north_east, 1) )
129  collected_tiles[top.x].insert(row_range(top.y, ++height));
130  // Extra increment so the middle column is tall enough.
131  height += 2;
132  // Don't start off the map edge (we allow loc to be off-board).
133  if ( top.x < col_begin ) {
134  const int col_shift = col_begin - top.x;
135  top = top.get_direction(south_east, col_shift);
136  height -= col_shift;
137  }
138  // The middle column and right side.
139  const int end_r = std::min(loc.x + radius_i + 1, col_end);
140  for ( ; top.x < end_r; top = top.get_direction(south_east, 1) )
141  collected_tiles[top.x].insert(row_range(top.y, --height));
142  }
143  }
144 
145  /**
146  * Function that interprets @a collected_tiles and adds to @a result those
147  * whose y-coordinate satisfies row_begin <= y < row_end.
148  * When passed to this function, @a result must not be empty. (This allows
149  * a code simplification and is currently always the case anyway.)
150  */
151  // Complexity: O(number of distinct hexes collected), assuming that the
152  // insertion hint makes insertions O(1). Furthermore, hexes outside the
153  // interval [row_begin, row_end) are skipped and do not count towards the
154  // complexity.
155  void ranges_to_tiles(std::set<map_location> & result,
156  const column_ranges & collected_tiles,
157  int row_begin, int row_end)
158  {
159  // This should help optimize the insertions (since we will be
160  // processing hexes in their lexicographical order).
161  // Note: This hint will get incremented later, which is the only
162  // reason we require result to be initially non-empty.
163  auto insert_hint = result.begin();
164 
165  for(const auto& [column, range] : collected_tiles) {
166  // For this loop, the order within the set is crucial; we need
167  // rows.first to be non-decreasing with each iteration.
168  // Loop invariant: within this column, all rows before next_row
169  // have been processed and either added to result or skipped.
170  // There is no going back (nor a need to).
171  int next_row = row_begin;
172 
173  for(const auto& [row_index, num_rows] : range) {
174  // Skipping some rows?
175  if(next_row < row_index) {
176  next_row = row_index;
177  }
178 
179  // Add this range of hexes.
180  const int end = std::min(row_index + static_cast<int>(num_rows), row_end);
181  for(; next_row < end; ++next_row) {
182  insert_hint = result.insert(++insert_hint, map_location(column, next_row));
183  }
184 
185  // Have we reached the end of the board?
186  if(next_row >= row_end) {
187  break;
188  }
189  }
190  }
191  }
192 
193 } // namespage for get_tiles_radius() helpers.
194 
195 
196 /**
197  * Function that will add to @a result all elements of @a locs, plus all
198  * on-board locations that are within @a radius tiles of an element of locs.
199  * @a result must be a std::set of locations.
200  */
201 // Complexity: O(nr lg(nr) + nr^2), where n = locs.size(), r = radius.
202 // The nr^2 term is bounded by the size of the board.
203 void get_tiles_radius(const gamemap& map, const std::vector<map_location>& locs,
204  std::size_t radius, std::set<map_location>& result,
205  bool with_border)
206 {
207  // Make sure the provided locations are included.
208  // This would be needed in case some of the provided locations are off-map.
209  // It also allows simpler processing if the radius is zero.
210  // For efficiency, do this first since locs is potentially unsorted.
211  result.insert(locs.begin(), locs.end());
212 
213  if ( radius != 0 && !locs.empty() )
214  {
215  const int border = with_border ? map.border_size() : 0;
216  column_ranges collected_tiles;
217 
218  // Collect the hexes within the desired disks into collected_tiles.
219  // This maps each x-value to a set of ranges of y-values that
220  // are covered by the disks around each element of locs.
221  // (So the data size at this point is proportional to the number
222  // of x-values involved, which is O(nr). The lg(nr) factor comes
223  // from the data being sorted.)
224  get_column_ranges(collected_tiles, locs, radius, -border, map.w() + border);
225 
226  // Now that all the tiles have been collected, add them to result.
227  // (There are O(nr^2) hexes to add.) By collecting before adding, each
228  // hex will be processed only once, even when disks overlap. This is
229  // how we can get good performance if there is significant overlap, and
230  // how the work required can be bound by the size of the board.
231  ranges_to_tiles(result, collected_tiles, -border, map.h() + border);
232  }
233 }
234 
235 
236 /**
237  * Function that will add to @a result all elements of @a locs, plus all
238  * on-board locations matching @a pred that are connected to elements of
239  * locs by a chain of at most @a radius tiles, each of which matches @a pred.
240  * @a result must be a std::set of locations.
241  */
242 void get_tiles_radius(const gamemap& map, const std::vector<map_location>& locs,
243  std::size_t radius, std::set<map_location> &result,
244  bool with_border, const xy_pred& pred)
245 {
246  typedef std::set<map_location> location_set;
247  location_set not_visited(locs.begin(), locs.end());
248 
249  get_tiles_radius(std::move(not_visited), radius, result,
250  [&](const map_location& l) {
251  return with_border ? map.on_board_with_border(l) : map.on_board(l);
252  },
253  [&](const map_location& l) {
254  return pred(l);
255  }
256  );
257 }
int w() const
Effective map width.
Definition: map.hpp:50
int h() const
Effective map height.
Definition: map.hpp:53
bool on_board_with_border(const map_location &loc) const
Definition: map.cpp:390
int border_size() const
Size of the map border.
Definition: map.hpp:56
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
@ border
The border of the map.
std::size_t i
Definition: function.cpp:1023
T end(const std::pair< T, T > &p)
std::set< map_location > location_set
void get_tiles_in_radius(const map_location &center, const int radius, std::vector< map_location > &result)
Function that will add to result all locations within radius tiles of center (excluding center itself...
Definition: pathutils.cpp:56
void get_tiles_radius(const map_location &center, std::size_t radius, std::set< map_location > &result)
Function that will add to result all locations within radius tiles of center (including center itself...
Definition: pathutils.cpp:70
void get_tile_ring(const map_location &center, const int radius, std::vector< map_location > &result)
Function that will add to result all locations exactly radius tiles from center (or nothing if radius...
Definition: pathutils.cpp:32
Encapsulates the map of the game.
Definition: location.hpp:45
map_location get_direction(direction dir, unsigned int n=1u) const
Definition: location.cpp:364
direction
Valid directions which can be moved in our hexagonal world.
Definition: location.hpp:47
static const map_location & null_location()
Definition: location.hpp:102
static map_location::direction n