]> git.pond.sub.org Git - empserver/blob - src/lib/common/pathfind.c
283afb380675b503e5d027d4bc592e465dc4204e
[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 "nat.h"
41 #include "optlist.h"
42 #include "prototypes.h"
43 #include "path.h"
44 #include "sect.h"
45
46 #ifdef PATH_FIND_DEBUG
47 #define DPRINTF(fmt, ...) ((void)printf(fmt , ## __VA_ARGS__))
48 #else
49 #define DPRINTF(fmt, ...) ((void)0)
50 #endif
51
52 static char *bufrotate(char *buf, size_t bufsz, size_t i);
53
54 /*
55  * A* algorithm.  Refer to your graph algorithm textbook for how it
56  * works.  Implementation is specialized to hex maps.
57  *
58  * Define PATH_FIND_STATS for performance statistics on stdout.
59  */
60
61 /*
62  * Array of sector data, indexed by sector uid
63  *
64  * We need to store a few values per sector visited by the path
65  * search.  An array is the stupidest data structure that could
66  * possibly work.
67  *
68  * Extra benefit: it works really well for distribution in a
69  * continental game, where we visit most sectors.  That's our most
70  * demanding use of path search, and its performance has noticable
71  * impact on the update.
72  *
73  * Island game distribution is much less demanding.  The array may not
74  * be the best choice here, but it's plainly good enough.  Same for
75  * path searches outside the update.
76  */
77
78 struct pf_map {
79     /*
80      * visit < pf_visit      : unvisited, remaining members invalid
81      * visit == pf_visit     : open, dir & cost tentative, heapi used
82      * visit == pf_visit + 1 : closed, dir & cost final, heapi unused
83      */
84     unsigned short visit;
85     signed char dir;            /* cheapest direction to source */
86     int heapi;                  /* index in heap, valid if open */
87     double cost;                /* cost from source */
88 };
89
90 static unsigned short pf_visit;
91 static struct pf_map *pf_map;
92
93 /*
94  * Binary heap, cost priority queue of all open sectors
95  *
96  * Again, we use the stupidest data structure that could possibly
97  * work: an array.  And we make it so large it can hold *all* sectors.
98  * In practice, we need much less space, but a tighter upper bound is
99  * not obvious to me right now.
100  */
101
102 struct pf_heap {
103     int uid;                    /* sector uid and */
104     coord x, y;                 /* coordinates, uid == XYOFFSET(x, y) */
105     double f;                   /* estimated cost from source to dest */
106 };
107
108 static int pf_nheap;            /* #entries in pf_nheap[] */
109 static struct pf_heap *pf_heap;
110
111 /*
112  * Source and costs
113  */
114 static coord pf_sx, pf_sy;
115 static int pf_suid;
116 static natid pf_actor;
117 static double (*pf_sct_cost)(natid, int);
118 static double (*pf_h)(coord, coord, coord, coord, double);
119
120 /*
121  * Performance statistics
122  */
123 #ifdef PATH_FIND_STATS
124 static unsigned pf_nsearch, pf_nsource, pf_nopen, pf_nreopen, pf_nclose;
125 static unsigned pf_nheap_max, pf_noway;
126 static double pf_sumcost;
127 #define STAT_INC(v) ((void)((v)++))
128 #define STAT_INCBY(v, i) ((void)((v) += i))
129 #define STAT_HIMARK(v, h) ((void)((v) < (h) ? (v) = (h) : (h)))
130 #else   /* !PATH_FIND_STATS */
131 #define STAT_INC(v) ((void)0)
132 #define STAT_INCBY(v, i) ((void)0)
133 #define STAT_HIMARK(v, h) ((void)0)
134 #endif  /* !PATH_FIND_STATS */
135
136 /* Is sector with uid UID open?  */
137 static int
138 pf_is_open(int uid)
139 {
140     return pf_map[uid].visit == pf_visit;
141 }
142
143 /* Is sector with uid UID closed?  */
144 static int
145 pf_is_closed(int uid)
146 {
147     /*
148      * optimization: check > pf_visit instead of == pf_visit + 1
149      * works because pf_map[uid].visit <= pf_visit + 1
150      */
151     return pf_map[uid].visit > pf_visit;
152 }
153
154 /* Is sector with uid UID unvisited?  */
155 static int
156 pf_is_unvisited(int uid)
157 {
158     return pf_map[uid].visit < pf_visit;
159 }
160
161 #ifdef PATH_FIND_DEBUG
162 static void
163 pf_check(void)
164 {
165     int i, uid, c;
166
167     for (i = 0; i < pf_nheap; i++) {
168         uid = pf_heap[i].uid;
169         assert(0 <= uid && uid < WORLD_SZ());
170         assert(pf_map[uid].heapi == i);
171         assert(pf_map[uid].visit == pf_visit);
172         assert(pf_map[uid].cost <= pf_heap[i].f);
173         c = 2 * i + 1;
174         assert(c >= pf_nheap || pf_heap[i].f <= pf_heap[c].f);
175         c++;
176         assert(c >= pf_nheap || pf_heap[i].f <= pf_heap[c].f);
177     }
178
179     for (uid = 0; uid < WORLD_SZ(); uid++) {
180         assert(pf_map[uid].visit <= pf_visit + 1);
181         if (pf_map[uid].visit == pf_visit) {
182             i = pf_map[uid].heapi;
183             assert(0 <= i && i < pf_nheap && pf_heap[i].uid == uid);
184         }
185     }
186 }
187 #else
188 #define pf_check() ((void)0)
189 #endif
190
191 /* Swap pf_heap's I-th and J-th elements.  */
192 static void
193 pf_heap_swap(int i, int j)
194 {
195     struct pf_heap tmp;
196
197     assert(0 <= i && i < pf_nheap);
198     assert(0 <= j && j < pf_nheap);
199     tmp = pf_heap[i];
200     pf_heap[i] = pf_heap[j];
201     pf_heap[j] = tmp;
202     pf_map[pf_heap[i].uid].heapi = i;
203     pf_map[pf_heap[j].uid].heapi = j;
204 }
205
206 /* Restore heap property after N-th element's cost increased.  */
207 static void
208 pf_sift_down(int n)
209 {
210     int r, c;
211
212     assert(0 <= n && n < pf_nheap);
213     for (r = n; (c = 2 * r + 1) < pf_nheap; r = c) {
214         if (c + 1 < pf_nheap && pf_heap[c].f > pf_heap[c + 1].f)
215             c++;
216         if (pf_heap[r].f < pf_heap[c].f)
217             break;
218         pf_heap_swap(r, c);
219     }
220 }
221
222 /* Restore heap property after N-th element's cost decreased.  */
223 static void
224 pf_sift_up(int n)
225 {
226     int c, p;
227
228     assert(0 <= n && n < pf_nheap);
229     for (c = n; (p = (c - 1) / 2), c > 0; c = p) {
230         if (pf_heap[p].f < pf_heap[c].f)
231             break;
232         pf_heap_swap(p, c);
233     }
234 }
235
236 /*
237  * Open the unvisited sector X,Y.
238  * UID is sector uid, it equals XYOFFSET(X,Y).
239  * Cheapest path from source comes from direction DIR and has cost COST.
240  * H is the estimated cost from X,Y to the destination.
241  */
242 static void
243 pf_open(int uid, coord x, coord y, int dir, double cost, double h)
244 {
245     int i;
246
247     if (pf_is_open(uid)) {
248         STAT_INC(pf_nreopen);
249         i = pf_map[uid].heapi;
250         DPRINTF("pf: reopen %d,%d %g %c %d\n", x, y, cost, dirch[dir], i);
251     } else {
252         assert(pf_is_unvisited(uid));
253         STAT_INC(pf_nopen);
254         i = pf_nheap++;
255         STAT_HIMARK(pf_nheap_max, (unsigned)pf_nheap);
256         DPRINTF("pf: open %d,%d %g %c %d\n", x, y, cost, dirch[dir], i);
257         pf_map[uid].heapi = i;
258         pf_map[uid].visit = pf_visit;
259         pf_heap[i].uid = uid;
260         pf_heap[i].x = x;
261         pf_heap[i].y = y;
262     }
263     pf_map[uid].dir = dir;
264     pf_map[uid].cost = cost;
265     pf_heap[i].f = cost + h;
266
267     pf_sift_up(i);
268     pf_check();
269 }
270
271 /*
272  * Close the sector at the top of the heap.
273  */
274 static void
275 pf_close(void)
276 {
277     int uid = pf_heap[0].uid;
278
279     STAT_INC(pf_nclose);
280     DPRINTF("pf: close %d,%d %d\n", pf_heap[0].x, pf_heap[0].y, pf_nheap);
281     assert(pf_is_open(uid));
282     if (--pf_nheap) {
283         pf_heap[0] = pf_heap[pf_nheap];
284         pf_map[pf_heap[0].uid].heapi = 0;
285         pf_sift_down(0);
286     }
287     pf_map[uid].visit = pf_visit + 1;
288     pf_check();
289 }
290
291 /* silence "not used" warning */
292 #ifdef PATH_FIND_DEBUG
293 /*
294  * Return cost from source to sector with uid UID.
295  * It must be open (cost is an estimate) or closed (cost is exact).
296  */
297 static double
298 pf_cost(int uid)
299 {
300     assert(!pf_is_unvisited(uid));
301     return pf_map[uid].cost;
302 }
303 #endif
304
305 static coord
306 x_in_dir(coord x, int dir)
307 {
308     int xx;
309
310     assert(0 <= x && x < WORLD_X);
311     assert(0 <= dir && dir <= DIR_LAST);
312     xx = x + diroff[dir][0];
313     if (xx < 0)
314         return xx + WORLD_X;
315     if (xx >= WORLD_X)
316         return xx - WORLD_X;
317     return xx;
318 }
319
320 static coord
321 y_in_dir(coord y, int dir)
322 {
323     int yy;
324
325     assert(0 <= y && y < WORLD_Y);
326     assert(0 <= dir && dir <= DIR_LAST);
327     yy = y + diroff[dir][1];
328     if (yy < 0)
329         return yy + WORLD_Y;
330     if (yy >= WORLD_Y)
331         return yy - WORLD_Y;
332     return yy;
333 }
334
335 static int
336 rev_dir(int dir)
337 {
338     assert(DIR_FIRST <= dir && dir <= DIR_LAST);
339     return dir >= DIR_FIRST + 3 ? dir - 3 : dir + 3;
340 }
341
342 /*
343  * Set the current source and cost function.
344  * SX,SY is the source.
345  * The cost to enter the sector with uid u is COST(ACTOR, u).
346  * Negative value means the sector can't be entered.
347  * Optional H() is the heuristic: the cost from x,y to the destination
348  * dx,dy is at least H(x, y, dx, dy, c1d), where c1d is the cost to
349  * enter dx,dy.  The closer H() is to the true cost, the more
350  * efficient this function works.
351  * With a null H(), A* degenerates into Dijkstra's algorithm.  You can
352  * then call path_find_to() multiple times for the same source.  This
353  * is faster when you need many destinations.
354  */
355 static void
356 pf_set_source(coord sx, coord sy, natid actor, double (*cost) (natid, int),
357               double (*h) (coord, coord, coord, coord, double))
358 {
359     STAT_INC(pf_nsource);
360     DPRINTF("pf: source %d,%d\n", sx, sy);
361     pf_sx = sx;
362     pf_sy = sy;
363     pf_suid = XYOFFSET(sx, sy);
364     pf_actor = actor;
365     pf_sct_cost = cost;
366     pf_h = h;
367
368     if (!pf_map) {
369         pf_map = calloc(WORLD_SZ(), sizeof(*pf_map));
370         pf_heap = malloc(WORLD_SZ() * sizeof(*pf_heap));
371         pf_visit = 1;
372     } else if ((unsigned short)(pf_visit + 3) < pf_visit) {
373         DPRINTF("pf: visit wrap-around\n");
374         memset(pf_map, 0, WORLD_SZ() * sizeof(*pf_map));
375         pf_visit = 1;
376     } else
377         pf_visit += 2;
378
379     pf_nheap = 0;
380 }
381
382 /*
383  * Find cheapest path from current source to DX,DY, return its cost.
384  */
385 double
386 path_find_to(coord dx, coord dy)
387 {
388     int duid;
389     int uid, nuid, i;
390     double c1d, cost, c1;
391     coord x, y, nx, ny;
392
393     STAT_INC(pf_nsearch);
394     DPRINTF("pf: dest %d,%d\n", dx, dy);
395     duid = XYOFFSET(dx, dy);
396     if (pf_is_closed(duid)) {
397         DPRINTF("pf: done old %g\n", pf_map[duid].cost);
398         STAT_INCBY(pf_sumcost, pf_map[duid].cost);
399         return pf_map[duid].cost;
400     }
401
402     c1d = pf_sct_cost(pf_actor, duid);
403     if (c1d < 0) {
404         DPRINTF("pf: done new %g\n", -1.0);
405         STAT_INC(pf_noway);
406         return -1;
407     }
408
409     if (pf_is_unvisited(pf_suid))
410         /* first search from this source */
411         pf_open(pf_suid, pf_sx, pf_sy, DIR_STOP, 0.0,
412                 pf_h ? pf_h(pf_sx, pf_sy, dx, dy, c1d) : 0.0);
413     else
414         assert(!pf_h);          /* multiple searches only w/o heuristic */
415
416     while (pf_nheap > 0 && (uid = pf_heap[0].uid) != duid) {
417         x = pf_heap[0].x;
418         y = pf_heap[0].y;
419         pf_close();
420         cost = pf_map[uid].cost;
421
422         for (i = 0; i < 6; i++) { /* for all neighbors */
423             nx = x_in_dir(x, DIR_FIRST + i);
424             ny = y_in_dir(y, DIR_FIRST + i);
425             nuid = XYOFFSET(nx, ny);
426             if (pf_h ? pf_is_closed(nuid) : !pf_is_unvisited(nuid))
427                 continue;
428             c1 = pf_sct_cost(pf_actor, nuid);
429             if (c1 < 0)
430                 continue;
431             if (pf_is_unvisited(nuid) || cost + c1 < pf_map[nuid].cost)
432                 pf_open(nuid, nx, ny, DIR_FIRST + i, cost + c1,
433                         pf_h ? pf_h(nx, ny, dx, dy, c1d) : 0);
434         }
435     }
436
437     DPRINTF("pf: done new %g\n", !pf_nheap ? -1.0 : pf_map[duid].cost);
438     if (!pf_nheap) {
439         STAT_INC(pf_noway);
440         return -1.0;
441     }
442     STAT_INCBY(pf_sumcost, pf_map[duid].cost);
443     return pf_map[duid].cost;
444 }
445
446 /*
447  * Write route from SX,SY to DX,DY to BUF[BUFSIZ], return its length.
448  * If the route is longer than BUFSIZ-1 characters, it's truncated.
449  * You must compute path cost first, with path_find_to().
450  * SX,SY must be on a shortest path from the current source to DX,DY.
451  */
452 size_t
453 path_find_route(char *buf, size_t bufsz,
454                 coord sx, coord sy, coord dx, coord dy)
455 {
456     coord x, y;
457     size_t i, len;
458     int suid, uid, d;
459
460     suid = XYOFFSET(sx, sy);
461     assert(bufsz > 0 && !pf_is_unvisited(suid));
462
463     i = bufsz;
464     buf[--i] = 0;
465     len = 0;
466
467     x = dx;
468     y = dy;
469     for (;;) {
470         DPRINTF("pf: %d,%d %.*s%.*s\n",
471                 x, y,
472                 (int)(bufsz - i), buf + i,
473                 len >= bufsz ? (int)i : 0, buf);
474         uid = XYOFFSET(x, y);
475         assert(!pf_is_unvisited(uid));
476         d = pf_map[uid].dir;
477         if (d == DIR_STOP || uid == suid)
478             break;
479         if (!i)
480             i = bufsz;
481         buf[--i] = dirch[d];
482         len++;
483         x = x_in_dir(x, rev_dir(d));
484         y = y_in_dir(y, rev_dir(d));
485     }
486
487     assert(x == sx && y == sy);
488     if (len >= bufsz)
489         bufrotate(buf, bufsz, i);
490     else {
491         assert(i + len < bufsz);
492         memmove(buf, buf + i, len + 1);
493     }
494     return len;
495 }
496
497 /*
498  * Rotate BUF[BUFSZ] to put BUF[I] into BUF[0], and zero-terminate.
499  */
500 static char *
501 bufrotate(char *buf, size_t bufsz, size_t i)
502 {
503     char tmp[64];
504     size_t n;
505
506     while (i) {
507         n = MIN(i, sizeof(tmp));
508         memcpy(tmp, buf, n);
509         memcpy(buf, buf + n, bufsz - n);
510         memcpy(buf + bufsz - n, tmp, n);
511         i -= n;
512     }
513     buf[bufsz - 1] = 0;
514     return buf;
515 }
516
517 #ifdef PATH_FIND_DEBUG
518 void
519 path_find_visualize(coord sx, coord sy, coord dx, coord dy)
520 {
521     int uid;
522     int xmin, xmax, ymin, ymax, x, y, odd, ch;
523     double c, u, cost;
524     char buf[1024];
525
526     assert(pf_cost(XYOFFSET(sx, sy)) == 0.0);
527     c = pf_cost(XYOFFSET(dx, dy));
528     u = c / 10.0;
529
530     /* find bounding box */
531     xmin = xmax = 0;
532     ymin = ymax = 0;
533     for (y = -WORLD_Y / 2; y < WORLD_Y / 2; y++) {
534         odd = ((sx + -WORLD_X / 2) ^ (sy + y)) & 1;
535         for (x = -WORLD_X / 2 + odd; x < WORLD_X / 2; x += 2) {
536             uid = XYOFFSET(XNORM(sx + x), YNORM(sy + y));
537             if (pf_is_unvisited(uid))
538                 continue;
539             if (xmin > x)
540                 xmin = x;
541             if (xmax < x)
542                 xmax = x;
543             if (ymin > y)
544                 ymin = y;
545             if (ymax < y)
546                 ymax = y;
547         }
548     }
549     printf("bbox %d:%d,%d:%d origin %d,%d\n",
550            xmin, xmax, ymin, ymax, sx, sy);
551
552     for (y = ymin; y <= ymax; y++) {
553         odd = ((sx + xmin) ^ (sy + y)) & 1;
554         if (odd)
555             printf(" ");
556         for (x = xmin + odd; x <= xmax; x += 2) {
557             uid = XYOFFSET(XNORM(sx + x), YNORM(sy + y));
558             if (pf_is_unvisited(uid))
559                 ch = ' ';
560             else if (uid == XYOFFSET(dx, dy))
561                 ch = 'D';
562             else if (uid == XYOFFSET(sx, sy))
563                 ch = 'S';
564             else {
565                 cost = pf_cost(uid);
566                 ch = cost > c ? '+' : '0' + (int)(10 * (cost / c));
567             }
568             printf(" %c", ch);
569         }
570         printf("\n");
571     }
572     path_find_route(buf, sizeof(buf), sx, sy, dx, dy);
573     printf("%s %g\n", buf, pf_cost(XYOFFSET(dx, dy)));
574 }
575 #endif
576
577 #ifdef PATH_FIND_STATS
578 void
579 path_find_print_stats(void)
580 {
581     printf("pathfind %u searches, %u sources,"
582            " %u opened, %u reopened, %u closed,"
583            " %u heap max, %zu bytes, %u noway, %g avg cost\n",
584            pf_nsearch, pf_nsource, pf_nopen, pf_nreopen, pf_nclose,
585            pf_nheap_max,
586            (WORLD_SZ() * (sizeof(*pf_map) + sizeof(*pf_heap))),
587            pf_noway, pf_nsearch ? pf_sumcost / pf_nsearch : 0.0);
588 }
589 #endif
590
591 /*
592  * Empire interface glue
593  */
594
595 static double
596 cost_land(natid actor, int uid, int mobtype)
597 {
598     /*
599      * Non-negative cost must not depend on ACTOR, see BestLandPath().
600      */
601     struct sctstr *sp = (void *)empfile[EF_SECTOR].cache;
602
603     if (sp[uid].sct_own != actor)
604         return -1.0;
605     return sector_mcost(&sp[uid], mobtype);
606 }
607
608 static double
609 cost_move(natid actor, int uid)
610 {
611     return cost_land(actor, uid, MOB_MOVE);
612 }
613
614 static double
615 cost_march(natid actor, int uid)
616 {
617     return cost_land(actor, uid, MOB_MARCH);
618 }
619
620 static double
621 cost_rail(natid actor, int uid)
622 {
623     return cost_land(actor, uid, MOB_RAIL);
624 }
625
626 static double
627 h_move(coord x, coord y, coord dx, coord dy, double c1d)
628 {
629     int md = mapdist(x, y, dx, dy);
630     return md ? c1d + (md - 1) * 0.001 : 0.0;
631 }
632
633 static double
634 h_march_rail(coord x, coord y, coord dx, coord dy, double c1d)
635 {
636     int md = mapdist(x, y, dx, dy);
637     return md ? c1d + (md - 1) * 0.02 : 0.0;
638 }
639
640 static double
641 cost_sail(natid actor, int uid)
642 {
643     struct sctstr *sp = (void *)empfile[EF_SECTOR].cache;
644     natid sctown = sp[uid].sct_own;
645     char *bmap;
646
647     if (sctown && relations_with(sctown, actor) == ALLIED) {
648         /* FIXME duplicates shp_check_nav() logic */
649         switch (dchr[sp[uid].sct_type].d_nav) {
650         case NAVOK:
651             return 1.0;
652         case NAV_CANAL:
653             /* FIXME return 1.0 when all ships have M_CANAL */
654             return -1.0;
655         case NAV_02:
656             return sp[uid].sct_effic >= 2 ? 1.0 : -1.0;
657         case NAV_60:
658             return sp[uid].sct_effic >= 60 ? 1.0 : -1.0;
659         default:
660             return -1.0;
661         }
662     }
663
664     bmap = ef_ptr(EF_BMAP, actor);
665     return bmap[uid] == '.' || bmap[uid] == ' ' || bmap[uid] == 0
666         ? 1.0 : -1.0;
667 }
668
669 static double
670 cost_fly(natid actor, int uid)
671 {
672     return 1.0;
673 }
674
675 static double
676 h_sail_fly(coord x, coord y, coord dx, coord dy, double c1d)
677 {
678     return mapdist(x, y, dx, dy);
679 }
680
681 static double (*cost_tab[])(natid, int) = {
682     cost_move, cost_march, cost_rail, cost_sail, cost_fly
683 };
684
685 static double (*h[])(coord, coord, coord, coord, double) = {
686     h_move, h_march_rail, h_march_rail, h_sail_fly, h_sail_fly
687 };
688
689 /*
690  * Start finding paths from SX,SY.
691  * Use mobility costs for ACTOR and MOBTYPE.
692  */
693 void
694 path_find_from(coord sx, coord sy, natid actor, int mobtype)
695 {
696     pf_set_source(sx, sy, actor, cost_tab[mobtype], NULL);
697 }
698
699 /*
700  * Find cheapest path from SX,SY to DX,DY, return its mobility cost.
701  * Use mobility costs for ACTOR and MOBTYPE.
702  */
703 double
704 path_find(coord sx, coord sy, coord dx, coord dy, natid actor, int mobtype)
705 {
706     pf_set_source(sx, sy, actor, cost_tab[mobtype], h[mobtype]);
707     return path_find_to(dx, dy);
708 }