wm: glendy

Download patch

ref: 1295f0e6835eba2fc4d87bcf1e3435b7cc55b130
author: mkf <mkf@cloud9p.org>
date: Wed Apr 17 13:29:43 EDT 2024

import sources

--- /dev/null
+++ b/Makefile
@@ -1,0 +1,22 @@
+CC=cc
+CFLAGS=-g -O0
+L=\
+	unix.o\
+	engine.o\
+
+all: cli
+
+%.o: %.c %.h
+	$(CC) $(CFLAGS) -c $< -o $@
+
+cli: ${L}
+	$(CC) $(CFLAGS) -o $@ ${L} cli.c
+
+srv: ${L}
+	$(CC) ${CFLAGS} -o $@ ${L} srv.c
+
+glendy-cli: cli.o ${L}
+	$(LD) $(LDFLAGS) -o $@ ${L} cli.o
+
+clean:
+	rm -f a.out *.8 *.6 *.o cli sdl srv ${L}
--- /dev/null
+++ b/README
@@ -1,0 +1,22 @@
+this is a improved version of games/glendy, also known as trap the cat game.
+
+game "engine" can be found in engine.c,
+it handles stuff related to game logic, and leaves client details to clients.
+clients must implment routines for interacting with user (taking input, showing the map).
+
+two clients currently are included, plan 9 client and cli client.
+new features:
+	- ablity to play as glenda
+	- multiplayer (local and networked)
+	- better colors!
+	- cli and sdl clients
+	- game server
+
+game logic is seprated from client details and interface,
+and can be found on engine.[ch], there are also some portablity code
+in unix.c for plan 9 systems.
+
+cli.c and gui9.c contain clients, there is also a server in srv.c
+neither of clients currently support networked multiplayer mode, so srv.c is unused currently :)
+
+there are some documention of engine details in doc.
--- /dev/null
+++ b/cli.c
@@ -1,0 +1,215 @@
+/*
+ * cli glendy, based on mitrchoviski's gui for plan 9
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+
+#include "unix.h"
+#include "engine.h"
+
+char *gface[2] = {"g", "g"}; /* glenda's face(es) */
+
+void
+drawlevel(void)
+{
+	int x, y;
+	
+	/* prints first row, this assumes SzX = SzY, is there a better way? */
+	printf("T%2d|", turn);
+	for(int i = 0 ; i < SzX ; i++)
+		printf("%2d |", i);
+
+	printf("\n");
+
+	for(x = 0; x < SzX; x++)
+	{
+		for(int i = 0 ; i < SzY+1 ; i++)
+			printf("----");
+		printf("\n");
+
+		/* show column number and have a zig-zag effect */
+		printf("%2d%s |", x, x % 2 ? "" : "| ");
+		// printf("%2d |", x);
+
+		for(y = 0; y < SzY; y++)
+		{
+			/* it's [y][x], not [x][y] */
+			switch(grid[y][x])
+			{
+				case Wall: 
+					printf(" * |");
+					break;
+				case Glenda:
+					/* fancy effect for glenda's face */
+					printf(" %s |", turn % 2 ? gface[0] : gface[1]);
+					break;
+				default:
+					printf("   |");
+					break;
+			}
+		}
+		printf("\n");
+	}
+	if(state == Won)
+	{
+		printf("trapper won\n");
+		exits(nil);
+	}
+	else if(state == Lost)
+	{
+		printf("glenda won\n");
+		exits(nil);
+	}
+}
+
+/* p x y */
+void
+proc_put(char *s)
+{
+	unsigned int x, y, r;
+
+	sscanf(s, "%u %n", &x, &r);
+	sscanf(s+r, "%u", &y);
+	
+	if(x > SzX || x < 0 || y > SzY || y < 0)
+	{
+		fprintf(stderr, "proc_put(): invalid input, x = %d, y = %d\n", x, y);
+		return;
+	}
+
+	if(doput(Pt(x, y)) == Wall)
+		fprintf(stderr, "There is already a wall in x = %d, y = %d\n", x, y);
+}
+
+/* m x y */
+void
+proc_move(char *s)
+{
+	int d;
+	
+	if(strcmp(s, "NE") == 0)
+		d = NE;
+	else if(strcmp(s, "E") == 0)
+		d =  E;
+	else if(strcmp(s, "SE") == 0)
+		d = SE;
+	else if(strcmp(s, "W") == 0)
+		d =  W;
+	else if(strcmp(s, "SW") == 0)
+		d = SW;
+	else if(strcmp(s, "NW") == 0)
+		d = NW;
+	else
+	{
+		fprintf(stderr, "proc_move(): huh?\n");
+		return;
+	}
+	
+	/* should check if there is a wall or something this way */
+	if(domove(d, findglenda()) == Wall)
+	{
+		fprintf(stderr, "There is a wall there!\n");
+		return;
+	}
+}
+
+/*
+ * handle input, which is in the form of
+ * p1> p xx yy
+ * which puts a wall in xx yy
+ * p2> m {NE, E, SE, W, SW, NW}
+ * which moves the bunny
+ * > r
+ * restarts the game
+ * > u
+ * undos last move
+ * > q
+ * quits the game
+ */
+void
+proc(char *s)
+{
+	char *t;
+	int oturn;
+
+	/* skip \n, so we don't need to process it later */
+	t = strchr(s, '\n');
+	*t = '\0';
+
+	oturn = turn;
+	/* s+2 skips command and first space after it */
+	switch(*s)
+	{
+		case 'p':
+			if(turn % 2 == 0)
+				proc_put(s+2);
+			else if(turn % 2 == 1)
+				fprintf(stderr, "glendy can't put!\n");
+			break;
+		case 'm':
+			if(turn % 2 == 0)
+				fprintf(stderr, "trapper can't move!\n");
+			else if(turn % 2 == 1)
+				proc_move(s+2);
+			break;
+		case 'u':
+			undo();
+			break;
+		case 'r':
+			/* maybe we need to put a confirm message here */
+			restart();
+			break;
+		case 'q':
+			exits(nil);
+			break;
+		default:
+			fprintf(stderr, "proc(): huh?\n");
+	}
+	/* only print the map if turn have changed */
+	if(turn != oturn)
+		drawlevel();
+}
+
+int
+input(void)
+{	char *s, *r;
+	int t;
+	
+	/* sang bozorg */
+	s = malloc(1024);
+
+	printf("%s> ", turn % 2 ? "glendy" : "trapper");
+	fflush(stdout); /* plan 9 */
+	r = fgets(s, 1024, stdin);
+	if(r == nil)
+	{
+		fprintf(stderr, "input(): error\n");
+		return Err;
+	}
+	else
+	{
+		proc(s);
+		return Ok;
+	}
+}
+
+int 
+main(int argc, char **argv)
+{
+	char r;
+	/*
+	 * todo: handle argv properly
+	 */
+
+	/* OpenBSD ignores this
+	 *	srand(time(nil));
+	 */
+	initlevel();
+	drawlevel();
+	while(input() != Err);
+
+	return 0;
+}
--- /dev/null
+++ b/engine.c
@@ -1,0 +1,347 @@
+#ifdef unix
+#include "unix.h"
+#else
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#endif
+
+#include "engine.h"
+
+int difficulty = DEasy;
+int state;
+int turn = 0;
+int ptype[2] = {Human, Computer}; /* human? computer? */
+
+int grid[SzX][SzY];
+int pgrid[SzX][SzY]; /* for undo */
+int ogrid[SzX][SzY]; /* so we can restart levels */
+
+void
+initlevel(void)
+{
+	int i, cnt, x, y;
+
+	for(x = 0; x < SzX; x++)
+		for(y = 0; y < SzY; y++)
+			ogrid[x][y] = Prev;
+
+	switch(difficulty)
+	{
+		case DEasy:
+			cnt = 10 + nrand(5);
+			break;
+		case DMed:
+			cnt = 5 + nrand(5);
+			break;
+		case DHard:
+			cnt = 1 + nrand(5);
+			break;
+		case DImp:
+		default:
+			cnt = 0;
+			break;
+	}
+	ogrid[SzX/2][SzY/2] = Glenda;
+
+	for(i = 0 ; i < cnt ; i++)
+	{
+		/*
+		it's unlikely, but possible:
+		we randomly pick a cell which already have a wall or glenda inside it
+		*/
+		do
+		{
+			x = nrand(SzX);
+			y = nrand(SzY);
+		}while(ogrid[x][y] != Prev);
+		ogrid[x][y] = Wall;
+	}
+
+	memcpy(grid, ogrid, sizeof grid);
+	state = Start;
+}
+
+Point
+movedir(int dir, Point p)
+{
+	int x = p.x;
+	int y = p.y;
+	
+	switch(dir)
+	{
+		case NE: 
+			return Pt(x+(y%2?1:0), y-1);
+		case E:
+			return Pt(x+1, y);
+		case SE:
+			return Pt(x+(y%2?1:0), y+1);
+		case SW:
+			return Pt(x+(y%2?0:-1), y+1);
+		case W:
+			return Pt(x-1, y);
+		case NW:
+			return Pt(x+(y%2?0:-1), y-1);
+		default:
+			sysfatal("andrey messed up big time");
+			/* should we keep that line around? it might be more useful than sysfatal */
+			// return Pt(-1, -1); 
+	}
+}
+
+/* reverse of movedir, tells the direction for a dst from src */
+int
+pointdir(Point src, Point dst)
+{
+	Point p;
+	/*
+	if(src.x < 0 || src.x > SzX || src.y < 0 || src.y > SzY)
+	||(dst.x < 0 || dst.x > SzX || dst.y < 0 || dst.y > SzY)
+		return Err;
+	*/
+	
+	/* hacky */
+	for(int i = NE ; i <= NW ; i++)
+	{
+		p = movedir(i, src);
+		if(p.x == dst.x && p.y == dst.y)
+			return i;
+	}
+	return Err;
+}
+
+/*
+Point
+movedir(int dir, Point p)
+{
+	switch(dir)
+	{
+		case NE:
+			if(p.y % 2)
+				return Pt(p.x+1, p.y-1);
+			else	
+				return Pt(p.x, p.y-1);
+		case E:
+			return Pt(p.x+1, p.y);
+		case SE:
+			if(p.y % 2)
+				return Pt(p.x+1, p.y+1);
+			else
+				return Pt(p.x, p.y+1);
+		case SW:
+			if(p.y % 2)
+				return Pt(p.x, p.y+1);
+			else
+				return Pt(p.x-1, p.y+1);
+		case W:
+			return Pt(p.x-1, p.y);
+		case NW:
+			if(p.y % 2)
+				return Pt(p.x, p.y-1);
+			else
+				return Pt(p.x-1, p.y-1);
+		default:
+			return Pt(-1, -1);
+	}
+}
+*/
+
+int
+domove(int dir, Point src)
+{
+	Point dst;
+
+	if(src.x == 0 || src.x == SzX-1 || src.y == 0 || src.y == SzY-1)
+		goto done;
+
+	dst = movedir(dir, src);
+	if(grid[dst.x][dst.y] == Wall)
+		return Wall;
+
+	grid[dst.x][dst.y] = Glenda;
+done:
+	grid[src.x][src.y] = Prev;
+
+	turn++;
+	return 0;
+}
+
+int
+doput(Point p)
+{
+	/* clients are expected to do their own error checking */
+	if(p.x > SzX || p.x < 0 || p.y > SzY || p.y < 0)
+		return Err;
+
+	if(grid[p.x][p.y] == Wall)
+		return Wall;
+
+	if(grid[p.x][p.y] == Glenda)
+		return Glenda;
+
+	/* take a copy for undo */
+	memcpy(pgrid, grid, sizeof grid);
+	grid[p.x][p.y] = Wall;
+
+
+	/* assumes defenders start game first */
+	if(state == Start)
+		state = Playing;
+
+	turn++;
+	/* reset the board scores */
+	for(int x = 0; x < SzX; x++)
+		for(int y = 0; y < SzY; y++)
+			if(grid[x][y] != Wall && grid[x][y] != Glenda)
+				grid[x][y] = 100;
+
+	/* we need it to check game state, even if not playing with computer */
+	nextglenda();
+	return Ok;
+
+}
+
+Point
+findglenda(void)
+{
+	for(int x = 0; x < SzX; x++)
+		for(int y = 0; y < SzY; y++)
+			if(grid[x][y] == 1000)
+				return Pt(x, y);
+	return Pt(-1, -1);
+}
+
+int
+checknext(int dir, Point p)
+{
+	int x = p.x;
+	int y = p.y;
+	
+	switch(dir)
+	{
+		case NE: 
+			return grid[x+(y%2?1:0)][y-1];
+		case E:
+			return grid[x+1][y];
+		case SE:
+			return grid[x+(y%2?1:0)][y+1];
+		case SW:
+			return grid[x+(y%2?0:-1)][y+1];
+		case W:
+			return grid[x-1][y];
+		case NW:
+			return grid[x+(y%2?0:-1)][y-1];
+		default:
+			sysfatal("andrey messed up big time");
+			// return -1; /* silence compiler */
+	}
+}
+
+/* the following two routines constitute the "game AI"
+* they score the field based on the number of moves
+* required to reach the edge from a particular point
+* scores > 100 are "dead spots" (this assumes the field 
+* is not larger than ~100*2
+* 
+* routines need to run at least twice to ensure a field is properly
+* scored: there are errors that creep up due to the nature of 
+* traversing the board
+*/
+int 
+score1(Point p)
+{
+	
+	int min = 999, next;
+
+	if(p.x == 0 || p.x == SzX-1 || p.y == 0 || p.y == SzY-1)
+		return 1; 		/* we can always escape from the edges */
+
+	for(int dir = NE; dir <= NW; dir++)
+	{
+		next = checknext(dir, p);
+		if(next < min)
+			min = next;
+	}
+	if(min == 999)
+		return 998;
+	return 1+min;
+}
+
+void
+calc(void)
+{
+	for(int i = 0; i < SzX; i++) /* assumes SzX = SzY */
+		for(int x = i; x < SzX-i; x++)
+			for(int y = i; y < SzY-i; y++)
+				if(grid[x][y] != Wall)
+					grid[x][y] = score1(Pt(x, y));
+}
+
+void
+nextglenda(void)
+{
+	int min = 1000, next, dir, nextdir = 0, count = 0;
+	Point p = findglenda();
+
+	calc();
+	calc();
+	calc();
+
+	grid[p.x][p.y] = Glenda;
+	
+	for(dir = NE; dir <= NW; dir++)
+	{
+		next = checknext(dir, p);
+		if(next < min)
+		{
+			min = next;
+			nextdir = dir;
+			++count;
+		}
+		else if(next == min)
+			nextdir = (nrand(++count) == 0) ? dir : nextdir;
+	}
+	if(min > 100 && min != 999)
+			state = Won;	
+	else if(ptype[1] == Computer)
+		domove(nextdir, p);
+
+	if(eqpt(findglenda(), Pt(-1, -1)))
+		state = Lost;
+}
+
+void
+restart(void)
+{
+	turn = 0;
+	memcpy(grid, ogrid, sizeof grid);
+	state = Start;
+}
+
+void
+undo(void)
+{
+	int g[SzX][SzY];
+
+	if(state == Start || turn < 2)
+		return;
+
+	/* revert turn counter, can be abused by one side */
+	turn -= 2;
+
+	/* swap grids */
+	memcpy(g, grid, sizeof grid);
+	memcpy(grid, pgrid, sizeof grid);
+	memcpy(pgrid, g, sizeof grid);
+}
+
+int
+checkstate(void)
+{
+	int i, j;
+	for(i = 0; i < SzX; i++)
+		for(j = 0; j < SzY; j++)
+			if(grid[i][j] == 'E')
+				return 0;
+	return 1;
+}
--- /dev/null
+++ b/engine.h
@@ -1,0 +1,66 @@
+enum
+{
+	/* difficulty levels (how many circles are initially occupied) */
+	DEasy,	/* 10≤x<15 */
+	DMed,	/* 5≤x<10 */
+	DHard,	/* 1≤x<5 */
+	DImp, /* 0 */
+
+	New = 0,
+	Undo,
+	Restart,
+	Exit,
+
+	/* dynamic? original game has a fixed grid size, but we don't need to abide by it */
+	SzX = 11,
+	SzY = 11, 
+
+	Border = 3,
+
+	/* movement directions */
+	NE,
+	E,
+	SE,
+	SW,
+	W,
+	NW,
+
+	/* player types */
+	Human,
+	Computer,
+	
+	Start = 0, /* game states */
+	Playing,
+	Won,	
+	Lost,
+
+	Prev = 100,
+	Wall = 999,
+	Glenda = 1000,
+
+	Ok,
+	Err,
+};
+
+extern int difficulty;
+extern int state;
+extern int turn;
+extern int ptype[2];
+
+extern int grid[SzX][SzY];
+extern int pgrid[SzX][SzY]; /* for undo */
+extern int ogrid[SzX][SzY]; /* so we can restart levels */
+
+void initlevel(void);
+Point movedir(int dir, Point p);
+int pointdir(Point src, Point dst);
+int domove(int dir, Point p);
+int doput(Point p);
+Point findglenda(void);
+int checknext(int dir, Point p);
+int score1(Point p);
+void calc(void);
+void nextglenda(void);
+void restart(void);
+void undo(void);
+int checkstate(void);
--- /dev/null
+++ b/gui9.c
@@ -1,0 +1,365 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include "engine.h"
+
+Font *font;
+
+Image	*gl;	/* glenda */
+Image 	*glm;	/* glenda's mask */
+Image	*cc; /* clicked */
+Image	*ec; /* empty; not clicked */
+Image 	*bg;
+Image 	*lost;
+Image	*won;
+
+int debug;
+
+char *mbuttons[] = 
+{
+	"Easy",
+	"Medium",
+	"Hard",
+	"Impossible",
+	0
+};
+
+char *rbuttons[] = 
+{
+	"New",
+	"Undo",
+	"Reset",
+	"Exit",
+	0
+};
+
+Menu mmenu = 
+{
+	mbuttons,
+};
+
+Menu rmenu =
+{
+	rbuttons,
+};
+
+Image *
+eallocimage(Rectangle r, int repl, uint color)
+{
+	Image *tmp;
+
+	tmp = allocimage(display, r, screen->chan, repl, color);
+	if(tmp == nil)
+		sysfatal("cannot allocate buffer image: %r");
+
+	return tmp;
+}
+
+Image *
+eloadfile(char *path)
+{
+	Image *img;
+	int fd;
+
+	fd = open(path, OREAD);
+	if(fd < 0) {
+		fprint(2, "cannot open image file %s: %r\n", path);
+		exits("image");
+	}
+	img = readimage(display, fd, 0);
+	if(img == nil)
+		sysfatal("cannot load image: %r");
+	close(fd);
+	
+	return img;
+}
+
+void
+allocimages(void)
+{
+	Rectangle one = Rect(0, 0, 1, 1);
+	
+	cc = eallocimage(one, 1, DGreyblue);
+	ec = eallocimage(one, 1, DPalebluegreen);
+	bg = eallocimage(one, 1, DPaleyellow);
+	lost = eallocimage(one, 1, DRed);
+	won = eallocimage(one, 1, DPalegreen);
+	gl = eloadfile("/lib/face/48x48x4/g/glenda.1");
+
+	glm = allocimage(display, Rect(0, 0, 48, 48), gl->chan, 1, DCyan);
+	if(glm == nil)
+        		sysfatal("cannot allocate mask: %r");
+
+    	draw(glm, glm->r, display->white, nil, ZP);
+    	gendraw(glm, glm->r, display->black, ZP, gl, gl->r.min);
+    	freeimage(gl);
+    	gl = display->black;
+
+
+}
+
+Point
+pix2board(int x, int y)
+{
+	float d, rx, ry, yh;
+	int ny, nx;
+
+	/* XXX: float→int causes small rounding errors */
+
+	d = (float)(Dx(screen->r) > Dy(screen->r)) ? Dy(screen->r) -20: Dx(screen->r)-20;
+	rx = d/(float)SzX;
+	rx = rx/2.0;
+	ry =d/(float)SzY;
+	ry = ry/2.0;
+
+	yh = ry/3.73205082;
+
+	/* reverse board2pix() */
+	ny = (int)(((float)y - ry)/(2*ry - ((y>2*ry)?yh:0.0)) + 0.5); /* ny = (y - ry)/(2ry-yh) */
+	nx = (int)(((float)x - rx - (ny%2?rx:0.0))/(rx*2.0) + 0.5); /* nx = (x - rx - rx)/2rx */
+	
+	if (nx >= SzX)
+		nx = SzX-1;
+	if (ny >=SzY)
+		ny = SzY-1;
+
+	return Pt(nx, ny);
+}
+
+/* unnecessary calculations here, but it's fine */
+Point
+board2pix(int x, int y)
+{
+	float d, rx, ry, yh;
+	int nx, ny;
+
+	d = (float)(Dx(screen->r) > Dy(screen->r)) ? Dy(screen->r) -20 : Dx(screen->r) -20;
+	rx = d/(float)SzX;
+	rx = rx/2.0;
+	ry = d/(float)SzY;
+	ry = ry/2.0;
+
+	yh = ry/3.73205082;
+
+	nx = (int)((float)x*rx*2.0+rx +(y%2?rx:0.0)); /* nx = x*(2rx) + rx + rx (conditional) */
+	ny = (int)((float)y*(ry*2.0-(y>0?yh:0.0)) + ry); /* ny = y*(2ry-yh) +ry */
+	return Pt(nx, ny);
+}
+
+void
+drawlevel(void)
+{
+	Point p;
+	int  x, y, rx, ry, d;
+	char *s = nil;
+
+	if(state == Won)
+		draw(screen, screen->r, won, nil, ZP);
+	else if(state == Lost)
+		draw(screen, screen->r, lost, nil, ZP);
+	else
+		draw(screen, screen->r, bg, nil, ZP);
+
+	d = (Dx(screen->r) > Dy(screen->r)) ? Dy(screen->r) -20: Dx(screen->r) -20;
+	rx = (int)ceil((float)(d-2*Border)/(float)SzX)/2;
+	ry = (int)ceil((float)(d-2*Border)/(float)SzY)/2;
+
+	for(x = 0; x < SzX; x++) {
+		for(y = 0; y < SzY; y++)
+		{
+			p = board2pix(x, y);
+			switch(grid[x][y])
+			{
+				case Wall: 
+					fillellipse(screen, addpt(screen->r.min, p), rx, ry, cc, ZP);
+					break;
+				case Glenda:
+					p = addpt(screen->r.min, p);
+					fillellipse(screen, p, rx, ry, ec, ZP);
+					p = subpt(p, Pt(24, 24));
+					draw(screen, Rpt(p, addpt(p, Pt(48, 48))), gl, glm, ZP);
+					break;
+				default:
+					fillellipse(screen, addpt(screen->r.min, p), rx, ry, ec, ZP);
+					USED(s);
+					if(debug)
+					{
+						s = smprint("%d", grid[x][y]);
+						string(screen, addpt(screen->r.min, p), display->black, ZP, font, s);
+						free(s);
+					}
+	
+					break;
+			}
+		}
+	}
+	flushimage(display, 1);
+}
+void
+move(Point m)
+{
+	int dir;
+	Point g, p, nm;
+
+	nm = subpt(m, screen->r.min);
+
+	/* figure out where the click falls */
+	p = pix2board(nm.x, nm.y);
+	g = findglenda();
+	
+	dir = pointdir(g, p);
+	
+	if(grid[p.x][p.y] >= 999 || dir == Err)
+		return;
+
+	/* find the direction to p from our currently pos, then move */
+	domove(dir, g);
+}
+
+void
+put(Point m)
+{
+	Point p, nm;
+
+	nm = subpt(m, screen->r.min);
+
+	/* figure out where the click falls */
+	p = pix2board(nm.x, nm.y);
+	
+	if(grid[p.x][p.y] >= 999)
+		return;
+
+	doput(p);
+}
+
+void
+resize(void)
+{
+	int fd, size = (Dx(screen->r) > Dy(screen->r)) ? Dy(screen->r) + 20 : Dx(screen->r)+20; 
+
+	fd = open("/dev/wctl", OWRITE);
+	if(fd >= 0)
+	{
+		fprint(fd, "resize -dx %d -dy %d", size, size);
+		close(fd);
+	}
+}
+
+void
+eresized(int new)
+{
+	if(new && getwindow(display, Refnone) < 0)
+		sysfatal("can't reattach to window");
+	
+	drawlevel();
+}
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-dg]\n", argv0);
+	exits("usage");
+}
+
+void 
+main(int argc, char **argv)
+{
+	Mouse m;
+	Event ev;
+	int e, mousedown=0;
+
+	/* todo, add flags for human playing */
+	ARGBEGIN{
+		case 'D':
+			debug++;
+			break;
+		case 'd':
+			//ptype[0] = Computer /* todo */
+			sysfatal("No computer player for defenders yet");
+			break;
+		case 'g':
+			ptype[1] = Computer;
+			break;
+		default:
+			usage();
+	}ARGEND
+	if(initdraw(nil, nil, "glendy") < 0)
+		sysfatal("initdraw failed: %r");
+	einit(Emouse);
+
+	resize();
+
+	srand(time(0));
+
+	allocimages();
+	initlevel();	/* must happen before "eresized" */
+	eresized(0);
+
+	for(;;)
+	{
+		e = event(&ev);
+		switch(e)
+		{
+			case Emouse:
+				m = ev.mouse;
+				if(m.buttons == 0)
+				{
+					if(mousedown && (state == Playing || state == Start))
+					{
+						mousedown = 0;
+						if(turn % 2 == 0)
+							put(m.xy);
+						else
+							move(m.xy);
+						drawlevel();
+					}
+				}
+				if(m.buttons&1)
+				{
+					mousedown = 1;
+				}
+				if(m.buttons&2)
+				{
+					switch(emenuhit(2, &m, &mmenu))
+					{
+					case 0:
+						difficulty = DEasy;
+						initlevel();
+						break;
+					case 1:				
+						difficulty = DMed;
+						initlevel();
+						break;
+					case 2:
+						difficulty = DHard;
+						initlevel();
+						break;
+					case 3:
+						difficulty = DImp;
+						initlevel();
+						break;
+					}
+					drawlevel();
+				}
+				if(m.buttons&4) {
+					switch(emenuhit(3, &m, &rmenu))
+					{
+						case New:
+							initlevel();
+							break;
+						case Undo:
+							undo();
+							break;
+						case Restart:
+							restart();
+							break;
+						case Exit:
+							exits(nil);
+					}
+					drawlevel();
+				}
+				break;
+			}
+	}
+}
--- /dev/null
+++ b/srv.c
@@ -1,0 +1,462 @@
+/*
+ * glendy (unix) server, based on cli version.
+ *
+ * it appears to my mind that socket.h and stdio.h have are in use
+ * since medieval times, which is really impressive of a design;
+ * ...for a torture device.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <time.h>
+#include <pthread.h>
+#include <sys/types.h> 
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include "unix.h"
+#include "engine.h"
+
+#define playersock sockfd[turn % 2]
+
+int pcount = 0;
+int sockfd[2];
+int debug = 1;
+char syncmsg[8];
+
+
+pthread_mutex_t pcount_mutex;
+// pthread_mutex_t print_mutex;
+
+/*
+ * it might feel odd why it starts from 10,
+ * becase then we can just read two bytes to see what's wrong.
+ */
+enum
+{
+	GLNDY_CONN = 10,
+	GLNDY_INIT = 11,
+	GLNDY_SYNC,
+	GLNDY_OK,
+	GLNDY_WAIT,
+	
+	GLNDY_TURN,
+	GLNDY_NOTTURN,
+	
+	GLNDY_CANTM,
+	GLNDY_CANTP,
+	GLNDY_INVALIDINPUT,
+	GLNDY_WALL,
+	
+	GLNDY_GWON,
+	GLNDY_GLOST,
+};
+
+static void
+error(const char *msg)
+{
+	perror(msg);
+	pthread_exit(NULL);
+}
+
+static void
+vprint(int fd, char *fmt, va_list arg)
+{
+	char *s = malloc(1024);
+	int n;
+
+	n = vsprintf(s, fmt, arg);
+	if(write(fd, s, n) < n)
+		error("couldn't write\n");
+	free(s);
+}
+
+static void
+print(int fd, char *fmt, ...)
+{
+	va_list arg;
+	va_start(arg, fmt);
+	
+	vprint(fd, fmt, arg);
+	va_end(arg);
+}
+
+static void
+printclients(char *fmt, ...)
+{
+	/* it seems arg gets changed during the first call to vprint, thus we need two */
+	va_list arg, arg2;
+	
+	va_start(arg, fmt);
+	va_start(arg2, fmt);
+	
+	vprint(sockfd[0], fmt, arg);
+	vprint(sockfd[1], fmt, arg2);
+	va_end(arg);
+	va_end(arg2);
+}
+
+static int
+dprint(char *fmt, ...)
+{
+	va_list va;
+	int n;
+
+	if(!debug)
+		return 0;
+	
+	va_start(va, fmt);
+	n = vfprintf(stderr, fmt, va);
+	va_end(va);
+	return n;
+}
+
+int
+setuplistener(int portno)
+{
+	int sockfd;
+	struct sockaddr_in serv_addr;
+
+	sockfd = socket(AF_INET, SOCK_STREAM, 0);
+	if (sockfd < 0) 
+		error("ERROR opening listener socket.");
+	
+	memset(&serv_addr, 0, sizeof(serv_addr));
+	serv_addr.sin_family = AF_INET;	
+	serv_addr.sin_addr.s_addr = INADDR_ANY;	
+	serv_addr.sin_port = htons(portno);		
+
+	if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
+		error("ERROR binding listener socket.");
+
+	dprint("listener on\n");
+
+	return sockfd;
+}
+
+void
+getclients(int lis_sockfd)
+{
+	int conns = 0;
+	socklen_t clilen;
+	struct sockaddr_in servaddr, clientaddr;
+	
+	while(conns < 2)
+	{
+		listen(lis_sockfd, 253 - pcount);
+		memset(&clientaddr, 0, sizeof(clientaddr));
+
+		clilen = sizeof(clientaddr);
+		sockfd[conns] = accept(lis_sockfd, (struct sockaddr *) &clientaddr, &clilen);
+	
+		if(sockfd[conns] < 0)
+			error("ERROR accepting a connection from a client.");
+
+		dprint("got connection %d\n", conns);
+		print(sockfd[conns], "CONN %d\n", conns);
+		dprint("client %d connected\n", conns);
+		
+		pthread_mutex_lock(&pcount_mutex);
+		pcount++;
+		dprint("pcount = %d\n", pcount);
+		pthread_mutex_unlock(&pcount_mutex);
+
+		if(conns == 0)
+		{
+			print(sockfd[conns], "WAIT\n");
+			dprint("someone is gonna wait until they friend comes\n");
+		}
+
+		conns++;
+	}
+}
+
+
+void
+drawlevel(void)
+{
+	/* prints first row, this assumes SzX = SzY, is there a better way? */
+	printf("T%2d|", turn);
+	for(int i = 0 ; i < SzX ; i++)
+		printf("%2d |", i);
+
+	printf("\n");
+
+	for(int x = 0; x < SzX; x++)
+	{
+		for(int i = 0 ; i < SzY+1 ; i++)
+			printf("----");
+		printf("\n");
+
+		/* show column number and have a zig-zag effect */
+		printf("%2d%s |", x, x % 2 ? "" : "| ");
+
+		for(int y = 0; y < SzY; y++)
+		{
+			/* it's [y][x], not [x][y] */
+			switch(grid[y][x])
+			{
+				case Wall: 
+					printf(" * |");
+					break;
+				case Glenda:
+					/* fancy effect for glenda's face */
+					printf(" g |");
+					break;
+				default:
+					printf("   |");
+					break;
+			}
+		}
+		printf("\n");
+	}
+}
+
+void
+sendlevel(void)
+{
+	if(state == Start)
+	{
+		printclients("INIT\n");
+	
+		for(int x = 0; x < SzX; x++)
+		{
+			for(int y = 0; y < SzY; y++)
+			{
+				/* it's [y][x], not [x][y] */
+				switch(grid[y][x])
+				{
+					case Wall: 
+						printclients("w %d %d\n", x, y);
+						break;
+					case Glenda:
+						printclients("g %d %d\n", x, y);
+						break;
+				}
+			}
+		}
+		printclients("SENT\n");
+	}
+	else if(state == Playing)
+	{
+		dprint("turn: %d\n", turn);
+		printclients("SYNC %d %s\n", turn, syncmsg);
+	}
+	else
+	{
+		if(state == Won)
+			printclients("WON\n");
+		else if(state == Lost)
+			printclients("LOST\n");
+			
+		restart(); /* will it execute twice? */
+	}
+}
+
+/* p x y */
+void
+proc_put(char *s)
+{
+	unsigned int x, y, r;
+
+	sscanf(s, "%u %n", &x, &r);
+	sscanf(s+r, "%u", &y);
+	
+	dprint("put %d %d\n", x, y);
+
+	if(x >= SzX || x < 0 || y >= SzY || y < 0)
+	{
+		print(playersock, "ERR invalidinput proc_put(): %d %d\n", x, y);
+		return;
+	}
+
+	r = doput(Pt(x, y)); 
+	if(r == Wall)
+		print(playersock, "WALL %d %d\n", x, y);
+	else
+	{
+		strncpy(syncmsg, s, 8);
+		dprint("syncmsg = %s\n", s);
+	}
+}
+
+/* m x y */
+void
+proc_move(char *s)
+{
+	int d;
+	Point p;
+	
+	p = findglenda();
+
+	if(strcmp(s, "NE") == 0)
+		d = NE;
+	else if(strcmp(s, "E") == 0)
+		d =  E;
+	else if(strcmp(s, "SE") == 0)
+		d = SE;
+	else if(strcmp(s, "W") == 0)
+		d =  W;
+	else if(strcmp(s, "SW") == 0)
+		d = SW;
+	else if(strcmp(s, "NW") == 0)
+		d = NW;
+	else
+	{
+
+		print(playersock, "ERR invalidinput proc_move(): %s\n", s);
+		return;
+	}
+	if(domove(d, p) == Wall)
+	{
+		print(playersock, "WALL %s %d %d\n", s, p.x, p.y);
+		return;
+	}
+	else
+	{
+		strncpy(syncmsg, s, 8);
+		dprint("syncmsg =  %s\n", syncmsg);
+	}
+}
+
+/*
+ * handle input, which is in the form of
+ * p1> p xx yy
+ * which puts a wall in xx yy
+ * p2> m {NE, E, SE, W, SW, NW}
+ * which moves the bunny
+ * > r
+ * restarts the game
+ * > u
+ * undos last move
+ * > q
+ * quits the game
+ */
+void
+proc(char *s)
+{
+	char *t;
+	int oturn, n;
+
+	/* skip \n, so we don't need to process it later */
+	t = strchr(s, '\n');
+	if(t != nil)
+		*t = '\0';
+
+	oturn = turn;
+	/* s+2 skips command and first space after it */
+	switch(*s)
+	{
+		case 'p':
+			if(turn % 2 == 0)
+				proc_put(s+2);
+			else if(turn % 2 == 1)
+				print(playersock, "CANTP\n");
+			break;
+		case 'm':
+			if(turn % 2 == 0)
+				print(playersock, "CANTM\n");
+			else if(turn % 2 == 1)
+				proc_move(s+2);
+			break;
+		case 'u':
+			undo();
+			break;
+		case 'r':
+			/* maybe we need to put a confirm message here */
+			restart();
+			break;
+		case 'q':
+		case '\0':
+			/* should we end the game at this point? XXX important */
+			break;
+		default:
+			print(playersock, "ERR invalidinput %c\n", *s);
+	}
+	/* only print the map if turn have changed */
+	if(turn != oturn)
+	{
+		if(debug)
+			drawlevel();
+		sendlevel();
+	}
+}
+
+int
+input(void)
+{	char *s, c;
+	int n = 0;
+	
+	/* sang bozorg */
+	s = malloc(1024);
+
+	print(playersock, "TURN\n");
+
+//	while((c = read(playersock, s + n++, 1)) != '\n' && c != '\0' && n < 1023)
+	
+	memset(s, 0, 1024);
+	while(read(playersock, s+n, 1) == 1 && n < 1024)
+	{
+		if(s[n] == '\n' || s[n] == '\0')
+		{
+			s[n] = '\0';
+			break;
+		}
+		n++;
+	}
+	dprint("got input: %s\n", s);
+
+	proc(s);
+	return Ok;
+}
+
+int 
+main(int argc, char **argv)
+{
+	int listenfd, port, result;
+	char r;
+//	pthread_t thread;
+	
+	/* it might not be a real human */
+	ptype[0] = Human;
+	ptype[1] = Human;
+
+	if(argc != 2)
+	{
+		fprintf(stderr, "usage: %s <port>\n", argv[0]);
+		exit(1);
+	}
+	port = atoi(argv[1]);
+	
+	listenfd = setuplistener(port);
+	pthread_mutex_init(&pcount_mutex, NULL);
+//	pthread_mutex_init(&print_mutex, NULL);
+	
+	
+	/* OpenBSD ignores this */
+	srand(time(nil));
+	
+	getclients(listenfd);
+	initlevel();
+	
+/*	result = pthread_create(&thread, NULL, (void*)input, NULL);
+//	if(result){
+//               printf("Thread creation failed with return code %d\n", result);
+//                exit(-1);
+//	}
+*/
+	
+	if(debug)
+		drawlevel();
+	
+	sendlevel();
+	while(input() != Err)
+		;
+	
+	close(listenfd);
+//	pthread_mutex_destroy(&pcount_mutex);
+//	pthread_mutex_destroy(&print_mutex);
+	return 0;
+}
--- /dev/null
+++ b/unix.c
@@ -1,0 +1,45 @@
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include "unix.h"
+
+Point
+Pt(int x, int y)
+{
+	Point p = {x, y};
+	return p;
+}
+
+_Noreturn void
+sysfatal(char *fmt, ...)
+{
+	va_list arg;
+
+	va_start(arg, fmt);
+	vfprintf(stderr, fmt, arg);
+	va_end(arg);
+
+	exit(127);
+}
+
+_Noreturn void
+exits(char *s)
+{
+	if(s == nil)
+		exit(0);
+	else
+		fprintf(stderr, "%s", s);
+	exit(127);
+}
+
+int
+eqpt(Point p, Point q)
+{
+	return p.x==q.x && p.y==q.y;
+}
+
+int
+nrand(int n)
+{
+	return rand() % n;
+}
--- /dev/null
+++ b/unix.h
@@ -1,0 +1,23 @@
+/* this code is uglier than what it should be */
+#pragma once
+#include <string.h>
+#include <stdio.h> /* replace with plan 9's print? */
+
+/* uncomment if your compiler doesn't support it (ANSI, C99)
+#define _Noreturn
+*/
+
+#define nil NULL
+
+typedef
+struct Point
+{
+	int x;
+	int y;
+}Point;
+
+Point Pt(int x, int y);
+_Noreturn void sysfatal(char *fmt, ...);
+_Noreturn void exits(char *s);
+int eqpt(Point p, Point q);
+int nrand(int n);