Commit | Line | Data |
---|---|---|
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 | ||
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 |