/* * Plumb -- a pipe-fitting game * Bugs: * must tweak timing * min * |------------------------------------| * | scorer | * |------------------------------------| * min * min |-----| |---------------------------| * | | | | * | | | | * | | | | * | | | | * stackr-----------|-> | | | * | | | | * | | | boardr | * | | | | * | | | | * | | | | * | | | | * | | | | * | | | | * | | | | * |-----| |---------------------------| * max max * * goo: flowing oil (red) * explode: explosion piece (large red circle) indicates busy to do something * resdisk[]: goo (oil) reservoir where time will be gotten while goo is filling it. */ #include #include #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 */ void (*f)(void); }agenda[NAGENDA]; int running; /* level still active */ int now; /* time of last event */ int currtime=0; /* time of last call to getrtime */ /* colors for plumb */ Image *black; Image *white; Image *red; Image *paleyellow; Image *darkgreen; Image *palegreen; Image *darkblue; Image *darkgrey; Image *medgreen; Image *bgcolor; Image *whitemask; void fillres(int); void rectf(Image *b, Rectangle r, Image *color){ draw(b, r, color, 0, ZP); } void message(char *s){ char buf[100]; sprintf(buf, "Level %2d. Score %6d. Dist %2d. %s", level, score, bonusdist, s); rectf(screen, Rpt(scorer.min, scorer.max), bgcolor); string(screen, addpt(scorer.min, Pt(1, 1)), paleyellow, ZP, font, buf); } void 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; if(timefd==-1){ timefd=open("/dev/cputime", OREAD); if(timefd<0){ perror("open /dev/cputime"); exits("cputime"); } } seek(timefd, 0L, 0); if(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 */ void sched(int time, void (*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 */ void resched(int time, void (*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 */ void fugit(void){ struct agenda *p; void (*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)(); } } /* put pieces read from /sys/games/lib/plumb/piece.x file */ void putpiece(int x, int y, struct piece *p){ Point pp; board[y][x].piece=p; pp = addpt(boardr.min, Pt(x*SIZE, y*SIZE)); draw(screen, Rpt(pp, addpt(pp, Pt(SIZE, SIZE))), p->image, 0, p->image->r.min); } /* * Should run explosion here */ #define NEXPLODE 30 /* # of cycles in explosion */ #define NDESTROY 10 /* # of cycles to destroy unused pieces */ #define XDELAY 33 int explodex, explodey; struct piece *explodepiece; void explode(void){ Point p; if(--busy==0){ putpiece(explodex, explodey, explodepiece); return; } p=addpt(boardr.min, Pt(explodex*SIZE, explodey*SIZE)); // draw(screen, Rpt(p, addpt(p, Pt(SIZE, SIZE))), explodepiece->image , black, ZP); draw(screen, Rpt(addpt(p, Pt(1,1)), addpt(p, Pt(SIZE-1, SIZE-1))), display->white, whitemask, ZP); flushimage(display, 1); 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 */ }; /* fill goo (flowing oil) */ void fillgoo(int from, int to, int alpha){ Point p, dp; p=addpt(position[from], addpt(boardr.min, Pt(goox*SIZE, gooy*SIZE))); dp=subpt(position[to], position[from]); p=addpt(p, divpt(mulpt(dp, alpha), SIZE/2)); /* draw goo flow */ rectf(screen, Rpt(subpt(p, Pt(RAD/2, RAD/2)), addpt(p, Pt(RAD/2, RAD/2))), red); } Image *resdisk[RESRAD]; /* reserviors */ Image *resmask[RESRAD]; /* reservior masks */ /* draw a red reservior */ void mkresdisks(void){ int i, x, y, xsq; for(i=0; i!=RESRAD; i++){ resdisk[i]=allocimage(display, Rect(0, 0, SIZE, SIZE), display->windows->chan, 0, DTransparent); resmask[i]=allocimage(display, Rect(0, 0, SIZE, SIZE), display->windows->chan, 0, DBlack); for(y=0; y!=SIZE/2; y++){ xsq=i*i - y*y; if(xsq>0){ for(x=1; x*xwhite, ZP); line(resmask[i], Pt(SIZE/2-x, SIZE/2+y), Pt(SIZE/2+x, SIZE/2+y), 0, 0, 0, display->white, ZP); } } } } } /* fill reservior */ void fillres(int alpha){ Point p; p=addpt(boardr.min, Pt(goox*SIZE, gooy*SIZE)); draw(screen, Rpt(p, addpt(p, Pt(SIZE, SIZE))), resdisk[alpha], resmask[alpha], ZP); } void 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); } void fillin(Image *b, int edge, Image *color){ switch(edge){ case NORTH: rectf(b, Rect(SIZE/2-RAD, 0, SIZE/2+RAD, SIZE/2+RAD), color); break; case EAST: rectf(b, Rect(SIZE/2-RAD, SIZE/2-RAD, SIZE, SIZE/2+RAD), color); rectf(b, Rect(SIZE-RAD, SIZE/2-3*RAD/2, SIZE, SIZE/2+3*RAD/2), color); break; case SOUTH: rectf(b, Rect(SIZE/2-RAD, SIZE/2-RAD, SIZE/2+RAD, SIZE), color); rectf(b, Rect(SIZE/2-3*RAD/2, SIZE-RAD, SIZE/2+3*RAD/2, SIZE), color); break; case WEST: rectf(b, Rect(0, SIZE/2-RAD, SIZE/2+RAD, SIZE/2+RAD), color); break; } } void 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) freeimage(p->image); p->image=readimage(display, f, 0); } close(f); } /* * An entry in the level file contains: * Level BonusDist Delay Speed PieceVersion * CURVE STR8 CROSS ONEWAY * END BLOCK RES BONUS */ FILE *param; void 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; } int 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; } } int 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; } void 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. */ int 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; 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; /* slide down stacks */ draw(screen, Rpt(addpt(stackr.min, Pt(0, SIZE)), stackr.max), screen, 0, stackr.min); /* put a new stack at the top */ draw(screen, Rpt(stackr.min, addpt(stackr.min, Pt(SIZE, SIZE))), p->image, 0, p->image->r.min); return v; } void 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); } void place(Point p){ Point cell; cell=divpt(subpt(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); /* draw red pipe where oil has flown */ /* use SIZE/2-2 to avoid touching (overwriting) border */ fillellipse(screen, addpt(mulpt(cell, SIZE), addpt(boardr.min, Pt(SIZE/2, SIZE/2))), SIZE/2-2, SIZE/2-2, red, ZP); sched(now, explode); } else putpiece(cell.x, cell.y, stackpiece()); } } void 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 eresized(int new){ int x, y, i; Image *color; if(new && getwindow(display, Refmesg) < 0) fprint(2,"can't reattach to window"); scorer.min=addpt(screen->r.min, Pt(3*BORDER, 3*BORDER)); scorer.max=addpt(scorer.min, Pt((NX+2)*SIZE, font->height+2)); stackr.min=addpt(scorer.min, Pt(0, font->height+2+3*BORDER)); stackr.max=addpt(stackr.min, Pt(SIZE, NSTACK*SIZE)); boardr.min=addpt(stackr.min, Pt(2*SIZE, 0)); boardr.max=addpt(boardr.min, Pt(NX*SIZE, NY*SIZE)); if(!ptinrect(addpt(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"); } color = medgreen; rectf(screen, screen->r, darkgreen); /* background of the screen */ border(screen, screen->r, BORDER, color, ZP); border(screen, scorer, -BORDER, color, ZP); border(screen, stackr, -BORDER, paleyellow, ZP); border(screen, boardr, -BORDER, color, ZP); for(i=0; i!=NSTACK; i++) if(stack[i]) /* redraw stack rectangular */ draw(screen, Rpt(stackr.min, addpt(stackr.min, Pt(SIZE, i*SIZE))), stack[i]->image, 0, stack[i]->image->r.min); for(y=0; y!=NY; y++) for(x=0; x!=NX; x++) if(board[y][x].piece) /* redraw pieces in the board rectangular */ draw(screen, Rpt(addpt(boardr.min, Pt(x*SIZE, y*SIZE)), addpt(boardr.min, Pt((x+1)*SIZE, (y+1)*SIZE))), board[y][x].piece->image, 0, board[y][x].piece->image->r.min); message(""); } void cleanscreen(void){ rectf(screen, screen->r, black); border(screen, screen->r, BORDER, medgreen, ZP); } void 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}; void menu2(Mouse *m){ switch(emenuhit(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, }; int confirm(int b){ Mouse down, up; esetcursor(&confirmcursor); do down=emouse(); while(!down.buttons); do up=emouse(); while(up.buttons); esetcursor(0); return down.buttons==(1<<(b-1)); } Menu m3={but3}; void menu3(Mouse *m){ switch(emenuhit(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 mouse3(Mouse m){ if(m.buttons==4) menu3(&m); return m; } void waitclick(void){ Mouse down, up; do down=mouse3(emouse()); while(!down.buttons); do up=mouse3(emouse()); while(up.buttons); } void waitticks(int n, ulong timek) { int i; ulong k; Event ev; i = 0; while(i < n) { k = eread(Emouse|timek, &ev); if (k == Emouse) mouse3(ev.mouse); if (k == timek) i++; } } void main(int argc, char *argv[]){ int x, y, doublecross, firstlevel; ulong timek, k; Event ev; Mouse oldm, m; char buf[100]; srand(time(0)); initdraw(0, 0, "plumb"); einit(Emouse|Ekeyboard); timek = etimer(0, 1); 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; black = allocimage(display, Rect(0,0,1,1), display->windows->chan, 1, DBlack); white = allocimage(display, Rect(0,0,1,1), display->windows->chan, 1, DWhite); red = allocimage(display, Rect(0,0,1,1), display->windows->chan, 1, DRed); paleyellow=allocimage(display, Rect(0, 0, 1, 1), display->windows->chan, 1, DPaleyellow); darkgreen=allocimage(display, Rect(0, 0, 1, 1), display->windows->chan, 1, DDarkgreen); palegreen=allocimage(display, Rect(0, 0, 1, 1), display->windows->chan, 1, DPalegreen); medgreen=allocimage(display, Rect(0, 0, 1, 1), display->windows->chan, 1, DMedgreen); darkblue = allocimage(display, Rect(0,0,1,1), display->windows->chan, 1, DDarkblue); darkgrey = allocimage(display, Rect(0,0,1,1), display->windows->chan, 1, DGreygreen); bgcolor = darkblue; whitemask = allocimage(display, Rect(0,0,1,1), CMAP8, 1, setalpha(DWhite, 0x1F)); mkresdisks(); m.buttons=0; eresized(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 */ while(ecanmouse()) mouse3(emouse()); do{ k = eread(Emouse|timek, &ev); if(k == Emouse){ oldm=m; m = mouse3(ev.mouse); 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) { k = eread(Emouse|timek, &ev); if(k == Emouse) mouse3(ev.mouse); fugit(); } doublecross=0; for(y=0; y!=NY; y++) for(x=0; x!=NX; x++){ if(board[y][x].piece->class