#include #include #include #include #include enum{PITS = 14, HALFPITS = PITS >> 1, SEEDS = 4, Eremote = 8, MSG = 8, MSGLEN = 128, RAD = 30, GAP = 8, ZZZ = 400}; Image *ground, *pit, *hl[2]; Point o[PITS], compass[4]; Rectangle r[3]; char msg[MSG][MSGLEN], p[2][64]; int b[PITS], t, msgn; void drawpit(int i, Image *color){ char buf[4]; fillellipse(screen, o[i], RAD, RAD, color, ZP); snprint(buf, 4, "%d", b[i]); string(screen, subpt(o[i], divpt(stringsize(font, buf), 2)), display->black, ZP, font, buf); } void drawpointer(void){ Point tri[3]; fillpoly(screen, compass, 4, 0, ground, ZP); if(t < 0){ tri[0] = compass[0]; tri[1] = compass[2]; if(b[HALFPITS] >= b[0]){ tri[2] = compass[1]; fillpoly(screen, tri, 3, 0, hl[0], ZP); } if(b[0] >= b[HALFPITS]){ tri[2] = compass[3]; fillpoly(screen, tri, 3, 0, hl[1], ZP); } } else{ tri[0] = compass[1]; tri[1] = compass[3]; tri[2] = compass[!t << 1]; fillpoly(screen, tri, 3, 0, hl[t], ZP); } } void drawlabel(int i){ draw(screen, r[i], ground, nil, ZP); string(screen, addpt(r[i].min, Pt(Dx(r[i]) - stringwidth(font, p[i]) >> 1, 0)), display->black, ZP, font, p[i]); } void drawmsgs(void){ Point o; int i; draw(screen, r[2], display->white, nil, ZP); for(o = r[2].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 || Dy(screen->r) != 6 * (RAD + GAP) + (MSG + 2) * font->height) && (i = open("/dev/wctl", OWRITE)) >= 0){ fprint(i, "resize -dy %d", 6 * (RAD + GAP) + (MSG + 2) * font->height + 2 * Borderwidth); close(i); } draw(screen, screen->r, ground, nil, ZP); compass[3].x = screen->r.min.x + screen->r.max.x >> 1; compass[3].y = screen->r.min.y + 3 * (RAD + GAP) + font->height; o[0] = subpt(compass[3], Pt(PITS * RAD + HALFPITS * GAP >> 1, 0)); o[HALFPITS] = addpt(o[0], Pt(PITS * RAD + HALFPITS * GAP, 0)); for(i = 1; i < HALFPITS; i++){ o[i] = addpt(o[0], Pt(i * (2 * RAD + GAP), 2 * RAD + GAP)); o[HALFPITS + i] = subpt(o[HALFPITS], Pt(i * (2 * RAD + GAP), 2 * RAD + GAP)); } for(i = 0; i < PITS; i++) drawpit(i, pit); compass[0] = subpt(compass[3], Pt(0, RAD)); /* north */ compass[1] = addpt(compass[3], Pt(RAD, 0)); /* east */ compass[2] = addpt(compass[3], Pt(0, RAD)); /* south */ compass[3] = subpt(compass[3], Pt(RAD, 0)); /* west */ drawpointer(); r[1] = rectaddpt(Rect(0, GAP, Dx(screen->r), GAP + font->height), screen->r.min); drawlabel(1); r[0] = rectaddpt(r[1], Pt(0, 6 * RAD + 4 * GAP + font->height)); drawlabel(0); r[2] = Rect(r[0].min.x, r[0].max.y + GAP, r[0].max.x, screen->r.max.y); drawmsgs(); flushimage(display, 1); } void flash(int n){ drawpit(n, hl[t]); flushimage(display, 1); sleep(ZZZ); drawpit(n, pit); } void sow(int n, int animate){ int i, mypot, yourpot; i = b[n]; if(animate) flash(n); b[n] = 0; if(animate) flash(n); yourpot = t * HALFPITS; mypot = yourpot ^ HALFPITS; while(i--){ n = ++n % PITS; n += n == yourpot; b[n]++; if(animate) flash(n); } if((n + yourpot) % PITS < HALFPITS && b[n] == 1 && b[PITS - n]){/* capture */ b[n] = 0; b[mypot]++; if(animate){ flash(n); flash(mypot); } b[mypot] += b[PITS - n]; /* capture */ b[PITS - n] = 0; if(animate){ flash(PITS - n); flash(mypot); } } t ^= n != mypot; for(i = 1; i < HALFPITS && !b[i]; i++); if(i == HALFPITS){ /* game over */ t = 1; for(i++; i < PITS; i++) if(b[i]){ b[0] += b[i]; b[i] = 0; if(animate){ flash(i); flash(0); } } t = -1; } else{ for(i = HALFPITS + 1; i < PITS && !b[i]; i++); if(i == PITS){ /* game over */ t = 0; for(i = 1; i < HALFPITS; i++) if(b[i]){ b[HALFPITS] += b[i]; b[i] = 0; if(animate){ flash(i); flash(HALFPITS); } } t = -1; } } if(t < 0){ i = b[HALFPITS] - b[0]; t = i == 0 ? -3 : i > 0 ? -2 : -1; } if(animate){ drawpointer(); 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(); flushimage(display, 1); } int αβ(int d, int α, int β, int *mv){ int i, j, yourpot, old[PITS], oldt, v; yourpot = t * HALFPITS; if(t < 0 || d < 1) return b[yourpot ^ HALFPITS] - b[yourpot]; for(j = 0; j < PITS; j++) old[j] = b[j]; oldt = t; for(i = yourpot + 1; α < β && i >> t < HALFPITS; i++){ if(!b[i]) continue; sow(i, 0); v = (t ^ oldt) ? -αβ(d - 1, -β, -α, nil) : αβ(d, α, β, nil); for(j = 0; j < PITS; j++) b[j] = old[j]; t = oldt; if(v > α){ α = v; if(mv != nil) *mv = i; } } return α; } void main(int argc, char **argv){ Event e; Cursor busy = {{-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}}; char buf[MSGLEN], moves[HALFPITS + 1], *f, *u, *s; int i, oldt, fd = -1; 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(initdraw(nil, nil, "kalah") < 0) sysfatal("initdraw: %r"); ground = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPaleyellow); pit = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DYellowgreen); hl[0] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPalegreygreen); hl[1] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPalegreen); if(ground == nil | pit == nil | hl[0] == nil | hl[1] == nil) sysfatal("allocimage: %r"); u = getuser(); einit(Emouse | Ekeyboard); srand(truerand()); new: strcpy(p[0], u); strcpy(p[1], u); for(i = 0; i < PITS; i++) b[i] = SEEDS; t = b[0] = b[HALFPITS] = 0; eresized(0); if(fd >= 0){ s = strchr(f, '\0') - 3; if(s >= f && !strcmp(s, "ctl")){ buf[0] = '2'; buf[1] = '\0'; do eenter("play first or second? [1/2]", buf, 2, &e.mouse); while(*buf != '1' && *buf != '2'); s = buf + 6; if(*buf == '1') s = strecpy(s, buf + MSGLEN, u) + 1; *s = '\0'; if((i = eenter("opponent's user name", s, MSGLEN + buf - s - 10, &e.mouse)) <= 0) sysfatal("player entry cancelled"); if(!strcmp(s, u)) sysfatal("Play with yourself in your own time!"); s += i; if(*buf == '2') s = strecpy(s + 1, buf + MSGLEN, u); strcpy(buf, "kalah"); i = write(fd, buf, s - buf); close(fd); s = strecpy(buf, buf + MSGLEN, f) - 3; s = seprint(s, buf + MSGLEN, "%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, MSGLEN - 1)) < 10) sysfatal("not a kalah game?"); buf[i] = '\0'; if(strcmp(buf, "kalah")) sysfatal("not a kalah game!"); strncpy(p[0], buf + 6, 63); for(s = buf + 6; *s++ != '\0';); if(s - buf >= i) sysfatal("couldn't get player 2 name"); strncpy(p[1], s, 63); strcpy(buf + 3, buf + 6); strncpy(s - 4, " vs ", 4); while(*s != '\0') s++; chat(buf + 3, s - buf - 3); if(!strcmp(p[1], u)){ strcpy(buf, p[0]); strcpy(p[0], p[1]); strcpy(p[1], buf); t = 1; } while(++s - buf < i && *s != ':') if(*s > '0' && *s < '0' + HALFPITS && b[*s - '0' + t * HALFPITS]) sow(*s - '0' + t * HALFPITS, 0); if(s - buf < i){ if(t >= 0) t = -1; if(buf + i > s + 2) chat(s + 2, buf + i - s - 2); } eresized(0); if(t >= 0 && strcmp(p[t], u)) esetcursor(&busy); estart(Eremote, fd, MSGLEN); strncpy(moves, " ", HALFPITS + 1); } else chat("new game: click user name to create ai player", 45); for(;;){ if(fd < 0 && !strncmp(p[t], "ai level ", 9)){ if(ecankbd()){ chat("ai stopped", 10); while(ecankbd()) ekbd(); /* gulp */ strcpy(p[t], u); drawlabel(t); esetcursor(nil); flushimage(display, 1); continue; } esetcursor(&busy); sleep(ZZZ); for(fd = 0, i = t * HALFPITS + 1; i >> t < HALFPITS; i++) if(b[i]) fd++; for(fd = nrand(fd), i = t * HALFPITS + 1; !b[i] || fd--; i++); αβ(p[t][9] - '1', -PITS * SEEDS, PITS * SEEDS, &i); sow(i, 1); if(t < 0) goto gameover; esetcursor(nil); } else switch(event(&e)){ case Eremote: if(e.n > 1 && e.data[0] == ' '){ if(e.data[1] == ':'){ esetcursor(nil); if(t >= 0) t = -3; if(e.n > 2) chat(e.data + 3, e.n - 3); continue; } if(e.n == strlen(moves) && !memcmp(moves, e.data, e.n)){ strncpy(moves, " ", HALFPITS + 1); continue; } if(t < 0) continue; for(i = 1; i < e.n; i++) if(e.data[i] > '0' && e.data[i] < '0' + HALFPITS && b[e.data[i] - '0' + t * HALFPITS]) sow(e.data[i] - '0' + t * HALFPITS, 1); if(t < 0){ if(t > -3) fprint(fd, " : %s won", p[t & 1]); else write(fd, " : draw", 7); continue; } esetcursor(strcmp(p[t], u) ? &busy : nil); } else chat(e.data, e.n); break; case Emouse: if(!e.mouse.buttons || strcmp(p[t], u) || t < 0) break; do eread(Emouse, &e); while(e.mouse.buttons); if(fd < 0 && (ptinrect(e.mouse.xy, r[i = 0]) || ptinrect(e.mouse.xy, r[i = 1]))){ *buf = '\0'; eenter("ai level ? [0–9]", buf, 2, &e.mouse); if(*buf == '0') strcpy(p[i], u); else if(*buf >= '1' && *buf <= '9'){ strcpy(p[i], "ai level x"); p[i][9] = *buf; } else continue; drawlabel(i); s = strecpy(buf, buf + MSGLEN, p[0]); s = strecpy(s, buf + MSGLEN, " vs "); s = strecpy(s, buf + MSGLEN, p[1]); chat(buf, s - buf); } else for(i = t * HALFPITS + 1; i >> t < HALFPITS; i++) if(sqrt((e.mouse.xy.x - o[i].x) * (e.mouse.xy.x - o[i].x) + (e.mouse.xy.y - o[i].y) * (e.mouse.xy.y - o[i].y)) < RAD && b[i]){ oldt = t; sow(i, 1); if(fd >= 0){ for(s = moves + 1; *s; s++); *s = '0' + i - oldt * HALFPITS; if(oldt != t) write(fd, moves, s - moves + 1); esetcursor(strcmp(p[t], u) ? &busy : nil); } if(t < 0 && fd < 0){gameover: esetcursor(nil); strcpy(buf, " : "); if(t > -3){ s = strecpy(buf + 3, buf + MSGLEN, p[t & 1]); s = strecpy(s, buf + MSGLEN, " won"); } else s = strcpy(buf + 3, "draw") + 4; chat(buf + 3, s - buf - 3); chat("press any key for a new game or DEL to quit", 43); if(ekbd() == 127) exits(nil); goto new; } break; } break; case Ekeyboard: switch(e.kbdc){ case 127: exits(nil); case 'n': if(fd < 0) goto new; default: if(fd >= 0){ buf[0] = e.kbdc; buf[1] = '\0'; if((i = eenter("message", buf, MSGLEN, &e.mouse)) < 1) break; if(buf[0] == ' ') buf[0] = '_'; if(!strcmp(buf, "!resign")) fprint(fd, " : %s resigned", u); else write(fd, buf, i); } break; } break; } } }