shithub: puzzles

ref: cef5fe0310139ee6d16db580e30cb0bc5657c54b
dir: /plan9.c/

View raw version
#include <npe.h>
#include <thread.h>
#include <draw.h>
#include <mouse.h>
#include <keyboard.h>
#include <control.h>

/********************

usage and controls:

n - new game
u - undo
r - redo
s - solve

Everything else is forwarded to the game.

If the game doesn't redraw properly, try pressing the "game"
button.

*********************/

#undef PI
#include "puzzles.h"

#ifdef COMBINED
#error Plan 9 should not be COMBINED
#endif

//#define PROFILE

int dolog = 0;
int logfd = -1;
int logpipe[2];
#define LOG(c) { if (dolog) fprint(logpipe[1], "%s\n", c); }
void
Log(char *fmt, ...)
{
	va_list arg;
	if (!dolog)
		return;
	va_start(arg, fmt);
	vfprint(logfd, fmt, arg);
	va_end(arg);
}

typedef struct Ft Ft;
struct Ft {
	int type;
	int size;
	Font *font;
};

struct frontend {
	midend *me;
	Image *background;
	Image **colors;
	int ncolors;
	Point ZP;
	Rectangle rect;
	Controlset *cs;
	Channel *c;
	Channel *settingschan;
	int showframe;
	int timeractive;
	Ft *fonts;
	int nfonts;
};

struct blitter {
	Image *blimg;
};

frontend *fe = nil;

struct proftimes {
	long draw;
} ptimes;

enum {
	GAME = 0,
	SETTINGS = 1,
};

Font*
findfontfile(int type, int size)
{
	Font *f;
	char buf[128];
	char *fixed = "/lib/font/bit/lucidasans/typeunicode.%d.font";
	char *variable = "/lib/font/bit/lucidasans/unicode.%d.font";
	
	/* find font based on size */
	while (size > 0) {
		snprint(buf, sizeof(buf), type == FONT_FIXED ? fixed : variable, size);
		if (access(buf, OREAD)) {
			f = openfont(display, buf);
			if (f == nil) {
				Log("error opening font file %s\n", buf);
				return font;
			}
			return f;
		}
	}
	Log("Unable to find proper font. Falling back to default font\n");
	return font;
}

Font*
findfont(frontend *fe, int type, int size)
{
	Ft *n;
	char buf[128];
	for (int i = 0; i < fe->nfonts; i++) {
		if (fe->fonts[i].type == type && fe->fonts[i].size == size)
			return fe->fonts[i].font;
	}
	fe->nfonts++;
	fe->fonts = realloc(fe->fonts, sizeof(Ft) * fe->nfonts);
	assert(fe->fonts);
	n = &fe->fonts[fe->nfonts-1];
	n->type = type;
	n->size = size;
	n->font = findfontfile(type, size);
	return n->font;
}

void
frontend_default_colour(frontend *, float *output)
{
	output[0] = .9;
	output[1] = .9;
	output[2] = .9;
}

void
get_random_seed(void **randseed, int *randseedsize)
{
	long *t = malloc(sizeof(long));
	assert(t);
	time(t);
	*randseed = (void*)t;
	*randseedsize = sizeof(long);
}

void
deactivate_timer(frontend *fe)
{
	fe->timeractive = 0;
}

void activate_timer(frontend *fe)
{
	fe->timeractive = 1;
}

void fatal(const char *fmt, ...)
{
    va_list ap;

    fprint(2, "fatal error: ");

    va_start(ap, fmt);
    vfprint(2, fmt, ap);
    va_end(ap);

    fprint(2, "\n");
    sysfatal("error");
}

#ifdef DEBUGGING
void debug_printf(const char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    vfprint(1, fmt, ap);
    va_end(ap);
}
#endif

void
usage(void)
{
	fprint(2, "usage: %s [-ho] [--options]\n", argv0);
	exits(nil);
}

char *showcmd = "c_game show";

