shithub: puzzles

ref: 9c7d5576c074bd7b9d0fe8a249df31da2df31c9e
dir: /plan9.c/

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

#undef PI
#include "puzzles.h"

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

struct frontend {
	Image *image;
	midend *me;
	Image *background;
	Image **colors;
	int ncolors;
	Point ZP;
	int topbarh;
	Controlset *cs;
	Channel *c;
	int showframe;
	Rectangle rect;
};

frontend *fe;

void
frontend_default_colour(frontend *fe, 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)
{
}

void activate_timer(frontend *fe)
{
}

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

static void p9_draw_text(void *handle, int x, int y, int fonttype, int fontsize, int align, int color, const char *text)
{
	frontend *fe = (frontend*)handle;
	string(fe->image, Pt(x, y), fe->colors[color], ZP, font, text);
}

static void
p9_draw_rect(void *handle, int x, int y, int w, int h, int color)
{
	frontend *fe = (frontend*)handle;
	draw(fe->image, Rect(x, y, x+w, y+h), 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(fe->image, Pt(x1, y1), Pt(x2, y2), Endsquare, Endsquare, 1, 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(fe->image, Pt(x1, y1), Pt(x2, y2), Endsquare, Endsquare, thickness, 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];
		points[i].y = coords[i*2+1];
	}
	
	if (fillcolor > 0)
		fillpoly(fe->image, points, npoints, 0, fe->colors[fillcolor], ZP);
	if (outlinecolor > 0)
		poly(fe->image, points, npoints, Endsquare, Endsquare, 1, 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 = Pt(cx, cy);
	fillellipse(fe->image, c, radius, radius, fe->colors[fillcolor], ZP);
	ellipse(fe->image, c, radius, radius, 0, fe->colors[outlinecolor], ZP);
}

static void
p9_draw_update(void *handle, int x, int y, int w, int h)
{
}

static void
p9_clip(void *handle, int x, int y, int w, int h)
{
}

static void
p9_unclip(void *handle)
{
}

static void
p9_start_draw(void *handle)
{
}

static void
p9_end_draw(void *handle)
{
	frontend* fe = (frontend*)handle;
	chanprint(fe->cs->ctl, "c_game image frame");
}

static void
p9_status_bar(void *handle, const char *text)
{
	frontend *fe = (frontend*)handle;
	
	chanprint(fe->cs->ctl, "l_status value %q", text);
	
	return;
	draw(fe->image, Rect(fe->ZP.x+10, fe->image->r.max.y-20, fe->image->r.max.x, fe->image->r.max.y), fe->background, nil, ZP);
	string(fe->image, Pt(fe->ZP.x+10, fe->image->r.max.y-20), display->black, ZP, font, text);
}

static blitter*
p9_blitter_new(void *handle, int w, int h)
{
	return nil;
}

static void
p9_blitter_free(void *handle, blitter *bl)
{
}

static void
p9_blitter_save(void *handle, blitter *bl, int x, int y)
{
}

static void
p9_blitter_load(void *handle, blitter *bl, int x, int y)
{
}

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_game, *c_settings, *stackmain, *menu;
	Control *l_status;
	Point p;
	
	menu = createrow(cs, "rowmain");
	chanprint(cs->ctl, "rowmain border 1");
	
	stackmain = createstack(cs, "stackmain");
	chanprint(cs->ctl, "stackmain border 1");
	controlwire(stackmain, "event", c);
	
	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 image i_white");
	chanprint(cs->ctl, "b_game light i_black");
	chanprint(cs->ctl, "b_game size %d %d 100 %d", p.x, p.y, p.y);
	c_game = createbox(cs, "c_game");
	chanprint(cs->ctl, "c_game image frame");
	chanprint(cs->ctl, "c_game border 1");
	controlwire(c_game, "event", c);
	controlwire(b_game, "event", c);
	
	b_settings = createtextbutton(cs, "b_settings");
	p = stringsize(font, "settings");
	chanprint(cs->ctl, "b_settings text settings");
	chanprint(cs->ctl, "b_settings border 1");
	chanprint(cs->ctl, "b_settings size %d %d 100 %d", p.x, p.y, p.y);
	c_settings = createcolumn(cs, "c_settings");
	controlwire(c_settings, "event", c);
	controlwire(b_settings, "event", c);
	
	l_status = createlabel(cs, "l_status");
	
	chanprint(cs->ctl, "stackmain add c_game c_settings");
	chanprint(cs->ctl, "rowmain add b_game\nrowmain add b_settings");
	
	activate(b_game);
	activate(b_settings);
	activate(c_game);
}

void
initfe(frontend *fe, Channel *c)
{
	float *colors;
	int ncolors;
	int r, g, b;
	int col;
	float bgcol[3];
	
	fe->topbarh = 20;
	fe->image = allocimage(display, screen->r, screen->chan, 0, 0);
	fe->ZP = screen->r.min;
	fe->ZP.y += fe->topbarh;
	
	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->cs = newcontrolset(fe->image, nil, nil, nil);
	fe->cs->clicktotype = 1;
	fe->c = c;
	ctldeletequits = 1;
	namectlimage(fe->image, "frame");
	namectlimage(display->black, "i_black");
	namectlimage(display->white, "i_white");
	initui(fe->cs, fe->c);
}

void
resize(void)
{
	int x, y;
	x = screen->r.max.x - screen->r.min.x;
	y = screen->r.max.y - screen->r.min.y;
	
	midend_size(fe->me, &x, &y, 1, 1.);
}

void
resizecontrolset(Controlset *cs)
{
	Rectangle rmenu, rarea, sarea;
	Control *ctl;
	
	if (getwindow(display, Refnone) < 0) {
		sysfatal("resize failed: %r");
	}
	
	rmenu = screen->r;
	rmenu.max.y = rmenu.min.y + 16;
	rarea = screen->r;
	rarea.min.y = rmenu.min.y + 16;
	rarea.max.y = rarea.max.y - 16;
	sarea = screen->r;
	sarea.min.y = sarea.max.y - 16;
	
	if (fe->image) {
		freeimage(fe->image);
		fe->image = nil;
	}
	fe->image = allocimage(display, screen->r, screen->chan, 0, 0);
	fe->rect = rarea;
	draw(screen, screen->r, fe->background, nil, ZP);
	resize();
	midend_redraw(fe->me);
	
	chanprint(cs->ctl, "rowmain rect %R\nrowmain show", rmenu);
	chanprint(cs->ctl, "c_game rect %R\nc_settings rect %R", rarea, rarea);
	chanprint(cs->ctl, "stackmain rect %R\nstackmain show", rarea);
	chanprint(cs->ctl, "stackmain reveal %d", fe->showframe);
	chanprint(cs->ctl, "l_status rect %R\nl_status show", sarea);
	
	ctl = controlcalled("c_game");
	fe->ZP = ctl->rect.min;
}

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
showframe(int frame)
{
	fe->showframe = frame;
	chanprint(fe->cs->ctl, "stackmain reveal %d", frame);
}

int
keyev(int k)
{
	switch (k) {
	case 'q':
	case 127:
		return 1; /* return 1 to quit */
	default:
		if (k >= 0 && midend_process_key(fe->me, 0, 0, k) == PKR_QUIT)
			return 1;
	}
	return 0;
}

void
threadmain(int argc, char **argv)
{
	int x, y, n;
	long l;
	char *s, *args[6];
	int doprintoptions = 0;
	char *wintitle;
	Channel *c;
	config_item *cfg;
	int changedprefs = 0;
	
	fe = new_window();
	
	wintitle = nil;
	cfg = midend_get_config(fe->me, CFG_SETTINGS, &wintitle);
	
	ARGBEGIN{
	case 'h':
		usage();
		break;
	case 'o':
		doprintoptions++;
		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);
	}
	
	if (initdraw(nil, nil, wintitle) < 0) {
		sysfatal("initdraw failed: %r");
	}
	
	initcontrols();
	
	c = chancreate(sizeof(char*), 0);
	initfe(fe, c);
	midend_new_game(fe->me);
	resizecontrolset(fe->cs);
	
	for (;;) {
		s = recvp(fe->c);
		n = tokenize(s, args, nelem(args));
		
		if (strcmp(args[0], "c_settings:") == 0) {
			print("c_settings event: %s\n", args[1]);
		} else
		if (strcmp(args[0], "c_game:") == 0) {
			if (strcmp(args[1], "mouse") == 0) {
				x = atoi(args[2]+1) - fe->ZP.x; /* ignore '[' */
				y = atoi(args[3]) - fe->ZP.y;
				n = atoi(args[4]);
				if (n&1)
					midend_process_key(fe->me, x, y, LEFT_BUTTON);
				if (n&2)
					midend_process_key(fe->me, x, y, MIDDLE_BUTTON);
				if (n&4)
					midend_process_key(fe->me, x, y, RIGHT_BUTTON);
			} else
			if (strcmp(args[1], "key") == 0) {
				l = strtol(args[2], nil, 0);
				if (keyev(l))
					break;
			} else
			print("c_game event: %s\n", args[1]);
		} else
		if (strcmp(args[0], "b_game:") == 0) {
			print("b_game pressed\n");
			showframe(0);
		} else
		if (strcmp(args[0], "b_settings:") == 0) {
			print("b_settings pressed\n");
			showframe(1);
		} else
			print("event from %s: %s\n", args[0], args[1]);
	}
	threadexitsall(nil);
}