Initial commit of GNU Go v3.8.
[sgk-go] / interface / gtp_examples / metamachine.c
CommitLineData
7eeb782e
AT
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
51void error(const char *msg);
52void gprintf(FILE *outputfile, const char *fmt, ...);
53void trace(const char *fmt, ...);
54void tell_gnugo(char *gnugo_line, const char *msg);
55
56int boardsize = 19;
57char delimiters[] = " \t\r\n";
58char gnugo_line[128], client_line[128];
59FILE *to_gnugo_stream, *from_gnugo_stream;
60
61int debug = 0;
62
63#define EMPTY 0
64#define WHITE 1
65#define BLACK 2
66
67#define GTP_BUFSIZE 1000
68
69void ask_gnugo(char *line, int verbose, const char *msg);
70
71int
72main(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
267void
268error(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
277void
278tell_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
292void
293ask_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
318void
319gprintf(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
330void
331trace(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
342int
343vgprintf(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
413int
414string_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