shithub: asif

Download patch

ref: 8e2a344a89b52e664f8cd8abf9dfa3848514129d
parent: d82bab87ecc28965c91621f1193efa8badc909df
author: qwx <qwx@sciops.net>
date: Sun Jun 5 07:13:36 EDT 2022

make path algos more amenable to drop in usage; move path(1) to its own dir

--- /dev/null
+++ b/app/path/client.c
@@ -1,0 +1,66 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <pool.h>
+#include "asif.h"
+#include "path.h"
+#include "dat.h"
+#include "fns.h"
+
+int	(*mousefn)(Mouse);
+int	(*keyfn)(Rune);
+
+static Keyboardctl *kc;
+static Mousectl *mc;
+
+void
+evloop(void)
+{
+	Rune r;
+
+	enum{
+		Aresize,
+		Amouse,
+		Akbd,
+		Aend,
+	};
+	Alt a[] = {
+		[Aresize] {mc->resizec, nil, CHANRCV},
+		[Amouse] {mc->c, &mc->Mouse, CHANRCV},
+		[Akbd] {kc->c, &r, CHANRCV},
+		[Aend] {nil, nil, CHANEND},
+	};
+	for(;;){
+		switch(alt(a)){
+		case Aresize:
+			if(getwindow(display, Refnone) < 0)
+				sysfatal("resize failed: %r");
+			resetdrw();
+			break;
+		case Amouse:
+			if(mousefn(mc->Mouse))
+				updatedrw();
+			break;
+		case Akbd:
+			keyfn(r);
+			break;
+		}
+	}
+}
+
+void
+init(int w, int h)
+{
+	fmtinstall('P', Pfmt);
+	fmtinstall('R', Rfmt);
+	initfs();
+	initmap(w, h);
+	initdrw();
+	if((kc = initkeyboard(nil)) == nil)
+		sysfatal("initkeyboard: %r");
+	if((mc = initmouse(nil, screen)) == nil)
+		sysfatal("initmouse: %r");
+}
--- /dev/null
+++ b/app/path/dat.h
@@ -1,0 +1,7 @@
+enum{
+	Nodesz = 8,
+};
+extern Node *selected;
+extern Node *start, *goal;
+
+extern int	(*pathfn)(Node*, Node*);
--- /dev/null
+++ b/app/path/drw.c
@@ -1,0 +1,190 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "asif.h"
+#include "path.h"
+#include "dat.h"
+#include "fns.h"
+
+QLock drawlock;
+Node *selected;
+
+typedef Vertex Point;
+
+enum{
+	Cbg,
+	Cgrid,
+	Cfree,
+	Cblocked,
+	Copen,
+	Cclosed,
+	Cpath,
+	Cstart,
+	Cgoal,
+	Cend,
+};
+static Image *col[Cend];
+static Point viewΔ;
+static Rectangle viewr, hudr;
+static Image *view;
+
+static Image *
+eallocimage(Rectangle r, int repl, ulong col)
+{
+	Image *i;
+
+	if((i = allocimage(display, r, screen->chan, repl, col)) == nil)
+		sysfatal("allocimage: %r");
+	return i;
+}
+
+Node *
+scrselect(Point p)
+{
+	p = subpt(addpt(subpt(p, screen->r.min), viewΔ), Pt(1,1));
+	if(!ptinrect(p, viewr)){
+		selected = nil;
+		return nil;
+	}
+	p = divpt(p, Nodesz);
+	p.x = MIN(p.x, gridwidth-1);
+	p.y = MIN(p.y, gridheight-1);
+	selected = grid + p.y * gridwidth + p.x;
+	return selected;
+}
+
+static void
+flushdrw(void)
+{
+	draw(screen, screen->r, view, nil, viewΔ);
+	flushimage(display, 1);
+}
+
+static void
+drawhud(void)
+{
+	char s[64], *sp;
+	Node *n;
+
+	draw(screen, hudr, col[Cbg], nil, ZP);
+	sp = seprint(s, s+sizeof s, "grid size: %d,%d", viewr.max.x-1, viewr.max.y-1);
+	if((n = selected) != nil){
+		assert(n >= grid && n < grid + gridwidth * gridheight);
+		sp = seprint(sp, s+sizeof s, " selected: %P%s",
+			Pt((n-grid) % gridwidth, (n-grid) / gridwidth),
+			isblocked(n) ? ": blocked" : "");
+	}
+	USED(sp);
+	string(screen, addpt(screen->r.min, subpt(Pt(2, viewr.max.y+2), viewΔ)), col[Cfree], ZP, font, s);
+}
+
+static void
+drawnodes(void)
+{
+	Node *n;
+	Rectangle r;
+	Image *c;
+
+	for(n=grid; n<grid+gridwidth*gridheight; n++){
+		if(isblocked(n))
+			c = col[Cblocked];
+		else if(n == start)
+			c = col[Cstart];
+		else if(n == goal)
+			c = col[Cgoal];
+		else if(n->to != nil)
+			c = col[Cpath];
+		else if(n->closed)
+			c = col[Cclosed];
+		else if(n->open)
+			c = col[Copen];
+		else
+			continue;
+		r.min = n2s(n);
+		r.max = addpt(r.min, Pt(Nodesz-1, Nodesz-1));
+		draw(view, r, c, nil, ZP);
+	}
+}
+
+static void
+drawfrom(void)
+{
+	Node *n;
+	Point p0, p1;
+
+	for(n=grid; n<grid+gridwidth*gridheight; n++){
+		if(!n->open)
+			continue;
+		p1 = addpt(n2s(n), Pt(Nodesz / 2, Nodesz / 2));
+		p0 = addpt(n2s(n->from), Pt(Nodesz / 2, Nodesz / 2));
+		line(view, p0, p1, Endarrow, 0, 0, col[Cgrid], ZP);
+	}
+}
+
+static void
+drawgrid(void)
+{
+	Rectangle r;
+
+	draw(view, view->r, col[Cfree], nil, ZP);
+	r = viewr;
+	while(r.min.x < viewr.max.x){
+		r.max.x = r.min.x;
+		line(view, r.min, r.max, 0, 0, 0, col[Cgrid], ZP);
+		r.min.x += Nodesz;
+	}
+	r = viewr;
+	while(r.min.y < viewr.max.y){
+		r.max.y = r.min.y;
+		line(view, r.min, r.max, 0, 0, 0, col[Cgrid], ZP);
+		r.min.y += Nodesz;
+	}
+	drawnodes();
+	drawfrom();
+}
+
+static void
+redraw(void)
+{
+	drawgrid();
+}
+
+void
+updatedrw(void)
+{
+	qlock(&drawlock);
+	redraw();
+	qunlock(&drawlock);
+	drawhud();
+	flushdrw();
+}
+
+void
+resetdrw(void)
+{
+	viewr = Rpt(ZP, Pt(gridwidth*Nodesz+1, gridheight*Nodesz+1));
+	viewΔ = divpt(addpt(subpt(ZP, subpt(screen->r.max, screen->r.min)), viewr.max), 2);
+	hudr.min = addpt(screen->r.min, subpt(Pt(2, viewr.max.y+2), viewΔ));
+	hudr.max = addpt(hudr.min, Pt(viewr.max.x-2, font->height*3));
+	freeimage(view);
+	view = eallocimage(viewr, 0, DNofill);
+	draw(screen, screen->r, col[Cbg], nil, ZP);
+	updatedrw();
+}
+
+void
+initdrw(void)
+{
+	if(initdraw(nil, nil, "path") < 0)
+		sysfatal("initdraw: %r");
+	col[Cbg] = display->black;
+	col[Cgrid] = eallocimage(Rect(0,0,1,1), 1, 0x222222ff);
+	col[Cblocked] = display->black;
+	col[Cfree] = eallocimage(Rect(0,0,1,1), 1, 0x777777ff);
+	col[Copen] = eallocimage(Rect(0,0,1,1), 1, 0x00ccccff);
+	col[Cclosed] = eallocimage(Rect(0,0,1,1), 1, 0x0000ccff);
+	col[Cpath] = eallocimage(Rect(0,0,1,1), 1, 0xcccc00ff);
+	col[Cstart] = eallocimage(Rect(0,0,1,1), 1, 0x00cc00ff);
+	col[Cgoal] = eallocimage(Rect(0,0,1,1), 1, 0xcc0000ff);
+	resetdrw();
+}
--- /dev/null
+++ b/app/path/fns.h
@@ -1,0 +1,11 @@
+void	init(int, int);
+Node*	scrselect(Point);
+void	updatedrw(void);
+void	evloop(void);
+void	initfs(void);
+void	initmap(int, int);
+void	initdrw(void);
+void	resetdrw(void);
+Vertex	n2s(Node*);
+void	clearmap(void);
+int	setparm(int, int, int);
--- /dev/null
+++ b/app/path/fs.c
@@ -1,0 +1,11 @@
+#include <u.h>
+#include <libc.h>
+#include "asif.h"
+#include "path.h"
+#include "dat.h"
+#include "fns.h"
+
+void
+initfs(void)
+{
+}
--- /dev/null
+++ b/app/path/map.c
@@ -1,0 +1,57 @@
+#include <u.h>
+#include <libc.h>
+#include "asif.h"
+#include "path.h"
+#include "dat.h"
+#include "fns.h"
+
+int movemode;
+int	(*pathfn)(Node*, Node*);
+
+Vertex
+n2s(Node *n)
+{
+	Vertex v;
+
+	v = n2p(n);
+	v.x = v.x * Nodesz + 1;
+	v.y = v.y * Nodesz + 1;
+	return v;
+}
+
+int
+setparm(int mmode, int alg, int dist)
+{
+	switch(mmode){
+	case Move8: /* wet floor */
+	case Move4: movemode = mmode; break;
+	default: sysfatal("setparm: unknown move mode %d", mmode);
+	}
+	switch(alg){
+	case 0: pathfn = a∗findpath; break;
+	default: sysfatal("setparm: unknown algo type %d", alg);
+	}
+	switch(dist){
+	case Deuclid: distfn = eucdist; break;
+	case Dmanhattan: distfn = manhdist; break;
+	case Doctile: distfn = octdist; break;
+	default: sysfatal("setparm: unknown distance function %d", dist);
+	}
+	clearmap();
+	return 0;
+}
+
+void
+clearmap(void)
+{
+	if(grid == nil)
+		return;
+	cleargrid();
+	start = goal = nil;
+}
+
+void
+initmap(int w, int h)
+{
+	initgrid(w, h);
+}
--- /dev/null
+++ b/app/path/mkfile
@@ -1,0 +1,28 @@
+</$objtype/mkfile
+BIN=$home/bin/$objtype/asif
+TARG=path
+HFILES=../../asif.h ../../path/path.h dat.h fns.h
+OFILES=\
+	../../path/a∗.$O\
+	../../path/grid.$O\
+	../../dprint.$O\
+	../../emalloc.$O\
+	../../pheap.$O\
+	../../vec.$O\
+	../../zalloc.$O\
+	client.$O\
+	drw.$O\
+	fs.$O\
+	map.$O\
+	path.$O\
+
+</sys/src/cmd/mkone
+
+CFLAGS=$CFLAGS -I../.. -I../../path
+%.$O:	%.c
+	$CC -o $target $CFLAGS $stem.c
+
+dicks:V:
+	mkdir -p $BIN
+install:V:	dicks
+CLEANFILES=$CLEANFILES $OFILES
--- /dev/null
+++ b/app/path/path.c
@@ -1,0 +1,114 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <pool.h>
+#include "asif.h"
+#include "path.h"
+#include "dat.h"
+#include "fns.h"
+
+extern int	(*mousefn)(Mouse);
+extern int	(*keyfn)(Rune);
+
+mainstacksize = 512*1024;
+
+Node *start, *goal;
+
+static int
+grmouse(Mouse m)
+{
+	static Node *old;
+	Node *n;
+
+	if(m.buttons == 0){
+		old = nil;
+		return 0;
+	}
+	if((n = scrselect(m.xy)) == nil || old == n)
+		return 0;
+	switch(m.buttons & 7){
+	case 1:
+		if(goal != n && !isblocked(n))
+			start = n;
+		break;
+	case 2:
+		if(start != n && !isblocked(n))
+			goal = n;
+		break;
+	case 4:
+		if(old == nil || isblocked(n) ^ isblocked(old))
+			toggleblocked(n);
+		break;
+	}
+	old = n;
+	if(start != nil && goal != nil)
+		if(pathfn(start, goal) < 0){
+			dprint(Logdebug, "grid::findpath: findpath from [%#p,%P] to [%#p,%P]: %r\n",
+				start, n2p(start), goal, n2p(goal));
+		}
+	return 1;
+}
+
+static int
+grkey(Rune r)
+{
+	switch(r){
+	case Kdel:
+	case 'q': threadexitsall(nil);
+	case 'r': cleargrid(); updatedrw(); break;
+	}
+	return 0;
+}
+
+static void
+usage(void)
+{
+	fprint(2, "usage: %s [-D4] [-s width[,height]]\n", argv0);
+	threadexits("usage");
+}
+
+void
+threadmain(int argc, char **argv)
+{
+	int w, h, d, m;
+	char *s;
+
+	w = 64;
+	h = 64;
+	d = Move8;
+	m = Doctile;
+	ARGBEGIN{
+	case 'D':
+		if(++debuglevel >= Logparanoid)
+			mainmem->flags |= POOL_NOREUSE | POOL_PARANOIA | POOL_LOGGING;
+		break;
+	case '4':
+		d = Move4;
+		m = Dmanhattan;
+		break;
+	case 's':
+		w = strtol(EARGF(usage()), &s, 0);
+		if(w <= 0)
+			usage();
+		if(*s != ','){
+			h = w;
+			break;
+		}
+		h = strtol(s+1, nil, 0);
+		if(h <= 0)
+			usage();
+		break;
+	default: usage();
+	}ARGEND
+	if(w <= 0 || w > 512
+	|| h <= 0 || h > 512)
+		sysfatal("invalid map size, must be in ]0,512]");
+	keyfn = grkey;
+	mousefn = grmouse;
+	setparm(d, 0, m);
+	init(w, h);
+	evloop();
+}
--- a/path/a∗.c
+++ b/path/a∗.c
@@ -1,16 +1,45 @@
 #include <u.h>
 #include <libc.h>
