Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

#include <u.h>
#include <libc.h>
#include <draw.h>

#include "sokoban.h"

static int dirlist[] = { Up, Down, Left, Right, Up, Down, Left, Right, };
static int ndir = 4;

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);
}

int
validpush(Point g, Step *s, Point *t)
{
        int i;
        Point m;

        if (s == nil)
                return 0;

        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
isvalid(Point s, Route* r, int (*isallowed)(Point, Step*, Point*))
{
        Point m;
        Step *p;

        if (r == nil)
                return 0;

        m = s;
        for (p = r->step; p < r->step + r->nstep; p++)
                if (!isallowed(m, p, &m))
                        return 0;
        return 1;
}

static Walk*
newwalk(void)
{
        Walk *w;

        w = mallocz(sizeof *w, 1);
        if (w == nil)
                sysfatal("cannot allocate walk");
        return w;
}

static void
resetwalk(Walk *w)
{
        Route **p;

        if (w == nil || w->route == nil)
                return;

        for (p=w->route; p < w->route + w->nroute; p++)
                freeroute(*p);
        w->nroute = 0;
}

static void
freewalk(Walk *w)
{
        if (w == nil)
                return;

        resetwalk(w);
        if(w->route)
                free(w->route);
        free(w);
}

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++;
}

void
freeroute(Route *r)
{
        if (r == nil)
                return;
        free(r->step);
        free(r);
}

Route*
extend(Route *rr, int dir, int count, Point dest)
{
        Route *r;

        r = mallocz(sizeof *r, 1);
        if (r == nil)
                sysfatal("cannot allocate route in extend");
        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;
}

static Step*
laststep(Route*r)
{
        if (r != nil && r->nstep > 0)
                return &r->step[r->nstep-1];
        return nil;
}

static int*
startwithdirfromroute(Route *r, int* dl, int n)
{
        Step *s;
        int *p;
        
        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;
}

static Route*
bfstrydir(Route *r, int dir, Visited *v)
{
        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);
                                }
                        }
                }
                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;
}