/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* 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
* To the client it appears to be a GTP engine.
* GTP client ----> metamachine -----> GTP engine
* 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. */
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
);
char delimiters
[] = " \t\r\n";
char gnugo_line
[128], client_line
[128];
FILE *to_gnugo_stream
, *from_gnugo_stream
;
void ask_gnugo(char *line
, int verbose
, const char *msg
);
main(int argc
, char *const *argv
)
char command
[GTP_BUFSIZE
];
for (k
= 1; k
< argc
; k
++)
if (argc
> 1 && strstr(argv
[k
], "debug"))
error("can't open pipe a");
error("can't open pipe b");
error("fork failed (try chopsticks)");
/* 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
);
/* 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");
if (!fgets(client_line
, GTP_BUFSIZE
, stdin
)
|| (strstr(client_line
, "quit") == client_line
)) {
tell_gnugo("quit\n", "a");
if ((p
= strchr(client_line
, '#')) != NULL
)
/* Look for an identification number. */
if (sscanf(p
, "%d%n", &id
, &n
) == 1)
id
= -1; /* No identification number. */
/* Look for command name. */
if (sscanf(p
, " %s %n", command
, &n
) < 1)
continue; /* Whitespace only on this line, ignore. */
trace("command: %s\n", command
);
if (!strncmp(command
, "boardsize", 9)) {
tell_gnugo(client_line
, "b");
ask_gnugo(gnugo_line
, 1, "1");
token
= strtok(client_line
, delimiters
);
token
= strtok(NULL
, delimiters
);
else if (!strncmp(command
, "gg_genmove", 10)) {
int move_i
[10], move_j
[10];
float move_value
[10], position_value
[10];
if (strstr(client_line
, "black"))
else if (strstr(client_line
, "white"))
tell_gnugo("top_moves_black\n", "c");
tell_gnugo("top_moves_white\n", "d");
ask_gnugo(gnugo_line
, 0, "2");
token
= strtok(gnugo_line
, delimiters
);
for (k
= 0; k
< 10; k
++) {
for (k
= 0; k
< 10; k
++) {
token
= strtok(NULL
, delimiters
);
string_to_location(boardsize
, token
, move_i
+k
, move_j
+k
);
token
= strtok(NULL
, delimiters
);
sscanf(token
, "%f", move_value
+k
);
trace("move %d: %m valued %f\n", k
,
move_i
[k
], move_j
[k
], move_value
[k
]);
fprintf(stderr
, "moves considered: %d\n",
for (k
= 0; k
< 2 && k
< moves_considered
; k
++) {
trace("%s %m\n", color
== BLACK
? "black" : "white",
gprintf(to_gnugo_stream
, "%s %m\n", color
== BLACK
? "black" : "white",
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");
sscanf(token
, "upper bound: %f, lower: %f%n",
error("can't read territory");
trace("upper %f, lower %f\n", upper
, lower
);
tell_gnugo("undo\n", "f");
ask_gnugo(gnugo_line
, 0, "5");
position_value
[k
] = - upper
;
position_value
[k
] = lower
;
trace("position value %f\n", position_value
[k
]);
if (moves_considered
== 0) {
gprintf(stdout
, "= PASS\n\n");
gprintf(stdout
, "=%d PASS\n\n", id
);
else if (moves_considered
== 1
|| position_value
[0] > position_value
[1]) {
gprintf(to_gnugo_stream
, "%s %m\n", color
== BLACK
? "black" : "white",
ask_gnugo(gnugo_line
, 0, "6");
gprintf(stdout
, "= %m\n\n", move_i
[0], move_j
[0]);
gprintf(stdout
, "=%d %m\n\n", id
, move_i
[0], move_j
[0]);
gprintf(to_gnugo_stream
, "%s %m\n", color
== BLACK
? "black" : "white",
ask_gnugo(gnugo_line
, 0, "7");
gprintf(stdout
, "= %m\n\n", move_i
[1], move_j
[1]);
gprintf(stdout
, "=%d %m\n\n", id
, move_i
[1], move_j
[1]);
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
);
fprintf(stderr
, "metamachine: %s\n", msg
);
tell_gnugo("quit\n", msg
);
/* Send a GTP command to the engine. */
tell_gnugo(char *gnugo_line
, const char *msg
)
gprintf(to_gnugo_stream
, gnugo_line
);
fprintf(stderr
, "%s: %s", msg
, gnugo_line
);
/* Obtains the engine's response to a GTP command. If verbose is true,
* the reply is echoed to stdout.
ask_gnugo(char *gnugo_line
, int verbose
, const char *msg
)
while (line_length
!= 1) {
if (!fgets(line
, 128, from_gnugo_stream
))
error("can't get response");
line_length
= strlen(line
);
&& (line
[0] == '=' || line
[0] == '?'))
strncpy(gnugo_line
, line
, 128);
fprintf(stderr
, "%s: %s\n", msg
, gnugo_line
);
/* Adapted from GNU Go. Formatted output with %m format. */
gprintf(FILE *outputfile
, const char *fmt
, ...)
vgprintf(outputfile
, fmt
, ap
);
trace(const char *fmt
, ...)
vgprintf(stderr
, fmt
, ap
);
vgprintf(FILE *outputfile
, const char *fmt
, va_list ap
)
/* rules of promotion => passed as int, not char */
fprintf(outputfile
, "%d", d
);
double f
= va_arg(ap
, double); /* passed as double, not float */
fprintf(outputfile
, "%.2f", f
);
char *s
= va_arg(ap
, char *);
fputs("PASS", outputfile
);
else if (m
< 0 || n
< 0 || m
>= 19 || n
>= 19)
fprintf(outputfile
, "[%d,%d]", m
, n
);
/* Generate the move name. */
sprintf(movename
+1, "%d", boardsize
-m
);
sprintf(movename
+1, "%-2d", boardsize
-m
);
fputs(movename
, outputfile
);
fprintf(outputfile
, "\n\nUnknown format character: '%c'\n", *fmt
);
/* Extracts coordinates from a location in algebraic notation */
string_to_location(int boardsize
, char *str
, int *m
, int *n
)
if (!isalpha((int) *str
))
*n
= tolower((int) *str
) - 'a';
if (tolower((int) *str
) >= 'i')
if (*n
< 0 || *n
> boardsize
- 1)
if (!isdigit((int) *(str
+1)))
*m
= boardsize
- atoi(str
+ 1);
if (*m
< 0 || *m
> boardsize
- 1)