/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ * This is GNU Go, a Go program. Contact gnugo@gnu.org, or see * * http://www.gnu.org/software/gnugo/ for more information. * * * * Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, * * 2008 and 2009 by the Free Software Foundation. * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation - version 3 * * or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License in file COPYING for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02111, USA. * \* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* * This program sits between a GTP client and a GTP engine, * passing commands back and forth and modifying them in * some cases. * * To the client it appears to be a GTP engine. * * stdin pipe a * GTP client ----> metamachine -----> GTP engine * <---- <----- * stdout pipe b * * Most commands are passed verbatim to the engine. The * exception is gg_genmove, which is intercepted then * processed differently. The top two moves are both * tried, the position evaluated by estimate_score, * and the move yielding the higher score is selected. * * Usage: no arguments gives normal GTP behavior. * 'metamachine --debug' sends diagnostics to stderr. */ #include #include #include #include #include void error(const char *msg); void gprintf(FILE *outputfile, const char *fmt, ...); void trace(const char *fmt, ...); void tell_gnugo(char *gnugo_line, const char *msg); int boardsize = 19; char delimiters[] = " \t\r\n"; char gnugo_line[128], client_line[128]; FILE *to_gnugo_stream, *from_gnugo_stream; int debug = 0; #define EMPTY 0 #define WHITE 1 #define BLACK 2 #define GTP_BUFSIZE 1000 void ask_gnugo(char *line, int verbose, const char *msg); int main(int argc, char *const *argv) { int pfd_a[2]; int pfd_b[2]; int id; int k; char command[GTP_BUFSIZE]; for (k = 1; k < argc; k++) if (argc > 1 && strstr(argv[k], "debug")) debug = 1; if (pipe(pfd_a) == -1) error("can't open pipe a"); if (pipe(pfd_b) == -1) error("can't open pipe b"); switch (fork()) { case -1: error("fork failed (try chopsticks)"); case 0: /* Attach pipe a to stdin */ if (dup2(pfd_a[0], 0) == -1) error("dup pfd_a[0] failed"); /* attach pipe b to stdout" */ if (dup2(pfd_b[1], 1) == -1) error("dup pfd_b[1] failed"); execlp("gnugo", "gnugo", "--mode", "gtp", "--quiet", NULL); error("execlp failed"); } /* Attach pipe a to to_gnugo_stream */ to_gnugo_stream = fdopen(pfd_a[1], "w"); /* Attach pipe b to from_gnugo_stream */ from_gnugo_stream = fdopen(pfd_b[0], "r"); while (1) { char *p; int n; if (!fgets(client_line, GTP_BUFSIZE, stdin) || (strstr(client_line, "quit") == client_line)) { tell_gnugo("quit\n", "a"); return 1; } /* remove comments */ if ((p = strchr(client_line, '#')) != NULL) *p = 0; p = client_line; /* Look for an identification number. */ if (sscanf(p, "%d%n", &id, &n) == 1) p += n; else id = -1; /* No identification number. */ trace("id = %d\n", id); /* Look for command name. */ if (sscanf(p, " %s %n", command, &n) < 1) continue; /* Whitespace only on this line, ignore. */ p += n; trace("command: %s\n", command); if (!strncmp(command, "boardsize", 9)) { char *token; tell_gnugo(client_line, "b"); ask_gnugo(gnugo_line, 1, "1"); token = strtok(client_line, delimiters); token = strtok(NULL, delimiters); boardsize = atoi(token); } else if (!strncmp(command, "gg_genmove", 10)) { int move_i[10], move_j[10]; float move_value[10], position_value[10]; int moves_considered; int k; char *token; int line_length = 0; int color; if (strstr(client_line, "black")) color = BLACK; else if (strstr(client_line, "white")) color = WHITE; else { color = EMPTY; printf("?\n\n"); } if (color == BLACK) tell_gnugo("top_moves_black\n", "c"); else tell_gnugo("top_moves_white\n", "d"); ask_gnugo(gnugo_line, 0, "2"); token = strtok(gnugo_line, delimiters); for (k = 0; k < 10; k++) { move_i[k] = -1; move_j[k] = -1; move_value[k] = 0.0; } for (k = 0; k < 10; k++) { token = strtok(NULL, delimiters); if (!token) break; string_to_location(boardsize, token, move_i+k, move_j+k); token = strtok(NULL, delimiters); if (!token) break; sscanf(token, "%f", move_value+k); trace("move %d: %m valued %f\n", k, move_i[k], move_j[k], move_value[k]); } moves_considered = k; if (debug) fprintf(stderr, "moves considered: %d\n", moves_considered); for (k = 0; k < 2 && k < moves_considered; k++) { float upper, lower; int n; trace("%s %m\n", color == BLACK ? "black" : "white", move_i[k], move_j[k]); gprintf(to_gnugo_stream, "%s %m\n", color == BLACK ? "black" : "white", move_i[k], move_j[k]); fflush(to_gnugo_stream); ask_gnugo(gnugo_line, 0, "3"); tell_gnugo("estimate_score\n", "e"); ask_gnugo(gnugo_line, 0, "4"); strtok(gnugo_line, "()\n"); token = strtok(NULL, "()\n"); trace("%s\n", token); sscanf(token, "upper bound: %f, lower: %f%n", &upper, &lower, &n); if (n < 2) error("can't read territory"); trace("upper %f, lower %f\n", upper, lower); tell_gnugo("undo\n", "f"); ask_gnugo(gnugo_line, 0, "5"); fflush(stdout); if (color == BLACK) position_value[k] = - upper; else position_value[k] = lower; trace("position value %f\n", position_value[k]); } if (moves_considered == 0) { if (id == -1) gprintf(stdout, "= PASS\n\n"); else gprintf(stdout, "=%d PASS\n\n", id); fflush(stdout); } else if (moves_considered == 1 || position_value[0] > position_value[1]) { gprintf(to_gnugo_stream, "%s %m\n", color == BLACK ? "black" : "white", move_i[0], move_j[0]); ask_gnugo(gnugo_line, 0, "6"); if (id == -1) gprintf(stdout, "= %m\n\n", move_i[0], move_j[0]); else gprintf(stdout, "=%d %m\n\n", id, move_i[0], move_j[0]); fflush(stdout); } else { gprintf(to_gnugo_stream, "%s %m\n", color == BLACK ? "black" : "white", move_i[1], move_j[1]); ask_gnugo(gnugo_line, 0, "7"); if (id == -1) gprintf(stdout, "= %m\n\n", move_i[1], move_j[1]); else gprintf(stdout, "=%d %m\n\n", id, move_i[1], move_j[1]); fflush(stdout); } } else { tell_gnugo(client_line, "g"); ask_gnugo(gnugo_line, 1, "8"); } /* loadsgf commands could change the boardsize, so we get * it from the engine, after the command is run. */ if (!strncmp(command, "loadsgf", 7)) { tell_gnugo("query_boardsize\n", "i"); ask_gnugo(gnugo_line, 0, "10"); if (!sscanf(gnugo_line, "= %d", &boardsize)) error("can't get boardsize"); trace("setting boardsize %d\n", boardsize); fflush(stderr); } } } /* bark and barf */ void error(const char *msg) { fprintf(stderr, "metamachine: %s\n", msg); tell_gnugo("quit\n", msg); abort(); } /* Send a GTP command to the engine. */ void tell_gnugo(char *gnugo_line, const char *msg) { gprintf(to_gnugo_stream, gnugo_line); fflush(to_gnugo_stream); if (debug) { fprintf(stderr, "%s: %s", msg, gnugo_line); fflush(stderr); } } /* Obtains the engine's response to a GTP command. If verbose is true, * the reply is echoed to stdout. */ void ask_gnugo(char *gnugo_line, int verbose, const char *msg) { int line_length = 0; char line[GTP_BUFSIZE]; while (line_length != 1) { if (!fgets(line, 128, from_gnugo_stream)) error("can't get response"); line_length = strlen(line); if (line_length > 1 && (line[0] == '=' || line[0] == '?')) strncpy(gnugo_line, line, 128); if (verbose) printf(line); if (debug) fprintf(stderr, "%s: %s\n", msg, gnugo_line); } if (verbose) fflush(stdout); fflush(stderr); } /* Adapted from GNU Go. Formatted output with %m format. */ void gprintf(FILE *outputfile, const char *fmt, ...) { va_list ap; va_start(ap, fmt); vgprintf(outputfile, fmt, ap); fflush(outputfile); va_end(ap); } /* print diagnostic */ void trace(const char *fmt, ...) { va_list ap; if (debug) { va_start(ap, fmt); vgprintf(stderr, fmt, ap); fflush(stderr); va_end(ap); } } int vgprintf(FILE *outputfile, const char *fmt, va_list ap) { for ( ; *fmt; ++fmt) { if (*fmt == '%') { switch (*++fmt) { case 'c': { /* rules of promotion => passed as int, not char */ int c = va_arg(ap, int); putc(c, outputfile); break; } case 'd': { int d = va_arg(ap, int); fprintf(outputfile, "%d", d); break; } case 'f': { double f = va_arg(ap, double); /* passed as double, not float */ fprintf(outputfile, "%.2f", f); break; } case 's': { char *s = va_arg(ap, char *); fputs(s, outputfile); break; } case 'm': case 'M': { char movename[4]; int m = va_arg(ap, int); int n = va_arg(ap, int); if (m == -1 && n == -1) fputs("PASS", outputfile); else if (m < 0 || n < 0 || m >= 19 || n >= 19) fprintf(outputfile, "[%d,%d]", m, n); else { /* Generate the move name. */ if (n < 8) movename[0] = n + 65; else movename[0] = n + 66; if (*fmt == 'm') sprintf(movename+1, "%d", boardsize-m); else sprintf(movename+1, "%-2d", boardsize-m); fputs(movename, outputfile); } break; } default: { fprintf(outputfile, "\n\nUnknown format character: '%c'\n", *fmt); abort(); } } } else putc(*fmt, outputfile); } fflush(outputfile); } /* Extracts coordinates from a location in algebraic notation */ int string_to_location(int boardsize, char *str, int *m, int *n) { if (*str == '\0') return 0; if (!isalpha((int) *str)) return 0; *n = tolower((int) *str) - 'a'; if (tolower((int) *str) >= 'i') --*n; if (*n < 0 || *n > boardsize - 1) return 0; if (!isdigit((int) *(str+1))) return 0; *m = boardsize - atoi(str + 1); if (*m < 0 || *m > boardsize - 1) return 0; return 1; }