/* * Plumb -- a pipe-fitting game * Bugs: * must tweak timing */ #include #include #include #include int score; /* * Per-piece data */ /* * Directions */ #define NORTH 0 /* exit north */ #define EAST 1 /* exit east */ #define SOUTH 2 /* exit south */ #define WEST 3 /* exit west */ #define START 4 /* begin block */ #define NDIR 5 #define END 5 /* finish block */ #define ILL 6 /* entry not allowed (illegal) */ /* * Classes (END and START are classes, as well as directions) */ #define CURVE 0 /* curved player pieces */ #define STR8 1 /* straight player pieces, pun -- 8==AIGHT */ #define CROSS 2 /* the cross piece */ #define ONEWAY 3 /* one-way pieces */ #define NPLAYER 4 /* pieces placed by player have classexit) */ int goodist; /* how far through the square has the goo gone? */ #define FAST 10 /* speed of fast goo */ #define RESDELAY 200 /* reservoir filling delay */ #define NAGENDA 5 struct agenda{ int time; /* in msec, as read from /dev/cputime */ int (*f)(void); }agenda[NAGENDA]; int running; /* level still active */ int now; /* time of last event */ int currtime=0; /* time of last call to getrtime */ rectf(Bitmap *b, Rectangle r, Fcode f){ bitblt(b, r.min, b, r, f); } myborder(Bitmap *b, Rectangle r, int n, Fcode f){ if(n<0) border(b, inset(r, n), -n, f); else border(b, r, n, f); } message(char *s){ char buf[100]; sprintf(buf, "Level %2d. Score %6d. Dist %2d. %s", level, score, bonusdist, s); string(&screen, add(scorer.min, Pt(1, 1)), font, buf, S); rectf(&screen, Rpt(add(scorer.min, Pt(1+strwidth(font, buf), 1)), scorer.max), 0); } addscore(int n, int mult){ if(mult && (bonusdist==0 || goospeed==FAST)) n*=2; score+=n; if(score<0) score=0; message(""); } /* * Get current real time */ int getrtime(void){ char buf[12*6]; static int timefd=-1; int n; if(timefd==-1){ timefd=open("/dev/cputime", OREAD); if(timefd<0){ perror("open /dev/cputime"); exits("cputime"); } } seek(timefd, 0L, 0); if((n=read(timefd, buf, sizeof buf))!=sizeof buf){ perror("read /dev/cputime"); exits("read cputime"); } currtime=atoi(buf+24); return currtime; } /* * Schedule f to be called at given time */ sched(int time, int (*f)(void)){ struct agenda *p, *q; if(timef && p->time<=time;p++); if(p==&agenda[NAGENDA]){ fprintf(stderr, "agenda full!\n"); exits("agenda full"); } for(q=&agenda[NAGENDA-1];q!=p;--q) q[0]=q[-1]; p->time=time; p->f=f; } /* * If f is on the schedule, reschedule it for the given time */ resched(int time, int (*f)(void)){ struct agenda *p; for(p=agenda;p!=&agenda[NAGENDA] && p->f;p++){ if(p->f==f){ for(;p!=&agenda[NAGENDA-1] && p[1].f;p++) p[0]=p[1]; if(p!=&agenda[NAGENDA-1]) p->f=0; sched(time, f); break; } } } /* * Call all functions scheduled to be called before current time */ fugit(void){ struct agenda *p; int (*f)(void); int future; if(!agenda[0].f) return; future=getrtime(); while(agenda[0].f && agenda[0].timef;p++) p[-1]=p[0]; p[-1].f=0; (*f)(); } bflush(); } putpiece(int x, int y, struct piece *p){ board[y][x].piece=p; bitblt(&screen, add(boardr.min, Pt(x*SIZE, y*SIZE)), p->image, p->image->r, S); } /* * Should run explosion here */ #define NEXPLODE 30 /* # of cycles in explosion */ #define NDESTROY 10 /* # of cycldes to destroy unused pieces */ #define XDELAY 33 int explodex, explodey; struct piece *explodepiece; explode(void){ Point p; if(--busy==0){ putpiece(explodex, explodey, explodepiece); return; } p=add(boardr.min, Pt(explodex*SIZE, explodey*SIZE)); rectf(&screen, Rpt(p, add(p, Pt(SIZE, SIZE))), F&~D); sched(now+XDELAY, explode); } Point position[]={ SIZE/2, 0, /* NORTH */ SIZE, SIZE/2, /* EAST */ SIZE/2, SIZE, /* SOUTH */ 0, SIZE/2, /* WEST */ SIZE/2, SIZE/2, /* START */ SIZE/2, SIZE/2, /* END */ }; fillgoo(int from, int to, int alpha){ Point p, dp; p=add(position[from], add(boardr.min, Pt(goox*SIZE, gooy*SIZE))); dp=sub(position[to], position[from]); p=add(p, div(mul(dp, alpha), SIZE/2)); rectf(&screen, Rpt(sub(p, Pt(RAD/2, RAD/2)), add(p, Pt(RAD/2, RAD/2))), 0); } Bitmap *resdisk[RESRAD]; mkresdisks(void){ int i, x, y, xsq; Bitmap *b; for(i=0;i!=RESRAD;i++){ b=resdisk[i]=balloc(Rect(0, 0, SIZE, SIZE), 1); for(y=0;y!=SIZE/2;y++){ xsq=i*i-y*y; if(xsq>0){ for(x=1;x*xr, D&~S); } movegoo(void){ int delay=goospeed; switch(goosquare->piece->class){ default: fprintf(stderr, "Bad piece class %d in animate\n", goosquare->piece->class); exits("bad class"); case EMPTY: case BLOCK: goto Stop; case CURVE: case STR8: case CROSS: case ONEWAY: case BONUS: if(goodistpiece->exit[goodir], goodist-SIZE/2); goodist+=RAD/2; if(goodist==SIZE) goto DoneSquare; break; case RES: if(goodistpiece->exit[goodir], goodist-SIZE/2-RESRAD); goodist+=RAD/2; } if(goodist==SIZE+RESRAD) goto DoneSquare; break; case START: fillgoo(END, goosquare->piece->exit[goodir], goodist); goodist+=RAD/2; if(goodist==SIZE/2) goto DoneSquare; break; case END: fillgoo(goodir, END, goodist); goodist+=RAD/2; if(goodist==SIZE/2) goto DoneSquare; break; } sched(now+delay, movegoo); return; Stop: running=0; return; DoneSquare: /* * Finished with this square, score it and move to the next square */ if(bonusdist) --bonusdist; switch(goosquare->nentry){ case 1: addscore(goosquare->piece->s0, 1); break; case 2: addscore(goosquare->piece->s1, 1); break; } switch(goosquare->piece->exit[goodir]){ case END: goto Stop; case NORTH: if(gooy--==0) goto Stop; goodir=SOUTH; break; case EAST: if(++goox==NX) goto Stop; goodir=WEST; break; case SOUTH: if(++gooy==NY) goto Stop; goodir=NORTH; break; case WEST: if(goox--==0) goto Stop; goodir=EAST; break; } goosquare=&board[gooy][goox]; if(goosquare->piece->exit[goodir]==ILL) goto Stop; goosquare->nentry++; goodist=0; sched(now+goospeed, movegoo); } fillin(Bitmap *b, int edge){ switch(edge){ case NORTH: rectf(b, Rect(SIZE/2-RAD, 0, SIZE/2+RAD, SIZE/2+RAD), F); break; case EAST: rectf(b, Rect(SIZE/2-RAD, SIZE/2-RAD, SIZE, SIZE/2+RAD), F); rectf(b, Rect(SIZE-RAD, SIZE/2-3*RAD/2, SIZE, SIZE/2+3*RAD/2), F); break; case SOUTH: rectf(b, Rect(SIZE/2-RAD, SIZE/2-RAD, SIZE/2+RAD, SIZE), F); rectf(b, Rect(SIZE/2-3*RAD/2, SIZE-RAD, SIZE/2+3*RAD/2, SIZE), F); break; case WEST: rectf(b, Rect(0, SIZE/2-RAD, SIZE/2+RAD, SIZE/2+RAD), F); break; } } pieceimages(int version){ struct piece *p; int f; char name[100]; static int first=1; static int lastversion=-1; if(version==lastversion) return; sprintf(name, "/sys/games/lib/plumb/pieces.%d", version); f=open(name, OREAD); if(f<0){ if(first){ perror(name); exits("Can't read pieces"); } return; } first=0; lastversion=version; for(p=piece;p!=&piece[NPIECE];p++){ if(p->image) bfree(p->image); p->image=rdbitmapfile(f); } close(f); } /* * An entry in the level file contains: * Level BonusDist Delay Speed PieceVersion * CURVE STR8 CROSS ONEWAY * END BLOCK RES BONUS */ FILE *param; levelparams(int really){ int i, piecenum, total; if(param==0){ param=fopen("/sys/games/lib/plumb/levels", "r"); if(param==0){ perror("/sys/games/lib/plumb/levels"); exits("can't open levels"); } } if(fscanf(param, "%d", &i)==1){ if(i!=level){ fprintf(stderr, "parameter phase error, level %d!=%d\n", level, i); exits("parameter phase"); } if(fscanf(param, "%d%d%d%d", &ibonusdist, &goodelay, &igoospeed, &piecenum)!=4){ Bad: fprintf(stderr, "parameter error, level %d\n", level); exits("bad levels"); } total=0; for(i=CURVE;i!=NPLAYER;i++){ if(fscanf(param, "%d", &dist[i])!=1) goto Bad; distp[i]=piece; total+=dist[i]; } if(total==0 || total>NUPCOMING) goto Bad; for(i=END;i!=NCLASS;i++) if(fscanf(param, "%d", &init[i])!=1) goto Bad; } if(really) pieceimages(piecenum); init[START]=1; } replacable(int x, int y){ struct square *p; if(y<0 || NY<=y || x<0 || NX<=x) return 0; p=&board[y][x]; if(p->nentry) return 0; switch(p->piece->class){ case CURVE: case STR8: case CROSS: case ONEWAY: case EMPTY: return 1; default: return 0; } } okdir(int x, int y, int dir){ switch(dir){ case NORTH: --y; break; case EAST: x++; break; case SOUTH: y++; break; case WEST: --x; break; default: return 1; } return 1<=x && xclass==EMPTY; } floodboard(int x, int y){ if(x<0 || NX<=x || y<0 || NY<=y || board[y][x].piece!=empty || board[y][x].mark) return; board[y][x].mark=1; floodboard(x+1, y); floodboard(x-1, y); floodboard(x, y-1); floodboard(x, y+1); } /* * Check that no exit is blocked and that the * empty cells form a single 4-connected component. */ okboard(void){ int x, y, x0=-1, y0=-1, i; struct piece *p; for(y=0;y!=NY;y++) for(x=0;x!=NX;x++){ board[y][x].mark=0; p=board[y][x].piece; if(p==empty){ x0=x; y0=y; } for(i=0;i!=NDIR;i++) if(p->exit[i]!=ILL){ if(!okdir(x, y, i)) return 0; if(!okdir(x, y, p->exit[i])) return 0; } } if(x0==-1){ fprintf(stderr, "Board full!\n"); exits("board full"); } floodboard(x0, y0); for(y=0;y!=NY;y++) for(x=0;x!=NX;x++) if(board[y][x].piece==empty && !board[y][x].mark) return 0; return 1; } struct piece *stackpiece(void){ struct piece *p, *v, **pp; int i, j, k; if(upcomingp==eupcoming){ eupcoming=upcomingp=upcoming; for(i=0;i!=NPLAYER;i++) for(j=0;j!=dist[i];j++){ do{ if(++distp[i]==&piece[NPIECE]) distp[i]=piece; }while(distp[i]->class!=i); ++eupcoming; pp=upcoming+nrand(eupcoming-upcoming); eupcoming[-1]=*pp; *pp=distp[i]; } } p=*upcomingp++; v=stack[0]; for(i=1;i!=NSTACK;i++) stack[i-1]=stack[i]; stack[NSTACK-1]=p; bitblt(&screen, add(stackr.min, Pt(0, SIZE)), &screen, Rpt(stackr.min, sub(stackr.max, Pt(0, SIZE))), S); bitblt(&screen, stackr.min, p->image, p->image->r, S); return v; } clearboard(void){ int x, y, i, j, n, ntry, nrestart=0; struct piece *p, *q; Restart: if(nrestart++==20){ fprintf(stderr, "plumb: clearboard failed\n"); exits("can't init"); } for(y=0;y!=NY;y++) for(x=0;x!=NX;x++){ board[y][x].nentry=0; board[y][x].piece=empty; } for(i=0;i!=NCLASS;i++) for(j=0;j!=init[i];j++){ n=0; q=0; for(p=piece;p!=&piece[NPIECE];p++) if(p->class==i && nrand(++n)==0) q=p; if(q==0){ fprintf(stderr, "No piece of class %d\n", i); exits("bad class"); } for(ntry=0;;ntry++){ if(ntry==200) goto Restart; x=nrand(NX); y=nrand(NY); if(board[y][x].piece==empty){ board[y][x].piece=q; if(okboard()) break; board[y][x].piece=empty; } } if(q->class==START){ goox=x; gooy=y; goodir=START; goosquare=&board[gooy][goox]; } } for(y=0;y!=NY;y++) for(x=0;x!=NX;x++) putpiece(x, y, board[y][x].piece); } place(Point p){ Point cell; cell=div(sub(p, boardr.min), SIZE); if(replacable(cell.x, cell.y)){ if(board[cell.y][cell.x].piece!=empty){ addscore(-50, 0); explodex=cell.x; explodey=cell.y; explodepiece=stackpiece(); busy=NEXPLODE; putpiece(cell.x, cell.y, empty); disc(&screen, add(mul(cell, SIZE), add(boardr.min, Pt(SIZE/2, SIZE/2))), SIZE/2, ~0, S); sched(now, explode); } else putpiece(cell.x, cell.y, stackpiece()); } } newlevel(void){ int i; levelparams(1); clearboard(); for(i=0;i!=NSTACK;i++) stackpiece(); now=getrtime(); bonusdist=ibonusdist; goospeed=igoospeed; goodist=0; running=1; busy=0; sched(now+goodelay, movegoo); } void ereshaped(Rectangle r){ int x, y, i; screen.r=r; scorer.min=add(screen.r.min, Pt(3*BORDER, 3*BORDER)); scorer.max=add(scorer.min, Pt((NX+2)*SIZE, font->height+2)); stackr.min=add(scorer.min, Pt(0, font->height+2+3*BORDER)); stackr.max=add(stackr.min, Pt(SIZE, NSTACK*SIZE)); boardr.min=add(stackr.min, Pt(2*SIZE, 0)); boardr.max=add(boardr.min, Pt(NX*SIZE, NY*SIZE)); if(!ptinrect(add(boardr.max, Pt(BORDER-1, BORDER-1)), screen.r)){ fprintf(stderr, "window too small, must be at least %d x %d\n", boardr.max.x-boardr.min.x+BORDER-1, boardr.max.y-boardr.min.y+BORDER-1); exits("window too small"); } rectf(&screen, screen.r, 0); border(&screen, screen.r, BORDER, F); myborder(&screen, screen.r, BORDER, F); myborder(&screen, scorer, -BORDER, F); myborder(&screen, stackr, -BORDER, F); myborder(&screen, boardr, -BORDER, F); for(i=0;i!=NSTACK;i++) if(stack[i]) bitblt(&screen, add(stackr.min, Pt(0, i*SIZE)), stack[i]->image, stack[i]->image->r, S); for(y=0;y!=NY;y++) for(x=0;x!=NX;x++) if(board[y][x].piece) bitblt(&screen, add(boardr.min, Pt(x*SIZE, y*SIZE)), board[y][x].piece->image, board[y][x].piece->image->r, S); } cleanscreen(void){ rectf(&screen, screen.r, 0); border(&screen, screen.r, BORDER, F); } outscore(void){ char buf[100]; int n; int f; if(score==0) return; f=open("/dev/user", OREAD); if(f<0 || (n=read(f, buf, sizeof buf))<=0){ strcpy(buf, "nobody"); n=6; } sprintf(buf+n, " %d %d\n", score, level); close(f); f=open("/sys/games/lib/plumb/scores", OWRITE); write(f, buf, strlen(buf)); close(f); } /* * int menuhit(int but, Mouse *m, Menu *menu) */ char *but2[]={ "fast", 0 }; Menu m2={but2}; menu2(Mouse *m){ switch(menuhit(2, m, &m2)){ case 0: goospeed=FAST; resched(now, movegoo); break; } } char *but3[]={ "exit", 0 }; Cursor confirmcursor={ 0, 0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x0E, 0x07, 0x1F, 0x03, 0x17, 0x73, 0x6F, 0xFB, 0xCE, 0xDB, 0x8C, 0xDB, 0xC0, 0xFB, 0x6C, 0x77, 0xFC, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x94, 0xA6, 0x63, 0x3C, 0x63, 0x18, 0x94, 0x90, }; confirm(int b){ Mouse down, up; cursorswitch(&confirmcursor); do down=emouse(); while(!down.buttons); do up=emouse(); while(up.buttons); cursorswitch(0); return down.buttons==(1<<(b-1)); } Menu m3={but3}; menu3(Mouse *m){ switch(menuhit(3, m, &m3)){ case 0: if(confirm(3)){ outscore(); cleanscreen(); exits(""); } } } /* * return a mouse event, preempting button 3 hits so * that the player can always exit. */ Mouse emouse3(void){ Mouse m; for(;;){ m=emouse(); if(m.buttons!=4) break; menu3(&m); } return m; } waitclick(void){ do;while(emouse3().buttons==0); do;while(emouse3().buttons!=0); } main(int argc, char *argv[]){ int i, x, y, doublecross, firstlevel; Mouse oldm, m; char buf[100]; srand(time(0)); binit(0, 0, 0); einit(Emouse|Ekeyboard); if(argc>1){ firstlevel=atoi(argv[1]); if(firstlevel<=0){ fprintf(stderr, "%s: start level must be positive\n", argv[0]); exits("level<1"); } } else firstlevel=1; ereshaped(screen.r); mkresdisks(); m.buttons=0; for(;;){ score=0; if(param){ fclose(param); param=0; } for(level=1;level!=firstlevel;level++) levelparams(0); for(;;level++){ newlevel(); addscore(0, 0); /* to force redisplay */ bflush(); while(ecanmouse()) emouse3(); do{ if(ecanmouse()){ oldm=m; m=emouse3(); if(oldm.buttons==0){ if(m.buttons==1 && !busy && goospeed!=FAST) place(m.xy); else if(m.buttons==2) menu2(&m); } } fugit(); }while(running); while(busy) fugit(); doublecross=0; for(y=0;y!=NY;y++) for(x=0;x!=NX;x++){ if(board[y][x].piece->class