The Battle for Wesnoth  1.19.5+dev
astarsearch.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2005 - 2024
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 
23 static 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 
28 namespace pathfind {
29 
30 
31 namespace {
32 double 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
58 const unsigned bad_search_counter = 0;
59 // The number of nodes already processed.
60 static unsigned search_counter = bad_search_counter;
61 
62 struct 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 
107 class comp {
108  const std::vector<node>& nodes_;
109 
110 public:
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 
117 class indexer {
118  std::size_t w_;
119 
120 public:
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, locs.data());
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
#define LOG_PF
Definition: astarsearch.cpp:24
map_location curr
Definition: astarsearch.cpp:64
double srch
Definition: astarsearch.cpp:63
static lg::log_domain log_engine("engine")
double t
Definition: astarsearch.cpp:63
double h
Definition: astarsearch.cpp:63
#define DBG_PF
Definition: astarsearch.cpp:25
std::size_t w_
double g
Definition: astarsearch.cpp:63
map_location prev
Definition: astarsearch.cpp:64
const std::vector< node > & nodes_
unsigned in
If equal to search_counter, the node is off the list.
Definition: astarsearch.cpp:70
bool empty() const
Definition: teleport.hpp:137
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.
std::size_t i
Definition: function.cpp:1028
const std::vector< node > & nodes
int w
static bool operator<(const placing_info &a, const placing_info &b)
Definition: game_state.cpp:106
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:479
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:550
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)
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:70
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:45
bool valid() const
Definition: location.hpp:110
static const map_location & null_location()
Definition: location.hpp:102
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_char c
mock_party p
static map_location::direction n
static map_location::direction s
#define b