Plan 9 from Bell Labs’s /usr/web/sources/contrib/axel/plumb/plumb.c

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


/*
 * 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 <u.h>
#include <libc.h>
#include <draw.h>
#include <event.h>
#include <cursor.h>
#include <stdio.h>

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 class<NPLAYER */
/*	START	4 */		/* start block */
/*	END	5 */		/* end block */
#define	BLOCK	6		/* blocked squares */
#define	RES	7			/* reservoirs */
#define	BONUS	8		/* bonus pipe */
#define	NCLASS	9
#define	EMPTY	9		/* empty squares */

struct piece{
	int exit[NDIR];		/* exit direction, for each entry */
	int class;			/* What kind of piece is this */
	int s0, s1;			/* score for each passage through */
	Image *image;
}piece[];

#define	NPIECE	(sizeof piece/sizeof piece[0])
struct piece piece[]={
/*	NORTH,  EAST, SOUTH,  WEST, START,  class,  s0,  s1, image */
	 EAST, NORTH,   ILL,   ILL,   ILL,  CURVE, 100,   0, 0, 			/*  0 N/E */
	SOUTH,   ILL, NORTH,   ILL,   ILL,   STR8, 100,   0, 0, 			/*  1 N/S */
	 WEST,   ILL,   ILL, NORTH,   ILL,  CURVE, 100,   0, 0, 			/*  2 N/W */
	  ILL, SOUTH,  EAST,   ILL,   ILL,  CURVE, 100,   0, 0, 			/*  3 E/S */
	  ILL,  WEST,   ILL,  EAST,   ILL,   STR8, 100,   0, 0, 			/*  4 E/W */
	  ILL,   ILL,  WEST, SOUTH,   ILL,  CURVE, 100,   0, 0, 			/*  5 S/W */
	SOUTH,  WEST, NORTH,  EAST,   ILL,  CROSS, 100, 400, 0, 		/*  6 N/S+E/W */
	 EAST,   ILL,   ILL,   ILL,   ILL, ONEWAY, 200,   0, 0, 			/*  7 N/E one way */
	SOUTH,   ILL,   ILL,   ILL,   ILL, ONEWAY, 200,   0, 0, 			/*  8 N/S one way */
	 WEST,   ILL,   ILL,   ILL,   ILL, ONEWAY, 200,   0, 0, 			/*  9 N/W one way */
	  ILL, NORTH,   ILL,   ILL,   ILL, ONEWAY, 200,   0, 0, 			/* 10 E/N one way */
	  ILL, SOUTH,   ILL,   ILL,   ILL, ONEWAY, 200,   0, 0, 			/* 11 E/S one way */
	  ILL,  WEST,   ILL,   ILL,   ILL, ONEWAY, 200,   0, 0, 			/* 12 E/W one way */
	  ILL,   ILL, NORTH,   ILL,   ILL, ONEWAY, 200,   0, 0, 			/* 13 S/N one way */
	  ILL,   ILL,  EAST,   ILL,   ILL, ONEWAY, 200,   0, 0, 			/* 14 S/E one way */
	  ILL,   ILL,  WEST,   ILL,   ILL, ONEWAY, 200,   0, 0, 			/* 15 S/W one way */
	  ILL,   ILL,   ILL, NORTH,   ILL, ONEWAY, 200,   0, 0, 			/* 16 W/N one way */
	  ILL,   ILL,   ILL,  EAST,   ILL, ONEWAY, 200,   0, 0, 			/* 17 W/E one way */
	  ILL,   ILL,   ILL, SOUTH,   ILL, ONEWAY, 200,   0, 0, 			/* 18 W/S one way */
	  ILL,   ILL,   ILL,   ILL, NORTH,  START,   0,   0, 0, 			/* 19 start N */
	  ILL,   ILL,   ILL,   ILL,  EAST,  START,   0,   0, 0, 				/* 20 start E */
	  ILL,   ILL,   ILL,   ILL, SOUTH,  START,   0,   0, 0, 			/* 21 start S */
	  ILL,   ILL,   ILL,   ILL,  WEST,  START,   0,   0, 0, 				/* 22 start W */
	  END,   ILL,   ILL,   ILL,   ILL,    END, 500,   0, 0, 				/* 23 finish N */
	  ILL,   END,   ILL,   ILL,   ILL,    END, 500,   0, 0, 				/* 24 finish E */
	  ILL,   ILL,   END,   ILL,   ILL,    END, 500,   0, 0, 				/* 25 finish S */
	  ILL,   ILL,   ILL,   END,   ILL,    END, 500,   0, 0, 				/* 26 finish W */
	SOUTH,   ILL, NORTH,   ILL,   ILL,    RES, 200,   0, 0, 			/* 27 N/S reservoir*/
	  ILL,  WEST,   ILL,  EAST,   ILL,    RES, 200,   0, 0, 			/* 28 E/W reservoir*/
	SOUTH,   ILL, NORTH,   ILL,   ILL,  BONUS, 200,   0, 0, 		/* 29 N/S bonus */
	  ILL,  WEST,   ILL,  EAST,   ILL,  BONUS, 200,   0, 0, 			/* 30 E/W bonus */
	  ILL,   ILL,   ILL,   ILL,   ILL,  BLOCK,   0,   0, 0, 				/* 31 obstacle */
	  ILL,   ILL,   ILL,   ILL,   ILL,  EMPTY,   0,   0, 0, 				/* 32 empty */
};