static void p9_draw_text(void *handle, int x, int y, int fonttype, int fontsize, int align, int color, const char *text)
{
	// todo: align, fontsize, fonttype
	Font *f;
	Point p, size;
	frontend *fe = (frontend*)handle;
	Rectangle cl;
	
	f = findfont(fe, fonttype, fontsize);
	
	p.x = x;
	p.y = y;
	size = stringsize(f, text);
	
	if (align & ALIGN_VCENTRE)
		p.y -= size.y / 2;
	else
		p.y -= size.y;
	
	if (align & ALIGN_HCENTRE)
		p.x -= size.x / 2;
	else if (align & ALIGN_HRIGHT)
		p.x -= size.x;
	
	cl = screen->clipr;
	screen->clipr = screen->r;
	string(screen, addpt(p, fe->ZP), fe->colors[color], ZP, f, text);
	screen->clipr = cl;
}

static void
p9_draw_rect(void *handle, int x, int y, int w, int h, int color)
{
	frontend *fe = (frontend*)handle;
	draw(screen, rectaddpt(Rect(x, y, x+w, y+h), fe->ZP), fe->colors[color], nil, ZP);
}

static void
p9_draw_line(void *handle, int x1, int y1, int x2, int y2, int color)
{
	frontend *fe = (frontend*)handle;
	line(screen, addpt(Pt(x1, y1), fe->ZP), addpt(Pt(x2, y2), fe->ZP), Endsquare, Endsquare, 0, fe->colors[color], ZP);
}

static void
p9_draw_thick_line(void *handle, float thickness, float x1, float y1, float x2, float y2, int color)
{
	frontend *fe = (frontend*)handle;
	line(screen, addpt(Pt(x1, y1), fe->ZP), addpt(Pt(x2, y2), fe->ZP), Endsquare, Endsquare, thickness-1, fe->colors[color], ZP);
}

static void
p9_draw_poly(void *handle, const int *coords, int npoints, int fillcolor, int outlinecolor)
{
	Point *points;
	frontend *fe = (frontend*)handle;
	
	points = malloc(npoints * sizeof(Point));
	for (int i = 0; i < npoints; i++) {
		points[i].x = coords[i*2+0] + fe->ZP.x;
		points[i].y = coords[i*2+1] + fe->ZP.y;
	}
	
	if (fillcolor >= 0)
		fillpoly(screen, points, npoints, 0, fe->colors[fillcolor], ZP);
	if (outlinecolor >= 0)
		poly(screen, points, npoints, Endsquare, Endsquare, 0, fe->colors[outlinecolor], ZP);
	
	free(points);
}

static void
p9_draw_circle(void *handle, int cx, int cy, int radius, int fillcolor, int outlinecolor)
{
	frontend *fe = (frontend*)handle;
	Point c = addpt(Pt(cx, cy), fe->ZP);
	fillellipse(screen, c, radius, radius, fe->colors[fillcolor], ZP);
	ellipse(screen, c, radius, radius, 0, fe->colors[outlinecolor], ZP);
}

static void
p9_draw_update(void *handle, int x, int y, int w, int h)
{
	USED(handle, x, y, w, h);
	//frontend *fe = (frontend*)handle;
}

static void
p9_clip(void *handle, int x, int y, int w, int h)
{
	frontend *fe = (frontend*)handle;
	screen->clipr = rectaddpt(Rect(x, y, x + w, y + h), fe->ZP);
}

static void
p9_unclip(void *handle)
{
	frontend *fe = (frontend*)handle;
	screen->clipr = screen->r;
}

long drawtime;

static void
p9_start_draw(void *handle)
{
	USED(handle);
	LOG("start_draw");
#ifdef PROFILE
	drawtime = times(nil);
#endif
}

static void
p9_end_draw(void *handle)
{
	frontend *fe = (frontend*)handle;
	flushimage(display, 1);
	LOG("end_draw");
#ifdef PROFILE
	ptimes.draw = times(nil) - drawtime;
#endif
}

static void
p9_status_bar(void *handle, const char *text)
{
	frontend *fe = (frontend*)handle;
	
	chanprint(fe->cs->ctl, "l_status value %q", text);
}

static blitter*
p9_blitter_new(void *handle, int w, int h)
{
	blitter *bl;
	USED(handle);
	bl = malloc(sizeof(blitter));
	bl->blimg = allocimage(display, Rect(0, 0, w, h), screen->chan, 0, 0);
	return bl;
}

