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

View Raw

More Information

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

/* absurdly overengineered noughts and crosses */
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <event.h>
#include <cursor.h>
void next(void);
enum{MOUSE, AI, REMOTE, Eremote = 8, MSG = 8, MSGLEN = 128};
enum{PLAY, VIEW, XWON, OWON, DRAW};
Rectangle sq[9], r;
Image *mark[2], *winner[2], *h[9], *arrow;
int state, t, view, p[2], b[2], msgn;
char m[10], msg[MSG][MSGLEN];
uchar arrowdata[] = {1, 3, 7, 15, 31, 63, 127, 255, 127, 63, 31, 15, 7, 3, 1};
Cursor curs[3] = {
	{{-7, -7},
	{0xf0, 0x0f, 0x88, 0x11, 0x84, 0x21, 0x82, 0x41,
	 0x41, 0x82, 0x20, 0x04, 0x10, 0x08, 0x08, 0x10,
	 0x08, 0x10, 0x10, 0x08, 0x20, 0x04, 0x41, 0x82,
	 0x82, 0x41, 0x84, 0x21, 0x88, 0x11, 0xf0, 0x0f},
	{0x00, 0x00, 0x70, 0x0e, 0x78, 0x1e, 0x7c, 0x3e,
	 0x3e, 0x7c, 0x1f, 0xf8, 0x0f, 0xf0, 0x07, 0xe0,
	 0x07, 0xe0, 0x0f, 0xf0, 0x1f, 0xf8, 0x3e, 0x7c,
	 0x7c, 0x3e, 0x78, 0x1e, 0x70, 0x0e, 0x00, 0x00}},
	{{-7, -7},
	{0x0f, 0xf0, 0x10, 0x08, 0x20, 0x04, 0x40, 0x02,
	 0x83, 0xc1, 0x84, 0x21, 0x88, 0x11, 0x88, 0x11,
	 0x88, 0x11, 0x88, 0x11, 0x84, 0x21, 0x83, 0xc1,
	 0x40, 0x02, 0x20, 0x04, 0x10, 0x08, 0x0f, 0xf0},
	{0x00, 0x00, 0x0f, 0xf0, 0x1f, 0xf8, 0x3f, 0xfc,
	 0x7c, 0x3e, 0x78, 0x1e, 0x70, 0x0e, 0x70, 0x0e,
	 0x70, 0x0e, 0x70, 0x0e, 0x78, 0x1e, 0x7c, 0x3e,
	 0x3f, 0xfc, 0x1f, 0xf8, 0x0f, 0xf0, 0x00, 0x00}},
	{{-7, -7},
	{0x7f, 0xfc, 0x40, 0x04, 0x20, 0x08, 0x20, 0x08,
	 0x10, 0x10, 0x08, 0x20, 0x04, 0x40, 0x04, 0x40,
	 0x0a, 0xa0, 0x16, 0xd0, 0x16, 0xd0, 0x2e, 0xe8,
	 0x2e, 0xe8, 0x2c, 0x68, 0x40, 0x04, 0x7f, 0xfc},
	{0x00, 0x00, 0x3f, 0xf8, 0x1f, 0xf0, 0x1f, 0xf0,
	 0x0f, 0xe0, 0x07, 0xc0, 0x03, 0x80, 0x03, 0x80,
	 0x05, 0x40, 0x09, 0x20, 0x09, 0x20, 0x11, 0x10,
	 0x11, 0x10, 0x13, 0x90, 0x3f, 0xf8, 0x00, 0x00}}
};
void drawmsgs(void){
	Point o; int i;
	draw(screen, r, display->white, nil, ZP);
	for(o = r.min, i = 0; i < MSG; o.y += font->height)
		string(screen, o, display->black, ZP, font, msg[msgn + i++ & MSG - 1]);
}
void eresized(int i){
	if(i && getwindow(display, Refnone) < 0)
		sysfatal("can't reattach to window");
	if((!i || Dx(screen->r) != 416 + MSG * font->height)
	&& (i = open("/dev/wctl", OWRITE)) >= 0){
		fprint(i, "resize -dy %d", 416 + MSG * font->height + 2 * Borderwidth);
		close(i);
	}
	draw(screen, screen->r, display->white, nil, ZP);
	r = Rect(-200, 8, 200, 408);
	r = rectaddpt(r, Pt(screen->r.min.x + screen->r.max.x >> 1, screen->r.min.y));
	draw(screen, r, display->black, nil, ZP);
	r.max = addpt(r.min, Pt(128, 128));
	for(i = 0; i < 9; i++){
		sq[i] = rectaddpt(r, Pt(i % 3 * 136, i / 3 * 136));
		draw(screen, sq[i], display->white, nil, ZP);
	}
	for(i = 0; i < view; i++)
		draw(screen, sq[m[i] - '0'], mark[i & 1], nil, ZP);
	for(i = 0; i < t; i++)
		draw(screen, screen->r, h[m[i] - '0'], nil, Pt(-4, -44 * i - 8));
	draw(screen, screen->r, display->black, arrow, Pt(-44, -44 * view));
	if(state & XWON)
		draw(screen, screen->r, display->black, winner[state & 1], Pt(-14, -44 * t - 4));
	r = screen->r;
	r.min.y += 416;
	drawmsgs();
}
int won(int bits){
	static int w[8] = {0700, 0070, 0007, 0111, 0222, 0444, 0124, 0421};
	int i;
	for(i = 0; i < 8 && bits & w[i] ^ w[i]; i++);
	return i < 8;
}
int αβ(int α, int β, int me, int you){
	int v, best, moves;
	if(won(you))
		return -1;
	moves = (me | you) ^ 0777;
	if(moves == 0)
		return 0;
	for(best = -1; moves; moves &= moves - 1){
		v = -αβ(-β, -α, you, me | moves & -moves);
		if(v > best)
			best = v;
		if(best >= β)
			break;
		if(best > α)
			α = best;
	}
	return best;
}
void move(int n){
	b[t & 1] |= 1 << n;
	m[t] = n + '0';
	state = won(b[t & 1]) ? XWON | t & 1 : t & 8 ? DRAW : PLAY;
	draw(screen, sq[n], mark[t & 1], nil, ZP);
	draw(screen, screen->r, h[n], nil, Pt(-4, -44 * t - 8));
	draw(screen, screen->r, display->white, arrow, Pt(-44, -44 * view));
	view = ++t;
	if(state & XWON)
		draw(screen, screen->r, display->black, winner[state & 1], Pt(-14, -44 * t - 4));
	draw(screen, screen->r, display->black, arrow, Pt(-44, -44 * view));
	flushimage(display, 1);
	next();
}
void next(void){
	int mv, i;
	if(state != PLAY)
		esetcursor(nil);
	else
		switch(p[t & 1]){
		case MOUSE:
			esetcursor(curs + (t & 1));
			break;
		case AI:
			esetcursor(curs + 2);
			sleep(1000);
			for(mv = 0, i = (b[0] | b[1]) ^ 0777; i; i &= i - 1){
				switch(αβ(-1, 1, b[~t & 1], b[t & 1] | i & -i)){
				case -1: mv = i; break;
				case 0: mv = i;
				case 1: continue;
				}
				break;
			}
			if(!mv) mv = (b[0] | b[1]) ^ 0777;
			for(i = 0, mv--; mv & 1; mv >>= 1) i++;
			move(i);
			break;
		case REMOTE:
			esetcursor(curs + 2);
			break;
		}
}
void back(void){
	if(state == PLAY){esetcursor(nil); state = VIEW;}
	draw(screen, screen->r, display->white, arrow, Pt(-44, -44 * view));
	view--;
	draw(screen, screen->r, display->black, arrow, Pt(-44, -44 * view));
	draw(screen, sq[m[view] - '0'], display->white, nil, ZP);
	flushimage(display, 1);
}
void fwd(void){
	draw(screen, sq[m[view] - '0'], mark[view & 1], nil, ZP);
	draw(screen, screen->r, display->white, arrow, Pt(-44, -44 * view));
	if(++view == t && state == VIEW){state = PLAY; next();}
	draw(screen, screen->r, display->black, arrow, Pt(-44, -44 * view));
	flushimage(display, 1);
}
void chat(void *s, int n){
	if(n >= MSGLEN) n = MSGLEN - 1;
	memmove(msg[msgn], s, n);
	msg[msgn][n] = '\0';
	msgn = msgn + 1 & MSG - 1;
	drawmsgs();
}
void main(int argc, char **argv){
	Event e;	int i, fd = -1; char buf[MSGLEN], *f, *s, *u,
	*player[] = {"human", "robot"},
	*menu[] = {"X is a human", "O is a human", "new game", nil};
	Menu b3 = {menu};
	SET(f); ARGBEGIN{
	default:
	 	sysfatal("usage: %s [-9 file] [movetext]", argv0);
	case '9':
		f = EARGF(sysfatal("bad argument to -9"));
		if((fd = open(f, ORDWR)) < 0) sysfatal("open: %r");
	}ARGEND
	if(argc && fd < 0)
		for(i = 0; i < argc; i++) for(s = argv[i]; *s != '\0'; s++)
			if(*s >= '0' && *s <= '8' && strchr(m, *s) == nil)
				m[t++] = *s;
	if(initdraw(nil, nil, "noughts & crosses") < 0)
		sysfatal("initdraw: %r");
	mark[0] = allocimage(display, Rect(0, 0, 128, 128), GREY1, 0, DNofill);
	mark[1] = allocimage(display, Rect(0, 0, 128, 128), GREY1, 0, DNofill);
	if(mark[0] == nil || mark[1] == nil) sysfatal("allocimage: %r");
	draw(mark[0], mark[0]->r, display->white, nil, ZP);
	draw(mark[1], mark[1]->r, display->white, nil, ZP);
	line(mark[0], Pt(0, 0), Pt(128, 128), 0, 0, 11, display->black, ZP);
	line(mark[0], Pt(128, 0), Pt(0, 128), 0, 0, 11, display->black, ZP);
	border(mark[0], mark[0]->r, 8, display->white, ZP);
	ellipse(mark[1], Pt(64, 64), 48, 48, 10, display->black, ZP);
	winner[0] = allocimage(display, Rect(0, 0, 16, 16), GREY1, 0, DNofill);
	winner[1] = allocimage(display, Rect(0, 0, 16, 16), GREY1, 0, DNofill);
	if(winner[0] == nil || winner[1] == nil) sysfatal("allocimage: %r");
	loadimage(winner[0], winner[0]->r, curs[0].set, 32);
	loadimage(winner[1], winner[1]->r, curs[1].set, 32);
	for(i = 0; i < 9; i++){
		h[i] = allocimage(display, Rect(0, 0, 36, 36), GREY1, 0, DNofill);
		if(h[i] == nil) sysfatal("allocimage: %r");
		draw(h[i], h[i]->r, display->white, nil, ZP);
		line(h[i], Pt(0, 11), Pt(36, 11), 0, 0, 1, display->black, ZP);
		line(h[i], Pt(0, 24), Pt(36, 24), 0, 0, 1, display->black, ZP);
		line(h[i], Pt(11, 0), Pt(11, 36), 0, 0, 1, display->black, ZP);
		line(h[i], Pt(24, 0), Pt(24, 36), 0, 0, 1, display->black, ZP);
		sq[0].min = Pt(i % 3 * 13 + 3, i / 3 * 13 + 3);
		sq[0].max = Pt(sq[0].min.x + 4, sq[0].min.y + 4);
		draw(h[i], sq[0], display->black, nil, ZP);
	}
	arrow = allocimage(display, Rect(0, 0, 8, 15), GREY1, 0, DNofill);
	if(arrow == nil) sysfatal("allocimage: %r");
	loadimage(arrow, arrow->r, arrowdata, sizeof(arrowdata));
	einit(Emouse | Ekeyboard);
	eresized(0);
	u = getuser();
	if(fd >= 0){
		s = strchr(f, '\0') - 3;
		if(s >= f && !strcmp(s, "ctl")){
			buf[0] = 'o'; buf[1] = '\0';
			do eenter("play as? [x/o]", buf, 2, &e.mouse);
				while(*buf != 'x' && *buf != 'o');
			s = buf + 10;
			if(*buf == 'x') s = strecpy(s, buf + sizeof(buf), u) + 1;
			i = sizeof(buf) + buf - s - 10;
			if((i = eenter("opponent's user name", s, i, &e.mouse)) <= 0)
				sysfatal("player entry cancelled");
			if(!strcmp(s, u)) sysfatal("Play with yourself in your own time!");
			s += i;
			if(*buf == 'o') s = strecpy(s + 1, buf + sizeof(buf), u);
			strcpy(buf, "tictactoe");
			i = write(fd, buf, s - buf);
			close(fd);
			s = strecpy(buf, buf + sizeof(buf), f) - 3;
			s = seprint(s, buf + sizeof(buf), "%d", i);
			if((fd = open(buf, ORDWR)) < 0) sysfatal("open: %r");
			chat(buf, s - buf);
		}else chat(f, strlen(f));
		if((i = read(fd, buf, sizeof(buf) - 1)) < 13) sysfatal("not a tictactoe game?");
		buf[i] = '\0';
		if(strcmp(buf, "tictactoe")) sysfatal("not a tictactoe game!");
		s = buf + 10;
		if(strcmp(s, u)) p[0] = REMOTE;
		s = strchr(s, '\0') + 1;
		if(s - buf >= i) sysfatal("couldn't get player 2 name");
		if(strcmp(s, u)) p[1] = REMOTE;
		strcpy(buf + 7, buf + 10);
		strncpy(s - 4, " vs ", 4);
		chat(buf + 7, strlen(buf + 7));
		u = strdup(buf + 7);
		s = strchr(s, '\0');
		while(++s - buf < i && *s != ':')
			if(*s >= '0' && *s <= '8' && strchr(m, *s) == nil)
				m[t++] = *s;
		if(s - buf < i){
			if(state < XWON) state = DRAW;
			if(buf + i > s + 2) chat(s + 2, buf + i - s - 2);
		}
		estart(Eremote, fd, MSGLEN);
	}
	next();
	for(t = 0; m[t];)
		move(m[t] - '0');
	for(;;)
		switch(event(&e)){
		case Eremote:
			if(e.n > 1 && e.data[0] == ' '){
				if(e.data[1] == ':'){
					esetcursor(nil);
					if(state < XWON) state = DRAW;
					if(e.n > 3) chat(e.data + 3, e.n - 3);
					continue;
				}
				i = e.data[1] - '0';
				if(state < XWON && e.n == 2 && i >= 0 && i <= 9
				&& !((b[0] | b[1]) & 1 << i)){
					while(view < t) fwd();
					move(i);
					if(state == XWON) write(fd, " : X won", 8);
					else if(state == OWON) write(fd, " : O won", 8);
					else if(state == DRAW) write(fd, " : draw", 7);
				}
			}
			else chat(e.data, e.n);
			break;
		case Ekeyboard:
			switch(e.kbdc){
			case 0x7f:
				if(fd >= 0){
					close(fd);
					s = strecpy(buf, buf + MSGLEN - 1, u);
					*s++ = '\n';
				} else s = buf;
				s = strecpy(s, buf + MSGLEN - 1, argv0);
				*s++ = ' ';
				s = strecpy(s, buf + MSGLEN - 1, m);
				*s++ = '\n';
				write(1, buf, s - buf);
				exits(nil);
			case 0xf011: if(view) back(); break;
			case 0xf012: if(view < t) fwd(); break;
			case 0xf800: while(view) back(); break;
			case 0xf00e: while(view < t) fwd(); break;
			default:
				if(fd >= 0){
					buf[0] = e.kbdc; buf[1] = '\0';
					if((i = eenter("message", buf, sizeof(buf), &e.mouse)) < 1)
						break;
					if(buf[0] == ' ') buf[0] = '_';
					write(fd, buf, i);
				}break;
			}break;
		case Emouse:
			if(state == PLAY & p[t & 1] == MOUSE & e.mouse.buttons){
				do eread(Emouse, &e); while(e.mouse.buttons);
				for(i = 0; i < 9; i++)
					if(ptinrect(e.mouse.xy, sq[i])){
						if(!((b[0] | b[1]) & 1 << i)){
							if(fd < 0)
								move(i);
							else{
								buf[0] = ' ';
								buf[1] = i + '0';
								write(fd, buf, 2);
							}
						}
						break;
					}
			}
			else if(e.mouse.buttons & 4 && fd < 0)
				switch(i = emenuhit(3, &e.mouse, &b3)){
				case 2:
					for(t = 0; t < 10; t++) m[t] = 0;
					b[0] = b[1] = t = view = state = 0;
					eresized(0);
					if(0)
				case 0: case 1: strcpy(menu[i] + 7, player[p[i] ^= AI]);
					next();
				}
			break;
		}
}