ref: 3376563bf1bead13cf8703bfe354661d6b1940ca
dir: /cflood.c/
#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;
Event e;
Mouse m;
Menu menu;
Rectangle r;
sid = Ssmall;
ARGBEGIN{
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(nil, nil, "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 = display->white;
colorb = 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);
}