💾 Archived View for runjimmyrunrunyoufuckerrun.com › src › games › chess.c captured on 2021-12-17 at 13:26:06.

View Raw

More Information

-=-=-=-=-=-=-

#include <u.h>
#include <libc.h>
#include <draw.h>
#include <event.h>
#include <keyboard.h>
#include <cursor.h>
enum {
	WHITE, BLACK, LSQ, DSQ, LASTMV, BAIZE, TARGET, BG, FG, HUES,
	P = 0, N = 4, B = 8, R = 12, Q = 16, K = 20,
	♖ = 2, ∅ = 28, ☠ = 31, PROMO = 3, OO = 160, OOO = 192, SPEC = 224,
	Enine = 8, Eengine = 16, PGN = 1, NINE, ENGINE, FORCE,
	MOVES = 512, MSGS = 6
};
ulong rgb[HUES] = {
	[WHITE] 0xBBBBBBFF, [BLACK] 0x333333FF, [LSQ] DPaleyellow,
	[DSQ] DYellowgreen, [LASTMV] = 0x2C00002C, [BAIZE] DDarkgreen,
	[TARGET] 0x00007070, [BG] DWhite, [FG] DBlack
};
typedef struct Msg Msg;
struct Msg{Msg *prev; char s[];} *lastmsg, *botmsg;
Image *hue[HUES], *mask[6];
Rectangle board, msgs;
int target[27], targets, king[2], ep, t, turns, mode, flip = 1;
char sq[175], buf[4096], p[2][64], *ms, *me, *pgnfile;
uchar src[MOVES], dst[MOVES], spec[MOVES];
int off[16] = {-15, -1, 1, 15, -16, -14, 14, 16, -31, -29, -17, -13, 13, 17, 29, 31};
char results[4][8] = {"*", "1-0", "0-1", "1/2-1/2"}, *result;
Cursor ⌛ = {{-1, -1},
	{0xff, 0x80, 0xff, 0x80, 0xff, 0x00, 0xfe, 0x00, 
	 0xff, 0x00, 0xff, 0x80, 0xff, 0xc0, 0xef, 0xe0, 
	 0xc7, 0xf0, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0xc0, 
	 0x03, 0xff, 0x03, 0xff, 0x03, 0xff, 0x03, 0xff},
	{0x00, 0x00, 0x7f, 0x00, 0x7e, 0x00, 0x7c, 0x00, 
	 0x7e, 0x00, 0x7f, 0x00, 0x6f, 0x80, 0x47, 0xc0, 
	 0x03, 0xe0, 0x01, 0xf0, 0x00, 0xe0, 0x00, 0x40, 
	 0x00, 0x00, 0x01, 0xb6, 0x01, 0xb6, 0x00, 0x00}};