static void
p9_blitter_free(void *handle, blitter *bl)
{
	USED(handle);
	freeimage(bl->blimg);
	free(bl);
}

static void
p9_blitter_save(void *handle, blitter *bl, int x, int y)
{
	frontend *fe = (frontend*)handle;
	draw(bl->blimg, Rect(x, y, x + bl->blimg->r.max.x, y + bl->blimg->r.max.y), screen, nil, Pt(x, y)); // fix position
}

static void
p9_blitter_load(void *handle, blitter *bl, int x, int y)
{
	frontend *fe = (frontend*)handle;
	draw(screen, Rect(x, y, x + bl->blimg->r.max.x, y + bl->blimg->r.max.y), bl->blimg, nil, Pt(x, y)); // fix position
}

static const drawing_api p9_drawing = {
	p9_draw_text,
	p9_draw_rect,
	p9_draw_line,
	p9_draw_poly,
	p9_draw_circle,
	p9_draw_update,
	p9_clip,
	p9_unclip,
	p9_start_draw,
	p9_end_draw,
	p9_status_bar,
	p9_blitter_new,
	p9_blitter_free,
	p9_blitter_save,
	p9_blitter_load,
	nil, nil, nil, nil, nil, nil, nil, nil, /* {begin,end}_{doc,page,puzzle}, line_width, line_dotted */
	nil, /* text_fallback */
#ifdef NO_THICK_LINE
	nil,
#else
	p9_draw_thick_line,
#endif
};

static int rgb2col(int r, int g, int b)
{
	return (r<<24) | (g<<16) | (b<<8) | 0xFF;
}

static frontend*
new_window(void)
{
	frontend *fe;
	
	fe = mallocz(sizeof(frontend), 1);
	if (!fe)
		sysfatal("error: out of memory!");
	
	fe->me = midend_new(fe, &thegame, &p9_drawing, fe);
	
	return fe;
}

void
initui(Controlset *cs, Channel *c)
{
	Control *b_game, *b_settings, *c_settings;
	Point p;
	
	createrow(cs, "rowmain");
	
	b_game = createtextbutton(cs, "b_game");
	p = stringsize(font, "game");
	chanprint(cs->ctl, "b_game border 1");
	chanprint(cs->ctl, "b_game align center");
	chanprint(cs->ctl, "b_game text game");
	chanprint(cs->ctl, "b_game size %d %d 500 %d", p.x, p.y, p.y);
	controlwire(b_game, "event", c);
	
	b_settings = createtextbutton(cs, "b_settings");
	p = stringsize(font, "settings");
	chanprint(cs->ctl, "b_settings border 1");
	chanprint(cs->ctl, "b_settings align center");
	chanprint(cs->ctl, "b_settings text settings");
	chanprint(cs->ctl, "b_settings size %d %d 500 %d", p.x, p.y, p.y);
	c_settings = createcolumn(cs, "c_settings");
	chanprint(cs->ctl, "c_settings hide");
	controlwire(b_settings, "event", c);
	
	createlabel(cs, "l_status");
	
	chanprint(cs->ctl, "rowmain add b_game\nrowmain add b_settings");
	
	activate(b_game);
	activate(b_settings);
}

void
initfe(frontend *fe, Mousectl *mousectl)
{
	float *colors;
	int ncolors;
	int r, g, b;
	float bgcol[3];
	Channel *c, *d;
	
	frontend_default_colour(fe, bgcol);
	fe->background = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, rgb2col(bgcol[0]*255., bgcol[1]*255., bgcol[2]*255.));
	
	colors = midend_colours(fe->me, &ncolors);
	fe->colors = mallocz(ncolors * sizeof(Image*), 1);
	for (int i = 0; i < ncolors; i++) {
		r = colors[i*3+0] * 255.;
		g = colors[i*3+1] * 255.;
		b = colors[i*3+2] * 255.;
		fe->colors[i] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, rgb2col(r, g, b));
	}
	free(colors);
	
	fe->settingschan = chancreate(sizeof(char*), 0);
	c = chancreate(sizeof(Mouse), 0);
	d = chancreate(sizeof(Rune), 0);
	fe->cs = newcontrolset(screen, d, c, mousectl->resizec);
	fe->c = chancreate(sizeof(char*), 0);
	ctldeletequits = 1;
	namectlimage(display->black, "i_black");
	namectlimage(display->white, "i_white");
	namectlimage(fe->background, "background");
	initui(fe->cs, fe->c);
}

