| | 1 | /* |
| | 2 | * Copyright 2003, 2006 Michael J. Roberts |
| | 3 | * |
| | 4 | * Path Finder. This module provides a class that implements Dijkstra's |
| | 5 | * Algorithm for finding the shortest path between two nodes in a graph. |
| | 6 | * The basic path finder class makes no assumptions about the nature of |
| | 7 | * the underlying graph; subclasses must provide the concrete details |
| | 8 | * about the graph being traversed. We provide a subclass that finds |
| | 9 | * paths in a game-world location map; this can be used for things like |
| | 10 | * making an NPC find its own way to a destination. |
| | 11 | * |
| | 12 | * Everyone is granted permission to use and modify this file for any |
| | 13 | * purpose, provided that the original author is credited. |
| | 14 | */ |
| | 15 | |
| | 16 | #include <tads.h> |
| | 17 | |
| | 18 | |
| | 19 | /* ------------------------------------------------------------------------ */ |
| | 20 | /* |
| | 21 | * The basic path finder class. This implements Dijkstra's algorithm to |
| | 22 | * find the shortest path from one node to another in a graph. This base |
| | 23 | * implementation doesn't make any assumptions about the nature of the |
| | 24 | * underlying graph; each subclass must override one method to provide |
| | 25 | * the concrete graph implementation. |
| | 26 | */ |
| | 27 | class BasePathFinder: object |
| | 28 | /* |
| | 29 | * Find the best path from 'fromNode' to 'toNode', which are nodes in |
| | 30 | * the underlying graph. We'll return a vector consisting of graph |
| | 31 | * nodes, in order, giving the shortest path between the nodes. Note |
| | 32 | * that 'fromNode' and 'toNode' are included in the returned vector. |
| | 33 | * |
| | 34 | * If the two nodes 'fromNode' and 'toNode' aren't connected in the |
| | 35 | * graph, we'll simply return nil. |
| | 36 | */ |
| | 37 | findPath(fromNode, toNode) |
| | 38 | { |
| | 39 | local i; |
| | 40 | local len; |
| | 41 | local cur; |
| | 42 | local workingList; |
| | 43 | local doneList; |
| | 44 | local toEntry; |
| | 45 | local ret; |
| | 46 | |
| | 47 | /* start with set containing the initial node */ |
| | 48 | workingList = new Vector(32); |
| | 49 | workingList.append(new PathFinderNode(fromNode)); |
| | 50 | |
| | 51 | /* |
| | 52 | * Work through the list. For each item in the list, add all of |
| | 53 | * the items adjacent to that item to the end of the list. Keep |
| | 54 | * visiting new elements until we've visited everything in the |
| | 55 | * list once. |
| | 56 | * |
| | 57 | * We'll only add an element to the list if it's not already in |
| | 58 | * the list. This guarantees that the loop will converge (since |
| | 59 | * the number of items in the graph must be finite). |
| | 60 | */ |
| | 61 | for (i = 1 ; i <= workingList.length() ; ++i) |
| | 62 | { |
| | 63 | /* add each adjacent item to the working list */ |
| | 64 | forEachAdjacent(workingList[i].graphNode, |
| | 65 | new function(adj, dist) |
| | 66 | { |
| | 67 | /* |
| | 68 | * add the adjacent node only if it's not already in the |
| | 69 | * working list |
| | 70 | */ |
| | 71 | if (workingList.indexWhich({x: x.graphNode == adj}) == nil) |
| | 72 | workingList.append(new PathFinderNode(adj)); |
| | 73 | }); |
| | 74 | } |
| | 75 | |
| | 76 | /* |
| | 77 | * if the destination isn't in the working list, then there's no |
| | 78 | * hope of finding a path to it |
| | 79 | */ |
| | 80 | if (workingList.indexWhich({x: x.graphNode == toNode}) == nil) |
| | 81 | return nil; |
| | 82 | |
| | 83 | /* start with an empty "done" list */ |
| | 84 | doneList = new Vector(32); |
| | 85 | |
| | 86 | /* we know the distance from the starting element to itself is zero */ |
| | 87 | cur = workingList[1]; |
| | 88 | cur.bestDist = 0; |
| | 89 | cur.predNode = nil; |
| | 90 | |
| | 91 | /* keep going while we have unresolved nodes */ |
| | 92 | while (workingList.length() != 0) |
| | 93 | { |
| | 94 | local minIdx; |
| | 95 | local minDist; |
| | 96 | |
| | 97 | /* find the working list element with the shortest distance */ |
| | 98 | minDist = nil; |
| | 99 | minIdx = nil; |
| | 100 | for (i = 1, len = workingList.length() ; i <= len ; ++i) |
| | 101 | { |
| | 102 | /* if this is the best so far, remember it */ |
| | 103 | cur = workingList[i]; |
| | 104 | if (cur.bestDist != nil |
| | 105 | && (minDist == nil || cur.bestDist < minDist)) |
| | 106 | { |
| | 107 | /* this is the best so far */ |
| | 108 | minDist = cur.bestDist; |
| | 109 | minIdx = i; |
| | 110 | } |
| | 111 | } |
| | 112 | |
| | 113 | /* move the best one to the 'done' list */ |
| | 114 | cur = workingList[minIdx]; |
| | 115 | doneList.append(cur = workingList[minIdx]); |
| | 116 | workingList.removeElementAt(minIdx); |
| | 117 | |
| | 118 | /* |
| | 119 | * update the best distance for everything adjacent to the |
| | 120 | * one we just finished |
| | 121 | */ |
| | 122 | forEachAdjacent(cur.graphNode, new function(adj, dist) |
| | 123 | { |
| | 124 | local newDist; |
| | 125 | local entry; |
| | 126 | |
| | 127 | /* |
| | 128 | * Find the working list entry from the adjacent room. |
| | 129 | * If there's no working list entry, there's nothing we |
| | 130 | * need to do here, since we must already be finished |
| | 131 | * with it. |
| | 132 | */ |
| | 133 | entry = workingList.valWhich({x: x.graphNode == adj}); |
| | 134 | if (entry == nil) |
| | 135 | return; |
| | 136 | |
| | 137 | /* |
| | 138 | * calculate the new distance to the adjacent room, if |
| | 139 | * we were to take a path from the room we just finished |
| | 140 | * - this is simply the path distance to the |
| | 141 | * just-finished room plus the distance from that room |
| | 142 | * to the adjacent room (i.e., 'dist') |
| | 143 | */ |
| | 144 | newDist = cur.bestDist + dist; |
| | 145 | |
| | 146 | /* |
| | 147 | * If this is better than the best estimate for the |
| | 148 | * adjacent room so far, assume we'll use this path. |
| | 149 | * Note that if the best estimate so far is nil, it |
| | 150 | * means we haven't found any path to the adjacent node |
| | 151 | * yet, so this new distance is definitely the best so |
| | 152 | * far. |
| | 153 | */ |
| | 154 | if (entry.bestDist == nil |
| | 155 | || newDist < entry.bestDist) |
| | 156 | { |
| | 157 | /* it's the best so far - remember it */ |
| | 158 | entry.bestDist = newDist; |
| | 159 | entry.predNode = cur; |
| | 160 | } |
| | 161 | }); |
| | 162 | } |
| | 163 | |
| | 164 | /* |
| | 165 | * We've exhausted the working list, so we know the best path to |
| | 166 | * every node. Now all that's left is to generate the list of |
| | 167 | * nodes that takes us from here to there. |
| | 168 | * |
| | 169 | * The information we have in the 'done' list is in reverse |
| | 170 | * order, because it tells us the predecessor for each node. |
| | 171 | * So, first find out how long the path is by traversing the |
| | 172 | * predecessor list from the ending point to the starting point. |
| | 173 | * Note that the predecessor of the starting element is nil, so |
| | 174 | * we can simply keep going until we reach a nil predecessor. |
| | 175 | */ |
| | 176 | toEntry = doneList.valWhich({x: x.graphNode == toNode}); |
| | 177 | for (cur = toEntry, len = 0 ; cur != nil ; |
| | 178 | cur = cur.predNode, ++len) ; |
| | 179 | |
| | 180 | /* create the vector that represents the path */ |
| | 181 | ret = new Vector(len); |
| | 182 | |
| | 183 | /* |
| | 184 | * Traverse the predecessor list again, filling in the vector. |
| | 185 | * Since the predecessor list gives us the path in the reverse |
| | 186 | * of the order we want, fill in the vector from the last |
| | 187 | * element backwards, so that the vector ends up in the order we |
| | 188 | * want. In the return vector, store the nodes from the |
| | 189 | * underlying graph (rather than our internal tracking entries). |
| | 190 | */ |
| | 191 | for (cur = toEntry, i = len ; cur != nil ; cur = cur.predNode, --i) |
| | 192 | ret[i] = cur.graphNode; |
| | 193 | |
| | 194 | /* that's it - return the path */ |
| | 195 | return ret; |
| | 196 | } |
| | 197 | |
| | 198 | /* |
| | 199 | * Iterate over everything adjacent to the given node in the |
| | 200 | * underlying graph. This routine must simply invoke the given |
| | 201 | * callback function once for each graph node adjacent to the given |
| | 202 | * graph node. |
| | 203 | * |
| | 204 | * The callback takes two arguments: the adjacent node, and the |
| | 205 | * distance from 'node' to the adjacent node. Note that the distance |
| | 206 | * must be a positive number, as Dijkstra's algorithm depends on |
| | 207 | * positive distances. If the graph isn't weighted by distance, |
| | 208 | * simply use 1 for all distances. |
| | 209 | * |
| | 210 | * This method isn't implemented in the base class, since we don't |
| | 211 | * make any assumptions about the underlying graph. Subclasses must |
| | 212 | * provide concrete implementations of this routine to define the |
| | 213 | * underlying graph. |
| | 214 | */ |
| | 215 | forEachAdjacent(node, func) { /* subclasses must override */ } |
| | 216 | ; |
| | 217 | |
| | 218 | /* |
| | 219 | * A node entry for the path finder - this encapsulates the node in the |
| | 220 | * underlying graph, along with the "label" information in the algorithm. |
| | 221 | * Note that this is NOT a node in the underlying graph; rather, this is |
| | 222 | * an internal data structure that we use in the path finder to keep |
| | 223 | * track of the underlying nodes and their status in the work queue. |
| | 224 | */ |
| | 225 | class PathFinderNode: object |
| | 226 | construct(node) |
| | 227 | { |
| | 228 | /* remember the underlying node */ |
| | 229 | graphNode = node; |
| | 230 | } |
| | 231 | |
| | 232 | /* the underlying node in the graph */ |
| | 233 | graphNode = nil |
| | 234 | |
| | 235 | /* |
| | 236 | * The best estimate of the shortest distance from the starting |
| | 237 | * point. We use nil to represent infinity here. |
| | 238 | */ |
| | 239 | bestDist = nil |
| | 240 | |
| | 241 | /* the best-path predecessor for this path element */ |
| | 242 | predNode = nil |
| | 243 | ; |
| | 244 | |
| | 245 | /* |
| | 246 | * Room path finder. This is a concrete implementation of the path |
| | 247 | * finder that finds a path from one Room to another in the game-world |
| | 248 | * map. |
| | 249 | * |
| | 250 | * This implementation traverses rooms based on the actual connections in |
| | 251 | * the game map. Note that this isn't appropriate for all purposes, |
| | 252 | * since it doesn't take into account what the actor knows about the game |
| | 253 | * map. This "omniscient" implementation is suitable for situations |
| | 254 | * where the actor's knowledge isn't relevant and we just want the actual |
| | 255 | * best path between the locations. |
| | 256 | */ |
| | 257 | roomPathFinder: BasePathFinder |
| | 258 | /* find the path for a given actor from one room to another */ |
| | 259 | findPath(actor, fromLoc, toLoc) |
| | 260 | { |
| | 261 | /* remember the actor */ |
| | 262 | actor_ = actor; |
| | 263 | |
| | 264 | /* run the normal algorithm */ |
| | 265 | return inherited(fromLoc, toLoc); |
| | 266 | } |
| | 267 | |
| | 268 | /* |
| | 269 | * iterate over the nodes adjacent in the underlying graph to the |
| | 270 | * given node |
| | 271 | */ |
| | 272 | forEachAdjacent(loc, func) |
| | 273 | { |
| | 274 | /* |
| | 275 | * run through the directions, and add the apparent destination |
| | 276 | * for each one |
| | 277 | */ |
| | 278 | foreach (local dir in Direction.allDirections) |
| | 279 | { |
| | 280 | local conn; |
| | 281 | local dest; |
| | 282 | |
| | 283 | /* |
| | 284 | * if there's a connector, and it has an apparent |
| | 285 | * destination, then the apparent destination is the |
| | 286 | * adjacent node |
| | 287 | */ |
| | 288 | if ((conn = loc.getTravelConnector(dir, actor_)) != nil |
| | 289 | && (dest = getDestination(loc, dir, conn)) != nil |
| | 290 | && includeRoom(dest)) |
| | 291 | { |
| | 292 | /* |
| | 293 | * This one seems to go somewhere - process the |
| | 294 | * destination. The standard room map has no concept of |
| | 295 | * distance, so use equal weightings of 1 for all |
| | 296 | * inter-room distances. |
| | 297 | */ |
| | 298 | (func)(dest, 1); |
| | 299 | } |
| | 300 | } |
| | 301 | } |
| | 302 | |
| | 303 | /* |
| | 304 | * Get the location adjacent to the given location, for the purposes |
| | 305 | * of finding the path. By default, we return the actual |
| | 306 | * destination, but subclasses might want to use other algorithms. |
| | 307 | * For example, if a subclass's goal is to make an NPC find its own |
| | 308 | * way from one location to another, then it should use the APPARENT |
| | 309 | * destination, from the NPC's perspective, rather than the actual |
| | 310 | * destination, since we'd want to construct the path based on the |
| | 311 | * NPC's knowledge of the map. |
| | 312 | */ |
| | 313 | getDestination(loc, dir, conn) |
| | 314 | { |
| | 315 | /* return the actual destination for the connector */ |
| | 316 | return conn.getDestination(loc, actor_); |
| | 317 | } |
| | 318 | |
| | 319 | /* |
| | 320 | * For easier customization, this method allows the map that we |
| | 321 | * search to be filtered so that it only includes a particular |
| | 322 | * subset of the map. This returns true if a given room is to be |
| | 323 | * included in the search, nil if not. By default, we include all |
| | 324 | * rooms. Note that this is only called to begin with for rooms |
| | 325 | * that have apparent connections to the starting room, so there's |
| | 326 | * no need to filter out areas of the map that aren't connected at |
| | 327 | * all to the starting search location. |
| | 328 | */ |
| | 329 | includeRoom(loc) { return true; } |
| | 330 | |
| | 331 | /* the actor who's finding the path */ |
| | 332 | actor_ = nil |
| | 333 | ; |
| | 334 | |
| | 335 | /* |
| | 336 | * An NPC goal-seeking path finder. This is a subclass of the basic room |
| | 337 | * path finder that |
| | 338 | */ |
| | 339 | npcRoomPathFinder: roomPathFinder |
| | 340 | /* |
| | 341 | * Get the destination. Unlike the base class implementation, we |
| | 342 | * take into the NPC's knowledge of the map by only using connector |
| | 343 | * destinations that are APPARENT to the NPC. |
| | 344 | */ |
| | 345 | getDestination(loc, dir, conn) |
| | 346 | { |
| | 347 | /* return the apparent destination of the connector */ |
| | 348 | return conn.getApparentDestination(loc, actor_); |
| | 349 | } |
| | 350 | ; |
| | 351 | |