#define	empty	(&piece[NPIECE-1])
#define	SIZE	48	/* width or height of square pieces */
#define	RAD	4	/* radius of pipes, must be even and divide RESRAD, SIZE */
#define	RESRAD	18	/* radius of reservoir */

/*
 * Per-level data
 */
int level;			/* current game level */
int goodelay;		/* delay before goo moves */
int igoospeed;		/* initial goo speed */
int goospeed;		/* how fast the goo flows */
int ibonusdist;		/* initial bonus distance */
int bonusdist;		/* distance before bonuses accrue */

int dist[NPLAYER];	/* distribution of pieces given to player */
struct piece *distp[NPLAYER];

#define	NUPCOMING	100
struct piece *upcoming[NUPCOMING];
struct piece **upcomingp=upcoming, **eupcoming=upcoming;
int init[NCLASS];	/* initial number of pieces of each class */

/*
 * Per-board data
 */
#define	BORDER	4
#define	NX	10
#define	NY	7

struct square{
	struct piece *piece;
	int nentry;
	int mark;
}board[NY][NX];

Rectangle boardr, stackr, scorer;

#define	NSTACK	5
struct piece *stack[NSTACK];

/*
 * animation goodies
 */
int busy;			/* should not respond to the mouse */
int goox, gooy;		/* square currently containing goo */
struct square *goosquare;
int goodir;		/* where did the goo enter? (index in piece->exit) */
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(time<now) fprintf(stderr, "time %d < now %d\n", time, now);
	for(p=agenda; p!=&agenda[NAGENDA] && p->f && 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].time<future){
		now=agenda[0].time;
		f=agenda[0].f;
		for(p=&agenda[1];p!=&agenda[NAGENDA] && p->f;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*x<xsq; x++) {
					line(resdisk[i], Pt(SIZE/2-x, SIZE/2-y), Pt(SIZE/2+x, SIZE/2-y), 0, 0, 0, red, ZP);
					line(resdisk[i], Pt(SIZE/2-x, SIZE/2+y), Pt(SIZE/2+x, SIZE/2+y), 0, 0, 0, red, ZP);
					line(resmask[i], Pt(SIZE/2-x, SIZE/2-y), Pt(SIZE/2+x, SIZE/2-y), 0, 0, 0, display->white, 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(goodist<SIZE/2) fillgoo(goodir, END, goodist);
		else fillgoo(END, goosquare->piece->exit[goodir], goodist-SIZE/2);
		goodist+=RAD/2;
		if(goodist==SIZE) goto DoneSquare;
		break;
	case RES:
		if(goodist<SIZE/2){
			fillgoo(goodir, END, goodist);
			goodist+=RAD/2;
		}
		else if(goodist<SIZE/2+RESRAD){
			fillres(goodist-SIZE/2);
			if(goospeed!=FAST) delay=RESDELAY;
			goodist++;
		}
		else{
			fillgoo(END, goosquare->piece->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 && x<NX-1 && 1<y && y<NY-1 && board[y][x].piece->class==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<NPLAYER){
					switch(board[y][x].nentry){
					case 0:
						addscore(-100, 0);
						explodex=x;
						explodey=y;
						explodepiece=empty;
						busy=NDESTROY;
						//putpiece(x, y, empty);
						sched(now, explode);
						while(busy) {
							k = eread(Emouse|timek, &ev);
							if(k == Emouse)
								mouse3(ev.mouse);
							fugit();
						}
						break;
					case 2:
						doublecross++;
						break;
					}
				}
			}
			// doublecross=doublecross/5*5; // too difficult for me -- Axel.
			if(doublecross){
				sprintf(buf, "Bonus for %d crosses", doublecross);
				message(buf);
				waitticks(200, timek);
				addscore(2000+1000*doublecross, 0);
			}
			while(ecanmouse()) mouse3(emouse());
			if(bonusdist) break;
			message("Click to continue");
			waitclick();
		}
		outscore();
		message("Game over [click]");
		waitclick();
		score=0;
		message("New game ...        ");
		waitticks(50, timek);
	}
}

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.