int windowset = 0;

Point
resize(int *resizenop)
{
	int x, y, fd;
	x = Dx(screen->r);
	y = Dy(screen->r) - 2*font->height;
	
	midend_size(fe->me, &x, &y, 1, 1.);
	
	// to test
	if (0 && !windowset) {
		fd = open("/dev/wctl", OWRITE);
		if (fd >= 0) {
			fprint(fd, "resize -dx %d -dy %d\n", x+2, y + 2*font->height);
			close(fd);
			windowset = 1;
		}
	} else
		windowset = 0;
	
	/* do not resize if we're waiting for the window resize */
	*resizenop = windowset;
	return Pt(x, y);
}

void
resizecontrolset(Controlset *cs)
{
	Rectangle rmenu, rarea, sarea;
	int resizenop;
	Control *ctl;
	Point newsize;
	
	if (getwindow(display, Refnone) < 0) {
		sysfatal("resize failed: %r");
	}
	
	rmenu = screen->r;
	rmenu.max.y = rmenu.min.y + font->height;
	rarea = screen->r;
	rarea.min.y = rmenu.min.y + font->height;
	rarea.max.y = rarea.max.y - font->height;
	sarea = screen->r;
	sarea.min.y = sarea.max.y - font->height;
	
	newsize = resize(&resizenop);
	if (0 && resizenop)
		return;
	draw(screen, screen->r, fe->background, nil, ZP);
	
	fe->rect = rarea;
	fe->ZP = rarea.min;
	
	chanprint(cs->ctl, "rowmain rect %R\nrowmain show", rmenu);
	chanprint(cs->ctl, "l_status rect %R\nl_status show", sarea);
	
	switch (fe->showframe) {
	case GAME:
		chanprint(cs->ctl, "c_settings hide");
		midend_force_redraw(fe->me);
		flushimage(display, 1);
		break;
	case SETTINGS:
		chanprint(cs->ctl, "c_settings rect %R\nc_settings reveal\nc_settings show", rarea);
		break;
	}
	
	LOG("resizecontrolset");
}

void
printoptions(config_item *c)
{
	char *t;
	char *s = nil, *cnames[16], *ckws[16];
	int n = 0, m;
	config_item *cfg = midend_get_config(fe->me, CFG_PREFS, &t);
	
	print("Options:\n");
	while (cfg->type != C_END) {
		switch (cfg->type) {
		case C_STRING:
			s = cfg->u.string.sval;
			break;
		case C_BOOLEAN:
			s = cfg->u.boolean.bval ? "1" : "0";
			break;
		case C_CHOICES:
			print("      Choices:\n");
			n = getfields(cfg->u.choices.choicenames, cnames, 16, 1, ":");
			m = getfields(cfg->u.choices.choicekws, ckws, 16, 1, ":");
			assert(n == m && cfg->u.choices.selected < n);
			s = ckws[cfg->u.choices.selected];
			break;
		}
		print("--%s=%s\n      %s\n", cfg->kw, s, cfg->name);
		if (cfg->type == C_CHOICES) {
			print("      Choices:\n");
			for (int i = 0; i < n; i++) {
				print("      %s: %s\n", ckws[i], cnames[i]);
			}
		}
		cfg++;
	}
}

