The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
astarsearch.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2005 - 2025
3 by Guillaume Melquiond <guillaume.melquiond@gmail.com>
4 Copyright (C) 2003 by David White <dave@whitevine.net>
5 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY.
13
14 See the COPYING file for more details.
15*/
16
17#include "log.hpp"
18#include "pathfind/pathfind.hpp"
19#include "pathfind/teleport.hpp"
20
21#include <queue>
22
23static lg::log_domain log_engine("engine");
24#define LOG_PF LOG_STREAM(info, log_engine)
25#define DBG_PF LOG_STREAM(debug, log_engine)
26#define ERR_PF LOG_STREAM(err, log_engine)
27
28namespace pathfind {
29
30
31namespace {
32double heuristic(const map_location& src, const map_location& dst)
33{
34 // We will mainly use the distances in hexes
35 // but we subtract a tiny bonus for shorter Euclidean distance
36 // based on how the path looks on the screen.
37
38 // 0.75 comes from the horizontal hex imbrication
39 double xdiff = (src.x - dst.x) * 0.75;
40 // we must add 0.5 to the y coordinate when x is odd
41 double ydiff = (src.y - dst.y) + ((src.x & 1) - (dst.x & 1)) * 0.5;
42
43 // we assume a map with a maximum diagonal of 300 (bigger than a 200x200)
44 // and we divide by 90000 * 10000 to avoid interfering with the defense subcost
45 // (see shortest_path_calculator::cost)
46 // NOTE: In theory, such heuristic is barely 'admissible' for A*,
47 // But not a problem for our current A* (we use heuristic only for speed)
48 // Plus, the euclidean fraction stay below the 1MP minimum and is also
49 // a good heuristic, so we still find the shortest path efficiently.
50 return distance_between(src, dst) + (xdiff*xdiff + ydiff*ydiff) / 900000000.0;
51
52 // TODO: move the heuristic function into the cost_calculator
53 // so we can use case-specific heuristic
54 // and clean the definition of these numbers
55}
56
57// values 0 and 1 mean uninitialized
58const unsigned bad_search_counter = 0;
59// The number of nodes already processed.
60static unsigned search_counter = bad_search_counter;
61
62struct node {
63 double g, h, t, srch;
65 /**
66 * If equal to search_counter, the node is off the list.
67 * If equal to search_counter + 1, the node is on the list.
68 * Otherwise it is outdated.
69 */
70 unsigned in;
71
72 node()
73 : g(1e25)
74 , h(1e25)
75 , t(1e25)
76 , curr()
77 , prev()
78 , in(bad_search_counter)
79 {
80 }
81 node(double s, const map_location &c, const map_location &p, const map_location &dst, bool i, const teleport_map* teleports, double srch_arg, double dsth):
82 g(s), h(heuristic(c, dst)), t(g + h), srch(srch_arg), curr(c), prev(p), in(search_counter + i)
83 {
84 if (teleports && !teleports->empty()) {
85 if(srch < 0) {
86 srch = 1.0;
87
88 for(const auto& it : teleports->get_sources()) {
89 const double tmp_srch = heuristic(c, it);
90 if (tmp_srch < srch) { srch = tmp_srch; }
91 }
92 }
93
94 double new_h = srch + dsth + 1.0;
95 if (new_h < h) {
96 h = new_h;
97 t = g + h;
98 }
99 }
100 }
101
102 bool operator<(const node& o) const {
103 return t < o.t;
104 }
105};
106
107class comp {
108 const std::vector<node>& nodes_;
109
110public:
111 comp(const std::vector<node>& n) : nodes_(n) { }
112 bool operator()(int a, int b) const {
113 return nodes_[b] < nodes_[a];
114 }
115};
116
117class indexer {
118 std::size_t w_;
119
120public:
121 indexer(std::size_t w) : w_(w) { }
122 std::size_t operator()(const map_location& loc) const {
123 return loc.y * w_ + loc.x;
124 }
125};
126}//anonymous namespace
127
128
130 double stop_at, const cost_calculator& calc,
131 const std::size_t width, const std::size_t height,
132 const teleport_map *teleports, bool border) {
133 //----------------- PRE_CONDITIONS ------------------
134 assert(src.valid(width, height, border));
135 assert(dst.valid(width, height, border));
136 assert(stop_at <= calc.getNoPathValue());
137 //---------------------------------------------------
138
139 DBG_PF << "A* search: " << src << " -> " << dst;
140
141 if (calc.cost(dst, 0) >= stop_at) {
142 LOG_PF << "aborted A* search because Start or Dest is invalid";
143 plain_route locRoute;
144 locRoute.move_cost = static_cast<int>(calc.getNoPathValue());
145 return locRoute;
146 }
147
148 // increment search_counter but skip the range equivalent to uninitialized
149 search_counter += 2;
150 if (search_counter - bad_search_counter <= 1u)
151 search_counter += 2;
152
153 double dsth = 1.0;
154 if (teleports && !teleports->empty()) {
155 for(const auto &it : teleports->get_targets()) {
156 const double tmp_dsth = heuristic(it, dst);
157 if (tmp_dsth < dsth) { dsth = tmp_dsth; }
158 }
159 }
160
161 static std::vector<node> nodes;
162 nodes.resize(width * height); // this create uninitialized nodes
163
164 indexer index(width);
165 comp node_comp(nodes);
166
167 node& dst_node = nodes[index(dst)];
168
169 dst_node.g = stop_at + 1;
170 nodes[index(src)] = node(0, src, map_location::null_location(), dst, true, teleports, -1, dsth);
171
172 std::vector<int> pq;
173 pq.push_back(index(src));
174
175 while (!pq.empty()) {
176 node& n = nodes[pq.front()];
177
178 n.in = search_counter;
179
180 std::pop_heap(pq.begin(), pq.end(), node_comp);
181 pq.pop_back();
182
183 if (n.t >= dst_node.g) break;
184
185 std::vector<map_location> locs(6);
186 get_adjacent_tiles(n.curr, utils::span{locs}.first<6>());
187
188 if (teleports && !teleports->empty()) {
189 const auto& allowed_teleports = teleports->get_adjacents(n.curr);
190 locs.insert(locs.end(), allowed_teleports.begin(), allowed_teleports.end());
191 }
192
193 for(auto i = locs.rbegin(); i != locs.rend(); ++i) {
194 const map_location& loc = *i;
195
196 if (!loc.valid(width, height, border)) continue;
197 if (loc == n.curr) continue;
198 node& next = nodes[index(loc)];
199 double srch;
200 double thresh;
201 if(next.in - search_counter <= 1u) {
202 srch = next.srch;
203 thresh = next.g;
204 } else {
205 srch = -1;
206 thresh = dst_node.g;
207 }
208 // cost() is always >= 1 (assumed and needed by the heuristic)
209 if (n.g + 1 >= thresh) continue;
210 double cost = n.g + calc.cost(loc, n.g);
211 if (cost >= thresh) continue;
212
213 bool in_list = next.in == search_counter + 1;
214
215 next = node(cost, loc, n.curr, dst, true, teleports, srch, dsth);
216
217 if (in_list) {
218 std::push_heap(pq.begin(), std::find(pq.begin(), pq.end(), static_cast<int>(index(loc))) + 1, node_comp);
219 } else {
220 pq.push_back(index(loc));
221 std::push_heap(pq.begin(), pq.end(), node_comp);
222 }
223 }
224 }
225
226 plain_route route;
227 if (dst_node.g <= stop_at) {
228 DBG_PF << "found solution; calculating it...";
229 route.move_cost = static_cast<int>(dst_node.g);
230 for (node curr = dst_node; curr.prev != map_location::null_location(); curr = nodes[index(curr.prev)]) {
231 route.steps.push_back(curr.curr);
232 }
233 route.steps.push_back(src);
234 std::reverse(route.steps.begin(), route.steps.end());
235 } else {
236 LOG_PF << "aborted a* search ";
237 route.move_cost = static_cast<int>(calc.getNoPathValue());
238 }
239
240 return route;
241}
242
243
244}//namespace pathfind
map_location loc
Definition move.cpp:172
#define LOG_PF
map_location curr
double srch
static lg::log_domain log_engine("engine")
double t
#define DBG_PF
std::size_t w_
map_location prev
const std::vector< node > & nodes_
unsigned in
If equal to search_counter, the node is off the list.
constexpr span< T, C > first() const
Definition span.hpp:315
const std::unordered_set< map_location > & get_targets() const
Returns the locations that are an exit of the tunnel.
Definition teleport.cpp:246
const std::unordered_set< map_location > & get_adjacents(map_location loc) const
Definition teleport.cpp:231
@ border
The border of the map.
static bool operator<(const placing_info &a, const placing_info &b)
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
Standard logging facilities (interface).
plain_route a_star_search(const map_location &src, const map_location &dst, double stop_at, const cost_calculator &calc, const std::size_t width, const std::size_t height, const teleport_map *teleports, bool border)
const std::vector< findroute_node > & nodes
Definition pathfind.cpp:220
int w
Definition pathfind.cpp:188
This module contains various pathfinding functions and utilities.
rect dst
Location on the final composed sheet.
rect src
Non-transparent portion of the surface to compose.
Encapsulates the map of the game.
Definition location.hpp:46
bool valid() const
Definition location.hpp:111
static const map_location & null_location()
Definition location.hpp:103
static double getNoPathValue()
Definition pathfind.hpp:65
virtual double cost(const map_location &loc, const double so_far) const =0
Structure which holds a single route between one location and another.
Definition pathfind.hpp:133
std::vector< map_location > steps
Definition pathfind.hpp:135
int move_cost
Movement cost for reaching the end of the route.
Definition pathfind.hpp:137
mock_party p
static map_location::direction n
static map_location::direction s
#define i
#define g
#define h
#define c
#define b