~ubuntu-branches/ubuntu/wily/gargoyle-free/wily-proposed

« back to all changes in this revision

Viewing changes to tads/tads3/lib/extensions/pathfind.t

  • Committer: Bazaar Package Importer
  • Author(s): Sylvain Beucler
  • Date: 2009-09-11 20:09:43 UTC
  • Revision ID: james.westby@ubuntu.com-20090911200943-idgzoyupq6650zpn
Tags: upstream-2009-08-25
ImportĀ upstreamĀ versionĀ 2009-08-25

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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