- be gentle to glenda, by allowing her to walk the shortest distance in a multi-step walk (found by using breadth-first search instead of depth-first search) - while walking with animation enabled, remain responsive, by splitting the animation of a walk in single-step moves that are 'scheduled' using etimer(2). This allows e.g. redirecting a walk during the animation, while glenda is still walking, or disabling animation during a walk. - reduce the amount of leaks reported by leak(1) by building the levels menu contents 'by hand' instead of relying on menuhit to call genlevel (would there be a way to avoid leaking while letting menuhit call genlevel?) Note: I still get some leaks, after popping up the B2 menu: acid: src(0x00009627); // 4 /sys/src/libc/fmt/vsmprint.c:38 I don't quite see where they come from. Axel. Reference: /n/sources/patch/applied/sokoban-bfs-responsive-leak Date: Sun Sep 4 22:28:34 CES 2005 --- /sys/src/games/sokoban/animation.c Thu Jan 1 00:00:00 1970 +++ /sys/src/games/sokoban/animation.c Sun Sep 4 21:50:30 2005 @@ -0,0 +1,60 @@ +#include +#include +#include + +#include "sokoban.h" + +void +initanimation(Animation *a) +{ + if (a == nil) + return; + + memset(a, 0, sizeof(Animation)); +} + +void +setupanimation(Animation *a, Route *r) +{ + if (a == nil || r == nil || r->step == nil) + return; + + a->route = r; + a->step = r->step; + if (a->step < a->route->step + a->route->nstep) + a->count = a->step->count; + else + stopanimation(a); +} + +int +onestep(Animation *a) +{ + if (a == nil) + return 0; + + if (a->count > 0 && a->step != nil && a->route != nil) { + move(a->step->dir); + a->count--; + if (a->count == 0) { + a->step++; + if (a->step < a->route->step + a->route->nstep) + a->count = a->step->count; + else + stopanimation(a); + } + } else if (a->count > 0 && (a->step == nil || a->route == nil)) + stopanimation(a); + return (a->count > 0); +} + +void +stopanimation(Animation *a) +{ + if (a == nil) + return; + + if (a->route != nil) + freeroute(a->route); + memset(a, 0, sizeof(Animation)); +} --- /sys/src/games/sokoban/route.c Sun Sep 4 21:50:41 2005 +++ /sys/src/games/sokoban/route.c Sun Sep 4 21:50:39 2005 @@ -4,8 +4,8 @@ #include "sokoban.h" -static int trydir(int, Point, Point, Route*, Visited*); -static int dofind(Point, Point, Route*, Visited*); +static int dirlist[] = { Up, Down, Left, Right, Up, Down, Left, Right, }; +static int ndir = 4; static Point dir2point(int dir) @@ -23,73 +23,23 @@ return Pt(0,0); } -Route* -newroute(void) -{ - Route *r = malloc(sizeof(Route)); - memset(r, 0, sizeof(Route)); - return r; -} - -void -freeroute(Route *r) -{ - if (r->step != nil) { - free(r->step); - memset(r, 0, sizeof(Route)); - } - free(r); -} - -void -reverseroute(Route *r) +int +validpush(Point g, Step *s, Point *t) { int i; - Step tmp; + Point m; - for (i=1; i< r->nstep; i++) { - tmp = r->step[i]; - r->step[i] = r->step[i-1]; - r->step[i-1] = tmp; - } -} + if (s == nil) + return 0; -void -pushstep(Route *r, int dir, int count) -{ - if (r->beyond < r->nstep+1) { - r->beyond = r->nstep+1; - r->step = realloc(r->step, sizeof(Step)*r->beyond); - } - if (r->step == nil) - exits("pushstep out of memory"); - r->step[r->nstep].dir = dir; - r->step[r->nstep].count = count; - r->nstep++; -} - -void -popstep(Route*r) -{ - if (r->nstep > 0) { - r->nstep--; - r->step[r->nstep].dir = 0; - r->step[r->nstep].count = 0; - } -} - -int -validpush(Point g, Step s, Point *t) -{ - int i; - Point m = dir2point(s.dir); + m = dir2point(s->dir); // first test for Cargo next to us (in direction dir) - if (s.count > 0) { + if (s->count > 0) { g = addpt(g, m); if (!ptinrect(g, Rpt(Pt(0,0), level.max))) return 0; - switch (level.board[g.x ][g.y]) { + switch (level.board[g.x][g.y]) { case Wall: case Empty: case Goal: @@ -97,11 +47,11 @@ } } // then test for enough free space for us _and_ Cargo - for (i=0; i < s.count; i++) { + for (i=0; i < s->count; i++) { g = addpt(g, m); if (!ptinrect(g, Rpt(Pt(0,0), level.max))) return 0; - switch (level.board[g.x ][g.y]) { + switch (level.board[g.x][g.y]) { case Wall: case Cargo: case GoalCargo: @@ -114,117 +64,224 @@ } int -validwalk(Point g, Step s, Point *t) +isvalid(Point s, Route* r, int (*isallowed)(Point, Step*, Point*)) { - int i; - Point m = dir2point(s.dir); + Point m; + Step *p; - for (i=0; i < s.count; i++) { - g = addpt(g, m); - if (!ptinrect(g, Rpt(Pt(0,0), level.max))) - return 0; - switch (level.board[g.x ][g.y]) { - case Wall: - case Cargo: - case GoalCargo: + if (r == nil) + return 0; + + m = s; + for (p=r->step; p < r->step +r->nstep; p++) + if (! isallowed(m, p, &m)) return 0; - } - } - if (t != nil) - *t = g; return 1; } -int -isvalid(Point s, Route* r, int (*isallowed)(Point, Step, Point*)) +static Walk* +newwalk(void) { - int i; - Point m = s; + Walk *w; - for (i=0; i< r->nstep; i++) - if (! isallowed(m, r->step[i], &m)) - return 0; - return 1; + w = malloc(sizeof(Walk)); + if (w->route == nil) + sysfatal("cannot allocate walk"); + memset(w, 0, sizeof(Walk)); + return w; } -static int -trydir(int dir, Point m, Point d, Route *r, Visited *v) +static void +resetwalk(Walk *w) { - Point p = dir2point(dir); - Point n = addpt(m, p); + Route **p; - if (ptinrect(n, Rpt(Pt(0,0), level.max)) && - v->board[n.x][n.y] == 0) { - v->board[n.x][n.y] = 1; - switch (level.board[n.x ][n.y]) { - case Empty: - case Goal: - pushstep(r, dir, 1); - if (dofind(n, d, r, v)) - return 1; - else - popstep(r); - } - } - return 0; + if (w == nil || w->route == nil) + return; + + for (p=w->route; p < w->route + w->nroute; p++) + freeroute(*p); + w->nroute = 0; } -static int -dofind(Point m, Point d, Route *r, Visited *v) +static void +freewalk(Walk *w) { - if (eqpt(m, d)) - return 1; + if (w == nil) + return; - v->board[m.x][m.y] = 1; + resetwalk(w); + if(w->route) + free(w->route); + free(w); +} - return trydir(Left, m, d, r, v) || - trydir(Up, m, d, r, v) || - trydir(Right, m, d, r, v) || - trydir(Down, m, d, r, v); +static void +addroute(Walk *w, Route *r) +{ + if (w == nil || r == nil) + return; + + if (w->beyond < w->nroute+1) { + w->beyond = w->nroute+1; + w->route = realloc(w->route, sizeof(Route*)*w->beyond); + } + if (w->route == nil) + sysfatal("cannot allocate route in addroute"); + w->route[w->nroute] = r; + w->nroute++; } -static int -dobfs(Point m, Point d, Route *r, Visited *v) +void +freeroute(Route *r) { - if (eqpt(m, d)) - return 1; + if (r == nil) + return; + if (r->step != nil) + free(r->step); + free(r); +} - v->board[m.x][m.y] = 1; +Route* +extend(Route *rr, int dir, int count, Point dest) +{ + Route *r; + + r = malloc(sizeof(Route)); + if (r == nil) + sysfatal("cannot allocate route in extend"); + + memset(r, 0, sizeof(Route)); + + r->dest = dest; + + if (count > 0) { + r->nstep = 1; + if (rr != nil) + r->nstep += rr->nstep; + + r->step = malloc(sizeof(Step)*r->nstep); + if (r->step == nil) + sysfatal("cannot allocate step in extend"); - return trydir(Left, m, d, r, v) || - trydir(Up, m, d, r, v) || - trydir(Right, m, d, r, v) || - trydir(Down, m, d, r, v); + if (rr != nil) + memmove(r->step, rr->step, sizeof(Step)*rr->nstep); + + r->step[r->nstep-1].dir = dir; + r->step[r->nstep-1].count = count; + } + return r; } -int -findwalk(Point src, Point dst, Route *r) +static Step* +laststep(Route*r) { - Visited* v; - int found; + if (r != nil && r->nstep > 0) { + return &r->step[r->nstep-1]; + } + return nil; +} - v = malloc(sizeof(Visited)); - memset(v, 0, sizeof(Visited)); - found = dofind(src, dst, r, v); - free(v); +static int* +startwithdirfromroute(Route *r, int* dl, int n) +{ + Step *s; + int *p; - return found; + if (r == nil || dl == nil) + return dl; + + s = laststep(r); + if (s == nil || s->count == 0) + return dl; + + for (p=dl; p < dl + n; p++) + if (*p == s->dir) + break; + return p; } -void -applyroute(Route *r) +static Route* +bfstrydir(Route *r, int dir, Visited *v) { - int j, i; - - for (i=0; i< r->nstep; i++) { - j = r->step[i].count; - while (j > 0) { - move(r->step[i].dir); - if (animate) { - drawscreen(); - sleep(200); + Point m, p, n; + + if (r == nil) + return nil; + + m = r->dest; + p = dir2point(dir); + n = addpt(m, p); + + if (ptinrect(n, Rpt(Pt(0,0), level.max)) && + v->board[n.x][n.y] == 0) { + v->board[n.x][n.y] = 1; + switch (level.board[n.x][n.y]) { + case Empty: + case Goal: + return extend(r, dir, 1, n); + } + } + return nil; +} + +static Route* +bfs(Point src, Point dst, Visited *v) +{ + Walk *cur, *new, *tmp; + Route *r, **p; + int progress, *dirs, *dirp; + Point n; + + if (v == nil) + return nil; + + cur = newwalk(); + new = newwalk(); + v->board[src.x][src.y] = 1; + r = extend(nil, 0, 0, src); + if (eqpt(src, dst)) { + freewalk(cur); + freewalk(new); + return r; + } + addroute(cur, r); + progress = 1; + while (progress) { + progress = 0; + for (p=cur->route; p < cur->route + cur->nroute; p++) { + dirs = startwithdirfromroute(*p, dirlist, ndir); + for (dirp=dirs; dirp < dirs + ndir; dirp++) { + r = bfstrydir(*p, *dirp, v); + if (r != nil) { + progress = 1; + n = r->dest; + if (eqpt(n, dst)) { + freewalk(cur); + freewalk(new); + return(r); + } + addroute(new, r); + } } - j--; } + resetwalk(cur); + tmp = cur; + cur = new; + new = tmp; } + freewalk(cur); + freewalk(new); + return nil; +} + +Route* +findroute(Point src, Point dst) +{ + Visited v; + Route* r; + + memset(&v, 0, sizeof(Visited)); + r = bfs(src, dst, &v); + return r; } --- /sys/src/games/sokoban/sokoban.c Sun Sep 4 21:50:56 2005 +++ /sys/src/games/sokoban/sokoban.c Sun Sep 4 21:50:53 2005 @@ -18,7 +18,6 @@ char *GoalImage = "/sys/games/lib/sokoban/images/goal.bit"; char *WinImage = "/sys/games/lib/sokoban/images/win.bit"; - char *buttons[] = { "restart", @@ -29,18 +28,36 @@ 0 }; +char **levelnames; + Menu menu = { - buttons + buttons, }; Menu lmenu = { - nil, - genlevels, - 0, + levelnames, }; +void +buildmenu(void) +{ + int i; + + if (levelnames != nil) { + for(i=0; levelnames[i] != 0; i++) + free(levelnames[i]); + } + levelnames = realloc(levelnames, sizeof(char*)*(numlevels+1)); + if (levelnames == nil) + sysfatal("cannot allocate levelnames"); + for(i=0; i < numlevels; i++) + levelnames[i] = genlevels(i); + levelnames[numlevels] = 0; + lmenu.item = levelnames; +} + Image * eallocimage(Rectangle r, int repl, uint color) { @@ -119,7 +136,7 @@ mouse2route(Mouse m) { Point p, q; - Route *r, *rr; + Route *r; p = subpt(m.xy, screen->r.min); p.x /= BoardX; @@ -129,35 +146,26 @@ // fprint(2, "x=%d y=%d\n", q.x, q.y); if (q.x == 0 && q.y == 0) - return newroute(); + return nil; - r = newroute(); - if (q.x < 0) - pushstep(r, Left, -q.x); - if (q.x > 0) - pushstep(r, Right, q.x); - - if (q.y < 0) - pushstep(r, Up, -q.y); - if (q.y > 0) - pushstep(r, Down, q.y); - - if ((q.x == 0 || q.y == 0) && isvalid(level.glenda, r, validpush)) - return r; - - if (isvalid(level.glenda, r, validwalk)) - return r; - reverseroute(r); - if (isvalid(level.glenda, r, validwalk)) - return r; - freeroute(r); - - rr = newroute(); - if (findwalk(level.glenda, p, rr)) - return rr; - freeroute(rr); + if (q.x == 0 || q.y == 0) { + if (q.x < 0) + r = extend(nil, Left, -q.x, Pt(level.glenda.x, p.y)); + else if (q.x > 0) + r = extend(nil, Right, q.x, Pt(level.glenda.x, p.y)); + else if (q.y < 0) + r = extend(nil, Up, -q.y, level.glenda); + else if (q.y > 0) + r = extend(nil, Down, q.y, level.glenda); + else + r = nil; + + if (r != nil && isvalid(level.glenda, r, validpush)) + return r; + freeroute(r); + } - return newroute(); + return findroute(level.glenda, p); } char * @@ -203,8 +211,13 @@ main(int argc, char **argv) { Mouse m; - Event e; + Event ev; + int e; Route *r; + int timer; + Animation a; + int animate; + if(argc == 2) levelfile = argv[1]; @@ -215,6 +228,7 @@ fprint(2, "usage: %s [levelfile]\n", argv[0]); exits("usage"); } + buildmenu(); animate = 0; buttons[3] = animate ? "noanimate" : "animate"; @@ -223,19 +237,28 @@ sysfatal("initdraw failed: %r"); einit(Emouse|Ekeyboard); + timer = etimer(0, 200); + initanimation(&a); + allocimages(); glenda = gright; eresized(0); for(;;) { - switch(event(&e)) { + e = event(&ev); + switch(e) { case Emouse: - m = e.mouse; + m = ev.mouse; if(m.buttons&1) { + stopanimation(&a); r = mouse2route(m); - applyroute(r); - freeroute(r); - drawscreen(); + if (r) + setupanimation(&a, r); + if (! animate) { + while(onestep(&a)) + ; + drawscreen(); + } } if(m.buttons&2) { int l; @@ -243,6 +266,7 @@ lmenu.lasthit = level.index; l=emenuhit(2, &m, &lmenu); if(l>=0){ + stopanimation(&a); level = levels[l]; drawlevel(); drawscreen(); @@ -251,17 +275,22 @@ if(m.buttons&4) switch(emenuhit(3, &m, &menu)) { case 0: + stopanimation(&a); level = levels[level.index]; drawlevel(); drawscreen(); break; case 1: + stopanimation(&a); loadlevels(LEasy); + buildmenu(); drawlevel(); drawscreen(); break; case 2: + stopanimation(&a); loadlevels(LHard); + buildmenu(); drawlevel(); drawscreen(); break; @@ -278,7 +307,9 @@ if(level.done) break; - switch(e.kbdc) { + stopanimation(&a); + + switch(ev.kbdc) { case 127: case 'q': case 'Q': @@ -310,12 +341,23 @@ case 61457: case 61458: case ' ': - move(key2move(e.kbdc)); + move(key2move(ev.kbdc)); drawscreen(); break; default: // fprint(2, "key: %d]\n", e.kbdc); break; + } + break; + + default: + if (e == timer) { + if (animate) + onestep(&a); + else + while(onestep(&a)) + ; + drawscreen(); } break; } --- /sys/src/games/sokoban/sokoban.h Sun Sep 4 21:51:13 2005 +++ /sys/src/games/sokoban/sokoban.h Sun Sep 4 21:51:10 2005 @@ -32,21 +32,33 @@ Maxlevels = 200, }; -typedef struct { +typedef struct Step { uint dir; /* direction */ uint count; /* number of single-step moves */ } Step; -typedef struct { - uint nstep; /* number of valid steps */ +typedef struct Route { + uint nstep; /* number of valid Step */ Step *step; - uint beyond; /* number of allocated Step */ + Point dest; /* result of step */ } Route; -typedef struct { +typedef struct Walk { + uint nroute; /* number of valid Route* */ + Route **route; + uint beyond; /* number of allocated Route* */ +} Walk; + +typedef struct Visited { uint board[MazeX][MazeY]; } Visited; +typedef struct Animation { + Route* route; + Step *step; + int count; +} Animation; + typedef struct { Point glenda; Point max; /* that's how much the board spans */ @@ -58,7 +70,6 @@ Level level; /* the current level */ Level levels[Maxlevels]; /* all levels from this file */ int numlevels; /* how many levels do we have */ -int animate; /* boolean: animate during multi-step move? */ Image *img; /* buffer */ Image *text; /* for text messages */ @@ -90,16 +101,18 @@ void move(int); /* route.c */ -Route* newroute(void); +int validpush(Point, Step*, Point*); +int isvalid(Point, Route*, int (*)(Point, Step*, Point*)); void freeroute(Route*); -void reverseroute(Route*); -void pushstep(Route*, int, int); -void popstep(Route*); -int validwalk(Point, Step, Point*); -int validpush(Point, Step, Point*); -int isvalid(Point, Route*, int (*)(Point, Step, Point*)); -int findwalk(Point, Point, Route*); -void applyroute(Route*); +Route* extend(Route*, int, int, Point); +Route* findroute(Point, Point); + +/* animation.c */ +void initanimation(Animation*); +void setupanimation(Animation*, Route*); +int onestep(Animation*); +void stopanimation(Animation*); + /* sokoban.c */ char *genlevels(int); --- /sys/src/games/sokoban/README Sun Sep 4 21:51:32 2005 +++ /sys/src/games/sokoban/README Sun Sep 4 21:51:30 2005 @@ -17,6 +17,6 @@ Otherwise, nothing will happen. -The search algorithm is pretty simplistic; +The breadth-first search algorithm should find a fast route. it can be seen in action by toggling the 'animate' entry in the button 3 menu.