uchar pixels[6][288] = {
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,128,0,0,0,0,15,224,0,0,0,0,31,240,0,0,0,0,31,240,0,0,0,0,63,248,0,0,0,0,63,248,0,0,0,0,63,248,0,0,0,0,31,240,0,0,0,0,63,248,0,0,0,0,127,252,0,0,0,0,127,252,0,0,0,0,255,254,0,0,0,0,255,254,0,0,0,0,255,254,0,0,0,0,255,254,0,0,0,0,255,254,0,0,0,0,255,254,0,0,0,0,255,254,0,0,0,0,127,252,0,0,0,0,127,252,0,0,0,0,255,254,0,0,0,1,255,255,0,0,0,3,255,255,128,0,0,7,255,255,192,0,0,7,255,255,192,0,0,15,255,255,224,0,0,15,255,255,224,0,0,31,255,255,240,0,0,31,255,255,240,0,0,31,255,255,240,0,0,63,255,255,248,0,0,63,255,255,248,0,0,63,255,255,248,0,0,63,255,255,248,0,0,63,255,255,248,0,0,63,255,255,248,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,6,0,0,0,0,7,15,0,0,0,0,7,159,0,0,0,0,7,255,128,0,0,0,7,255,248,0,0,0,7,255,254,0,0,0,7,255,255,128,0,0,15,255,255,192,0,0,31,255,255,224,0,0,31,255,255,240,0,0,63,255,255,248,0,0,63,255,255,252,0,0,127,255,255,252,0,0,127,255,255,254,0,0,255,255,255,254,0,1,255,255,255,254,0,1,255,255,255,255,0,3,255,255,255,255,0,3,255,255,255,255,0,3,255,254,255,255,128,7,255,248,255,255,128,7,255,241,255,255,128,7,255,225,255,255,128,7,255,193,255,255,128,7,255,3,255,255,192,3,254,7,255,255,192,3,252,15,255,255,192,0,60,31,255,255,192,0,0,63,255,255,192,0,0,127,255,255,192,0,0,127,255,255,192,0,0,255,255,255,192,0,0,255,255,255,192,0,1,255,255,255,192,0,1,255,255,255,192,0,1,255,255,255,192,0,1,255,255,255,192,0,1,255,255,255,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,128,0,0,0,0,7,224,0,0,0,0,7,224,0,0,0,0,15,240,0,0,0,0,7,224,0,0,0,0,7,224,0,0,0,0,7,224,0,0,0,0,15,240,0,0,0,0,31,248,0,0,0,0,127,254,0,0,0,0,255,255,0,0,0,0,255,255,0,0,0,1,255,255,128,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,7,255,255,224,0,0,7,255,255,224,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,1,255,255,128,0,0,0,255,255,0,0,0,0,255,255,0,0,0,0,255,255,0,0,0,1,255,255,128,0,0,1,255,255,128,0,0,1,255,255,128,0,0,1,255,255,128,0,0,1,255,255,128,0,0,0,255,255,0,0,0,0,63,252,0,0,0,0,31,248,0,0,0,255,255,255,255,0,3,255,255,255,255,192,7,255,255,255,255,224,7,255,255,255,255,224,3,192,0,0,3,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,7,224,248,0,0,63,143,241,252,0,0,63,255,255,252,0,0,63,255,255,252,0,0,63,255,255,252,0,0,63,255,255,252,0,0,63,255,255,252,0,0,31,255,255,248,0,0,15,255,255,240,0,0,7,255,255,224,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,3,255,255,192,0,0,7,255,255,224,0,0,15,255,255,240,0,0,15,255,255,240,0,0,31,255,255,248,0,0,31,255,255,248,0,0,31,255,255,248,0,0,127,255,255,254,0,0,255,255,255,255,0,0,255,255,255,255,0,0,255,255,255,255,0,0,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,192,0,0,0,7,7,224,224,0,0,15,135,225,240,0,0,15,199,227,240,0,6,15,199,227,240,96,31,15,129,129,240,248,31,135,1,128,225,248,31,131,1,128,193,248,31,3,131,193,192,248,15,3,131,193,192,240,7,3,131,193,192,224,3,131,195,195,193,192,3,195,195,195,195,192,3,195,231,231,195,192,3,227,231,231,199,192,1,227,231,231,199,128,1,243,247,239,207,128,1,251,255,255,223,128,1,255,255,255,255,128,1,255,255,255,255,128,0,255,255,255,255,0,0,255,255,255,255,0,0,255,255,255,255,0,0,255,255,255,255,0,0,127,255,255,254,0,0,127,255,255,254,0,0,63,255,255,252,0,0,31,255,255,248,0,0,31,255,255,248,0,0,31,255,255,248,0,0,31,255,255,248,0,0,31,255,255,248,0,0,63,255,255,252,0,0,63,255,255,252,0,0,127,255,255,254,0,0,127,255,255,254,0,0,63,255,255,252,0,0,31,255,255,248,0,0,3,255,255,192,0,0,0,31,248,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,3,192,0,0,0,0,3,192,0,0,0,0,3,192,0,0,0,0,3,192,0,0,0,0,63,252,0,0,0,0,63,252,0,0,0,0,63,252,0,0,0,0,63,252,0,0,0,0,3,192,0,0,0,0,3,192,0,0,0,0,3,192,0,0,0,0,3,192,0,0,0,0,7,224,0,0,0,0,15,240,0,0,0,0,15,240,0,0,0,127,15,240,254,0,0,255,207,243,255,0,1,255,255,255,255,128,3,255,255,255,255,192,7,255,255,255,255,224,7,255,255,255,255,224,7,255,255,255,255,224,7,255,255,255,255,224,7,255,255,255,255,224,7,255,255,255,255,224,7,255,255,255,255,224,3,255,255,255,255,192,1,255,255,255,255,128,0,255,255,255,255,0,0,127,255,255,254,0,0,63,255,255,252,0,0,31,255,255,248,0,0,31,255,255,248,0,0,31,255,255,248,0,0,31,255,255,248,0,0,31,255,255,248,0,0,31,255,255,248,0,0,31,255,255,248,0,0,31,255,255,248,0,0,15,255,255,240,0,0,7,255,255,224,0,0,7,255,255,224,0,0,1,255,255,128,0,0,0,31,248,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}};
void
highlight(int i, int h){
	Point o;
	o = flip < 0 ? Pt(8 - i % 15, i / 15 - 2) : Pt(i % 15 - 1, 9 - i / 15);
	o = addpt(board.min, mulpt(o, 48));
	draw(screen, Rpt(o, addpt(o, Pt(48, 48))), hue[h], nil, ZP);
}
void
drawboard(void){
	Point o;
	int i, j, n;
	char *s, *e;
	n = flip < 0 ? 143 : 31;
	for(o.y = board.max.y, i = 0; i < 8; i++){
		o.x = board.min.x;
		o.y -= 48;
		for(j = 0; j < 8; j++){
			draw(screen, Rect(o.x, o.y, o.x + 48, o.y + 48), hue[LSQ | n & 1], nil, ZP);
			if(sq[n] != ∅)
				draw(screen, Rect(o.x, o.y, o.x + 48, o.y + 48),
					hue[sq[n] & BLACK], mask[sq[n] >> 2], ZP);
			o.x += 48;
			n += flip;
		}
		n += 7 * flip;
	}
	for(i = 0; i < targets; i++) highlight(target[i], TARGET);
	if(t){
		highlight(src[t - 1], LASTMV);
		highlight(dst[t - 1], LASTMV);
	}
	o = screen->r.min;
	draw(screen, Rect(o.x, o.y, board.min.x, msgs.min.y), hue[BAIZE], nil, ZP);
	i = t - 480 / font->height & ~1;
	if(i < 0){
		o.y += i / -2 * font->height;
		i = 0;
	}
	for(j = 0, s = ms; j < i; j++) do ++s; while(s[-1] != ' ');
	while(i < turns && o.y + font->height < msgs.min.y){
		for(e = s + 3; e < me && e[-1] != ' '; e++);
		o = stringn(screen, o, hue[(i < t) << 1], ZP, font, s, e - s);
		s = e;
		if(i++ & BLACK) o = Pt(screen->r.min.x, o.y + font->height);
	}
	if(i & BLACK) o = Pt(screen->r.min.x, o.y + font->height);
	if(o.y + font->height < msgs.min.y && s < me)
		stringn(screen, o, hue[(t == turns) << 1], ZP, font, s, me - s - 1);
}
void
drawmsgs(void){
	Msg *msg;
	int y;
	draw(screen, msgs, hue[BG], nil, ZP);
	for(y = msgs.max.y, msg = botmsg; msg != nil && y > msgs.min.y; msg = msg->prev)
		string(screen, Pt(msgs.min.x, y -= font->height), hue[FG], ZP, font, msg->s);
}
void
eresized(int i){
	Point o;
	if(i && getwindow(display, Refnone) < 0)
		sysfatal("can't reattach to window");
	if((!i || Dy(screen->r) != 480 + MSGS * font->height)
	&& (i = open("/dev/wctl", OWRITE)) >= 0){
		fprint(i, "resize -dy %d", 480 + MSGS * font->height + 2 * Borderwidth);
		close(i);
	}
	draw(screen, screen->r, hue[BAIZE], nil, ZP);
	o = Pt(screen->r.min.x + screen->r.max.x - 384 >> 1, screen->r.min.y + 48);
	board = rectaddpt(Rect(0, 0, 384, 384), o);
	msgs = screen->r;
	msgs.min.y += 480;
	me[0] = flip > 0 ? 'a' : 'h';
	me[1] = '\0';
	o = Pt(board.min.x + 24 - stringwidth(font, "g") / 2, board.max.y + 4);
	for(i = 0; i < 8; i++, me[0] += flip, o.x += 48)
		string(screen, o, hue[LSQ], ZP, font, me);
	me[0] = flip > 0 ? '1' : '8';
	o = Pt(board.max.x + 8, board.max.y - 24 - font->height / 2);
	for(i = 0; i < 8; i++, me[0] += flip, o.y -= 48)
		string(screen, o, hue[LSQ], ZP, font, me);
	if(mode == PGN) strecpy(me, buf + sizeof(buf), pgnfile ? pgnfile : argv0);
	else seprint(me, buf + sizeof(buf), "%s vs %s", p[0], p[1]);
	o.x = board.min.x + board.max.x - stringwidth(font, me) >> 1;
	o.y = screen->r.min.y + 24 - font->height / 2;
	string(screen, o, hue[LSQ], ZP, font, me);
	drawboard();
	drawmsgs();
}
void
addmsg(void *s, int len){
	if((botmsg = malloc(sizeof(Msg) + len + 1)) == nil){
		fprint(2, "addmsg: malloc failed: %r\n");
		botmsg = lastmsg;
	}else{
		memcpy(botmsg->s, s, len);
		botmsg->s[len] = '\0';
		botmsg->prev = lastmsg;
		lastmsg = botmsg;
	}
	drawmsgs();
}
void
scrollmsgs(int dir){
	Msg *msg;
	int n;
	n = 1 - MSGS;
	switch(dir){
	case 1:
		for(msg = lastmsg; msg != botmsg; msg = msg->prev) n++;
		for(botmsg = lastmsg; n > 0; n--) botmsg = botmsg->prev;
		break;
	case -1:
		for(msg = botmsg; msg != nil && ++n < MSGS; msg = msg->prev);
		while(--n > 0) botmsg = botmsg->prev;
		break;
	case 0:
		for(msg = botmsg; msg != nil; msg = msg->prev) n++;
		while(--n > 0) botmsg = botmsg->prev;
		break;
	}
	drawmsgs();
}
int
safe(int victim, int enemy){
	int attacker, i;
	i = enemy == WHITE ? -16 : 14;
	if(sq[victim + i] == enemy) return 0;
	if(sq[victim + i + 2] == enemy) return 0;
	for(i = 0; i < 8; i++){
		for(attacker = victim + off[i]; sq[attacker] == ∅; attacker += off[i]);
		if((sq[attacker] & BLACK) == enemy)
			switch(sq[attacker] & ∅){
			case K: if(attacker == victim + off[i])
			case Q: return 0; break;
			case R: if(i < 4) return 0; break;
			case B: if(i & 4) return 0; break;
			}
	}
	for(enemy |= N; i < 16 && sq[victim + off[i]] != enemy; i++);
	return i == 16;
}
void try(int mover, int to){
	int capture;
	if(sq[to] != ☠ && (sq[to] == ∅ || (mover ^ sq[to]) & BLACK)){
		capture = sq[to];
		sq[to] = mover;
		if(safe((mover & ∅) == K ? to : king[mover & BLACK], ~mover & BLACK))
			target[targets++] = to;
		sq[to] = capture;
	}
}
int
findtargets(int from){
	int mover, enemy, to, i, j;
	mover = sq[from];
	sq[from] = ∅;
	enemy = ~mover & BLACK;
	targets = 0;
	switch(mover & ∅){
	case P:
		i = enemy * 30 - 15;
		if(sq[to = from + i] == ∅){
			try(mover, to);
			if(from < 61 || from > 120) try(mover, to + i);
		}
		if(sq[to - 1] != ∅) try(mover, to - 1);
		if(sq[to + 1] != ∅) try(mover, to + 1);
		if(to - 1 == ep || to + 1 == ep){
			sq[ep - i] = ∅;
			try(mover, ep);
			sq[ep - i] = P | enemy;
		}
		break;
	case K:
		if(mover & â™– && safe(from, enemy)){
			for(to = from + 1; sq[to] == ∅; to++);
			if(sq[to] == (R | â™– | !enemy)){
				sq[to] = ∅;
				for(i = 141 - enemy * 105, j = i < to ? 1 : -1; i != to && sq[i] == ∅; i += j);
				if(i == to){
					for(i = 142 - enemy * 105, j = i < from ? 1 : -1;
						i != from && sq[i] == ∅ && safe(i, enemy);
						i += j);
					if(i == from) target[targets++] = to;
				}
				sq[to] = (R | â™– | !enemy);
			}
			for(to = from - 1; sq[to] == ∅; to--);
			if(sq[to] == (R | â™– | !enemy)){
				sq[to] = ∅;
				for(i = 139 - enemy * 105, j = i < to ? 1 : -1; i != to && sq[i] == ∅; i += j);
				if(i == to){
					for(i = 138 - enemy * 105, j = i < from ? 1 : -1;
						i != from && sq[i] == ∅ && safe(i, enemy);
						i += j);
					if(i == from) target[targets++] = to;
				}
				sq[to] = (R | â™– | !enemy);
			}
		}
		if(i = 0)
	case N:
		i = 8;
		for(j = i + 8; i < j; i++) try(mover, from + off[i]);
		break;
	case Q: i = 0; j = 8; goto slide;
	case R: i = 0; j = 4; goto slide;
	case B: i = 4; j = 8; slide:
		for(; i < j; i++){
			for(to = from + off[i]; sq[to] == ∅; to += off[i]) try(mover, to);
			try(mover, to);
		}
		break;
	}
	sq[from] = mover;
	return targets;
}
void
fwd(void){
	int piece, colour, from, to;
	colour = t & BLACK;
	from = src[t];
	to = dst[t];
	piece = sq[from];
	sq[from] = sq[to] = ∅;
	switch(spec[t++] & SPEC){
	case SPEC:
		piece &= ~â™–;
		break;
	case OO:
		sq[king[colour] = 37 + 105 * colour] = K | colour;
		sq[king[colour] - 1] = R | colour;
		ep = 0;
		return;
	case OOO:
		sq[king[colour] = 33 + 105 * colour] = K | colour;
		sq[king[colour] + 1] = R | colour;
		ep = 0;
		return;
	default:
		piece |= (spec[t - 1] & SPEC) >> PROMO;
	case 0:
		break;
	}
	sq[to] = piece;
	piece &= ∅;
	if(piece == K) king[colour] = to;
	if(to == ep && piece == P) sq[ep + 30 * colour - 15] = ∅;
	ep = piece == P && (from == to + 30 || to == from + 30) ? to + from >> 1 : 0;
}
void
bwd(void){
	int colour, from, to;
	colour = --t & BLACK;
	from = src[t];
	to = dst[t];
	switch(spec[t] & SPEC){
	case SPEC:
		sq[to] |= â™–;
		break;
	case OO:
		sq[king[colour] - 1] = ∅;
		if(0)
	case OOO:
		sq[king[colour] + 1] = ∅;
		sq[king[colour]] = ∅;
		sq[king[colour] = from] = K | â™– | colour;
		sq[to] = R | â™– | colour;
		return;
	default:
		sq[to] = P | colour;
	case 0:
		break;
	}
	sq[from] = sq[to];
	sq[to] = spec[t] & ☠;
	if((sq[from] & ∅) == K) king[colour] = from;
	ep = 0;
	if(t){
		from = src[t - 1];
		to = dst[t - 1];
		if((sq[to] & ∅) == P && (to == from + 30 || from == to + 30)){
			ep = to + from >> 1;
			if(dst[t] == ep && (sq[src[t]] & ∅) == P)
				sq[ep + 30 * colour - 15] = P | !colour;
		}
	}
}
char*
sanmove(char *s){
	int piece, from, rankfrom, to, rankto, tmp;
	from = to = rankfrom = rankto = 0;
	while(t < turns) fwd();
	spec[t] = 0;
	if((*s == '0' || *s == '1') && (s[1] == '-' || s[1] == '/')){
		esetcursor(nil);
		return strchr(s, 0);
	}
	if(~t & BLACK){
		while(*s >= '0' && *s <= '9') s++;
		if(*s++ != '.') return "turn indicator missing";
	}
	switch(*s++){
	default: return "bad piece selector";
	case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h':
		piece = P;
		s--;
		break;
	case 'N': piece = N; break;
	case 'B': piece = B; break;
	case 'R': piece = R; break;
	case 'Q': piece = Q; break;
	case 'K': piece = K; break;
	case 'O': 
		if(s[0] != '-' || s[1] != 'O') return "bad castling indication";
		from = src[t] = king[t & 1];
		tmp = s[2] == '-' ? -1 : 1;
		for(to = from + tmp; sq[to] == ∅; to += tmp);
		if(sq[to] != (R | â™– | t & 1)) return "can't castle";
		s += tmp == 1 ? 2 : 4;
		if(*s++ != ' ') return "terminating space missing";
		dst[t] = to;
		spec[t] = tmp == 1 ? OO : OOO;
		turns++;
		fwd();
		return s;
	}
	for(tmp = 1; tmp; s++) switch(*s){
	case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h':
		from = to;
		to = *s - 'a' + 1;
		break;
	case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8':
		rankfrom = rankto;
		rankto = *s - '1' + 2;
		break;
	case '=':
		switch(*(++s)){
		case 'N': spec[t] = N << PROMO; break;
		case 'B': spec[t] = B << PROMO; break;
		case 'R': spec[t] = R << PROMO; break;
		case 'Q': spec[t] = Q << PROMO; break;
		default: return "bad promotion";
		}
		break;
	case ' ':
		tmp = 0;
	case 'x': case '+': case '#':
		break;
	default:
		return "unknown char";
	}
	if(!to || !rankto) return "bad destination square";
	to += 15 * rankto;
	if(from && rankfrom) from += 15 * rankfrom;
	else{
		if(piece == K) from = king[t & 1];
		else if(piece == P){
			from = from ? from + 15 * rankto : to;
			tmp = 30 * (t & BLACK) -15;
			if(sq[from += tmp] != (piece | t & BLACK)) from += tmp;
		}else{
			tmp = sq[to];
			sq[to] = piece | ~t & BLACK;
			findtargets(to);
			sq[to] = tmp;
			while(targets--){
				tmp = target[targets];
				if(sq[tmp] != ∅ && (sq[tmp] & ∅) == piece){
					if(!from && !rankfrom) break;
					if(tmp % 15 == from) break;
					if(tmp / 15 == rankfrom) break;
				}
			}
			if(targets < 0){
				targets = 0;
				return "couldn't find a piece to move there";
			}
			targets = 0;
			from = tmp;
		}
	}
	if(sq[from] == ∅ || (sq[from] ^ t) & BLACK
	|| sq[to] != ∅ && (sq[to] ^ ~t) & BLACK) return "illegal move";
	src[t] = from;
	dst[t] = to;
	if(sq[from] & â™–) spec[turns] = SPEC;
	spec[t] |= sq[to];
	turns++;
	fwd();
	if(s[-2] == '#'){
		esetcursor(nil);
		return me = strchr(me, 0);
	}
	return s;
}
char *
move(int from, int to, int promotion){
	int ambiguous[8], i, j, n, slider;
	char *s;
	if(me + 40 > buf + sizeof(buf)){
		esetcursor(nil);
		addmsg("ran out of space for movetext!", 30);
		me[-1] = '\n';
		return nil;
	}
	src[t] = from;
	dst[t] = to;
	spec[t] = sq[to];
	if(sq[from] & â™–) spec[t] |= SPEC;
	if(t < turns)
		for(turns = 0, me = ms; turns < t; turns++)
			do ++me; while(me[-1] != ' ');
	s = me;
	if(~t & BLACK) s = seprint(s, buf + sizeof(buf), "%d.", t + 2 >> 1);
	n = 0;
	switch(sq[from] & ∅){
	case P:
		if((to - from) % 15){
			*s++ = from % 15 + 'a' - 1;
			*s++ = 'x';
		}
		*s++ = to % 15 + 'a' - 1;
		*s++ = to / 15 + '1' - 2;
		if(promotion){
			*s++ = '=';
			*s++ = promotion;
			switch(promotion){
			case 'N': spec[t] |= N << PROMO; break;
			case 'B': spec[t] |= B << PROMO; break;
			case 'R': spec[t] |= R << PROMO; break;
			case 'Q': spec[t] |= Q << PROMO; break;
			}
		}
		goto check;
	case N:
		*s++ = 'N';
		for(i = 8; i < 16; i++)
			if(sq[to + off[i]] == sq[from] && to + off[i] != from)
				ambiguous[n++] = to + off[i];
		goto disambiguate;
	case Q: *s++ = 'Q'; i = 0; j = 8; goto slide;
	case R: *s++ = 'R'; i = 0; j = 4; goto slide;
	case B: *s++ = 'B'; i = 4; j = 8; slide:
		for(; i < j; i++){
			for(slider = to + off[i]; sq[slider] == ∅; slider += off[i]);
			if(!((sq[slider] ^ sq[from]) & ~â™–) && slider != from)
				ambiguous[n++] = slider;
		}
	disambiguate:
		for(i = j = 0; i < n; i++)
			j |= (ambiguous[i] - from) % 15 ? 1 : 2;
		if(j & 1) *s++ = from % 15 + 'a' - 1;
		if(j & 2) *s++ = from / 15 + '1' - 2;
		break;
	case K:
		if((sq[to] ^ sq[from]) == (K ^ R)){
			*s++ = 'O';
			*s++ = '-';
			*s++ = 'O';
			if(from > to){
				*s++ = '-';
				*s++ = 'O';
				spec[t] = OOO;
			}else spec[t] = OO;
			goto check;
		}
		*s++ = 'K';
		break;
	}
	if(sq[to] != ∅) *s++ = 'x';
	*s++ = to % 15 + 'a' - 1;
	*s++ = to / 15 + '1' - 2;
check:
	*s++ = ' ';
	fwd();
	turns = t;
	n = t & BLACK;
	if(!safe(king[n], !n)){
		s[-1] = '#';
		*s++ = ' ';
		for(i = 31; i < 144 && (sq[i] == ∅ || sq[i] & BLACK ^ n || !findtargets(i)); i++);
		targets = 0;
		if(i < 144) s[-2] = '+';
		else{
			esetcursor(nil);
			s = strecpy(s, buf + sizeof(buf),
				n ? "1-0 {White mates}\n" : "0-1 {Black mates}\n");
		}
	}
	return s;
}
int click(Point o){
	int n;
	n = (board.max.y - o.y) / 48 * 15 + (o.x - board.min.x) / 48;
	return flip > 0 ? 31 + n : 143 - n;
}
void
main(int argc, char **argv){
	Event e;
	Tm *tm;
	char *u, *s, setup[] = "RNBQKBNR";
	int from, to, i, fd, fd9 = -1, fde[2];
	u = getuser();
	ARGBEGIN{
	default: usage:
		sysfatal("usage: %s [-p pgnfile | -e engine | -9 9pfile] [-f setup] [movetext]", argv0);
	case '9':
		if(mode) goto usage;
		mode = NINE;
		ms = s = EARGF(sysfatal("bad argument to -9"));
		if(strncmp(s, "chess~", 6)) sysfatal("filename must start with chess~");
		while(*s++ != '~');
		for(i = 0; i < 63 && s[i] != '~'; i++) p[0][i] = s[i];
		if(i == 63) sysfatal("bad filename format: player separator ~ not found");
		p[0][i] = 0;
		s += i + 1;
		for(i = 0; i < 63 && s[i]; i++) p[1][i] = s[i];
		if(i == 63) sysfatal("bad filename format: player separator ~ not found");
		p[1][i] = 0;
		if((fd9 = create(ms, ORDWR, 0666)) < 0) sysfatal("create: %r");
		break;
	case 'p':
		if(mode) goto usage;
		if(strcmp(setup, "RNBQKBNR")) sysfatal("-p and -f mutually exclusive");
		mode = PGN;
		pgnfile = strdup(EARGF(sysfatal("bad argument to -p")));
		break;
	case 'e':
		if(mode) goto usage;
		mode = ENGINE;
		s = EARGF(sysfatal("bad argument to -e"));
		strncpy(p[0], u, 63);
		strncpy(p[1], s, 63);
		if(*s == '/') strcpy(buf, s);
		else sprint(buf, "/bin/games/chessengines/%s", s);
		if(access(buf, AEXEC) < 0) sysfatal("engine %r");
		if(pipe(fde) < 0) sysfatal("pipe: %r");
		switch(fork()){
		case -1: sysfatal("fork: %r");
		case 0:
			close(fde[0]);
			dup(fde[1], 0);
			dup(fde[1], 1);
			dup(fde[1], 2);
			close(fde[1]);
			execl(buf, buf, nil);
			sysfatal("execl: %r");
		}
		close(fde[1]);
		write(fde[0], "xboard\n", 7);
		write(fde[0], "new\n", 4);
		tm = localtime(time(0));
		ms = seprint(buf, buf + sizeof(buf),
			"[Event \"Engine Game\"]\n[Site \"9front\"]\n"
			"[Date \"%.4d.%.2d.%.2d\"]\n[Round \"-\"]\n"
			"[White \"%s\"]\n[Black \"%s\"]\n[Result \"*\"]\n ",
			tm->year + 1900, tm->mon + 1, tm->mday, p[0], p[1]);
		if(strcmp(setup, "RNBQKBNR")){
			ms = seprint(ms - 1, buf + sizeof(buf),
			"[FEN \"%c%c%c%c%c%c%c%c"
			"/pppppppp/8/8/8/8/PPPPPPPP/%s w KQkq - 0 1\"]\n"
			"[Setup \"1\"]\n[Variant \"Chess960\"]\n ",
			setup[0] + 'a' - 'A', setup[1] + 'a' - 'A', setup[2] + 'a' - 'A',
			setup[3] + 'a' - 'A', setup[4] + 'a' - 'A', setup[5] + 'a' - 'A',
			setup[6] + 'a' - 'A', setup[7] + 'a' - 'A', setup);
			write(fde[0], "edit\n", 5);	/* individual writes in case */
			write(fde[0], "#\n", 2);		/* of simple engine io */
			sprint(ms + 1, "a1\nPa2\n");
			for(i = 0; i < 8; i++){
				ms[0] = setup[i];
				write(fde[0], ms, 4);
				write(fde[0], ms + 4, 4);
				ms[1] = ++ms[5];
			}
			write(fde[0], "c\n", 2);
			sprint(ms + 1, "a8\nPa7\n");
			for(i = 0; i < 8; i++){
				ms[0] = setup[i];
				write(fde[0], ms, 4);
				write(fde[0], ms + 4, 4);
				ms[1] = ++ms[5];
			}
			write(fde[0], ".\n", 2);
		}
		me = ms;
		break;
	case 'f':
		if(mode == PGN) sysfatal("-p and -f mutually exclusive");
		strncpy(setup, EARGF(sysfatal("bad argument to -f")), 8);
		break;
	}ARGEND
	switch(mode){
	case NINE:
		i = read(fd9, buf, sizeof(buf));
		if(i == 0){
			tm = localtime(time(0));
			ms = seprint(buf, buf + sizeof(buf),
				"[Event \"GamesFS Match\"]\n[Site \"9front\"]\n"
				"[Date \"%.4d.%.2d.%.2d\"]\n[Round \"-\"]\n"
				"[White \"%s\"]\n[Black \"%s\"]\n[Result \"*\"]\n\n",
				tm->year + 1900, tm->mon + 1, tm->mday, p[0], p[1]);
			if(strcmp(setup, "RNBQKBNR"))
				ms = seprint(ms - 1, buf + sizeof(buf),
				"[FEN \"%c%c%c%c%c%c%c%c"
				"/pppppppp/8/8/8/8/PPPPPPPP/%s w KQkq - 0 1\"]\n"
				"[Setup \"1\"]\n[Variant \"Chess960\"]\n\n",
				setup[0] + 'a' - 'A', setup[1] + 'a' - 'A', setup[2] + 'a' - 'A',
				setup[3] + 'a' - 'A', setup[4] + 'a' - 'A', setup[5] + 'a' - 'A',
				setup[6] + 'a' - 'A', setup[7] + 'a' - 'A', setup);
			me = ms;
			for(i = 0; i < argc; i++)
				me = seprint(me, buf + sizeof(buf), "%s ", argv[i]);
			if(write(fd9, buf, me - buf) != me - buf)
				sysfatal("could not create new game: %r");
		}else{
			if(argc)
				sysfatal("movetext incompatible with -9 unless creating a new game");
			if(strcmp(setup, "RNBQKBNR"))
				sysfatal("-9 and -f mutually exclusive unless creating a new game");
			buf[i] = 0;
			s = strstr(buf, "\n\n");
			if(i < 50 || i >= sizeof(buf) || strncmp(buf, "[Event ", 7) || s == nil)
				sysfatal("read %d bytes: not a PGN file?", i);
			ms = s + 2;
			me = buf + i;
		}
		ms[-1] = ' ';
		break;
	case PGN:
		if(argc) sysfatal("movetext incompatible with -p");
		if((fd = open(pgnfile, OREAD)) < 0) sysfatal("open: %r");
		i = read(fd, buf, sizeof(buf));
		close(fd);
		buf[i] = 0;
		s = strstr(buf, "\n\n");
		if(i < 50 || i >= sizeof(buf) || strncmp(buf, "[Event ", 7) || buf[i - 1] != '\n' || s == nil)
			sysfatal("read %d bytes: not a PGN file?", i);
		ms = s + 2;
		me = buf + i;
		for(s++; s < me; s++) if(*s == '\n') *s = ' ';
		if((s = strstr(buf, "[White \"")) == nil) sysfatal("no White tag!");
		for(i = 0, s += 8; i < 63 && s < ms && *s != '"'; i++) p[WHITE][i] = *s++;
		if((s = strstr(s, "[Black \"")) == nil) sysfatal("no Black tag!");
		for(i = 0, s += 8; i < 63 && s < ms && *s != '"'; i++) p[BLACK][i] = *s++;
		break;
	case ENGINE:
		if(argc){
			write(fde[0], "force\n", 6);
			mode = FORCE;
			for(i = 0; i < argc; i++)
				me = seprint(me, buf + sizeof(buf), "%s ", argv[i]);
		}
		break;
	default:
		ms = me = buf;
		break;
	}
	for(i = 0; i < 175; i++) sq[i] = ☠;
	for(i = 0; i < 8; i++){
		sq[46 + i] = P | WHITE;
		sq[121 + i] = P | BLACK;
		sq[61 + i] = sq[76 + i] = sq[91 + i] = sq[106 + i] = ∅;
		switch(setup[i]){
		case 'N': sq[31 + i] = sq[136 + i] = N; break;
		case 'B': sq[31 + i] = sq[136 + i] = B; break;
		case 'R': sq[31 + i] = sq[136 + i] = R | â™–; break;
		case 'Q': sq[31 + i] = sq[136 + i] = Q; break;
		case 'K':
			sq[31 + i] = sq[136 + i] = K | â™–;
			king[0] = 31 + i;
			king[1] = 136 + i;
			break;
		default: sysfatal("bad setup char: %c", setup[i]);
		}
		sq[31 + i] |= WHITE;
		sq[136 + i] |= BLACK;
	}
	if(initdraw(nil, nil, argv[0]) < 0) sysfatal("initdraw: %r");
	for(i = 0; i < HUES; i++)
		if((hue[i] = allocimage(display, Rect(0,0,1,1), RGBA32, 1, rgb[i])) == nil)
			sysfatal("allocimage: %r");
	for(i = 0; i < 6; i++)
		if((mask[i] = allocimage(display, Rect(0, 0, 48, 48), GREY1, 0, DNofill)) == nil
		|| loadimage(mask[i], mask[i]->r, pixels[i], 288) != 288)
			sysfatal("allocimage: %r");
	einit(Emouse | Ekeyboard);
	if(!mode){
		mode = PGN;
		tm = localtime(time(0));
		strcpy(buf, "[Event \"Correspondence Game");
		if((i = eenter("Event", s = buf + 8, 128, &e.mouse)) < 1)
			ixnay: sysfatal("PGN entry cancelled");
		strcpy(s += i, "\"]\n[Site \"e4ec.org");
		if((i = eenter("Site", s += 10, 128, &e.mouse)) < 1) goto ixnay;
		sprint(s += i, "\"]\n[Date \"%.4d.%.2d.%.2d",
			tm->year + 1900, tm->mon + 1, tm->mday);
		if((i = eenter("Date", s + 10, 12, &e.mouse)) != 10) goto ixnay;
		sprint(s + 20, "\"]\n[Round \"-");
		if((i = eenter("Round", s += 31, 128, &e.mouse)) < 1) goto ixnay;
		strcpy(s += i, "\"]\n[White \"");
		strcpy(s += 11, u);
		if((i = eenter("White", s, 64, &e.mouse)) < 1) goto ixnay;
		strcpy(p[0], s);
		strcpy(s += i, "\"]\n[Black \"");
		strcpy(s += 11, u);
		if((i = eenter("Black", s, 64, &e.mouse)) < 1) goto ixnay;
		strcpy(p[1], s);
		s = strecpy(s += i, buf + sizeof(buf), "\"]\n[Result \"*\"]\n ");
		if(strcmp(setup, "RNBQKBNR")) s = seprint(s, buf + sizeof(buf),
			"[FEN \"%c%c%c%c%c%c%c%c"
			"/pppppppp/8/8/8/8/PPPPPPPP/%s w KQkq - 0 1\"]\n"
			"[Setup \"1\"]\n[Variant \"Chess960\"]\n ",
			setup[0] + 'a' - 'A', setup[1] + 'a' - 'A', setup[2] + 'a' - 'A',
			setup[3] + 'a' - 'A', setup[4] + 'a' - 'A', setup[5] + 'a' - 'A',
			setup[6] + 'a' - 'A', setup[7] + 'a' - 'A', setup);
		ms = me = s;
		for(i = 0; i < argc; i++)
			me = seprint(me, buf + sizeof(buf), "%s ", argv[i]);
	}
	for(s = buf; s + 1 < ms; s += i + 1){
		for(i = 0; s[i] != '\n'; i++);
		addmsg(s, i);
	}
	if(!strcmp(u, p[1]) && strcmp(u, p[0])) flip = -1;
	if(mode != PGN && strcmp(u, p[t & 1])) esetcursor(&⌛);
	if((fd = open("/dev/label", OWRITE)) >= 0){
		if(mode == PGN) fprint(fd, "%s: %s", argv0, pgnfile);
		else fprint(fd, "%s: %s vs %s", argv0, p[0], p[1]);
		close(fd);
	}
	for(s = ms; s >= ms && s < me; s = sanmove(s));
	if(s != me) sysfatal("bad movetext: %s", s);
	switch(mode){
	case NINE:
		estart(Enine, fd9, 256);
		break;
	case FORCE:
		for(t = 0; t < turns; t++){
			s = me;
			*s++ = 'a' + src[t] % 15 - 1;
			*s++ = '1' + src[t] / 15 - 2;
			i = spec[t] & SPEC;
			to = i == OO || i == OOO ? king[t & BLACK] : dst[t];
			*s++ = 'a' + to % 15 - 1;
			*s++ = '1' + to / 15 - 2;
			switch(i >> PROMO){
			case N: *s++ = 'n'; break;
			case B: *s++ = 'b'; break;
			case R: *s++ = 'r'; break;
			case Q: *s++ = 'q'; break;
			}
			*s++ = '\n';
			write(fde[0], me, s - me);
		}
		if(t & BLACK){
			write(fde[0], "go\n", 3);
			mode = ENGINE;
		}
	case ENGINE:
		estart(Eengine, fde[0], 256);
		break;
	}
	for(eresized(0);;)
		switch(event(&e)){
		case Ekeyboard:
			switch(e.kbdc){
			case Kleft: if(t) bwd(); break;
			case Kright: if(t < turns) fwd(); break;
			case Kup: while(t) bwd(); break;
			case Kdown: while(t < turns) fwd(); break;
			case Kpgdown: scrollmsgs(1); break;
			case Kpgup: scrollmsgs(-1); break;
			case Kend: botmsg = lastmsg; drawmsgs(); break;
			case Khome: scrollmsgs(0); break;
			case Kesc: flip *= -1; break;
			case Kbs:
				if(mode == ENGINE && turns > 1){
					while(t < turns) fwd();
					bwd(); bwd();
					turns -= 2;
					do me--; while(me[-1] != ' ');
					do me--; while(me[-1] != ' ');
					write(fde[0], "remove\n", 7);
					break;
				}
				continue;
			case '\n':
				if(pgnfile) strecpy(me, buf + sizeof(buf), pgnfile);
				else *me = 0;
				if(eenter("write pgn file", me, buf + sizeof(buf) - me, &e.mouse) < 1)
					continue;
				if((fd = create(me, OWRITE, 0666)) < 0){
					s = seprint(me, buf + sizeof(buf), "create: %r");
					addmsg(me, s - me);
					continue;
				}
				if(pgnfile == nil || strcmp(me, pgnfile)){
					free(pgnfile);
					pgnfile = strdup(me);
					if(mode == PGN && (i = open("/dev/label", OWRITE)) >= 0){
						s = seprint(me, buf + sizeof(buf), "%s: %s", argv0, pgnfile);
						write(i, me, s - me);
						close(i);
						eresized(0);
					}
				}
				for(s = ms - 1; s < me; s += 81){
					while(*s != ' ') s--;
					*s = '\n';
				}
				me[-1] = '\n';
				if(write(fd, buf, me - buf) != me - buf)
					s = seprint(me, buf + sizeof(buf), "write: %r");
				else
					s = seprint(me, buf + sizeof(buf), "saved %s", pgnfile);
				addmsg(me, s - me);
				close(fd);
				for(s = ms - 1; s <= me; s++)
					if(*s == '\n') *s = ' ';
				continue;
			case Kdel:
				for(s = ms - 1; s < me; s += 81){
					while(*s != ' ') s--;
					*s = '\n';
				}
				me[-1] = '\n';
				write(1, buf, me - buf);
				if(mode == ENGINE){
					write(fde[0], "quit\n", 5);
					close(fde[0]);
				}
				exits(nil);
			default:
				if(mode == PGN) break;
				me[0] = e.kbdc;
				me[1] = '\0';
				i = eenter("message", me, buf + sizeof(buf) - me, &e.mouse);
				if(i < 1) break;
				if(mode == ENGINE || mode == FORCE){
					me[i] = '\n';
					write(fde[0], me, i + 1);
					me[i] = 0;
					if(!strcmp(me, "remove")){
						if(turns < 2) break;
						while(t < turns) fwd();
						bwd(); bwd();
						turns -= 2;
						do me--; while(me[-1] != ' ');
						do me--; while(me[-1] != ' ');
						break;
					}
					if(!strcmp(me, "new")){
						while(t) bwd();
						turns = 0;
						me = ms;
						if(strcmp(p[0], u)){
							strcpy(p[1], p[0]);
							strcpy(p[0], u);
							s = strstr(buf, "[White \"") + 8;
							s = seprint(s, ms, "%s\"]\n[Black \"%s", p[0], p[1]);
							*s = '"';
						}
						flip = 1;
						eresized(0);
						break;
					}
					if(!strcmp(me, "go")){
						i = !strcmp(u, p[0]);
						flip = i ? -1 : 1;
						strcpy(p[!i], p[i]);
						strcpy(p[i], u);
						esetcursor(&⌛);
						s = strstr(buf, "[White \"") + 8;
						s = seprint(s, ms, "%s\"]\n[Black \"%s", p[0], p[1]);
						*s = '"';
						break;
					}
				}
				else{
					if(!strcmp(me, "draw!") || !strcmp(me, "resign!")){
						if(*me == 'd')
							s = strecpy(me, buf + sizeof(buf), "1/2-1/2\n");
						else if(t & BLACK)
							s = strecpy(s, buf + sizeof(buf), "1-0 {Black resigned}\n");
						else
							s = strecpy(s, buf + sizeof(buf), "0-1 {White resigned}\n");
						i = s - me;
					}
					else if(me[i - 1] == ' ') me[i - 1] = '_';
					if(write(fd9, me, i) != i){
						s = seprint(me, buf + sizeof(buf), "write: %r");
						addmsg(me, s - me);
					}
				}
				continue;
			}
			drawboard();
			break;
		case Emouse:
			if(e.mouse.buttons & 24){
				if(e.mouse.buttons & 16 && t < turns) fwd();
				if(e.mouse.buttons & 8 && t) bwd();
				drawboard();
				continue;
			}
			if(!e.mouse.buttons
			|| mode != PGN && (t != turns || strcmp(u, p[t & 1]) || me[-1] == '\n'))
				continue;
			do eread(Emouse, &e); while(e.mouse.buttons);
			if(ptinrect(e.mouse.xy, board)){
				from = click(e.mouse.xy);
				if(sq[from] == ∅ || (sq[from] ^ t) & BLACK || !findtargets(from)) continue;
				for(i = 0; i < targets; i++) highlight(target[i], TARGET);
				highlight(from, TARGET);
				do eread(Emouse, &e); while(!e.mouse.buttons);
				do eread(Emouse, &e); while(e.mouse.buttons);
				to = click(e.mouse.xy);
				for(i = 0; i < targets && target[i] != to; i++);
				if(i < targets){
					i = 0;
					if((sq[from] & ∅) == P && (to < 39 || to > 135)){
						me[0] = 'Q'; me[1] = '\0';
						do eenter("promote to?", me, 2, &e.mouse);
							while(!strchr("NBRQ", *me));
						i = *me;
					}
					s = me;
					me = move(from, to, i);
					if(mode == NINE){
						if(write(fd9, s, me - s) != me - s){
							bwd();
							do me--; while(me[-1] != ' ');
							s = seprint(me, buf + sizeof(buf), "write: %r");
							addmsg(me, s - me);
							break;
						}
						if(me[-1] == ' ') esetcursor(&⌛);
						do eread(Enine, &e);
							while(e.n != me - s || strncmp((char*)e.data, s, me - s));
					}
					if(mode == FORCE || mode == ENGINE){
						if(me[-2] == 'O' || me[-3] == 'O') to = king[~t & BLACK];
						s = me;
						*s++ = 'a' + from % 15 - 1;
						*s++ = '1' + from / 15 - 2;
						*s++ = 'a' + to % 15 - 1;
						*s++ = '1' + to / 15 - 2;
						if(i) *s++ = i + 'a' - 'A';
						*s++ = '\n';
						write(fde[0], me, s - me);
						esetcursor(&⌛);
						if(mode == FORCE){
							write(fde[0], "go\n", 3);
							mode = ENGINE;
						}
					}
				}
				targets = 0;
				drawboard();
			}
			break;
		case Eengine:
			if(e.n > 8 && !strncmp((char*)e.data, "move ", 5)){
				while(t < turns) fwd();
				from = 31 + 15 * (e.data[6] - '1') + e.data[5] - 'a';
				to = 31 + 15 * (e.data[8] - '1') + e.data[7] - 'a';
				i = strchr("nbrq", e.data[9]) ? e.data[9] + 'A' - 'a' : 0;
				if(!i && (sq[from] & ∅) == P && (to < 39 || to > 135)) i = 'Q';
				if(from == king[t & 1]){
					if(to == from + 2) to++;
					else if(to + 2 == from) to -= 2;
				}
				me = move(from, to, i);
				esetcursor(nil);
				drawboard();
			}else addmsg(e.data, e.n - 1);
			break;
		case Enine:
			if(e.n > 2 && me + e.n < buf + sizeof(buf)
			&& (e.data[e.n - 1] == ' ' || e.data[e.n - 1] == '\n')){
				s = me;
				me += e.n;
				memcpy(s, e.data, e.n);
				s[e.n] = 0;
				do s = sanmove(s); while(s > ms && s < me); 
				if(s == me){
					esetcursor(me[-1] == ' ' && strcmp(u, p[t & BLACK]) ? &⌛ : nil);
					drawboard();
					break;
				}
				addmsg("received bad movetext:", 22);
				addmsg(s, strlen(s));
			}
			addmsg(e.data, e.n);
			break;
		}
}