-#include <thread.h>
 #include <draw.h>
-#include <mouse.h>
-#include <keyboard.h>
-#include "../asif.h"
-#include "dat.h"
-#include "fns.h"
+#include "asif.h"
+#include "path.h"
 
-/* FIXME: nope, non-optimal paths + too many nodes opened, again
- * (dijsktra works fine) */
+/* FIXME: assumes grid */
 
+typedef Vertex Point;
+
+typedef struct PNode PNode;
+struct PNode{
+	Node *n;
+	double g;
+	double h;
+	double Δg;
+	Pairheap *pq;
+};
+
+static PNode**	(*successorfn)(Node*);
+static Zpool *zpool;
+
+static void
+cleanup(Pairheap **queue)
+{
+	Pairheap *p;
+
+	while((p = popqueue(queue)) != nil){
+		zfree(p->aux);
+		free(p);
+	}
+}
+
+static void
+backtrack(Node *a, Node *b)
+{
+	Node *n;
+
+	for(n=b; n!=a; n=n->from)
+		n->from->to = n;
+}
+
 /* 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
@@ -17,6 +46,7 @@
  * 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 */
+ 	// FIXME: ^- reverify that it does not work
 static double
 movecost(int Δx, int Δy)
 {
@@ -23,60 +53,142 @@
 	return Δx != 0 && Δy != 0 ? 1.001 : 1.0;
 }
 
