/* absurdly overengineered noughts and crosses */ #include #include #include #include #include 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; } }