A multi-step-move change to the original Plan 9 sokoban. The new behaviour is only visible in the Button 3 menu (new entry [no]animate), and when you click button 1 on a square not adjacent to glenda - otherwise the behaviour should be as before. This is essentially the same change I already reported to 9fans, only the code has been cleaned up slightly. Axel. Reference: /n/sources/patch/applied/sokoban-multistep Date: Tue Dec 7 18:04:51 CET 2004 --- /sys/src/games/sokoban/README Thu Jan 1 00:00:00 1970 +++ /sys/src/games/sokoban/README Tue Dec 7 18:04:51 2004 @@ -0,0 +1,22 @@ +If you click with mouse button 1 +on a (destination) square then the following may happen. + +If + - the destination square is on the same row or column as glenda, and + - there is a ball next to glenda + (in the direction of the destination square), and + - all squares between the ball and the destination square + (where glenda will move) are empty, and + - the square next to the destination square where glenda will + push the ball is empty too, +then glenda will move to the destination square, pushing the +ball while moving. + +Otherwise, if glenda can go to the destination square +without touching anything, it will do so. + +Otherwise, nothing will happen. + +The search algorithm is pretty simplistic; +it can be seen in action by toggling the 'animate' +entry in the button 3 menu. --- /sys/src/games/sokoban/mkfile Tue Dec 7 18:04:51 2004 +++ /sys/src/games/sokoban/mkfile Tue Dec 7 18:04:51 2004 @@ -7,6 +7,7 @@ move.$O\ graphics.$O\ level.$O\ + route.$O\ HFILES=sokoban.h\ --- /sys/src/games/sokoban/route.c Thu Jan 1 00:00:00 1970 +++ /sys/src/games/sokoban/route.c Tue Dec 7 18:04:51 2004 @@ -0,0 +1,230 @@ +#include +#include +#include + +#include "sokoban.h" + +static int trydir(int, Point, Point, Route*, Visited*); +static int dofind(Point, Point, Route*, Visited*); + +static Point +dir2point(int dir) +{ + switch(dir) { + case Up: + return Pt(0, -1); + case Down: + return Pt(0, 1); + case Left: + return Pt(-1, 0); + case Right: + return Pt(1, 0); + } + 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 i; + Step tmp; + + for (i=1; i< r->nstep; i++) { + tmp = r->step[i]; + r->step[i] = r->step[i-1]; + r->step[i-1] = tmp; + } +} + +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); + + // first test for Cargo next to us (in direction dir) + 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]) { + case Wall: + case Empty: + case Goal: + return 0; + } + } + // then test for enough free space for us _and_ Cargo + 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: + return 0; + } + } + if (t != nil) + *t = g; + return 1; +} + +int +validwalk(Point g, Step s, Point *t) +{ + int i; + Point m = dir2point(s.dir); + + 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: + return 0; + } + } + if (t != nil) + *t = g; + return 1; +} + +int +isvalid(Point s, Route* r, int (*isallowed)(Point, Step, Point*)) +{ + int i; + Point m = s; + + for (i=0; i< r->nstep; i++) + if (! isallowed(m, r->step[i], &m)) + return 0; + return 1; +} + +static int +trydir(int dir, Point m, Point d, Route *r, Visited *v) +{ + Point p = dir2point(dir); + Point 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: + pushstep(r, dir, 1); + if (dofind(n, d, r, v)) + return 1; + else + popstep(r); + } + } + return 0; +} + +static int +dofind(Point m, Point d, Route *r, Visited *v) +{ + if (eqpt(m, d)) + return 1; + + v->board[m.x][m.y] = 1; + + 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 int +dobfs(Point m, Point d, Route *r, Visited *v) +{ + if (eqpt(m, d)) + return 1; + + v->board[m.x][m.y] = 1; + + 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); +} + +int +findwalk(Point src, Point dst, Route *r) +{ + Visited* v; + int found; + + v = malloc(sizeof(Visited)); + memset(v, 0, sizeof(Visited)); + found = dofind(src, dst, r, v); + free(v); + + return found; +} + +void +applyroute(Route *r) +{ + 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); + } + j--; + } + } +} --- /sys/src/games/sokoban/sokoban.c Tue Dec 7 18:04:51 2004 +++ /sys/src/games/sokoban/sokoban.c Tue Dec 7 18:04:51 2004 @@ -24,6 +24,7 @@ "restart", "easy", "hard", + "noanimate", /* this menu string initialized in main */ "exit", 0 }; @@ -113,23 +114,50 @@ return k; } -int -mousemove(Mouse m) + +static Route* +mouse2route(Mouse m) { - Point p; + Point p, q; + Route *r, *rr; p = subpt(m.xy, screen->r.min); p.x /= BoardX; p.y /= BoardY; - if(eqpt(p, addpt(level.glenda, Pt(0, -1)))) - return Up; - if(eqpt(p, addpt(level.glenda, Pt(0, 1)))) - return Down; - if(eqpt(p, addpt(level.glenda, Pt(-1, 0)))) - return Left; - if(eqpt(p, addpt(level.glenda, Pt(1, 0)))) - return Right; + q = subpt(p, level.glenda); + // fprint(2, "x=%d y=%d\n", q.x, q.y); + + if (q.x == 0 && q.y == 0) + return newroute(); + + 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); + + return newroute(); } char * @@ -176,6 +204,7 @@ { Mouse m; Event e; + Route *r; if(argc == 2) levelfile = argv[1]; @@ -187,6 +216,9 @@ exits("usage"); } + animate = 0; + buttons[3] = animate ? "noanimate" : "animate"; + if(initdraw(nil, nil, "sokoban") < 0) sysfatal("initdraw failed: %r"); einit(Emouse|Ekeyboard); @@ -200,7 +232,9 @@ case Emouse: m = e.mouse; if(m.buttons&1) { - move(mousemove(m)); + r = mouse2route(m); + applyroute(r); + freeroute(r); drawscreen(); } if(m.buttons&2) { @@ -232,6 +266,10 @@ drawscreen(); break; case 3: + animate = !animate; + buttons[3] = animate ? "noanimate" : "animate"; + break; + case 4: exits(nil); } break; @@ -274,6 +312,9 @@ case ' ': move(key2move(e.kbdc)); drawscreen(); + break; + default: + // fprint(2, "key: %d]\n", e.kbdc); break; } break; --- /sys/src/games/sokoban/sokoban.h Tue Dec 7 18:04:51 2004 +++ /sys/src/games/sokoban/sokoban.h Tue Dec 7 18:04:51 2004 @@ -33,6 +33,21 @@ }; typedef struct { + uint dir; /* direction */ + uint count; /* number of single-step moves */ +} Step; + +typedef struct { + uint nstep; /* number of valid steps */ + Step *step; + uint beyond; /* number of allocated Step */ +} Route; + +typedef struct { + uint board[MazeX][MazeY]; +} Visited; + +typedef struct { Point glenda; Point max; /* that's how much the board spans */ uint index; @@ -43,6 +58,7 @@ 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 */ @@ -67,12 +83,23 @@ void resize(Point); Point boardsize(Point); - /* level.c */ int loadlevels(char *); /* move.c */ void move(int); + +/* route.c */ +Route* newroute(void); +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*); /* sokoban.c */ char *genlevels(int);