shithub: cflood

ref: 3eb5bf930b5cce1f8c6eb7851ebaa158ceaadfc8
dir: /games/cflood.c/

View raw version
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <event.h>

enum{
	/* sid */
	Ssmall,
	Snormal,
	Slarge,
	Scustom,

	/* state */
	Tgame,
	Twin,
	Tfail,

	/* cell flags & masks */
	Flood = 1<<7,
	Redraw = 1<<6,
	ColorMask = 0x0f,

	NumColors = 6,
	ButtonSize = 32
};

static int sizes[] = {
	14, 21, 28, 1
};

static int turns[] = {
	25, 35, 50, 1
};

static const ulong srccolors[NumColors] = {
	0x6060a8ff,
	0xf6f61dff,
	0x46b0e0ff,
	0x7ea020ff,
	0xf070a0ff,
	0xdc4a20ff
};

static char *mstr[] = {
	"14x14 / 25",
	"25x25 / 35",
	"28x28 / 50",
	"exit",
	nil, /* the extra for "custom" */
	nil
};

static int size;
static int sid;
static int state;
static int clickwait;
static int turnsleft;
static uchar *cells;
static Image *colors[NumColors];
static Rectangle buttons[NumColors];
static Image *colora, *colorb;

static void
floodneighbours(uchar color, int x, int y);

static void
redraw(Image *screen, int full)
{
	static Point p, sp, strsize;
	int i, x, y, w, h, csize, left;
	uchar *c;
	Rectangle r;
	char s[64];
	Font *f;

	sp.x = sp.y = 0;

	/* clear the old caption */
	draw(screen, Rect(p.x, p.y, p.x+strsize.x, p.y+strsize.y), colora, nil, ZP);

	if(state == Tgame)
		sprint(s, "%d", turnsleft);
	else if(state == Twin)
		sprint(s, "You won using %d turns (of %d)", turns[sid]-turnsleft, turns[sid]);
	else if(state == Tfail)
		sprint(s, "You failed");

	f = display->defaultfont;
	strsize = stringsize(f, s);

	w = Dx(screen->r);
	h = Dy(screen->r) - ButtonSize - 4 - strsize.y;
	csize = (w < h ? w : h) / size;
	w = size*csize;
	left = screen->r.min.x + (Dx(screen->r) - w)/2;

	/* buttons top */
	y = screen->r.min.y + h + 2 + strsize.y;

	if(full){
		/* background */
		draw(screen, screen->r, colora, nil, ZP);

		/* buttons */
		x = left + (w - NumColors*ButtonSize)/2;
		for(i = 0; i < NumColors; i++, x += ButtonSize){
			buttons[i] = Rect(x, y, x+ButtonSize, y+ButtonSize);
			draw(screen, buttons[i], colors[i], nil, ZP);
		}
	}

	/* caption */
	p.x = left + w/2 - strsize.x/2;
	p.y = y - strsize.y;
	string(screen, p, colorb, sp, f, s);

	/* cells */
	c = cells;
	for(x = 0; x < size; x++){
		for(y = 0; y < size; y++, c++){
			if((*c & Redraw) != 0 || full){
				*c &= ~Redraw;
				r.min.x = left + x*csize;
				r.min.y = screen->r.min.y + 2 + y*csize;
				r.max.x = r.min.x + csize;
				r.max.y = r.min.y + csize;
				draw(screen, r, colors[*c & ColorMask], nil, ZP);
			}
		}
	}

	flushimage(display, 1);
}

static void
floodrecurse(uchar color, int x, int y)
{
	uchar *c;
	c = &cells[x + y*size];
	if((*c & Flood) == 0 && (*c & ColorMask) == color){
		floodneighbours(color, x, y);
	}
}

static void
floodneighbours(uchar color, int x, int y)
{
	cells[x + y*size] = color|Flood|Redraw;

	if(x > 0)
		floodrecurse(color, x-1, y);
	if(x < size-1)
		floodrecurse(color, x+1, y);
	if(y > 0)
		floodrecurse(color, x, y-1);
	if(y < size-1)
		floodrecurse(color, x, y+1);
}

