/*
* Trivial breakout/arkanoid clone.
* Federico G. Benavento <benavento@gmail.com>
*/
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <draw.h>
#include <event.h>
#include <keyboard.h>
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; i<NY; i++) for(j=0; j<NX; j++)
if(board[i][j])
drawbr(screen, Pt(rboard.min.x+j*brsz.x, rboard.min.y+i*brsz.y), board[i][j]);
drawpd();
score(0);
}
void
drawbl(int clear)
{
int rad;
rad = ballsz/2;
fillellipse(screen, Pt(rball.min.x+rad,rball.min.y+rad),
rad-1, rad-1, clear? display->white: 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; i<NY; i++) for(j=0; j<NX; j++){
r.min = Pt(rboard.min.x+j*brsz.x, rboard.min.y+i*brsz.y);
r.max = addpt(r.min, brsz);
if(rectXrect(r, rball)){
if(board[i][j] == 0)
continue;
if(board[i][j] != BRed)
board[i][j]--;
s = 25;
if(board[i][j] == 0)
pending--, s += 50;
if(balldir.y==UP && rball.min.y<r.max.y){
balldir.y = DOWN;
rball.min.y = r.max.y, rball.max.y = rball.min.y+ballsz;
}else if(balldir.y==DOWN && rball.max.y>r.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.x<r.max.x){
balldir.x = RIGHT;
rball.min.x = r.max.x, rball.max.x = rball.min.x+ballsz;
}else if(balldir.x==RIGHT && rball.max.x>r.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.x<rpad.min.x+padsz/3)
balldir.x = LEFT;
if(balldir.x==LEFT && rball.min.x>rpad.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<NY; i++) for(j=0; j<NX; j++)
if(board[i][j] && board[i][j]!=BRed) pending++;
score(100*level++);
balls = NBALL;
launched = 0;
rpad.min.x = rboard.min.x + Dx(rboard)/2 - padsz/2;
rpad.max.x = rpad.min.x + padsz;
drawboard();
}
e = event(&ev);
switch(e){
case Emouse:
m = ev.mouse;
if(m.buttons & 1)
goto Left;
else if(m.buttons & 2){
if(!launched)
launch();
}else if(m.buttons & 4)
goto Right;
else{
if(lastmx < 0)
lastmx = m.xy.x;
if(m.xy.x > 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; i<NCOL; i++){
br[i] = allocimage(display, Rect(0,0,1,1), screen->chan, 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);
}
|