Use breadth-first search to find shortest move sequence.
[rsc] --rw-rw-r-- M 209623 rsc sys 799 Sep 5 07:25 sys/src/games/sokoban/README
/n/sourcesdump/2005/0905/plan9/sys/src/games/sokoban/README:17,22 -
/n/sources/plan9/sys/src/games/sokoban/README:17,22
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.
[rsc] --rw-rw-r-- M 209623 rsc sys 973 Sep 5 07:25 sys/src/games/sokoban/animation.c
[rsc] --rw-rw-r-- M 209623 rsc sys 4486 Sep 5 07:25 sys/src/games/sokoban/route.c
/n/sourcesdump/2005/0905/plan9/sys/src/games/sokoban/route.c:4,11 -
/n/sources/plan9/sys/src/games/sokoban/route.c:4,11
#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)
/n/sourcesdump/2005/0905/plan9/sys/src/games/sokoban/route.c:23,95 -
/n/sources/plan9/sys/src/games/sokoban/route.c:23,45
return Pt(0,0);
}
- Route*
- newroute(void)
+ int
+ validpush(Point g, Step *s, Point *t)
{
- 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 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++;
- }
+ m = dir2point(s->dir);
- 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);
-
// 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:
/n/sourcesdump/2005/0905/plan9/sys/src/games/sokoban/route.c:97,107 -
/n/sources/plan9/sys/src/games/sokoban/route.c:47,57
}
}
// 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:
/n/sourcesdump/2005/0905/plan9/sys/src/games/sokoban/route.c:114,230 -
/n/sources/plan9/sys/src/games/sokoban/route.c:64,287
}
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)))
+ if (r == nil)
+ return 0;
+
+ m = s;
+ for (p=r->step; p < r->step +r->nstep; p++)
+ if (! isallowed(m, p, &m))
return 0;
- switch (level.board[g.x ][g.y]) {
- case Wall:
- case Cargo:
- case GoalCargo:
- 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;
- 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);
+ 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");
+
+ 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;
}
[rsc] --rw-rw-r-- M 209623 jmk sys 6358 Sep 5 07:25 sys/src/games/sokoban/sokoban.c
/n/sourcesdump/2005/0905/plan9/sys/src/games/sokoban/sokoban.c:18,24 -
/n/sources/plan9/sys/src/games/sokoban/sokoban.c:18,23
char *GoalImage = "/sys/games/lib/sokoban/images/goal.bit";
char *WinImage = "/sys/games/lib/sokoban/images/win.bit";
-
char *buttons[] =
{
"restart",
/n/sourcesdump/2005/0905/plan9/sys/src/games/sokoban/sokoban.c:29,46 -
/n/sources/plan9/sys/src/games/sokoban/sokoban.c:28,63
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)
{
/n/sourcesdump/2005/0905/plan9/sys/src/games/sokoban/sokoban.c:119,125 -
/n/sources/plan9/sys/src/games/sokoban/sokoban.c:136,142
mouse2route(Mouse m)
{
Point p, q;
- Route *r, *rr;
+ Route *r;
p = subpt(m.xy, screen->r.min);
p.x /= BoardX;
/n/sourcesdump/2005/0905/plan9/sys/src/games/sokoban/sokoban.c:129,163 -
/n/sources/plan9/sys/src/games/sokoban/sokoban.c:146,171
// 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.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 (q.y < 0)
- pushstep(r, Up, -q.y);
- if (q.y > 0)
- pushstep(r, Down, q.y);
+ if (r != nil && isvalid(level.glenda, r, validpush))
+ return r;
+ freeroute(r);
+ }
- 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);
-
- return newroute();
+ return findroute(level.glenda, p);
}
char *
/n/sourcesdump/2005/0905/plan9/sys/src/games/sokoban/sokoban.c:203,211 -
/n/sources/plan9/sys/src/games/sokoban/sokoban.c:211,224
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];
else
/n/sourcesdump/2005/0905/plan9/sys/src/games/sokoban/sokoban.c:215,220 -
/n/sources/plan9/sys/src/games/sokoban/sokoban.c:228,234
fprint(2, "usage: %s [levelfile]\n", argv[0]);
exits("usage");
}
+ buildmenu();
animate = 0;
buttons[3] = animate ? "noanimate" : "animate";
/n/sourcesdump/2005/0905/plan9/sys/src/games/sokoban/sokoban.c:223,241 -
/n/sources/plan9/sys/src/games/sokoban/sokoban.c:237,264
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;
/n/sourcesdump/2005/0905/plan9/sys/src/games/sokoban/sokoban.c:243,248 -
/n/sources/plan9/sys/src/games/sokoban/sokoban.c:266,272
lmenu.lasthit = level.index;
l=emenuhit(2, &m, &lmenu);
if(l>=0){
+ stopanimation(&a);
level = levels[l];
drawlevel();
drawscreen();
/n/sourcesdump/2005/0905/plan9/sys/src/games/sokoban/sokoban.c:251,267 -
/n/sources/plan9/sys/src/games/sokoban/sokoban.c:275,296
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;
/n/sourcesdump/2005/0905/plan9/sys/src/games/sokoban/sokoban.c:278,284 -
/n/sources/plan9/sys/src/games/sokoban/sokoban.c:307,315
if(level.done)
break;
- switch(e.kbdc) {
+ stopanimation(&a);
+
+ switch(ev.kbdc) {
case 127:
case 'q':
case 'Q':
/n/sourcesdump/2005/0905/plan9/sys/src/games/sokoban/sokoban.c:310,321 -
/n/sources/plan9/sys/src/games/sokoban/sokoban.c:341,363
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;
}
[rsc] --rw-rw-r-- M 209623 jmk sys 2062 Sep 5 07:25 sys/src/games/sokoban/sokoban.h
/n/sourcesdump/2005/0905/plan9/sys/src/games/sokoban/sokoban.h:32,52 -
/n/sources/plan9/sys/src/games/sokoban/sokoban.h:32,64
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 */
/n/sourcesdump/2005/0905/plan9/sys/src/games/sokoban/sokoban.h:58,64 -
/n/sources/plan9/sys/src/games/sokoban/sokoban.h:70,75
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 */
/n/sourcesdump/2005/0905/plan9/sys/src/games/sokoban/sokoban.h:90,105 -
/n/sources/plan9/sys/src/games/sokoban/sokoban.h:101,118
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);
[jmk] --rw-rw-r-- M 209623 jmk sys 261 Sep 5 23:11 sys/src/games/sokoban/mkfile
/n/sourcesdump/2005/0905/plan9/sys/src/games/sokoban/mkfile:3,14 -
/n/sourcesdump/2005/0906/plan9/sys/src/games/sokoban/mkfile:3,14
TARG=sokoban
OFILES=\
- sokoban.$O\
- move.$O\
+ animation.$O\
graphics.$O\
level.$O\
+ move.$O\
+ sokoban.$O\
route.$O\
-
HFILES=sokoban.h\
|