The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
pathutils.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 * 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 */
32void 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 */
56void 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 */
70void 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
82namespace { // 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.
203void 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 */
242void 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}
map_location loc
Definition move.cpp:172
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:352
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:347
Encapsulates the map of the game.
Definition map.hpp:176
@ border
The border of the map.
std::set< map_location > location_set
T end(const std::pair< T, T > &p)
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:46
map_location get_direction(direction dir, unsigned int n=1u) const
Definition location.cpp:362
direction
Valid directions which can be moved in our hexagonal world.
Definition location.hpp:48
static map_location::direction n
#define i