ref: 442bfd1d2755876f902ba9edc7e51e516b1286fa
parent: bad4d8986b497e23c9b245660830578e60818611
author: qwx <qwx@sciops.net>
date: Sat Apr 9 14:58:04 EDT 2022
path: "fixes" j'en ai plein le CUL - fix stack overflow crashing path(1) on larger maps - revised path straightening - dijkstra: don't attempt to reprioritize at all - logging adjustments dijkstra and bfs now work as expected, and a∗ is broken i'm good at this
--- a/path/a∗.c
+++ b/path/a∗.c
@@ -8,6 +8,21 @@
#include "dat.h"
#include "fns.h"
+/* FIXME: nope, non-optimal paths + too many nodes opened, again
+ * (dijsktra works fine) */
+
+/* slightly penalize diagonal movement for nicer-looking paths; cf.:
+ * https://www.redbloblgames.com/pathfinding/a-star/implementation.html
+ * one addition: make cost function to increase at a slower rate to
+ * resolve tie-breakers in favor of closer nodes, otherwise we will
+ * explore all nodes in the rectangle between the two points; does
+ * NOT work with 4-way movement and manhattan distance for some reason */
+static double
+movecost(int Δx, int Δy)
+{
+ return Δx != 0 && Δy != 0 ? 1.001 : 1.0;
+}
+
static Node *
a∗(Node *a, Node *b)
{
@@ -18,8 +33,8 @@
assert(a != nil && b != nil);
assert(a != b);
queue = nil;
- x = a;
a->pq = pushqueue(distfn(a, b), a, &queue);
+ x = a;
while((pn = popqueue(&queue)) != nil){
x = pn->aux;
free(pn);
@@ -26,6 +41,8 @@
if(x == b)
break;
x->closed = 1;
+ dprint(Logtrace, "a∗: closed [%#p,%P] h %.4f g %.4f\n",
+ x, n2p(x), x->h, x->g);
if((sl = successorfn(x)) == nil)
sysfatal("a∗: %r");
for(s=*sl++; s!=nil; s=*sl++){
@@ -39,8 +56,12 @@
s->open = 1;
s->h = distfn(s, b);
s->g = g;
+ dprint(Logtrace, "a∗: opened [%#p,%P] h %.4f g %.4f f %.4f\n",
+ s, n2p(s), s->h, s->g, s->h + s->g);
s->pq = pushqueue(s->g + s->h, s, &queue);
}else if(Δg > 0){
+ dprint(Logtrace, "a∗: decrease [%#p,%P] h %.4f g %.4f Δg %.4f → f %.4f\n",
+ s, n2p(s), s->h, s->g, Δg, s->h + s->g - Δg);
s->from = x;
s->g -= Δg;
decreasekey(s->pq, Δg, &queue);
@@ -56,6 +77,6 @@
threadmain(int argc, char **argv)
{
init(argc, argv);
- initgrid(a∗);
+ initgrid(a∗, movecost);
evloop();
}
--- a/path/bfs.c
+++ b/path/bfs.c
@@ -42,6 +42,6 @@
threadmain(int argc, char **argv)
{
init(argc, argv);
- initgrid(bfs);
+ initgrid(bfs, nil);
evloop();
}
--- a/path/client.c
+++ b/path/client.c
@@ -9,6 +9,8 @@
#include "dat.h"
#include "fns.h"
+mainstacksize = 512*1024;
+
extern QLock drawlock;
Node* scrselect(Point);
void updatedrw(void);
@@ -86,7 +88,7 @@
mapheight = 64;
ARGBEGIN{
case 'D':
- if(++debuglevel >= Logtrace)
+ if(++debuglevel >= Logparanoid)
mainmem->flags |= POOL_NOREUSE | POOL_PARANOIA | POOL_LOGGING;
break;
case '4':
@@ -106,6 +108,9 @@
break;
default: usage();
}ARGEND
+ if(mapwidth <= 0 || mapwidth > 512
+ || mapheight <= 0 || mapheight > 512)
+ sysfatal("invalid map size, must be in ]0,512]");
fmtinstall('P', Pfmt);
fmtinstall('R', Rfmt);
initfs();
--- a/path/dat.h
+++ b/path/dat.h
@@ -31,4 +31,3 @@
extern double (*distfn)(Node*, Node*);
extern Node** (*successorfn)(Node*);
-extern Node* (*pathfn)(Node*, Node*);
--- a/path/dijkstra.c
+++ b/path/dijkstra.c
@@ -8,6 +8,15 @@
#include "dat.h"
#include "fns.h"
+/* currently uniform cost search and no reprioritizing (decrease-key operation) */
+
+/* explore in all directions at the same time */
+static double
+movecost(int Δx, int Δy)
+{
+ return Δx != 0 && Δy != 0 ? SQRT2 : 1.0;
+}
+
static Node *
dijkstra(Node *a, Node *b)
{
@@ -18,8 +27,8 @@
assert(a != nil && b != nil);
assert(a != b);
queue = nil;
- x = a;
a->pq = pushqueue(0, a, &queue);
+ x = a;
while((pn = popqueue(&queue)) != nil){
x = pn->aux;
free(pn);
@@ -26,6 +35,8 @@
if(x == b)
break;
x->closed = 1;
+ dprint(Logtrace, "dijkstrdijkstra: closed [%#p,%P] h %.4f g %.4f\n",
+ x, n2p(x), x->h, x->g);
if((sl = successorfn(x)) == nil)
sysfatal("dijkstra: %r");
for(s=*sl++; s!=nil; s=*sl++){
@@ -38,12 +49,9 @@
s->from = x;
s->open = 1;
s->g = g;
+ dprint(Logtrace, "dijkstra: opened [%#p,%P] h %.4f g %.4f f %.4f\n",
+ s, n2p(s), s->h, s->g, s->h + s->g);
s->pq = pushqueue(s->g, s, &queue);
- }else if(Δg > 0){
- s->from = x;
- s->g -= Δg;
- decreasekey(s->pq, Δg, &queue);
- assert(s->g >= 0);
}
}
}
@@ -55,6 +63,6 @@
threadmain(int argc, char **argv)
{
init(argc, argv);
- initgrid(dijkstra);
+ initgrid(dijkstra, movecost);
evloop();
}
--- a/path/fns.h
+++ b/path/fns.h
@@ -1,7 +1,7 @@
void init(int, char**);
void evloop(void);
void initfs(void);
-void initgrid(Node* (*)(Node*, Node*));
+void initgrid(Node* (*)(Node*, Node*), double (*)(int, int));
void resetmap(void);
void clearmap(void);
void initmap(void);
--- a/path/grid.c
+++ b/path/grid.c
@@ -13,10 +13,12 @@
double (*distfn)(Node*, Node*);
Node** (*successorfn)(Node*);
-Node* (*pathfn)(Node*, Node*);
Node *start, *goal;
+static Node* (*pathfn)(Node*, Node*);
+static double (*costfn)(int, int);
+
static void
backtrack(void)
{
@@ -32,8 +34,8 @@
{
static Node *suc[8+1];
static dtab[2*(nelem(suc)-1)]={
- 0,-1, 1,0, 0,1, -1,0,
- 1,-1, 1,1, -1,1, -1,-1,
+ 1,0, 0,-1, -1,0, 0,1,
+ -1,-1, -1,1, 1,-1, 1,1,
};
int i;
Node *s, **np;
@@ -50,7 +52,7 @@
assert(s >= map && s < map + mapwidth * mapheight);
if(isblocked(s))
continue;
- s->Δg = dtab[i] != 0 && dtab[i+1] != 0 ? 1+0.001 : 1.0;
+ s->Δg = costfn != nil ? costfn(dtab[i], dtab[i+1]) : 0;
*np++ = s;
}
return suc;
@@ -60,10 +62,12 @@
successors4(Node *n)
{
static Node *suc[4+1];
- static dtab[2*(nelem(suc)-1)]={
- 0,-1, -1,0, 1,0, 0,1,
+ static int dtab[2*(nelem(suc)-1)]={
+ 1,0, -1,0, 0,-1, 0,1,
+ }, rdtab[nelem(dtab)]={
+ 0,1, 0,-1, -1,0, 1,0,
};
- int i;
+ int i, *t;
Node *s, **np;
Point p;
Rectangle r;
@@ -71,14 +75,17 @@
memset(suc, 0, sizeof suc);
p = n2p(n);
r = Rect(0, 0, mapwidth, mapheight);
+ /* path straightening; cf.:
+ * https://www.redbloblgames.com/pathfinding/a-star/implementation.html */
+ t = (p.x + p.y) % 2 == 0 ? rdtab : dtab;
for(i=0, np=suc; i<nelem(dtab); i+=2){
- if(!ptinrect(addpt(p, Pt(dtab[i], dtab[i+1])), r))
+ if(!ptinrect(addpt(p, Pt(t[i], t[i+1])), r))
continue;
- s = n + dtab[i+1] * mapwidth + dtab[i];
+ s = n + t[i+1] * mapwidth + t[i];
assert(s >= map && s < map + mapwidth * mapheight);
if(isblocked(s))
continue;
- s->Δg = 1;
+ s->Δg = costfn != nil ? costfn(t[i], t[i+1]) : 0;
*np++ = s;
}
return suc;
@@ -92,6 +99,8 @@
return -1;
}
resetmap();
+ dprint(Logdebug, "grid::findpath: findpath from [%#p,%P] to [%#p,%P]\n",
+ start, n2p(start), goal, n2p(goal));
if(pathfn(start, goal) != goal){
werrstr("findpath: failed");
return -1;
@@ -120,7 +129,7 @@
}
void
-initgrid(Node* (*f)(Node*, Node*))
+initgrid(Node* (*f)(Node*, Node*), double (*cost)(int, int))
{
if(fourdir){
distfn = manhdist;
@@ -132,4 +141,5 @@
mousefn = grmouse;
keyfn = grkey;
pathfn = f;
+ costfn = cost;
}
--- a/pheap.c
+++ b/pheap.c
@@ -6,6 +6,11 @@
* heap: A new form of self-adjusting heap. Algorithmica 1, 111–129
* (1986). */
+/* this implementation requires a bigger stack size if the heap can
+ * grow big (8192 is already insufficient with 40-50 nodes);
+ * otherwise, stack overflows hidden behind more cryptic memory pool
+ * corruption errors will occur */
+
static void
checkheap(Pairheap *p, Pairheap *queue)
{
@@ -53,7 +58,7 @@
static void
debugqueue(Pairheap **queue)
{
- if(debuglevel < Logtrace || queue == nil)
+ if(debuglevel < Logparanoid || queue == nil)
return;
printqueue(queue);
checkheap(*queue, *queue);
@@ -62,7 +67,7 @@
static Pairheap *
mergequeue(Pairheap *a, Pairheap *b)
{
- dprint(Logtrace, "pheap::mergequeue %#p %.6f b %#p %.6f\n",
+ dprint(Logparanoid, "pheap::mergequeue %#p %.6f b %#p %.6f\n",
a, a->n, b, b!=nil ? b->n : NaN());
if(b == nil)
return a;
@@ -108,7 +113,7 @@
{
Pairheap *p;
- dprint(Logtrace, "pheap::nukequeue %#p\n", queue);
+ dprint(Logparanoid, "pheap::nukequeue %#p\n", queue);
while((p = popqueue(queue)) != nil){
debugqueue(&p);
free(p);
@@ -123,7 +128,7 @@
p = *queue;
if(p == nil)
return nil;
- dprint(Logtrace, "pheap::popqueue %#p %.6f\n", p, p->n);
+ dprint(Logparanoid, "pheap::popqueue %#p %.6f\n", p, p->n);
*queue = mergepairs(p->left, p);
debugqueue(queue);
return p;
@@ -134,7 +139,7 @@
{
Pairheap *q;
- dprint(Logtrace, "pheap::decreasekey %#p %.6f Δ %.6f\n", p, p->n, Δ);
+ dprint(Logparanoid, "pheap::decreasekey %#p %.6f Δ %.6f\n", p, p->n, Δ);
p->n -= Δ;
if(p->parent != nil && p->n < p->parent->n){
assert(p->parent->left != nil);
@@ -161,7 +166,7 @@
p = emalloc(sizeof *p);
p->n = n;
p->aux = aux;
- dprint(Logtrace, "pheap::pushqueue %#p %.6f\n", p, p->n);
+ dprint(Logparanoid, "pheap::pushqueue %#p %.6f\n", p, p->n);
*queue = mergequeue(p, *queue);
debugqueue(queue);
return p;