void
parseoption(config_item *cfg, char *keyval)
{
	char *arg[2];
	int n;
	
	n = getfields(keyval, arg, 2, 1, "=");
	if (n != 2)
		usage(); // exits
	
	while (cfg && cfg->type != C_END)
		if (strcmp(cfg->kw, arg[0]) != 0)
			cfg++;
	
	if (!cfg || cfg->type == C_END) {
		fprint(2, "no valid option\n");
		return;
	}
	
	print("%s : %s\n", cfg->kw, cfg->name);
	return;
	
	switch (cfg->type) {
	case C_STRING:
		cfg->u.string.sval = arg[1];
		print("is string");
		break;
	case C_BOOLEAN:
		n = atoi(arg[1]);
		print("is boolean");
		cfg->u.boolean.bval = n ? 1 : 0;
		break;
	case C_CHOICES:
		// TODO
		print("is choices");
		fprint(2, "not implemented yet!\n");
		break;
	case C_END:
	default:
		print("not found");
		break;
	}
}

void
addoption(config_item *cfg)
{
	char buf[128];
	int isnew;
	Control *label, *entry;
	
	snprint(buf, 128, "l_%s", cfg->name);
	label = controlcalled(buf);
	isnew = 0;
	if (!label) {
		label = createlabel(fe->cs, buf);
		isnew = 1;
	}
	chanprint(fe->cs->ctl, "%q value %q", buf, cfg->name);
	chanprint(fe->cs->ctl, "%q align center", buf);
	chanprint(fe->cs->ctl, "%q size 100 %d 500 %d", buf, font->height*2, font->height*2);
	if (isnew)
		chanprint(fe->cs->ctl, "c_settings add %q", buf);
	snprint(buf, 128, "e_%s", cfg->name);
	
	isnew = 0;
	entry = controlcalled(buf);
	if (!entry)
		isnew = 1;
	switch (cfg->type) {
	case C_STRING:
		if (!entry)
			entry = createentry(fe->cs, buf);
		chanprint(fe->cs->ctl, "%q border 1", buf);
		chanprint(fe->cs->ctl, "%q size 100 %d 500 %d", buf, font->height, font->height);
		chanprint(fe->cs->ctl, "%q value %q", buf, cfg->u.string.sval);
		break;
	case C_BOOLEAN:
		if (!entry)
			entry = createbutton(fe->cs, buf);
		chanprint(fe->cs->ctl, "%q border 1", buf);
		chanprint(fe->cs->ctl, "%q size 100 %d 500 %d", buf, font->height, font->height);
		chanprint(fe->cs->ctl, "%q value %d", buf, cfg->u.boolean.bval);
		break;
	case C_CHOICES:
		// todo
		break;
	}
	if (entry && isnew) {
		controlwire(entry, "event", fe->settingschan);
		activate(entry);
		chanprint(fe->cs->ctl, "c_settings add %q", buf);
	}
}

int configcats[] = { CFG_SETTINGS, /* CFG_SEED, */ CFG_PREFS };
config_item *configs[] = { nil, /* nil, */ nil };

void
loadoptions(void)
{
	char *t;
	config_item *cfg;
	Control *c, *info;
	
	for (int i = 0; i < nelem(configcats); i++) {
		cfg = midend_get_config(fe->me, configcats[i], &t);
		if (configs[i]) {
			//free_cfg(configs[i]);
			configs[i] = nil;
		}
		configs[i] = cfg;
		
		while (cfg && cfg->type != C_END) {
			addoption(cfg);
			cfg++;
		}
	}
	
	c = controlcalled("b_savecfg");
	/* if already set up, early return */
	if (c)
		return;
	
	info = createlabel(fe->cs, "l_cfginfo");
	chanprint(fe->cs->ctl, "l_cfginfo align centerleft");
	chanprint(fe->cs->ctl, "l_cfginfo size 50 %d 500 %d", font->height, font->height);
	chanprint(fe->cs->ctl, "c_settings add l_cfginfo");
	
	c = createtextbutton(fe->cs, "b_savecfg");
	chanprint(fe->cs->ctl, "b_savecfg text Save");
	chanprint(fe->cs->ctl, "b_savecfg border 1");
	chanprint(fe->cs->ctl, "b_savecfg align center");
	chanprint(fe->cs->ctl, "b_savecfg size 50 %d 100 %d", font->height, font->height);
	chanprint(fe->cs->ctl, "c_settings add b_savecfg");
	controlwire(c, "event", fe->c);
	activate(c);
}