-static Node *
+static PNode **
+successors8(Node *nu)
+{
+	static PNode *suc[8+1];
+	static dtab[2*(nelem(suc)-1)]={
+		1,0, 0,-1, -1,0, 0,1,
+		-1,-1, -1,1, 1,-1, 1,1,
+	};
+	int i;
+	Node *nv;
+	PNode *v, **vp;
+	Point p;
+	Rectangle r;
+
+	memset(suc, 0, sizeof suc);
+	p = n2p(nu);
+	r = Rect(0, 0, gridwidth, gridheight);
+	for(i=0, vp=suc; i<nelem(dtab); i+=2){
+		if(!ptinrect(addpt(p, Pt(dtab[i], dtab[i+1])), r))
+			continue;
+		nv = nu + dtab[i+1] * gridwidth + dtab[i];
+		assert(nv >= grid && nv < grid + gridwidth * gridheight);
+		if(isblocked(nv))
+			continue;
+		v = zalloc(zpool);
+		v->n = nv;
+		v->Δg = movecost(dtab[i], dtab[i+1]);
+		*vp++ = v;
+	}
+	return suc;
+}
+
+static PNode **
+successors4(Node *nu)
+{
+	static PNode *suc[4+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, *t;
+	Node *nv;
+	PNode *v, **vp;
+	Point p;
+	Rectangle r;
+
+	memset(suc, 0, sizeof suc);
+	p = n2p(nu);
+	r = Rect(0, 0, gridwidth, gridheight);
+	/* path straightening; cf.:
+	 * https://www.redbloblgames.com/pathfinding/a-star/implementation.html */
+	t = (p.x + p.y) % 2 == 0 ? rdtab : dtab;
+	for(i=0, vp=suc; i<nelem(dtab); i+=2){
+		if(!ptinrect(addpt(p, Pt(t[i], t[i+1])), r))
+			continue;
+		nv = nu + t[i+1] * gridwidth + t[i];
+		assert(nv >= grid && nv < grid + gridwidth * gridheight);
+		if(isblocked(nv))
+			continue;
+		v = zalloc(zpool);
+		v->n = nv;
+		v->Δg = movecost(t[i], t[i+1]);
+		*vp++ = v;
+	}
+	return suc;
+}
+
+static int
 a∗(Node *a, Node *b)
 {
 	double g, Δg;
-	Node *x, *s, **sl;
+	PNode *u, *v, **vl;
+	Node *nu, *nv;
 	Pairheap *queue, *pn;
 
-	assert(a != nil && b != nil);
-	assert(a != b);
 	queue = nil;
-	a->pq = pushqueue(distfn(a, b), a, &queue);
-	x = a;
+	nu = a;
+	u = zalloc(zpool);
+	u->n = a;
+	u->pq = pushqueue(distfn(a, b), u, &queue);
 	while((pn = popqueue(&queue)) != nil){
-		x = pn->aux;
+		u = pn->aux;
+		nu = u->n;
 		free(pn);
-		if(x == b)
+		if(nu == b)
 			break;
-		x->closed = 1;
+		nu->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)
+			u, n2p(nu), u->h, u->g);
+		if((vl = successorfn(nu)) == nil)
 			sysfatal("a∗: %r");
-		for(s=*sl++; s!=nil; s=*sl++){
-			if(s->closed)
+		for(v=*vl++; v!=nil; v=*vl++){
+			nv = v->n;
+			if(nv->closed)
 				continue;
-			assert(!isblocked(s));
-			g = x->g + s->Δg;
-			Δg = s->g - g;
-			if(!s->open){
-				s->from = x;
-				s->open = 1;
-				s->h = distfn(s, b);
-				s->g = g;
+			g = u->g + v->Δg;
+			Δg = v->g - g;
+			if(!nv->open){
+				nv->from = nu;
+				nv->open = 1;
+				v->h = distfn(nv, b);
+				v->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);
+					v, n2p(nv), v->h, v->g, v->h + v->g);
+				v->pq = pushqueue(v->g + v->h, v, &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);
-				assert(s->g >= 0);
+					v, n2p(nv), v->h, v->g, Δg, v->h + v->g - Δg);
+				nv->from = u->n;
+				v->g -= Δg;
+				decreasekey(v->pq, Δg, &queue);
+				assert(v->g >= 0);
 			}
 		}
 	}
