]> git.pond.sub.org Git - empserver/blob - src/lib/common/pathfind.c
a7f9636182f8779a39e7e3aeaa17a66e4ae7ac7a
[empserver] / src / lib / common / pathfind.c
1 /*
2  *  Empire - A multi-player, client/server Internet based war game.
3  *  Copyright (C) 1986-2011, Dave Pare, Jeff Bailey, Thomas Ruschak,
4  *                Ken Stevens, Steve McClure, Markus Armbruster
5  *
6  *  Empire 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 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  *  ---
20  *
21  *  See files README, COPYING and CREDITS in the root of the source
22  *  tree for related information and legal notices.  It is expected
23  *  that future projects/authors will amend these files as needed.
24  *
25  *  ---
26  *
27  *  pathfind.c: Find cheapest paths
28  *
29  *  Known contributors to this file:
30  *     Markus Armbruster, 2011
31  */
32
33 #include <config.h>
34
35 #include <assert.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include "file.h"
40 #include "optlist.h"
41 #include "path.h"
42 #include "sect.h"
43
44 #ifdef PATH_FIND_DEBUG
45 #define DPRINTF(fmt, ...) ((void)printf(fmt , ## __VA_ARGS__))
46 #else
47 #define DPRINTF(fmt, ...) ((void)0)
48 #endif
49
50 static char *bufrotate(char *buf, size_t bufsz, size_t i);
51
52 /*
53  * Dijkstra's algorithm.  Refer to your graph algorithm textbook for
54  * how it works.  Implementation is specialized to hex maps.
55  *
56  * Define PATH_FIND_STATS for performance statistics on stdout.
57  */
58
59 /*
60  * Array of sector data, indexed by sector uid
61  *
62  * We need to store a few values per sector visited by the path
63  * search.  An array is the stupidest data structure that could
64  * possibly work.
65  *
66  * Extra benefit: it works really well for distribution in a
67  * continental game, where we visit most sectors.  That's our most
68  * demanding use of path search, and its performance has noticable
69  * impact on the update.
70  *
71  * Island game distribution is much less demanding.  The array may not
72  * be the best choice here, but it's plainly good enough.  Same for
73  * path searches outside the update.
74  */
75
76 struct pf_map {
77     /*
78      * visit < pf_visit      : unvisited, remaining members invalid
79      * visit == pf_visit     : open, dir & cost tentative, heapi used
80      * visit == pf_visit + 1 : closed, dir & cost final, heapi unused
81      */
82     unsigned short visit;
83     signed char dir;            /* cheapest direction to source */
84     int heapi;                  /* index in heap, valid if open */
85     double cost;                /* cost from source */
86 };
87
88 static unsigned short pf_visit;
89 static struct pf_map *pf_map;
90
91 /*
92  * Binary heap, cost priority queue of all open sectors
93  *
94  * Again, we use the stupidest data structure that could possibly
95  * work: an array.  And we make it so large it can hold *all* sectors.
96  * In practice, we need much less space, but a tighter upper bound is
97  * not obvious to me right now.
98  */
99
100 struct pf_heap {
101     int uid;                    /* sector uid and */
102     coord x, y;                 /* coordinates, uid == XYOFFSET(x, y) */
103     double cost;                /* cost from source */
104 };
105
106 static int pf_nheap;            /* #entries in pf_nheap[] */
107 static struct pf_heap *pf_heap;
108
109 /*
110  * Source and costs
111  */
112 static coord pf_sx, pf_sy;
113 static int pf_suid;
114 static natid pf_actor;
115 static double (*pf_sct_cost)(natid, int);
116
117 /*
118  * Performance statistics
119  */
120 #ifdef PATH_FIND_STATS
121 static unsigned pf_nsearch, pf_nsource, pf_nopen, pf_nclose;
122 static unsigned pf_nheap_max, pf_noway;
123 static double pf_sumcost;
124 #define STAT_INC(v) ((void)((v)++))
125 #define STAT_INCBY(v, i) ((void)((v) += i))
126 #define STAT_HIMARK(v, h) ((void)((v) < (h) ? (v) = (h) : (h)))
127 #else   /* !PATH_FIND_STATS */
128 #define STAT_INC(v) ((void)0)
129 #define STAT_INCBY(v, i) ((void)0)
130 #define STAT_HIMARK(v, h) ((void)0)
131 #endif  /* !PATH_FIND_STATS */
132
133 #ifndef NDEBUG                  /* silence "not used" warning */
134 /* Is sector with uid UID open?  */
135 static int
136 pf_is_open(int uid)
137 {
138     return pf_map[uid].visit == pf_visit;
139 }
140 #endif
141
142 /* Is sector with uid UID closed?  */
143 static int
144 pf_is_closed(int uid)
145 {
146     /*
147      * optimization: check > pf_visit instead of == pf_visit + 1
148      * works because pf_map[uid].visit <= pf_visit + 1
149      */
150     return pf_map[uid].visit > pf_visit;
151 }
152
153 /* Is sector with uid UID unvisited?  */
154 static int
155 pf_is_unvisited(int uid)
156 {
157     return pf_map[uid].visit < pf_visit;
158 }
159
160 #ifdef PATH_FIND_DEBUG
161 static void
162 pf_check(void)
163 {
164     int i, uid, c;
165
166     for (i = 0; i < pf_nheap; i++) {
167         uid = pf_heap[i].uid;
168         assert(0 <= uid && uid < WORLD_SZ());
169         assert(pf_map[uid].heapi == i);
170         assert(pf_map[uid].visit == pf_visit);
171         assert(pf_map[uid].cost <= pf_heap[i].cost);
172         c = 2 * i + 1;
173         assert(c >= pf_nheap || pf_heap[i].cost <= pf_heap[c].cost);
174         c++;
175         assert(c >= pf_nheap || pf_heap[i].cost <= pf_heap[c].cost);
176     }
177
178     for (uid = 0; uid < WORLD_SZ(); uid++) {
179         assert(pf_map[uid].visit <= pf_visit + 1);
180         if (pf_map[uid].visit == pf_visit) {
181             i = pf_map[uid].heapi;
182             assert(0 <= i && i < pf_nheap && pf_heap[i].uid == uid);
183         }
184     }
185 }
186 #else
187 #define pf_check() ((void)0)
188 #endif
189
190 /* Swap pf_heap's I-th and J-th elements.  */
191 static void
192 pf_heap_swap(int i, int j)
193 {
194     struct pf_heap tmp;
195
196     assert(0 <= i && i < pf_nheap);
197     assert(0 <= j && j < pf_nheap);
198     tmp = pf_heap[i];
199     pf_heap[i] = pf_heap[j];
200     pf_heap[j] = tmp;
201     pf_map[pf_heap[i].uid].heapi = i;
202     pf_map[pf_heap[j].uid].heapi = j;
203 }
204
205 /* Restore heap property after N-th element's cost increased.  */
206 static void
207 pf_sift_down(int n)
208 {
209     int r, c;
210
211     assert(0 <= n && n < pf_nheap);
212     for (r = n; (c = 2 * r + 1) < pf_nheap; r = c) {
213         if (c + 1 < pf_nheap && pf_heap[c].cost > pf_heap[c + 1].cost)
214             c++;
215         if (pf_heap[r].cost < pf_heap[c].cost)
216             break;
217         pf_heap_swap(r, c);
218     }
219 }
220
221 /* Restore heap property after N-th element's cost decreased.  */
222 static void
223 pf_sift_up(int n)
224 {
225     int c, p;
226
227     assert(0 <= n && n < pf_nheap);
228     for (c = n; (p = (c - 1) / 2), c > 0; c = p) {
229         if (pf_heap[p].cost < pf_heap[c].cost)
230             break;
231         pf_heap_swap(p, c);
232     }
233 }
234
235 /*
236  * Open the unvisited sector X,Y.
237  * UID is sector uid, it equals XYOFFSET(X,Y).
238  * Cheapest path from source comes from direction DIR and has cost COST.
239  */
240 static void
241 pf_open(int uid, coord x, coord y, int dir, double cost)
242 {
243     int i;
244
245     STAT_INC(pf_nopen);
246     i = pf_nheap++;
247     STAT_HIMARK(pf_nheap_max, (unsigned)pf_nheap);
248     DPRINTF("pf: open %d,%d %g %c %d\n", x, y, cost, dirch[dir], i);
249     assert(pf_is_unvisited(uid));
250     pf_map[uid].visit = pf_visit;
251     pf_map[uid].dir = dir;
252     pf_map[uid].heapi = i;
253     pf_map[uid].cost = cost;
254     pf_heap[i].uid = uid;
255     pf_heap[i].x = x;
256     pf_heap[i].y = y;
257     pf_heap[i].cost = cost;
258
259     pf_sift_up(i);
260     pf_check();
261 }
262
263 /*
264  * Close the sector at the top of the heap.
265  */
266 static void
267 pf_close(void)
268 {
269     int uid = pf_heap[0].uid;
270
271     STAT_INC(pf_nclose);
272     DPRINTF("pf: close %d,%d %d\n", pf_heap[0].x, pf_heap[0].y, pf_nheap);
273     assert(pf_is_open(uid));
274     if (--pf_nheap) {
275         pf_heap[0] = pf_heap[pf_nheap];
276         pf_map[pf_heap[0].uid].heapi = 0;
277         pf_sift_down(0);
278     }
279     pf_map[uid].visit = pf_visit + 1;
280     pf_check();
281 }
282
283 static coord
284 x_in_dir(coord x, int dir)
285 {
286     int xx;
287
288     assert(0 <= x && x < WORLD_X);
289     assert(0 <= dir && dir <= DIR_LAST);
290     xx = x + diroff[dir][0];
291     if (xx < 0)
292         return xx + WORLD_X;
293     if (xx >= WORLD_X)
294         return xx - WORLD_X;
295     return xx;
296 }
297
298 static coord
299 y_in_dir(coord y, int dir)
300 {
301     int yy;
302
303     assert(0 <= y && y < WORLD_Y);
304     assert(0 <= dir && dir <= DIR_LAST);
305     yy = y + diroff[dir][1];
306     if (yy < 0)
307         return yy + WORLD_Y;
308     if (yy >= WORLD_Y)
309         return yy - WORLD_Y;
310     return yy;
311 }
312
313 static int
314 rev_dir(int dir)
315 {
316     assert(DIR_FIRST <= dir && dir <= DIR_LAST);
317     return dir >= DIR_FIRST + 3 ? dir - 3 : dir + 3;
318 }
319
320 /*
321  * Set the current source and cost function.
322  * SX,SY is the source.
323  * The cost to enter the sector with uid u is COST(ACTOR, u).
324  * Negative value means the sector can't be entered.
325  */
326 static void
327 pf_set_source(coord sx, coord sy, natid actor, double (*cost)(natid, int))
328 {
329     STAT_INC(pf_nsource);
330     DPRINTF("pf: source %d,%d\n", sx, sy);
331     pf_sx = sx;
332     pf_sy = sy;
333     pf_suid = XYOFFSET(sx, sy);
334     pf_actor = actor;
335     pf_sct_cost = cost;
336
337     if (!pf_map) {
338         pf_map = calloc(WORLD_SZ(), sizeof(*pf_map));
339         pf_heap = malloc(WORLD_SZ() * sizeof(*pf_heap));
340         pf_visit = 1;
341     } else if ((unsigned short)(pf_visit + 3) < pf_visit) {
342         DPRINTF("pf: visit wrap-around\n");
343         memset(pf_map, 0, WORLD_SZ() * sizeof(*pf_map));
344         pf_visit = 1;
345     } else
346         pf_visit += 2;
347
348     pf_nheap = 0;
349
350     pf_open(pf_suid, pf_sx, pf_sy, DIR_STOP, 0.0);
351 }
352
353 /*
354  * Find cheapest path from current source to DX,DY, return its cost.
355  */
356 double
357 path_find_to(coord dx, coord dy)
358 {
359     int duid;
360     int uid, nuid, i;
361     double cost, c1;
362     coord x, y, nx, ny;
363
364     STAT_INC(pf_nsearch);
365     DPRINTF("pf: dest %d,%d\n", dx, dy);
366     duid = XYOFFSET(dx, dy);
367     if (pf_is_closed(duid)) {
368         DPRINTF("pf: done old %g\n", pf_map[duid].cost);
369         STAT_INCBY(pf_sumcost, pf_map[duid].cost);
370         return pf_map[duid].cost;
371     }
372
373     while (pf_nheap > 0 && (uid = pf_heap[0].uid) != duid) {
374         x = pf_heap[0].x;
375         y = pf_heap[0].y;
376         cost = pf_heap[0].cost;
377         pf_close();
378
379         for (i = 0; i < 6; i++) { /* for all neighbors */
380             nx = x_in_dir(x, DIR_FIRST + i);
381             ny = y_in_dir(y, DIR_FIRST + i);
382             nuid = XYOFFSET(nx, ny);
383             /*
384              * Cost to enter NX,NY doesn't depend on direction of
385              * entry.  This X,Y is at least as expensive as any
386              * previous one.  Therefore, cost to go to NX,NY via X,Y
387              * is at least as high as any previously found route.
388              * Skip neighbors that have a route already.
389              */
390             if (!pf_is_unvisited(nuid))
391                 continue;
392             c1 = pf_sct_cost(pf_actor, nuid);
393             if (c1 < 0)
394                 continue;
395             pf_open(nuid, nx, ny, DIR_FIRST + i, cost + c1);
396         }
397     }
398
399     DPRINTF("pf: done new %g\n", !pf_nheap ? -1.0 : pf_map[duid].cost);
400     if (!pf_nheap) {
401         STAT_INC(pf_noway);
402         return -1.0;
403     }
404     STAT_INCBY(pf_sumcost, pf_map[duid].cost);
405     return pf_map[duid].cost;
406 }
407
408 /*
409  * Write route from SX,SY to DX,DY to BUF[BUFSIZ], return its length.
410  * If the route is longer than BUFSIZ-1 characters, it's truncated.
411  * You must compute path cost first, with path_find_to().
412  * SX,SY must be on a shortest path from the current source to DX,DY.
413  */
414 size_t
415 path_find_route(char *buf, size_t bufsz,
416                 coord sx, coord sy, coord dx, coord dy)
417 {
418     coord x, y;
419     size_t i, len;
420     int suid, uid, d;
421
422     suid = XYOFFSET(sx, sy);
423     assert(bufsz > 0 && !pf_is_unvisited(suid));
424
425     i = bufsz;
426     buf[--i] = 0;
427     len = 0;
428
429     x = dx;
430     y = dy;
431     for (;;) {
432         DPRINTF("pf: %d,%d %.*s%.*s\n",
433                 x, y,
434                 (int)(bufsz - i), buf + i,
435                 len >= bufsz ? (int)i : 0, buf);
436         uid = XYOFFSET(x, y);
437         assert(!pf_is_unvisited(uid));
438         d = pf_map[uid].dir;
439         if (d == DIR_STOP || uid == suid)
440             break;
441         if (!i)
442             i = bufsz;
443         buf[--i] = dirch[d];
444         len++;
445         x = x_in_dir(x, rev_dir(d));
446         y = y_in_dir(y, rev_dir(d));
447     }
448
449     assert(x == sx && y == sy);
450     if (len >= bufsz)
451         bufrotate(buf, bufsz, i);
452     else {
453         assert(i + len < bufsz);
454         memmove(buf, buf + i, len + 1);
455     }
456     return len;
457 }
458
459 /*
460  * Rotate BUF[BUFSZ] to put BUF[I] into BUF[0], and zero-terminate.
461  */
462 static char *
463 bufrotate(char *buf, size_t bufsz, size_t i)
464 {
465     char tmp[64];
466     size_t n;
467
468     while (i) {
469         n = MIN(i, sizeof(tmp));
470         memcpy(tmp, buf, n);
471         memcpy(buf, buf + n, bufsz - n);
472         memcpy(buf + bufsz - n, tmp, n);
473         i -= n;
474     }
475     buf[bufsz - 1] = 0;
476     return buf;
477 }
478
479 #ifdef PATH_FIND_STATS
480 void
481 path_find_print_stats(void)
482 {
483     printf("pathfind %u searches, %u sources, %u opened, %u closed,"
484            " %u heap max, %zu bytes, %u noway, %g avg cost\n",
485            pf_nsearch, pf_nsource, pf_nopen, pf_nclose,
486            pf_nheap_max,
487            (WORLD_SZ() * (sizeof(*pf_map) + sizeof(*pf_heap))),
488            pf_noway, pf_nsearch ? pf_sumcost / pf_nsearch : 0.0);
489 }
490 #endif
491
492 /*
493  * Empire interface glue
494  */
495
496 static double
497 cost_land(natid actor, int uid, int mobtype)
498 {
499     /*
500      * Non-negative cost must not depend on ACTOR, see BestLandPath().
501      */
502     struct sctstr *sp = (void *)empfile[EF_SECTOR].cache;
503
504     if (sp[uid].sct_own != actor)
505         return -1.0;
506     return sector_mcost(&sp[uid], mobtype);
507 }
508
509 static double
510 cost_move(natid actor, int uid)
511 {
512     return cost_land(actor, uid, MOB_MOVE);
513 }
514
515 static double
516 cost_march(natid actor, int uid)
517 {
518     return cost_land(actor, uid, MOB_MARCH);
519 }
520
521 static double
522 cost_rail(natid actor, int uid)
523 {
524     return cost_land(actor, uid, MOB_RAIL);
525 }
526
527 static double (*cost_tab[])(natid, int) = {
528     cost_move, cost_march, cost_rail
529 };
530
531 /*
532  * Start finding paths from SX,SY.
533  * Use mobility costs for ACTOR and MOBTYPE.
534  */
535 void
536 path_find_from(coord sx, coord sy, natid actor, int mobtype)
537 {
538     pf_set_source(sx, sy, actor, cost_tab[mobtype]);
539 }
540
541 /*
542  * Find cheapest path from SX,SY to DX,DY, return its mobility cost.
543  * Use mobility costs for ACTOR and MOBTYPE.
544  */
545 double
546 path_find(coord sx, coord sy, coord dx, coord dy, natid actor, int mobtype)
547 {
548     pf_set_source(sx, sy, actor, cost_tab[mobtype]);
549     return path_find_to(dx, dy);
550 }