static int
reflood(uchar color)
{
	int n, x, y;

	color &= ColorMask;

	n = 0;
	for(x = 0; x < size; x++){
		for(y = 0; y < size; y++){
			if(cells[x + y*size] & Flood){
				floodneighbours(color, x, y);
				n++;
			}
		}
	}
	return n;
}

static void
flood(uchar color)
{
	int n;

	if((cells[0] & Flood) != 0 && (cells[0] & ColorMask) == color)
		return;

	if(!turnsleft)
		return;

	turnsleft--;
	n = reflood(color);

	if(n == size*size){
		state = Twin;
		clickwait = 1;
	}else if(!turnsleft){
		state = Tfail;
		clickwait = 1;
	}

	redraw(screen, 0);
}

static void
newgame(int sid)
{
	uchar *c;
	int i, maxsize;

	state = Tgame;
	size = sizes[sid];
	turnsleft = turns[sid];
	clickwait = 0;

	if(cells == nil){
		maxsize = (size > sizes[Slarge]) ? size : sizes[Slarge];
		cells = malloc(maxsize*maxsize);
	}

	/* randomize */
	c = cells;
	for(i = 0; i < size*size; i++){
		*c++ = nrand(NumColors);
	}

	cells[0] |= Flood|Redraw;
	reflood(cells[0]);
	redraw(screen, 1);
}

void
eresized(int new)
{
	if(new && getwindow(display, Refnone) < 0)
		sysfatal("can't reattach to window: %r");
	redraw(screen, 1);
}

static void
usage(void)
{
	fprint(2, "usage: cflood [-s size] [-t turns]\n");
	exits("usage");
}

void
main(int argc, char** argv)
{
	int key, p, i, oldbuttons, sidmax, inv;
	Event e;
	Mouse m;
	Menu menu;
	Rectangle r;

	sid = Ssmall;
	inv = 0;
	ARGBEGIN{
	case 'b':
		inv = 1;
		break;
	case 's':
		sid = Scustom;
		sizes[sid] = atoi(ARGF());
		if(sizes[sid] < 1)
			usage();
		break;
	case 't':
		sid = Scustom;
		turns[sid] = atoi(ARGF());
		if(turns[sid] < 1)
			usage();
		break;
	default:
		usage();
	}ARGEND

	if(initdraw(0, 0, "cflood") < 0)
		sysfatal("initdraw failed");

	r = Rect(0, 0, 1, 1);
	for(i = 0; i < NumColors; i++)
		colors[i] = allocimage(display, r, CMAP8, 1, srccolors[i]);

	colora = inv ? display->black : display->white;
	colorb = inv ? display->white : display->black;

	einit(Emouse);
	menu.item = mstr;
	menu.lasthit = 0;
	srand(time(0));

	sidmax = (sid > Slarge) ? sid : Slarge;
	if(sid == Scustom){
		/* move "exit" and add "custom" size/turns */
		sidmax = sid;
		mstr[Scustom+1] = mstr[Scustom];
		mstr[Scustom] = smprint("%dx%d / %d", sizes[sid], sizes[sid], turns[sid]);
	}

	newgame(sid);

	for(oldbuttons = 0;;){
		key = event(&e);
		m = e.mouse;

		if(key != Emouse)
			continue;

		if(m.buttons & 4){
			p = emenuhit(3, &m, &menu);
			if(p >= Ssmall && p <= sidmax)
				newgame(sid = p);
			else if(p > 0)
				break;
		}else if(clickwait && !oldbuttons && m.buttons){
			newgame(sid);
		}else if(m.buttons & 1){
			for(i = 0; i < NumColors; i++){
				if(ptinrect(m.xy, buttons[i])){
					flood(i);
					break;
				}
			}
		}

		oldbuttons = m.buttons;
	}

	exits(nil);
}