int
saveoptions(void)
{
	char *s;
	int r = 1;
	for (int i = 0; i < nelem(configcats); i++) {
		s = midend_set_config(fe->me, configcats[i], configs[i]);
		if (s) {
			chanprint(fe->cs->ctl, "l_cfginfo value %q", s);
			r = 0;
		}
	}
	loadoptions();
	LOG("saved options");
	return r;
}

void
setoption(char *name, char *value)
{
	int n;
	config_item *cfg;
	for (int i = 0; i < nelem(configs); i++) {
		cfg = configs[i];
		while (cfg && cfg->type != C_END) {
			if (strcmp(cfg->name, name) == 0)
				goto Found;
			cfg++;
		}
	}
	return;
Found:
	switch (cfg->type) {
	case C_STRING:
		cfg->u.string.sval = value;
		break;
	case C_BOOLEAN:
		n = atoi(value);
		cfg->u.boolean.bval = n ? 1 : 0;
		break;
	case C_CHOICES:
		// todo
		break;
	}
}

void
showframe(int frame)
{
	fe->showframe = frame;
	resizecontrolset(fe->cs);
	LOG("showframe");
}

int
keyev(Rune k)
{
	if (fe->showframe != GAME) {
		send(fe->cs->kbdc, &k);
		return 0;
	}
	
	switch (k) {
	case 'q':
	case 127:
		return 1; /* return 1 to quit */
	case 'n':
		midend_process_key(fe->me, 0, 0, UI_NEWGAME);
		break;
	case 'u':
		midend_process_key(fe->me, 0, 0, UI_UNDO);
		break;
	case 'r':
		midend_process_key(fe->me, 0, 0, UI_REDO);
		break;
	case 's':
		midend_process_key(fe->me, 0, 0, UI_SOLVE);
		break;
	default:
		if (midend_process_key(fe->me, 0, 0, k) == PKR_QUIT)
			return 1;
	}
	return 0;
}

void
tick(float delta)
{
#ifdef PROFILE
	char msg[128];
#endif
	
	if (fe->timeractive) {
		midend_timer(fe->me, delta);
	}
	
#ifdef PROFILE
	snprint(msg, 128, "draw: %ld", ptimes.draw);
	chanprint(fe->cs->ctl, "l_status value %q", msg);
#endif
}

typedef struct Tickinfo Tickinfo;
struct Tickinfo {
	long totaltime;
	long lasttick;
	float delta;
};

void
timerproc(void *v)
{
	Channel *c;
	Tickinfo ti;
	long newtime;
	
	c = v;
	ti.totaltime = times(nil);
	
	for (;;) {
		sleep(20);
		newtime = times(nil);
		ti.delta = (newtime - ti.totaltime) / 1000.;
		ti.totaltime = newtime;
		send(c, &ti.delta);
	}
}

void
processmouse(Mouse *m, int *lm)
{
	int x, y, r;
	
	if (fe->showframe != GAME)
		goto Ctrl;
	
	if (!ptinrect(m->xy, fe->rect))
		goto Ctrl;
	
	x = m->xy.x - fe->rect.min.x;
	y = m->xy.y - fe->rect.min.y;
	r = -1;
	
	if ( ((*lm)&1) && !(m->buttons&1))
		r = midend_process_key(fe->me, x, y, LEFT_RELEASE);
	if ( ((*lm)&1) &&  (m->buttons&1))
		r = midend_process_key(fe->me, x, y, LEFT_DRAG);
	if (!((*lm)&1) &&  (m->buttons&1))
		r = midend_process_key(fe->me, x, y, LEFT_BUTTON);
	if ( ((*lm)&2) && !(m->buttons&2))
		r = midend_process_key(fe->me, x, y, MIDDLE_RELEASE);
	if ( ((*lm)&2) &&  (m->buttons&2))
		r = midend_process_key(fe->me, x, y, MIDDLE_DRAG);
	if (!((*lm)&2) &&  (m->buttons&2))
		r = midend_process_key(fe->me, x, y, MIDDLE_BUTTON);
	if ( ((*lm)&4) && !(m->buttons&4))
		r = midend_process_key(fe->me, x, y, RIGHT_RELEASE);
	if ( ((*lm)&4) &&  (m->buttons&4))
		r = midend_process_key(fe->me, x, y, RIGHT_DRAG);
	if (!((*lm)&4) &&  (m->buttons&4))
		r = midend_process_key(fe->me, x, y, RIGHT_BUTTON);
	if (r >= 0) {
	}
	*lm = m->buttons;

	return;

Ctrl:
	send(fe->cs->mousec, m);
}

