1 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
2  * This is GNU Go, a Go program. Contact gnugo@gnu.org, or see       *
3  * http://www.gnu.org/software/gnugo/ for more information.          *
4  *                                                                   *
5  * Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,   *
6  * 2008 and 2009 by the Free Software Foundation.                    *
7  *                                                                   *
8  * This program is free software; you can redistribute it and/or     *
9  * modify it under the terms of the GNU General Public License as    *
10  * published by the Free Software Foundation - version 3             *
11  * or (at your option) any later version.                            *
12  *                                                                   *
13  * This program is distributed in the hope that it will be useful,   *
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of    *
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the     *
16  * GNU General Public License in file COPYING for more details.      *
17  *                                                                   *
18  * You should have received a copy of the GNU General Public         *
19  * License along with this program; if not, write to the Free        *
20  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor,       *
21  * Boston, MA 02111, USA.                                            *
22 \* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
23 
24 /*
25  * This program sits between a GTP client and a GTP engine,
26  * passing commands back and forth and modifying them in
27  * some cases.
28  *
29  * To the client it appears to be a GTP engine.
30  *
31  *             stdin             pipe a
32  *  GTP client ----> metamachine -----> GTP engine
33  *             <----             <-----
34  *            stdout             pipe b
35  *
36  * Most commands are passed verbatim to the engine. The
37  * exception is gg_genmove, which is intercepted then
38  * processed differently. The top two moves are both
39  * tried, the position evaluated by estimate_score,
40  * and the move yielding the higher score is selected.
41  *
42  * Usage: no arguments gives normal GTP behavior.
43  * 'metamachine --debug' sends diagnostics to stderr.  */
44 
45 #include <stdio.h>
46 #include <unistd.h>
47 #include <stdarg.h>
48 #include <string.h>
49 #include <ctype.h>
50 
51 void error(const char *msg);
52 void gprintf(FILE *outputfile, const char *fmt, ...);
53 void trace(const char *fmt, ...);
54 void tell_gnugo(char *gnugo_line, const char *msg);
55 
56 int boardsize = 19;
57 char delimiters[] = " \t\r\n";
58 char gnugo_line[128], client_line[128];
59 FILE *to_gnugo_stream, *from_gnugo_stream;
60 
61 int debug = 0;
62 
63 #define EMPTY        0
64 #define WHITE        1
65 #define BLACK        2
66 
67 #define GTP_BUFSIZE  1000
68 
69 void ask_gnugo(char *line, int verbose, const char *msg);
70 
71 int
main(int argc,char * const * argv)72 main(int argc, char *const *argv)
73 {
74   int pfd_a[2];
75   int pfd_b[2];
76   int id;
77   int k;
78   char command[GTP_BUFSIZE];
79 
80   for (k = 1; k < argc; k++)
81     if (argc > 1 && strstr(argv[k], "debug"))
82       debug = 1;
83   if (pipe(pfd_a) == -1)
84     error("can't open pipe a");
85   if (pipe(pfd_b) == -1)
86     error("can't open pipe b");
87   switch (fork()) {
88   case -1:
89     error("fork failed (try chopsticks)");
90   case 0:
91     /* Attach pipe a to stdin */
92     if (dup2(pfd_a[0], 0) == -1)
93       error("dup pfd_a[0] failed");
94     /* attach pipe b to stdout" */
95     if (dup2(pfd_b[1], 1) == -1)
96       error("dup pfd_b[1] failed");
97     execlp("gnugo", "gnugo", "--mode", "gtp", "--quiet", NULL);
98     error("execlp failed");
99   }
100   /* Attach pipe a to to_gnugo_stream  */
101   to_gnugo_stream = fdopen(pfd_a[1], "w");
102   /* Attach pipe b to from_gnugo_stream */
103   from_gnugo_stream = fdopen(pfd_b[0], "r");
104 
105   while (1) {
106     char *p;
107     int n;
108 
109     if (!fgets(client_line, GTP_BUFSIZE, stdin)
110 	|| (strstr(client_line, "quit") == client_line)) {
111       tell_gnugo("quit\n", "a");
112 	return 1;
113     }
114 
115     /* remove comments */
116     if ((p = strchr(client_line, '#')) != NULL)
117       *p = 0;
118 
119     p = client_line;
120 
121     /* Look for an identification number. */
122     if (sscanf(p, "%d%n", &id, &n) == 1)
123       p += n;
124     else
125       id = -1; /* No identification number. */
126     trace("id = %d\n", id);
127 
128     /* Look for command name. */
129     if (sscanf(p, " %s %n", command, &n) < 1)
130       continue; /* Whitespace only on this line, ignore. */
131     p += n;
132     trace("command: %s\n", command);
133 
134     if (!strncmp(command, "boardsize", 9)) {
135       char *token;
136       tell_gnugo(client_line, "b");
137       ask_gnugo(gnugo_line, 1, "1");
138 
139       token = strtok(client_line, delimiters);
140       token = strtok(NULL, delimiters);
141       boardsize = atoi(token);
142     }
143     else if (!strncmp(command, "gg_genmove", 10)) {
144       int move_i[10], move_j[10];
145       float move_value[10], position_value[10];
146       int moves_considered;
147       int k;
148       char *token;
149       int line_length = 0;
150       int color;
151 
152       if (strstr(client_line, "black"))
153 	color = BLACK;
154       else if (strstr(client_line, "white"))
155 	color = WHITE;
156       else {
157 	color = EMPTY;
158 	printf("?\n\n");
159       }
160 
161       if (color == BLACK)
162 	tell_gnugo("top_moves_black\n", "c");
163       else
164 	tell_gnugo("top_moves_white\n", "d");
165 
166       ask_gnugo(gnugo_line, 0, "2");
167       token = strtok(gnugo_line, delimiters);
168       for (k = 0; k < 10; k++) {
169 	move_i[k] = -1;
170 	move_j[k] = -1;
171 	move_value[k] = 0.0;
172       }
173       for (k = 0; k < 10; k++) {
174 	token = strtok(NULL, delimiters);
175 	if (!token)
176 	  break;
177 	string_to_location(boardsize, token, move_i+k, move_j+k);
178 	token = strtok(NULL, delimiters);
179 	if (!token)
180 	  break;
181 	sscanf(token, "%f", move_value+k);
182 	trace("move %d: %m valued %f\n", k,
183 		  move_i[k], move_j[k], move_value[k]);
184       }
185       moves_considered = k;
186       if (debug)
187 	fprintf(stderr, "moves considered: %d\n",
188 		moves_considered);
189       for (k = 0; k < 2 && k < moves_considered; k++) {
190 	float upper, lower;
191 	int n;
192 
193 	trace("%s %m\n", color == BLACK ? "black" : "white",
194 		  move_i[k], move_j[k]);
195 	gprintf(to_gnugo_stream, "%s %m\n", color == BLACK ? "black" : "white",
196 		move_i[k], move_j[k]);
197 	fflush(to_gnugo_stream);
198 	ask_gnugo(gnugo_line, 0, "3");
199 	tell_gnugo("estimate_score\n", "e");
200 	ask_gnugo(gnugo_line, 0, "4");
201 	strtok(gnugo_line, "()\n");
202 	token = strtok(NULL, "()\n");
203 	trace("%s\n", token);
204 	sscanf(token, "upper bound: %f, lower: %f%n",
205 	       &upper, &lower, &n);
206 	if (n < 2)
207 	  error("can't read territory");
208 	trace("upper %f, lower %f\n", upper, lower);
209 	tell_gnugo("undo\n", "f");
210 	ask_gnugo(gnugo_line, 0, "5");
211 	fflush(stdout);
212 	if (color == BLACK)
213 	  position_value[k] = - upper;
214 	else
215 	  position_value[k] = lower;
216 	trace("position value %f\n", position_value[k]);
217       }
218       if (moves_considered == 0) {
219 	if (id == -1)
220 	  gprintf(stdout, "= PASS\n\n");
221 	else
222 	  gprintf(stdout, "=%d PASS\n\n", id);
223 	fflush(stdout);
224       }
225       else if (moves_considered == 1
226 	       || position_value[0] > position_value[1]) {
227 	gprintf(to_gnugo_stream, "%s %m\n", color == BLACK ? "black" : "white",
228 		move_i[0], move_j[0]);
229 	ask_gnugo(gnugo_line, 0, "6");
230 	if (id == -1)
231 	  gprintf(stdout, "= %m\n\n", move_i[0], move_j[0]);
232 	else
233 	  gprintf(stdout, "=%d %m\n\n", id, move_i[0], move_j[0]);
234 	fflush(stdout);
235       }
236       else {
237 	gprintf(to_gnugo_stream, "%s %m\n", color == BLACK ? "black" : "white",
238 		move_i[1], move_j[1]);
239 	ask_gnugo(gnugo_line, 0, "7");
240 	if (id == -1)
241 	  gprintf(stdout, "= %m\n\n", move_i[1], move_j[1]);
242 	else
243 	  gprintf(stdout, "=%d %m\n\n", id, move_i[1], move_j[1]);
244 	fflush(stdout);
245       }
246     }
247     else {
248       tell_gnugo(client_line, "g");
249       ask_gnugo(gnugo_line, 1, "8");
250     }
251     /* loadsgf commands could change the boardsize, so we get
252      * it from the engine, after the command is run. */
253     if (!strncmp(command, "loadsgf", 7)) {
254       tell_gnugo("query_boardsize\n", "i");
255       ask_gnugo(gnugo_line, 0, "10");
256       if (!sscanf(gnugo_line, "= %d", &boardsize))
257 	error("can't get boardsize");
258       trace("setting boardsize %d\n", boardsize);
259       fflush(stderr);
260     }
261   }
262 }
263 
264 
265 /* bark and barf */
266 
267 void
error(const char * msg)268 error(const char *msg)
269 {
270   fprintf(stderr, "metamachine: %s\n", msg);
271   tell_gnugo("quit\n", msg);
272   abort();
273 }
274 
275 /* Send a GTP command to the engine. */
276 
277 void
tell_gnugo(char * gnugo_line,const char * msg)278 tell_gnugo(char *gnugo_line, const char *msg)
279 {
280   gprintf(to_gnugo_stream, gnugo_line);
281   fflush(to_gnugo_stream);
282   if (debug) {
283     fprintf(stderr, "%s: %s", msg, gnugo_line);
284     fflush(stderr);
285   }
286 }
287 
288 /* Obtains the engine's response to a GTP command. If verbose is true,
289  * the reply is echoed to stdout.
290  */
291 
292 void
ask_gnugo(char * gnugo_line,int verbose,const char * msg)293 ask_gnugo(char *gnugo_line, int verbose, const char *msg)
294 {
295   int line_length = 0;
296   char line[GTP_BUFSIZE];
297 
298   while (line_length != 1) {
299     if (!fgets(line, 128, from_gnugo_stream))
300       error("can't get response");
301     line_length = strlen(line);
302     if (line_length > 1
303 	&& (line[0] == '=' || line[0] == '?'))
304       strncpy(gnugo_line, line, 128);
305     if (verbose)
306       printf(line);
307     if (debug)
308       fprintf(stderr, "%s: %s\n", msg, gnugo_line);
309   }
310   if (verbose)
311     fflush(stdout);
312   fflush(stderr);
313 }
314 
315 
316 /* Adapted from GNU Go. Formatted output with %m format. */
317 
318 void
gprintf(FILE * outputfile,const char * fmt,...)319 gprintf(FILE *outputfile, const char *fmt, ...)
320 {
321   va_list ap;
322   va_start(ap, fmt);
323   vgprintf(outputfile, fmt, ap);
324   fflush(outputfile);
325   va_end(ap);
326 }
327 
328 /* print diagnostic */
329 
330 void
trace(const char * fmt,...)331 trace(const char *fmt, ...)
332 {
333   va_list ap;
334   if (debug) {
335     va_start(ap, fmt);
336     vgprintf(stderr, fmt, ap);
337     fflush(stderr);
338     va_end(ap);
339   }
340 }
341 
342 int
vgprintf(FILE * outputfile,const char * fmt,va_list ap)343 vgprintf(FILE *outputfile, const char *fmt, va_list ap)
344 {
345   for ( ; *fmt; ++fmt) {
346     if (*fmt == '%') {
347       switch (*++fmt) {
348       case 'c':
349 	{
350 	  /* rules of promotion => passed as int, not char */
351 
352 	  int c = va_arg(ap, int);
353 	  putc(c, outputfile);
354 	  break;
355 	}
356       case 'd':
357 	{
358 	  int d = va_arg(ap, int);
359 	  fprintf(outputfile, "%d", d);
360 	  break;
361 	}
362       case 'f':
363 	{
364 	  double f = va_arg(ap, double); /* passed as double, not float */
365 	  fprintf(outputfile, "%.2f", f);
366 	  break;
367 	}
368       case 's':
369 	{
370 	  char *s = va_arg(ap, char *);
371 	  fputs(s, outputfile);
372 	  break;
373 	}
374       case 'm':
375       case 'M':
376 	{
377 	  char movename[4];
378 	  int m = va_arg(ap, int);
379 	  int n = va_arg(ap, int);
380 	  if (m == -1 && n == -1)
381 	    fputs("PASS", outputfile);
382 	  else if (m < 0 || n < 0 || m >= 19 || n >= 19)
383 	    fprintf(outputfile, "[%d,%d]", m, n);
384 	  else {
385 	    /* Generate the move name. */
386 	    if (n < 8)
387 	      movename[0] = n + 65;
388 	    else
389 	      movename[0] = n + 66;
390 	    if (*fmt == 'm')
391 	      sprintf(movename+1, "%d", boardsize-m);
392 	    else
393 	      sprintf(movename+1, "%-2d", boardsize-m);
394 	    fputs(movename, outputfile);
395 	  }
396 	  break;
397 	}
398       default:
399 	{
400 	  fprintf(outputfile, "\n\nUnknown format character: '%c'\n", *fmt);
401 	  abort();
402 	}
403       }
404     }
405     else
406       putc(*fmt, outputfile);
407   }
408   fflush(outputfile);
409 }
410 
411 /* Extracts coordinates from a location in algebraic notation */
412 
413 int
string_to_location(int boardsize,char * str,int * m,int * n)414 string_to_location(int boardsize, char *str, int *m, int *n)
415 {
416   if (*str == '\0')
417     return 0;
418 
419   if (!isalpha((int) *str))
420     return 0;
421   *n = tolower((int) *str) - 'a';
422   if (tolower((int) *str) >= 'i')
423     --*n;
424   if (*n < 0 || *n > boardsize - 1)
425     return 0;
426 
427   if (!isdigit((int) *(str+1)))
428     return 0;
429   *m = boardsize - atoi(str + 1);
430   if (*m < 0 || *m > boardsize - 1)
431     return 0;
432 
433   return 1;
434 }
435 
436