#include #include #include #include #include #include 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; } }