-	nukequeue(&queue);
-	return x;
+	cleanup(&queue);
+	if(nu != b)
+		return -1;
+	backtrack(a, b);
+	return 0;
 }
 
-void
-threadmain(int argc, char **argv)
+int
+a∗findpath(Node *a, Node *b)
 {
-	init(argc, argv);
-	initgrid(a∗, movecost);
-	evloop();
+	assert(a != nil && b != nil && a != b);
+	clearpath();
+	if(zpool == nil)
+		zpool = znew(sizeof(PNode));
+	successorfn = movemode == Move8 ? successors8 : successors4;
+	dprint(Logdebug, "grid::a∗findpath: a∗ from [%#p,%P] to [%#p,%P]\n",
+		a, n2p(a), b, n2p(b));
+	if(a∗(a, b) < 0){
+		dprint(Logdebug, "grid::a∗findpath: failed to find a path\n");
+		return -1;
+	}
+	return 0;
 }
--- a/path/client.c
+++ /dev/null
@@ -1,123 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <thread.h>
-#include <draw.h>
-#include <mouse.h>
-#include <keyboard.h>
-#include <pool.h>
-#include "../asif.h"
-#include "dat.h"
-#include "fns.h"
-
-mainstacksize = 512*1024;
-
-extern QLock drawlock;
-Node*	scrselect(Point);
-void	updatedrw(void);
-
-int	(*mousefn)(Node*, Mouse, Node*);
-int	(*keyfn)(Rune);
-
-static Keyboardctl *kc;
-static Mousectl *mc;
-
-void
-evloop(void)
-{
-	Rune r;
-	Mouse m;
-	Node *n, *p;
-
-	enum{
-		Aresize,
-		Amouse,
-		Akbd,
-		Aend,
-	};
-	Alt a[] = {
-		[Aresize] {mc->resizec, nil, CHANRCV},
-		[Amouse] {mc->c, &mc->Mouse, CHANRCV},
-		[Akbd] {kc->c, &r, CHANRCV},
-		[Aend] {nil, nil, CHANEND},
-	};
-	p = nil;
-	for(;;){
-		switch(alt(a)){
-		case Aresize:
-			if(getwindow(display, Refnone) < 0)
-				sysfatal("resize failed: %r");
-			resetdrw();
-			break;
-		case Amouse:
-			if(mc->buttons == 0){
-				p = nil;
-				break;
-			}
-			if((n = scrselect(m.xy)) != nil && p != n){
-				if(mousefn(n, mc->Mouse, p) < 0)
-					fprint(2, "%r\n");
-				p = n;
-			}
-			updatedrw();
-			break;
-		case Akbd:
-			switch(r){
-			case Kdel: threadexitsall(nil);
-			case 'r': clearmap(); updatedrw(); break;
-			}
-			keyfn(r);
-			break;
-		}
-		m = mc->Mouse;
-	}
-}
-
-static void
-usage(void)
-{
-	fprint(2, "usage: %s [-D4] [-s width[,height]]\n", argv0);
-	threadexits("usage");
-}
-
-void
-init(int argc, char **argv)
-{
-	char *s;
-
-	mapwidth = 64;
-	mapheight = 64;
-	ARGBEGIN{
-	case 'D':
-		if(++debuglevel >= Logparanoid)
-			mainmem->flags |= POOL_NOREUSE | POOL_PARANOIA | POOL_LOGGING;
-		break;
-	case '4':
-		fourdir = 1;
-		break;
-	case 's':
-		mapwidth = strtol(EARGF(usage()), &s, 0);
-		if(mapwidth <= 0)
-			usage();
-		if(*s != ','){
-			mapheight = mapwidth;
-			break;
-		}
-		mapheight = strtol(s+1, nil, 0);
-		if(mapheight <= 0)
-			usage();
-		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();
-	initmap();
-	initdrw();
-	if((kc = initkeyboard(nil)) == nil)
-		sysfatal("initkeyboard: %r");
-	if((mc = initmouse(nil, screen)) == nil)
-		sysfatal("initmouse: %r");
-}
--- a/path/dat.h
+++ /dev/null
@@ -1,33 +1,0 @@
-typedef struct Vertex Vertex;
-typedef struct Node Node;
-typedef struct PNode PNode;
-
-struct Vertex{
-	int x;
-	int y;
-};
-struct PNode{
-	int open;
-	int closed;
-	double g;
-	double h;
-	double Δg;
-	Node *to;
-	Node *from;
-	Pairheap *pq;
-};
-enum{
-	Nodesz = 8,
-};
-struct Node{
-	int blocked;
-	PNode;
-};
-extern Node *map;
-extern int mapwidth, mapheight;
-extern Node *selected;
-extern Node *start, *goal;
-extern int fourdir;
-
-extern double	(*distfn)(Node*, Node*);
-extern Node**	(*successorfn)(Node*);
--- a/path/drw.c
+++ /dev/null
@@ -1,188 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <draw.h>
-#include "../asif.h"
-#include "dat.h"
-#include "fns.h"
-
-QLock drawlock;
-Node *selected;
-
-typedef Vertex Point;
-
-enum{
-	Cbg,
-	Cgrid,
-	Cfree,
-	Cblocked,
-	Copen,
-	Cclosed,
-	Cpath,
-	Cstart,
-	Cgoal,
-	Cend,
-};
-static Image *col[Cend];
-static Point viewΔ;
-static Rectangle viewr, hudr;
-static Image *view;
-
-static Image *
-eallocimage(Rectangle r, int repl, ulong col)
-{
-	Image *i;
-
-	if((i = allocimage(display, r, screen->chan, repl, col)) == nil)
-		sysfatal("allocimage: %r");
-	return i;
-}
-
-Node *
-scrselect(Point p)
-{
-	p = subpt(addpt(subpt(p, screen->r.min), viewΔ), Pt(1,1));
-	if(!ptinrect(p, viewr)){
-		selected = nil;
-		return nil;
-	}
-	p = divpt(p, Nodesz);
-	p.x = MIN(p.x, mapwidth-1);
-	p.y = MIN(p.y, mapheight-1);
-	selected = map + p.y * mapwidth + p.x;
-	return selected;
-}
-
-static void
-flushdrw(void)
-{
-	draw(screen, screen->r, view, nil, viewΔ);
-	flushimage(display, 1);
-}
-
-static void
-drawhud(void)
-{
-	char s[64], *sp;
-	Node *n;
-
-	draw(screen, hudr, col[Cbg], nil, ZP);
-	sp = seprint(s, s+sizeof s, "map size: %d,%d", viewr.max.x-1, viewr.max.y-1);
-	if((n = selected) != nil){
-		assert(n >= map && n < map + mapwidth * mapheight);
-		sp = seprint(sp, s+sizeof s, " selected: %P%s",
-			Pt((n-map) % mapwidth, (n-map) / mapwidth),
-			n->blocked ? ": blocked" : "");
-	}
-	USED(sp);
-	string(screen, addpt(screen->r.min, subpt(Pt(2, viewr.max.y+2), viewΔ)), col[Cfree], ZP, font, s);
-}
-
-static void
-drawnodes(void)
-{
-	Node *n;
-	Rectangle r;
-	Image *c;
-
-	for(n=map; n<map+mapwidth*mapheight; n++){
-		if(n->blocked)
-			c = col[Cblocked];
-		else if(n == start)
-			c = col[Cstart];
-		else if(n == goal)
-			c = col[Cgoal];
-		else if(n->to != nil)
-			c = col[Cpath];
-		else if(n->closed)
-			c = col[Cclosed];
-		else if(n->open)
-			c = col[Copen];
-		else
-			continue;
-		r.min = n2s(n);
-		r.max = addpt(r.min, Pt(Nodesz-1, Nodesz-1));
-		draw(view, r, c, nil, ZP);
-	}
-}
-
-static void
-drawfrom(void)
-{
-	Node *n;
-	Point p0, p1;
-
-	for(n=map; n<map+mapwidth*mapheight; n++){
-		if(!n->open)
-			continue;
-		p1 = addpt(n2s(n), Pt(Nodesz / 2, Nodesz / 2));
-		p0 = addpt(n2s(n->from), Pt(Nodesz / 2, Nodesz / 2));
-		line(view, p0, p1, Endarrow, 0, 0, col[Cgrid], ZP);
-	}
-}
-static void
-drawmap(void)
-{
-	Rectangle r;
-
-	draw(view, view->r, col[Cfree], nil, ZP);
-	r = viewr;
-	while(r.min.x < viewr.max.x){
-		r.max.x = r.min.x;
-		line(view, r.min, r.max, 0, 0, 0, col[Cgrid], ZP);
-		r.min.x += Nodesz;
-	}
-	r = viewr;
-	while(r.min.y < viewr.max.y){
-		r.max.y = r.min.y;
-		line(view, r.min, r.max, 0, 0, 0, col[Cgrid], ZP);
-		r.min.y += Nodesz;
-	}
-	drawnodes();
-	drawfrom();
-}
-
-static void
-redraw(void)
-{
-	drawmap();
-}
-
-void
-updatedrw(void)
-{
-	qlock(&drawlock);
-	redraw();
-	qunlock(&drawlock);
-	drawhud();
-	flushdrw();
-}
-
-void
-resetdrw(void)
-{
-	viewr = Rpt(ZP, Pt(mapwidth*Nodesz+1, mapheight*Nodesz+1));
-	viewΔ = divpt(addpt(subpt(ZP, subpt(screen->r.max, screen->r.min)), viewr.max), 2);
-	hudr.min = addpt(screen->r.min, subpt(Pt(2, viewr.max.y+2), viewΔ));
-	hudr.max = addpt(hudr.min, Pt(viewr.max.x-2, font->height*3));
-	freeimage(view);
-	view = eallocimage(viewr, 0, DNofill);
-	draw(screen, screen->r, col[Cbg], nil, ZP);
-	updatedrw();
-}
-
-void
-initdrw(void)
-{
-	if(initdraw(nil, nil, "path") < 0)
-		sysfatal("initdraw: %r");
-	col[Cbg] = display->black;
-	col[Cgrid] = eallocimage(Rect(0,0,1,1), 1, 0x222222ff);
-	col[Cblocked] = display->black;
-	col[Cfree] = eallocimage(Rect(0,0,1,1), 1, 0x777777ff);
-	col[Copen] = eallocimage(Rect(0,0,1,1), 1, 0x00ccccff);
-	col[Cclosed] = eallocimage(Rect(0,0,1,1), 1, 0x0000ccff);
-	col[Cpath] = eallocimage(Rect(0,0,1,1), 1, 0xcccc00ff);
-	col[Cstart] = eallocimage(Rect(0,0,1,1), 1, 0x00cc00ff);
-	col[Cgoal] = eallocimage(Rect(0,0,1,1), 1, 0xcc0000ff);
-	resetdrw();
-}
--- a/path/fns.h
+++ /dev/null
@@ -1,16 +1,0 @@
-void	init(int, char**);
-void	evloop(void);
-void	initfs(void);
-void	initgrid(Node* (*)(Node*, Node*), double (*)(int, int));
-void	resetmap(void);
-void	clearmap(void);
-void	initmap(void);
-void	initdrw(void);
-void	resetdrw(void);
-double	eucdist(Node*, Node*);
-double	octdist(Node*, Node*);
-double	manhdist(Node*, Node*);
-Vertex	n2p(Node*);
-Node*	p2n(Vertex);
-Vertex	n2s(Node*);
-int	isblocked(Node*);
--- a/path/fs.c
+++ /dev/null
@@ -1,10 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include "../asif.h"
-#include "dat.h"
-#include "fns.h"
-
-void
-initfs(void)
-{
-}
--- a/path/grid.c
+++ b/path/grid.c
@@ -1,145 +1,104 @@
 #include <u.h>
 #include <libc.h>
-#include <thread.h>
-#include <draw.h>
-#include <mouse.h>
-#include <keyboard.h>
-#include "../asif.h"
-#include "dat.h"
-#include "fns.h"
+#include "asif.h"
+#include "path.h"
 
-extern int	(*mousefn)(Node*, Mouse, Node*);
-extern int	(*keyfn)(Rune);
-
+Node *grid;
+int gridwidth, gridheight;
 double	(*distfn)(Node*, Node*);
-Node**	(*successorfn)(Node*);
 
-Node *start, *goal;
+double
+eucdist(Node *a, Node *b)
+{
+	int dx, dy;
 
-static Node*	(*pathfn)(Node*, Node*);
-static double	(*costfn)(int, int);
+	dx = a->x - b->x;
+	dy = a->y - b->y;
+	return sqrt(dx *dx + dy *dy);
+}
 
-static void
-backtrack(void)
+double
+octdist(Node *a, Node *b)
 {
-	Node *n;
+	int dx, dy;
 
-	assert(goal != start);
-	for(n=goal; n!=start; n=n->from)
-		n->from->to = n;
+	dx = abs(a->x - b->x);
+	dy = abs(a->y - b->y);
+	return 1 * (dx + dy) + MIN(dx, dy) * (SQRT2 - 2 * 1);
 }
 
-static Node **
-successors8(Node *n)
+double
+manhdist(Node *a, Node *b)
 {
-	static Node *suc[8+1];
-	static dtab[2*(nelem(suc)-1)]={
-		1,0, 0,-1, -1,0, 0,1,
-		-1,-1, -1,1, 1,-1, 1,1,
-	};
-	int i;
-	Node *s, **np;
-	Point p;
-	Rectangle r;
+	int dx, dy;
 
-	memset(suc, 0, sizeof suc);
-	p = n2p(n);
-	r = Rect(0, 0, mapwidth, mapheight);
-	for(i=0, np=suc; i<nelem(dtab); i+=2){
-		if(!ptinrect(addpt(p, Pt(dtab[i], dtab[i+1])), r))
-			continue;
-		s = n + dtab[i+1] * mapwidth + dtab[i];
-		assert(s >= map && s < map + mapwidth * mapheight);
-		if(isblocked(s))
-			continue;
-		s->Δg = costfn != nil ? costfn(dtab[i], dtab[i+1]) : 0;
-		*np++ = s;
-	}
-	return suc;
+	dx = abs(a->x - b->x);
+	dy = abs(a->y - b->y);
+	return dx + dy;
 }
 
-static Node **
-successors4(Node *n)
+void
+toggleblocked(Node *n)
 {
-	static Node *suc[4+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, *t;
-	Node *s, **np;
-	Point p;
-	Rectangle r;
+	n->blocked ^= 1;
+}
 
-	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(t[i], t[i+1])), r))
-			continue;
-		s = n + t[i+1] * mapwidth + t[i];
-		assert(s >= map && s < map + mapwidth * mapheight);
-		if(isblocked(s))
-			continue;
-		s->Δg = costfn != nil ? costfn(t[i], t[i+1]) : 0;
-		*np++ = s;
-	}
-	return suc;
+int
+isblocked(Node *n)
+{
+	return n->blocked;
 }
 