void
doexit(void)
{
	if (dolog) {
		close(logfd);
	}
}

void
threadmain(int argc, char **argv)
{
	int lastmouse;
	char *s, *args[6];
	int doprintoptions = 0;
	char *wintitle;
	config_item *cfg;
	int changedprefs = 0;
	float delta;
	Mousectl *mousectl;
	Mouse m;
	Keyboardctl *keyboardctl;
	Rune rune;
	Alt a[] = {
		{ nil, &s, CHANRCV },
		{ nil, &delta, CHANRCV },
		{ nil, &m, CHANRCV },
		{ nil, &rune, CHANRCV },
		{ nil, &s, CHANRCV },
		{ nil, nil, CHANEND },
	};
	
	fe = new_window();
	
	wintitle = nil;
	cfg = midend_get_config(fe->me, CFG_SETTINGS, &wintitle);
	
	ARGBEGIN{
	case 'h':
		usage();
		break;
	case 'o':
		doprintoptions++;
		break;
	case 'l':
		dolog++;
		break;
	case '-':
		parseoption(cfg, ARGF());
		changedprefs++;
		break;
	}ARGEND;
	
	if (changedprefs) {	
		s = midend_set_config(fe->me, CFG_SETTINGS, cfg);
		if (s) {
			fprint(2, "error: %s\n", s);
			exits("error");
		}
	}
	
	if (doprintoptions) {
		printoptions(cfg);
		exits(nil);
	}
	
	atexit(doexit);
	
	if (dolog) {
		pipe(logpipe);
		logfd = create("/srv/puzzles", OWRITE|ORCLOSE, 0666);
		if (logfd < 0)
			sysfatal("error opening log file /srv/puzzles: %r");
		fprint(logfd, "%d", logpipe[0]);
		close(logpipe[0]);
		LOG(thegame.name);
	}
	
	if (initdraw(nil, nil, thegame.name) < 0) {
		sysfatal("initdraw failed: %r");
	}
	
	mousectl = initmouse(nil, screen);
	keyboardctl = initkeyboard(nil);
	
	initcontrols();
	
	initfe(fe, mousectl);
	loadoptions();
	midend_new_game(fe->me);
	resizecontrolset(fe->cs);
	
	a[0].c = fe->c;
	a[1].c = chancreate(sizeof(float), 0);
	a[2].c = mousectl->c;
	a[3].c = keyboardctl->c;
	a[4].c = fe->settingschan;
	
	proccreate(timerproc, a[1].c, 4096);
	
	for (;;) {
		switch (alt(a)) {
		case 0: /* libcontrol event channel */	
			tokenize(s, args, nelem(args));
			
			if (strcmp(args[0], "b_game:") == 0) {
				showframe(GAME);
				chanprint(fe->cs->ctl, "b_game value 0");
			} else
			if (strcmp(args[0], "b_settings:") == 0) {
				showframe(SETTINGS);
				chanprint(fe->cs->ctl, "b_settings value 0");
			} else
			if (strcmp(args[0], "b_savecfg:") == 0) {
				chanprint(fe->cs->ctl, "b_savecfg value 0");
				if (saveoptions()) {
					midend_new_game(fe->me);
					showframe(GAME);
				}
			} else {
				Log("unknown command: %s\n", args[0]);
			}
			break;
		case 1: /* timer */
			tick(delta);
			break;
		case 2: /* mouse */
			processmouse(&m, &lastmouse);
			break;
		case 3: /* keyboard */
			if (keyev(rune))
				goto Out;
			break;
		case 4: /* settings */
			tokenize(s, args, nelem(args));
			s = args[0] + 2; /* get rid of "e_" */
			s[strlen(s)-1] = 0; /* get rid of sender ":" */
			setoption(s, args[2]);
			break;
		}
	}
Out:
	threadexitsall(nil);
}