#include #include #include #include #include #include #include #include #include <9p.h> #include "trafficfs.h" /* * we generate a three-level directory structure: * root/ * trafficfs (our own root) * ctl (toc of generated files; generate files) * toc (table of contents of nr files) * 21/ (group of levels with same minimal solution move count) * 0 (first level in this group) * 1 (second level in this group) * ... * 22/ * 0 (first level in this group) * 1 (second level in this group) * ... * ... * foo (generated file) * bar (generated file) */ typedef struct Aux Aux; struct Aux { int doff; int gnr; int gidx; int lnr; }; enum { Qroot = 0, Qdir, Qctl, Qtoc, Qgen, Qgrp, Qlvl, }; #define PATH(qid,grp,lvl) (((lvl)<<16)|(((grp)&0xFF)<<8)|((qid)&0xFF)) #define QDIR(qid) ((int)((qid)&0xFF)) #define GRP(qid) (((qid)>>8) &0xFF) #define LVL(qid) ((qid)>>16) int debug; ulong starttime; /* representation of levels */ Directory *directory; /* stuff used to on-the-fly generate the combined level files */ int *choicelist; /* array from which we choose problems */ int maxchoicelist; /* max nr of entries in choicelist (without subtracting seen levels) */ Seen *dirseen; /* keeps track of levels that user has seen */ Generated *generated; /* on-the-fly generated files of levels */ int ngenerated; /* number of items in generated */ void printlevel(char *s, Level *l); void chooseproblems(Generated *g); void* emalloc(ulong sz) { void *v; v = malloc(sz); if(v == nil) { abort(); sysfatal("malloc %lud fails\n", sz); } setmalloctag(v, getcallerpc(&sz)); memset(v, 0, sz); return v; } void* erealloc(void *p, ulong sz) { void *v; v = realloc(p, sz); if(v == nil) sysfatal("realloc %lud fails\n", sz); setrealloctag(v, getcallerpc(&p)); return v; } static Point lin2grouplvl(int n) { Group *gp; Level *lp; lp = directory->level + n; for (gp=directory->group; gpgroup+directory->ngroup; gp++) { if (lp >= gp->base && lp < gp->beyond) return Pt(gp->moves, lp-gp->base); } return Pt(0,0); } int index(int n) { if (n < directory->base) return -1; if (n > directory->beyond) return -1; return n - directory->base; } int isseen(uchar *seen, int n) { int i, r; i = n / 8; r = n % 8; return (seen[i] & 1< %d (%d), %d\n", n, p.x, i, p.y); if (!isseen(s->seen, n)) { setseen(s->seen, n); s->group[i].nseen++; } } void resetseen(Seen *s, int base, int beyond) { int baselvl, beyondlvl, count, nextlvl, i; baselvl = directory->group[base - directory->base].base - directory->level; beyondlvl = directory->group[beyond - directory->base - 1].beyond - directory->level; if (debug) fprint(2, "resetseen base %d beyond %d baselvl %d beyondlvl %d\n", base, beyond, baselvl, beyondlvl); /* first bits in byte containing also bits for previous level */ for(i=baselvl; iseen, i); if (debug) fprint(2, "resetseen %d beyondlvl=%d\n", i, beyondlvl); } nextlvl = i; count = beyondlvl - nextlvl; /* complete bytes containing only bits for this level */ if (count/8 > 0) { if (debug) fprint(2, "resetseen off %d count %d\n", nextlvl/8, count/8); memset(s->seen+(nextlvl/8), 0, count/8); nextlvl += (count/8)*8; } /* last bits in byte containing also bits for next level */ for(i=nextlvl; iseen, i); if (debug) fprint(2, "resetseen %d nextlvl=%d beyondlvl=%d\n", i, nextlvl, beyondlvl); } for (i=base - directory->base; i < beyond - directory->base; i++) s->group[i].nseen = 0; } static void fsattach(Req *r) { r->fid->qid = (Qid){Qroot, 0, QTDIR}; r->ofcall.qid = r->fid->qid; r->fid->aux = emalloc(sizeof(Aux)); respond(r, nil); } static char* fsclone(Fid *old, Fid *new) { Aux *na; na = emalloc(sizeof(Aux)); *na = *((Aux*)old->aux); new->aux = na; return nil; } static char* fswalk1(Fid *fid, char *name, Qid *qid) { int i, n; char *p; Aux *a; a = fid->aux; switch(QDIR(fid->qid.path)) { case Qroot: if(strcmp(name, "..") == 0) { *qid = (Qid){Qroot, 0, QTDIR}; return nil; } if(strcmp(name, "trafficfs") == 0) { *qid = (Qid){Qdir, 0, QTDIR}; return nil; } return "file not found"; case Qdir: if(strcmp(name, "..") == 0) { *qid = (Qid){Qroot, 0, QTDIR}; return nil; } if(strcmp(name, "toc") == 0) { *qid = (Qid){Qtoc, 0, QTFILE}; return nil; } if(strcmp(name, "ctl") == 0) { *qid = (Qid){Qctl, 0, QTFILE}; return nil; } /* group directories */ n = strtoul(name, &p, 0); if('0'<=name[0] && name[0]<='9' && *p=='\0'){ for(i=0; ingroup; i++) if(directory->group[i].moves == n) { *qid = (Qid){PATH(Qgrp,i,0), 0, QTDIR}; a->gnr = n; a->gidx = i; return nil; } } /* generated files */ for (i=0; i < ngenerated; i++) { if(strcmp(generated[i].name, name) ==0) { *qid = (Qid){PATH(Qgen,i,0), 0, QTFILE}; return nil; } } return "file not found"; case Qgrp: if(strcmp(name, "..") == 0) { *qid = (Qid){Qdir, 0, QTDIR}; return nil; } /* level files */ n = strtoul(name, &p, 0); if('0'<=name[0] && name[0]<='9' && *p=='\0'){ if (n >= 0 && n < directory->group[a->gidx].count) { *qid = (Qid){PATH(Qlvl,a->gidx,n), 0, QTFILE}; a->lnr = n; return nil; } } return "file not found"; default: /* bug: lib9p could handle this */ return "walk in non-directory"; } } static void fsopen(Req *r) { int omode; omode = r->ifcall.mode; if(omode == OREAD) respond(r, nil); else if ((omode&~OTRUNC) == OWRITE && QDIR(r->fid->qid.path) == Qctl) respond(r, nil); else respond(r, "permission denied"); } int fillstat(ulong qid, Dir *d) { char buf[32]; memset(d, 0, sizeof(Dir)); d->uid = estrdup9p("rushhour"); d->gid = estrdup9p("rushhour"); d->muid = estrdup9p(""); d->qid = (Qid){qid, 0, 0}; d->atime = d->mtime = starttime; switch(QDIR(qid)){ case Qroot: d->name = estrdup9p("/"); d->qid.type = QTDIR; d->mode = DMDIR|0555; break; case Qdir: d->name = estrdup9p("trafficfs"); d->qid.type = QTDIR; d->mode = DMDIR|0555; break; case Qctl: d->name = estrdup9p("ctl"); d->qid.type = QTFILE; d->mode = 0666; break; case Qtoc: d->name = estrdup9p("toc"); d->qid.type = QTFILE; d->mode = 0444; break; case Qgen: d->name = estrdup9p(generated[GRP(qid)].name); d->qid.type = QTFILE; d->mode = 0444; d->mtime = generated[GRP(qid)].mtime; d->length = generated[GRP(qid)].ndata; break; case Qgrp: sprint(buf, "%d", directory->group[GRP(qid)].moves); d->name = estrdup9p(buf); d->qid.type = QTDIR; d->mode = DMDIR|0555; break; case Qlvl: sprint(buf, "%uld", LVL(qid)); d->name = estrdup9p(buf); d->qid.type = QTFILE; d->mode = 0444; break; default: /* should not happen */ return 0; break; } return 1; } static void fsstat(Req *r) { fillstat((ulong)r->fid->qid.path, &r->d); respond(r, nil); } static int genroot(int n, Dir *dir, void *aux) { USED(aux); if (n==0) return fillstat(PATH(Qdir,0, 0), dir); else return -1; } static int gendir(int n, Dir *dir, void *aux) { USED(aux); if (n==0) return fillstat(PATH(Qctl,0, 0), dir); else if (n == 1) return fillstat(PATH(Qtoc,0, 0), dir); else if (n < ngenerated+1+1) return fillstat(PATH(Qgen,n-1-1, 0), dir); else if (n < 1+1+ngenerated+directory->ngroup) return fillstat(PATH(Qgrp,n-1-1-ngenerated, 0), dir); else return -1; } static int gengrp(int n, Dir *dir, void *aux) { Aux *a; a = aux; if (n < directory->group[a->gidx].count) return fillstat(PATH(Qlvl,a->gidx,n), dir); else return -1; } static void fsread(Req *r) { int j, m, cum; Fid *fid; vlong offset; char s[1024]; Aux *a; Generated *g; Level *lp; fid = r->fid; offset = r->ifcall.offset; a = fid->aux; switch(QDIR(fid->qid.path)) { case Qroot: dirread9p(r, genroot, a); respond(r, nil); break; case Qdir: dirread9p(r, gendir, a); respond(r, nil); break; case Qgrp: dirread9p(r, gengrp, a); respond(r, nil); break; case Qlvl: /* XXX make sure that s is big enough */ sprint(s, "; moves %d level %d\n", a->gnr, a->lnr); sprint(s+strlen(s), ":%d/%d\n", a->gnr, a->lnr); lp = directory->group[a->gidx].base + a->lnr; printlevel(s, lp); readstr(r, s); respond(r, nil); break; case Qctl: /* XXX make sure that s is big enough */ sprint(s,""); for(g=&generated[0]; g < &generated[ngenerated]; g++) sprint(s+strlen(s), "%s %d of %d (%d-%d) in [%d,%d) (requested %d in [%d,%d)) \n", g->name, g->count, g->avail, g->total, g->seen, g->min, g->max, g->req.count, g->req.min, g->req.max); s[strlen(s)] = '\0'; readstr(r, s); respond(r, nil); break; case Qtoc: m = directory->ngroup; sprint(s,""); cum=0; for(j=0; jgroup[j].count; sprint(s+strlen(s), "%d %d %d %d\n", directory->group[j].moves, directory->group[j].count, cum, dirseen->group[j].nseen); } s[strlen(s)] = '\0'; readstr(r, s); respond(r, nil); break; case Qgen: g = &generated[GRP(fid->qid.path)]; readbuf(r, g->data, g->ndata); respond(r, nil); break; default: /* should not happen */ break; } } static int parsectl(Req *r, char *data) { int ok, i, j, n, w; char *lines[25]; char *words[25]; int min, max, count; char *name, *p; Generated *gp; GrpSeen *sgp; ok = 1; n = gettokens(data, lines, 25, "\r\n"); for (j=0; jname, name) == 0) break; } if (gp == generated+ngenerated) { generated = erealloc(generated, sizeof(Generated)*(ngenerated+1)); ngenerated += 1; gp = generated+ngenerated-1; gp->name = estrdup9p(name); gp->data = nil; gp->ndata = 0; } gp->req.min = min; gp->req.max = max; gp->req.count = count; chooseproblems(gp); } else if (strcmp(words[0], "reset") == 0 && w == 2) { name = words[1]; for(gp=generated; gpname, name) == 0) { resetseen(dirseen, gp->min, gp->max); break; } } if (gp == generated+ngenerated) { respond(r, "no such name"); return 0; } } else if (strcmp(words[0], "reset") == 0 && w == 1) { memset(dirseen->seen, 0, (directory->nlevel/8)+1); for(sgp=dirseen->group; sgpgroup+dirseen->ngroup; sgp++) sgp->nseen = 0; } else { ok = 0; for(i=0; ifid; count = r->ifcall.count; offset = r->ifcall.offset; switch(QDIR(fid->qid.path)) { case Qctl: /* if (offset == 0) { */ if (ndata < count+1){ data = erealloc(data, count+1); ndata = count+1; } memcpy(data, r->ifcall.data, count); data[count] = '\0'; if (!parsectl(r, data)) return; /* } */ r->ofcall.count = count; respond(r, nil); break; default: respond(r, "permission denied"); break; } } static void fsdestroyfid(Fid *fid) { Aux *a; a = fid->aux; if(a == nil) return; free(a); } Srv fs = { .attach= fsattach, .destroyfid= fsdestroyfid, .clone= fsclone, .walk1= fswalk1, .open= fsopen, .read= fsread, .write= fswrite, .stat= fsstat, }; int levelscount(int min, int max) { int base, beyond, res; if (min == max) return 0; if (min < directory->base) min = directory->base; if (min > directory->beyond) min = directory->beyond; if (max < directory->base) max = directory->base; if (max > directory->beyond) max = directory->beyond; if (min == max) return 0; base = min - directory->base; beyond = max - directory->base; if (beyond < 1) return 0; res = directory->group[beyond-1].beyond - directory->group[base].base; // fprint(2, "levelscount min %d max %d base %d beyond %d res %d\n", min, max, base, beyond, res); return res; } int buildchoicelist(int cum, int min, int max, Generated *g, Seen*s) { int i, j, lvl, off; int base, beyond; maxchoicelist = cum; free(choicelist); choicelist = emalloc(sizeof(int)*maxchoicelist); base = min - directory->base; beyond = max - directory->base; USED(beyond); off = directory->group[base].base - directory->level; lvl = off; j = 0; for(i=0; iseen, lvl)) { choicelist[j] = lvl; j++; } else if (debug) fprint(2, "buildchoicelist(%d, %d, %d) seen %d\n", cum, min, max, lvl); lvl++; } g->avail = j; g->total = lvl - off; g->seen = g->total - g->avail ; return j; } int intcmp(void *pa, void *pb) { int *a, *b; a = pa; b = pb; return (*a - *b); } /* * we generate g->count random numbers for * the levels in [g->min, g->max) * we may generated duplicate random numbers. * we just throw out duplicates, but thus may * end up with < g->count numbers */ void chooseproblems(Generated *g) { int i, *levels, n, cum, prev, v, r, k; Point p; char *lbuf, *lp; uchar *seen; Level *lvlp; g->mtime =time(0); g->min = g->req.min; g->max = g->req.max; if (g->min < directory->base) g->min = directory->base; if (g->max > directory->beyond) g->max = directory->beyond; if (g->min > g->max) g->min = g->max; cum = levelscount(g->min, g->max); g->avail = buildchoicelist(cum, g->min, g->max, g, dirseen); if (g->avail > g->req.count) n = g->req.count; else n = g->avail; levels = emalloc(sizeof(int) * n); lbuf = emalloc(sizeof(char)*n*150); /* be sure 150 is enough */ if (debug) fprint(2, "levelscount %d %d -> %d ; n = %d\n", g->min, g->max, g->avail, n); if (g->avail > g->req.count) { seen = emalloc((g->avail/8)+1); for(i=0; iavail); v = choicelist[r]; k = 0; while (isseen(seen, r) && k < g->avail){ r = nrand(g->avail); v = choicelist[r]; k++; } // if (k > 0) fprint(2, "rand i=%d r=%d v=%d k=%d\n", i, r, v,k); if (debug) fprint(2, "rand i=%d r=%d v=%d k=%d\n", i, r, v,k); setseen(seen, r); levels[i] = v; dirsetseen(dirseen, v); } free(seen); qsort(levels, n, sizeof(int), intcmp); } else { for(i=0; icount = 0; for(i=0; icount ++; p = lin2grouplvl(levels[i]); sprint(lp, "; moves %d level %d\n", p.x, p.y); sprint(lp+strlen(lp), ":%d/%d\n", p.x, p.y); lvlp = directory->level + levels[i]; printlevel(lp, lvlp); lp += strlen(lp); } free(levels); free(g->data); g->data = lbuf; g->ndata = lp - lbuf; } Seen* seeninit(Directory *d) { Seen* s; s = emalloc(sizeof(Seen)); s->ngroup = d->ngroup; s->group = emalloc(sizeof(GrpSeen)*s->ngroup); s->seen = emalloc((d->nlevel/8)+1); return s; } int readint(Biobuf *bp) { int i, n; uchar b; n = 0; for (i=0; i < 4; i++) { Bread(bp, &b, 1); n+= (b << (i*8)); } return n; } Directory* readlevels(Biobuf *bp) { Directory *d; Group *gp; Level *lp; int lvl, *off, *op, noff; char ignored[10]; d = emalloc(sizeof(Directory)); d->base = readint(bp) - 1; /* should be 21-1 */ d->beyond = readint(bp); /* should be 52 */ d->ngroup = d->beyond - d->base; d->group = emalloc(sizeof(Group)*d->ngroup); noff = d->ngroup + 1; /* +1 for offset to end of file */ off = emalloc(sizeof(int)*noff); lvl = d->base; op = off; for (gp=d->group; gpgroup+d->ngroup; gp++) { gp->moves = lvl++; *(op++) = readint(bp); } *op = readint(bp); /* one more time for offset to end of file */ d->nlevel = (off[d->ngroup] - off[0])/8; /* 8 bytes per level */ d->level = emalloc(sizeof(Level)*d->nlevel); op = off; lp = d->level; for (gp=d->group; gpgroup+d->ngroup; gp++) { if (debug) fprint(2, "Group %d\n", gp->moves); gp->base = lp; gp->count = (*(op+1) - *op)/8; gp->beyond = lp + gp->count; for (; lpbeyond; lp++) { Bread(bp, lp->row, 3); Bread(bp, ignored, 1); Bread(bp, lp->col, 3); Bread(bp, ignored, 1); } op++; } free(off); return d; } void setboard(char **board, Point p, int l, int orient, int v) { int i; Point dir[] = { [OCol] Pt(1,0), [ORow] Pt(0,1), }; if (orient == OCol) p = Pt(p.y,p.x); for (i=0; irow[0] & 0x0F; bl[1] = (l->row[0] >> 4) & 0x0F; bl[2] = l->row[1] & 0x0F; bl[3] = (l->row[1] >> 4) & 0x0F; bl[4] = l->row[2] & 0x0F; bl[5] = (l->row[2] >> 4) & 0x0F; for (i=0; i< 6; i++) if (i == 2) decstrip(board, i, bl[i], ORow, &us, &truck); else decstrip(board, i, bl[i], ORow, &car, &truck); bl[0] = l->col[0] & 0x0F; bl[1] = (l->col[0] >> 4) & 0x0F; bl[2] = l->col[1] & 0x0F; bl[3] = (l->col[1] >> 4) & 0x0F; bl[4] = l->col[2] & 0x0F; bl[5] = (l->col[2] >> 4) & 0x0F; for (i=0; i< 6; i++) decstrip(board, i, bl[i], OCol, &car, &truck); for (i=0; i<8; i++) sprint(s+strlen(s), "%s\n", board[i]); sprint(s+strlen(s), "\n"); for(i=0; i<8; i++) free(board[i]); } void usage(void) { fprint(2, "Usage: %s [-m mntpnt] [-s srv] [levelfile]\n", argv0); exits("usage"); } void main(int argc, char**argv) { Biobuf *bp; char *mtpt, *srvpost, *srvname, *srvfile, *levelfile; mtpt = "/mnt"; srvpost = nil; srvname = nil; levelfile = "/sys/games/lib/rushhour/levels/ttraffic.levels"; debug = 0; ARGBEGIN{ case 'd': debug++; break; case 'D': chatty9p++; break; case 'm': mtpt = EARGF(usage()); break; case 's': srvpost = EARGF(usage()); break; default: usage(); }ARGEND if (mtpt != nil && *mtpt == 0) mtpt = nil; if (argc > 1) usage(); if (argc == 1) levelfile = argv[0]; starttime = time(0); srand(time(0)); bp = Bopen(levelfile, OREAD); if (bp == nil) sysfatal("cannot open levelfile: %r"); directory = readlevels(bp); Bterm(bp); dirseen = seeninit(directory); ngenerated = 0; generated = nil; if(srvpost){ srvname = smprint("trafficfs.%s", srvpost); srvfile = smprint("/srv/%s", srvname); remove(srvfile); free(srvfile); } postmountsrv(&fs, srvname, mtpt, MBEFORE); }