shithub: puzzles

ref: 3224333549a381765671e17c31043211ac6bd4c4
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

//#define DIRECTDRAW
//#define PROFILE

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

struct blitter {
	Image *blimg;
};

frontend *fe = nil;

struct proftimes {
	long draw;
} ptimes;

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

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
	frontend *fe = (frontend*)handle;
#ifdef DIRECTDRAW
	string(screen, addpt(Pt(x, y), fe->ZP), fe->colors[color], ZP, font, text);
#else
	string(fe->image, Pt(x, y), fe->colors[color], ZP, font, text);
#endif
}

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

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

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

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++) {
#ifdef DIRECTDRAW
		points[i].x = coords[i*2+0] + fe->ZP.x;
		points[i].y = coords[i*2+1] + fe->ZP.y;
#else
		points[i].x = coords[i*2+0];
		points[i].y = coords[i*2+1];
#endif
	}
	
#ifdef DIRECTDRAW
	if (fillcolor > 0)
		fillpoly(screen, points, npoints, 0, fe->colors[fillcolor], ZP);
	if (outlinecolor > 0)
		poly(screen, points, npoints, Endsquare, Endsquare, 1, fe->colors[outlinecolor], ZP);
#else
	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);
#endif
	
	free(points);
}

static void
p9_draw_circle(void *handle, int cx, int cy, int radius, int fillcolor, int outlinecolor)
{
	frontend *fe = (frontend*)handle;
#ifdef DIRECTDRAW
	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);
#else
	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);
#endif
}

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;
	//print("draw_update\n");
	//chanprint(fe->cs->ctl, showcmd);
}

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

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

long drawtime;

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

static void
p9_end_draw(void *handle)
{
	frontend *fe = (frontend*)handle;
	chanprint(fe->cs->ctl, showcmd);
#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), fe->image, nil, Pt(x, y)); // fix ZP if needed
}

static void
p9_blitter_load(void *handle, blitter *bl, int x, int y)
{
	frontend *fe = (frontend*)handle;
	draw(fe->image, Rect(x, y, x + bl->blimg->r.max.x, y + bl->blimg->r.max.y), bl->blimg, nil, Pt(x, 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;
	Point p;
	
	createrow(cs, "rowmain");
	
	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 size %d %d 500 %d", p.x, p.y, p.y);
	c_game = createbox(cs, "c_game");
	chanprint(cs->ctl, "c_game border 1");
#ifdef DIRECTDRAW
	chanprint(cs->ctl, "c_game image background");
#else
	chanprint(cs->ctl, "c_game image frame");
#endif
	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 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");
	controlwire(c_settings, "event", c);
	controlwire(b_settings, "event", c);
	
	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, Mousectl *mousectl)
{
	float *colors;
	int ncolors;
	int r, g, b;
	float bgcol[3];
	Channel *c, *d;
	
	fe->image = allocimage(display, screen->r, screen->chan, 0, 0);
	
	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);
	
	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(fe->image, "frame");
	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;
	
	if (fe->image) {
		freeimage(fe->image);
		fe->image = nil;
	}
	newsize = resize(&resizenop);
	fe->image = allocimage(display, Rect(0, 0, newsize.x, newsize.y), screen->chan, 0, 0);
	if (0 && resizenop)
		return;
	draw(screen, screen->r, fe->background, nil, ZP);
	
#ifndef DIRECTDRAW
	midend_force_redraw(fe->me);
#endif
	
	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);
	
#ifdef DIRECTDRAW
	if (fe->showframe == GAME)
		midend_force_redraw(fe->me);
#endif
	
	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)
{
	if (frame == GAME)
		midend_force_redraw(fe->me);
	fe->showframe = frame;
	chanprint(fe->cs->ctl, "stackmain reveal %d", frame);
}

int
keyev(Rune k)
{
	switch (k) {
	case 'q':
	case 127:
		return 1; /* return 1 to quit */
	case 'n':
		midend_process_key(fe->me, 0, 0, UI_NEWGAME);
		chanprint(fe->cs->ctl, showcmd);
		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;
	Control *c;
	
	if (fe->showframe != GAME)
		goto Ctrl;
	
	c = controlcalled("c_game");
	
	if (!ptinrect(m->xy, c->rect))
		goto Ctrl;
	
	x = m->xy.x - c->rect.min.x;
	y = m->xy.y - c->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_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_BUTTON);
	if ( ((*lm)&4) && !(m->buttons&4))
		r = midend_process_key(fe->me, x, y, RIGHT_RELEASE);
	if (!((*lm)&4) &&  (m->buttons&2))
		r = midend_process_key(fe->me, x, y, RIGHT_BUTTON);
	if (r >= 0) {
		chanprint(fe->cs->ctl, showcmd);
	}
	*lm = m->buttons;

	return;

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

void
threadmain(int argc, char **argv)
{
	int lastmouse;
	char *s, *args[6];
	int doprintoptions = 0;
	char *wintitle;
	Channel *c;
	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, 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 '-':
		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");
	}
	
	mousectl = initmouse(nil, screen);
	keyboardctl = initkeyboard(nil);
	
	initcontrols();
	
	initfe(fe, mousectl);
	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;
	
	proccreate(timerproc, a[1].c, 4096);
	
	for (;;) {
		switch (alt(a)) {
		case 0: /* libcontrol event channel */	
			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], "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");
			}
			break;
		case 1: /* timer */
			tick(delta);
			break;
		case 2: /* mouse */
			processmouse(&m, &lastmouse);
			break;
		case 3: /* keyboard */
			if (keyev(rune))
				goto Out;
			break;
		}
	}
Out:
	threadexitsall(nil);
}