Plan 9 from Bell Labs’s /usr/web/sources/contrib/fgb/root/sys/src/games/breakout/breakout.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


/*
 * 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);
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.