/* * Trivial breakout/arkanoid clone. * Federico G. Benavento */ #include #include #include #include #include #include enum { NCOL = 6, NX = 10, NY = 6, NOFF = 12, NLEV = 20, NBALL = 5, }; enum { XXX, TOP, BOT, UP, DOWN, RIGHT, LEFT }; int cols[NCOL] = { DYellow, /* yellow */ DCyan, /* cyan */ DGreen, /* lime green */ DGreyblue, /* slate */ DRed, /* red */ 0xCCCCCCFF, /* grey */ }; Image *br[NCOL]; enum { BYellow = 1, /* yellow '-' */ BCyan, /* cyan '=' */ BGreen, /* lime green '/' */ BGreyblue, /* slate '#' */ BRed, /* red ' :' */ BGrey, }; char levels[NLEV][NY][NX]; char board[NY][NX]; int DMOUSE; int DSTEP; Rectangle rboard; Rectangle rball; Rectangle rpad; Point brsz; Point pscore; Point scoresz; Point balldir; int padsz; int ballsz; int balls; long points; int launched; int pending; int tsleep; int loadlevels(char *path) { Biobuf *b; int x, y, l; char c; if(path == nil) return 0; b = Bopen(path, OREAD); if(b == nil) return 0; memset(levels, 0, sizeof(levels)); x = y = l = 0; while((c = Bgetc(b)) > 0){ switch(c) { case ';': /* no ';'-comments in the middle of a level */ while(Bgetc(b) != '\n') ; break; case '\n': x = 0; y++; c = Bgetc(b); if(c == '\n' || c == Beof){ /* end of level */ if(++l == NLEV) goto Done; x = 0; y = 0; } else Bungetc(b); break; case ' ': x++; break; case '-': levels[l][y][x] = BYellow; x++; break; case '=': levels[l][y][x] = BCyan; x++; break; case '/': levels[l][y][x] = BGreen; x++; break; case '#': levels[l][y][x] = BGreyblue; x++; break; case ':': levels[l][y][x] = BRed; x++; break; default: fprint(2, "invalid char in level %d: %c\n", l+1, c); return 0; } } Done: Bterm(b); return 1; } void score(int p) { char buf[128]; points += p; snprint(buf, sizeof(buf), "s:%.6ld b:%d", points, balls); draw(screen, Rpt(pscore, addpt(pscore, scoresz)), display->white, nil, ZP); string(screen, pscore, display->black, ZP, font, buf); } void drawpd(void) { draw(screen, rpad, display->black, nil, ZP); draw(screen, insetrect(rpad, 1), br[BGrey-1], nil, ZP); } void drawbr(Image *b, Point p, int ptx) { Rectangle r; r.min = p; r.max.x = r.min.x+brsz.x; r.max.y = r.min.y+brsz.y; if(ptx > 0){ draw(b, r, display->black, nil, ZP); draw(b, insetrect(r, 1), br[ptx-1], nil, ZP); }else draw(b, r, display->white, nil, ZP); } void drawboard(void) { int i, j; draw(screen, screen->r, display->white, nil, ZP); border(screen, insetrect(rboard, -4), 2, display->black, ZP); for(i=0; iwhite: display->black, ZP); } void mright(void) { if(rpad.max.x == rboard.max.x) return; draw(screen, rpad, display->white, nil, ZP); rpad = rectaddpt(rpad, Pt(Dx(rpad)/3, 0)); if(rpad.max.x > rboard.max.x-Dx(rpad)) rpad.min.x = rboard.max.x-Dx(rpad), rpad.max.x = rboard.max.x; drawpd(); } void mleft(void) { if(rpad.min.x == rboard.min.x) return; draw(screen, rpad, display->white, nil, ZP); rpad = rectsubpt(rpad, Pt(Dx(rpad)/3, 0)); if(rpad.min.x < rboard.min.x) rpad.max.x = rboard.min.x+Dx(rpad), rpad.min.x = rboard.min.x; drawpd(); } int hitbrick(void) { Rectangle r; int i, j, s; r = Rpt(rboard.min, Pt(rboard.min.x+NX*brsz.x,rboard.min.y+NY*brsz.y)); if(!rectXrect(rball, r)) return 0; for(i=0; ir.min.y){ balldir.y = UP; rball.max.y = r.min.y, rball.min.y = rball.min.y-ballsz; }else if(balldir.x==LEFT && rball.min.xr.min.x){ balldir.x = LEFT; rball.max.x = r.min.x, rball.min.x = rball.max.x-ballsz; } drawbr(screen, r.min, board[i][j]); score(s); return 1; } } return 0; } void mvball(void) { int x, y; drawbl(1); x = y = 0; if(balldir.x == RIGHT) x += DSTEP; else x -= DSTEP; if(balldir.y == DOWN) y += DSTEP; else y -= DSTEP; rball = rectaddpt(rball, Pt(x,y)); hitbrick(); if(rectXrect(rpad, rball)){ /* paddle */ balldir.y = UP; if(balldir.x==RIGHT && rball.max.xrpad.min.x+(padsz/3)*2) balldir.x = RIGHT; rball.max.y = rpad.min.y, rball.min.y = rball.max.y-ballsz; }else if(rball.min.x < rboard.min.x){ /* left wall */ balldir.x = RIGHT; rball.min.x = rboard.min.x, rball.max.x = rball.min.x+ballsz; }else if(rball.max.x > rboard.max.x){ /* right wall */ balldir.x = LEFT; rball.max.x = rboard.max.x, rball.min.x = rball.max.x-ballsz; }else if(rball.min.y < rboard.min.y){ /* top */ balldir.y = DOWN; rball.min.y = rboard.min.y, rball.max.y = rball.min.y+ballsz; }else if(rball.max.y > rboard.max.y){ /* bottom */ launched = 0, balls--; score(0); return; } drawbl(0); } void launch(void) { launched = 1; rball.min.x = rpad.min.x+Dx(rpad)/2; rball.max.x = rball.min.x + ballsz; rball.max.y = rpad.min.y-2; rball.min.y = rball.max.y - ballsz; balldir.x = rand()%2 ? RIGHT : LEFT; balldir.y = UP; } int play(void) { Mouse m; Event ev; ulong timer; int e, level, dt; int lastmx, i, j; dt = 64; level = pending = 0; lastmx = -1; timer = etimer(0, tsleep); for(;;){ if(pending == 0){ memcpy(board, levels[level%NLEV], sizeof(board)); for(i=0; i lastmx+DMOUSE){ mright(); lastmx = m.xy.x; } if(m.xy.x < lastmx-DMOUSE){ mleft(); lastmx = m.xy.x; } } break; case Ekeyboard: switch(ev.kbdc){ case Kleft: Left: mleft(); break; case Kright: Right: mright(); break; case ' ': if(!launched) launch(); break; case 'p': ekbd(); break; case 'q': return 0; } break; default: if(e==timer && launched){ dt -= tsleep; if(dt < 0){ i = 1; dt = 16 * (points+nrand(10000)-5000) / 10000; if(dt >= 32){ i += (dt-32)/16; dt = 32; } dt = 52-dt; while(i-- > 0) mvball(); } } break; } if(balls == 0) return 1; } } void eresized(int new) { Rectangle r; long dx, dy; if(new && getwindow(display, Refnone) < 0) sysfatal("can't reattach to window: %r"); r = insetrect(screen->r, 2); rpad.min.x = (rpad.min.x - rboard.min.x) / brsz.x; rpad.max.x = rpad.min.x + brsz.x; dx = r.max.x - r.min.x; dy = r.max.y - r.min.y - 2*32; brsz.x = (dx/2)/NX; brsz.y = brsz.x/3; if(brsz.y*(NY+NOFF) > dy) brsz.y = dy / (NY+NOFF); if(brsz.y < 8) sysfatal("screen too small: %r"); ballsz = brsz.x/4; DSTEP = ballsz/2; DMOUSE = brsz.y/6; rboard = r; rboard.min.x += (dx-brsz.x*NX)/2; rboard.min.y += (dy-brsz.y*(NY+NOFF))/2+32; rboard.max.x = rboard.min.x+NX*brsz.x; rboard.max.y = rboard.min.y+(NY+NOFF)*brsz.y; pscore.x = rboard.min.x+8; pscore.y = rboard.min.y-32; scoresz = stringsize(font, "s:000000 b:0"); padsz = brsz.y * 5; rpad.min.x = rpad.min.x*padsz + rboard.min.x; rpad.max.x = rpad.min.x + padsz; rpad.max.y = brsz.y*(NY+NOFF) + rboard.min.y; rpad.min.y = rpad.max.y - brsz.y/3; drawboard(); flushimage(display, 1); } void main(int argc, char *argv[]) { char *levelpath = "/sys/games/lib/breakout/default.slc"; char *scorespath = "/sys/games/lib/breakout/scores"; long starttime, endtime; int i, scores; tsleep = 20; ARGBEGIN{ case 'f': levelpath = ARGF(); if(levelpath == nil) goto Usage; break; default: goto Usage; }ARGEND if(argc){ Usage: fprint(2, "usage: %s [-f file]\n", argv0); exits("usage"); } if(!loadlevels(levelpath)) sysfatal("can't open: %s: %r", levelpath); scores = open(scorespath, OWRITE); if(scores < 0) sysfatal("can't open %s: %r", scorespath); if(initdraw(0,0,"breakout") < 0) sysfatal("initdraw failed: %r"); for(i=0; ichan, 1, cols[i]); if(br[i] == nil) sysfatal("allocimage failed: %r"); } brsz = Pt(16, 32); starttime = time(0); srand(starttime); memset(board, 0, sizeof(board)); einit(Emouse|Ekeyboard); eresized(0); if(play()){ endtime = time(0); fprint(scores, "%ld\t%s\t%lud\t%ld\n", points, getuser(), starttime, endtime-starttime); } exits(nil); }