-static int
-findpath(void)
+void
+clearpath(void)
 {
-	if(start == nil || goal == nil){
-		werrstr("findpath: start and goal not undefined");
-		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;
-	}
-	backtrack();
-	return 0;
+	Node *n;
+
+	if(grid == nil)
+		return;
+	for(n=grid; n<grid+gridwidth*gridheight; n++)
+		memset(&n->PState, 0, sizeof n->PState);
 }
 
-static int
-grmouse(Node *n, Mouse m, Node *old)
+void
+cleargrid(void)
 {
-	switch(m.buttons & 7){
-	case 1: if(goal != n && !n->blocked) start = n; break;
-	case 2: if(start != n && !n->blocked) goal = n; break;
-	case 4: if(old == nil || n->blocked ^ old->blocked) n->blocked ^= 1; return 0;
-	}
-	if(start != nil && goal != nil)
-		return findpath();
-	return 0;
+	Node *n;
+
+	if(grid == nil)
+		return;
+	for(n=grid; n<grid+gridwidth*gridheight; n++)
+		memset(&n->State, 0, sizeof n->State);
 }
 
-static int
-grkey(Rune)
+Node *
+p2n(Vertex p)
 {
-	return 0;
+	return grid + p.y * gridwidth + p.x;
 }
 
-void
-initgrid(Node* (*f)(Node*, Node*), double (*cost)(int, int))
+Vertex
+n2p(Node *n)
 {
-	if(fourdir){
-		distfn = manhdist;
-		successorfn = successors4;
-	}else{
-		distfn = octdist;
-		successorfn = successors8;
+	return (Vertex){(n - grid) % gridwidth, (n - grid) / gridheight};
+}
+
+Node *
+initgrid(int w, int h)
+{
+	int x, y;
+	Node *n;
+
+	grid = emalloc(w * h * sizeof *grid);
+	for(n=grid, x=0, y=0; n<grid+w*h; n++){
+		n->x = x;
+		n->y = y;
+		if(++x == w){
+			x = 0;
+			y++;
+		}
 	}
-	mousefn = grmouse;
-	keyfn = grkey;
-	pathfn = f;
-	costfn = cost;
+	gridwidth = w;
+	gridheight = h;
+	return grid;
 }
--- a/path/map.c
+++ /dev/null
@@ -1,103 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include "../asif.h"
-#include "dat.h"
-#include "fns.h"
-
-Node *start, *goal;
-
-Node *map;
-int mapwidth, mapheight;
-int fourdir;
-
-Vertex
-n2p(Node *n)
-{
-	return (Vertex){(n - map) % mapwidth, (n - map) / mapwidth};
-}
-
-Node *
-p2n(Vertex p)
-{
-	return map + p.y * mapwidth + p.x;
-}
-
-Vertex
-n2s(Node *n)
-{
-	Vertex v;
-
-	v = n2p(n);
-	v.x = v.x * Nodesz + 1;
-	v.y = v.y * Nodesz + 1;
-	return v;
-}
-
-double
-eucdist(Node *a, Node *b)
-{
-	int dx, dy;
-	Vertex p, q;
-
-	p = n2p(a);
-	q = n2p(b);
-	dx = p.x - q.x;
-	dy = p.y - q.y;
-	return sqrt(dx *dx + dy *dy);
-}
-
-double
-octdist(Node *a, Node *b)
-{
-	int dx, dy;
-	Vertex p, q;
-
-	p = n2p(a);
-	q = n2p(b);
-	dx = abs(p.x - q.x);
-	dy = abs(p.y - q.y);
-	return 1 * (dx + dy) + MIN(dx, dy) * (SQRT2 - 2 * 1);
-}
-
-double
-manhdist(Node *a, Node *b)
-{
-	int dx, dy;
-	Vertex p, q;
-
-	p = n2p(a);
-	q = n2p(b);
-	dx = abs(p.x - q.x);
-	dy = abs(p.y - q.y);
-	return dx + dy;
-}
-
-int
-isblocked(Node *n)
-{
-	return n->blocked;
-}
-
-void
-resetmap(void)
-{
-	Node *n;
-
-	for(n=map; n<map+mapwidth*mapheight; n++)
-		memset(&n->PNode, 0, sizeof n->PNode);
-}
-
-void
-clearmap(void)
-{
-	Node *n;
-
-	for(n=map; n<map+mapwidth*mapheight; n++)
-		memset(n, 0, sizeof *n);
-}
-
-void
-initmap(void)
-{
-	map = emalloc(mapwidth * mapheight * sizeof *map);
-}
--- a/path/mkfile
+++ /dev/null
@@ -1,29 +1,0 @@
-</$objtype/mkfile
-BIN=$home/bin/$objtype/path
-TARG=\
-	a∗\
-	bfs\
-	dijkstra\
-
-HFILES=../asif.h dat.h fns.h
-OFILES=\
-	../emalloc.$O\
-	../dprint.$O\
-	client.$O\
-	drw.$O\
-	fs.$O\
-	grid.$O\
-	map.$O\
-
-</sys/src/cmd/mkmany
-
-$O.dijkstra: $OFILES dijkstra.$O ../pheap.$O
-$O.a∗: $OFILES a∗.$O ../pheap.$O
-$O.bfs: $OFILES bfs.$O ../vec.$O
-
-dicks:V:
-	mkdir -p $BIN
-install:V:	dicks
-%.$O:	%.c
-	$CC -o $target $CFLAGS $stem.c
-CLEANFILES=`{echo ../*.[$OS]}
--- /dev/null
+++ b/path/path.h
@@ -1,0 +1,53 @@
+typedef struct Vertex Vertex;
+typedef struct State State;
+typedef struct PState PState;
+typedef struct Node Node;
+
+struct Vertex{
+	int x;
+	int y;
+};
+struct PState{
+	int open;
+	int closed;
+	Node *from;
+	Node *to;
+};
+struct State{
+	int blocked;
+	PState;
+};
+struct Node{
+	Vertex;
+	State;
+};
+int	isblocked(Node*);
+void	toggleblocked(Node*);
+
+enum{
+	Deuclid,
+	Dmanhattan,
+	Doctile,
+};
+double	eucdist(Node*, Node*);
+double	octdist(Node*, Node*);
+double	manhdist(Node*, Node*);
+extern double	(*distfn)(Node*, Node*);
+
+Node*	p2n(Vertex);
+Vertex	n2p(Node*);
+
+enum{
+	Move8,
+	Move4,
+};
+extern int movemode;
+
+extern Node *grid;
+extern int gridwidth, gridheight;
+
+void	clearpath(void);
+void	cleargrid(void);
+Node*	initgrid(int, int);
+
+int	a∗findpath(Node*, Node*);