Updated README: Equal sign not required with `--mode` flag.
[sgk-go] / engine / oracle.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 or *
11 * (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/* The functions in this file implement a mechanism whereby
26 * GNU Go can fork a second gnugo process, called the oracle.
27 * The two processes communicate by means of the GTP.
28 * The functions oracle_trymove() and oracle_popgo() call
29 * trymove and popgo in the primary gnugo processes but
30 * actually play and undo the move in the oracle. This
31 * the oracle can be queried for information which is
32 * normally only available at the top level.
33 */
34
35#include "config.h"
36
37#if ORACLE
38
39#include "gnugo.h"
40#include "liberty.h"
41#include "patterns.h"
42
43#include <stdio.h>
44#include <unistd.h>
45#include <string.h>
46#include <stdlib.h>
47#include <stdarg.h>
48
49#define USE_POSIX 1
50
51FILE *to_gnugo_stream, *from_gnugo_stream;
52char gnugo_line[128];
53int gnugo_line_length;
54
55int pfd_a[2];
56int pfd_b[2];
57
58#define TELL_ORACLE(x, args...) do { \
59 if (debug & DEBUG_ORACLE_STREAM) fprintf(stderr, x, ##args); \
60 if (fprintf(to_gnugo_stream, x, ##args) < 0) \
61 error("can't write command in to_gnugo_stream"); \
62 fflush(to_gnugo_stream); \
63 } while (0)
64
65#define ASK_ORACLE do { \
66 gnugo_line_length = 0; \
67 while (gnugo_line_length != 1) { \
68 if (!fgets(gnugo_line, 128, from_gnugo_stream)) \
69 error("can't get response"); \
70 gnugo_line_length = strlen(gnugo_line); \
71 if (debug & DEBUG_ORACLE_STREAM) \
72 fprintf(stderr, gnugo_line); \
73 } \
74 } while (0)
75
76#define MAX_ORACLE_MOVES 10
77
78struct oracle_move_data {
79 int pos; /* move coordinate */
80 int color; /* color to play */
81 int value; /* value */
82 int ab_value; /* alpha-beta value */
83 const char *reason; /* why this move */
84};
85
86static void oracle_callback(int anchor, int color, struct pattern *pattern,
87 int ll, void *data);
88static void oracle_add_move(struct oracle_move_data *moves,
89 int this_move, int this_value,
90 const char *this_reason);
91void do_consult_oracle(int color);
92void error(const char *msg);
93static int oracle_trymove(int pos, int color, const char *message, int str,
94 int komaster, int kom_pos);
95static void oracle_popgo(void);
96static void tell_oracle(const char *fmt, ...);
97static void ask_oracle(void);
98static int search_width(void);
99
100
101/*** * *** * *** * *** * *** * *** * *** * *** * *** * *** * *** * ***\
102 * Primary Oracle Functions *
103\*** * *** * *** * *** * *** * *** * *** * *** * *** * *** * *** * ***/
104
105/* Forks and attaches pipes to a new GNU Go process in gtp mode.
106 * Loads the sgf file
107 */
108
109void
110summon_oracle(void)
111{
112 if (pipe(pfd_a) == -1)
113 error("can't open pipe a");
114 if (pipe(pfd_b) == -1)
115 error("can't open pipe b");
116 switch (fork()) {
117 case -1:
118 error("fork failed (try chopsticks)");
119 case 0:
120 /* Attach pipe a to stdin */
121 if (dup2(pfd_a[0], 0) == -1)
122 error("dup pfd_a[0] failed");
123 /* attach pipe b to stdout" */
124 if (dup2(pfd_b[1], 1) == -1)
125 error("dup pfd_b[1] failed");
126 execlp("gnugo", "gnugo", "--mode", "gtp", "--quiet", NULL);
127 error("execlp failed");
128 }
129 oracle_exists = 1;
130 /* Attach pipe a to to_gnugo_stream */
131 to_gnugo_stream = (FILE *) fdopen(pfd_a[1], "w");
132 /* Attach pipe b to from_gnugo_stream */
133 from_gnugo_stream = (FILE *) fdopen(pfd_b[0], "r");
134}
135
136/* load an sgf file */
137
138void
139oracle_loadsgf(char *infilename, char *untilstring)
140{
141 if (untilstring)
142 TELL_ORACLE("loadsgf %s %s\n", infilename, untilstring);
143 else
144 TELL_ORACLE("loadsgf %s\n", infilename);
145 ASK_ORACLE;
146 fflush(to_gnugo_stream);
147 gnugo_line_length = 0;
148}
149
150/* Tell the oracle to go away. */
151
152void
153dismiss_oracle(void)
154{
155 if (oracle_exists)
156 TELL_ORACLE("quit\n");
157 oracle_exists = 0;
158}
159
160/* complain and die! */
161
162void
163error(const char *msg)
164{
165 fprintf(stderr, "oracle: %s\n", msg);
166 abort();
167}
168
169/* Call trymove in the primary process, and have the oracle actually
170 * play the move.
171 */
172static int
173oracle_trymove(int pos, int color, const char *message, int str,
174 int komaster, int kom_pos)
175{
176 if (!trymove(pos, color, message, str))
177 return 0;
178 if (debug & DEBUG_ORACLE_STREAM)
179 gfprintf(stderr, "%o%s %1m\n",
180 color == BLACK ? "black" : "white", pos);
181 gfprintf(to_gnugo_stream, "%o%s %1m\n",
182 color == BLACK ? "black" : "white", pos);
183 fflush(to_gnugo_stream);
184 ASK_ORACLE;
185 return 1;
186}
187
188/* Undo the move.
189 */
190static void
191oracle_popgo(void)
192{
193 popgo();
194 TELL_ORACLE("undo\n");
195 ASK_ORACLE;
196}
197
198/* Play the move.
199 */
200
201int
202oracle_play_move(int pos, int color)
203{
204 play_move(pos, color);
205
206 if (debug & DEBUG_ORACLE_STREAM)
207 gfprintf(stderr, "%o%s %1m\n",
208 color == BLACK ? "black" : "white", pos);
209 gfprintf(to_gnugo_stream, "%o%s %1m\n",
210 color == BLACK ? "black" : "white", pos);
211 fflush(to_gnugo_stream);
212 ASK_ORACLE;
213 return 1;
214}
215
216/* FIXME: Debugging needed. This variadic function doesn't work right if we
217 * try to pass a const *char argument, like the infilename in
218 * oracle_loadsgf. So for the time being we stick with the variadic macro
219 * TELL_ORACLE.
220 */
221static void
222tell_oracle(const char *fmt, ...)
223{
224 va_list ap;
225 va_start(ap, fmt);
226 if (debug & DEBUG_ORACLE_STREAM) fprintf(stderr, fmt, ap);
227 if (fprintf(to_gnugo_stream, fmt, ap) < 0)
228 error("can't write command in to_gnugo_stream");
229 fflush(to_gnugo_stream);
230 va_end(ap);
231}
232
233/* FIXME: Debugging needed. This variadic function seems a little more
234 * reliable than the corresponding variadic macro ASK_ORACLE.
235 */
236
237static void
238ask_oracle(void)
239{
240 int line_length = 0;
241 char line[128];
242
243 while (line_length != 1) {
244 if (!fgets(line, 128, from_gnugo_stream))
245 error("can't get response");
246 line_length = strlen(line);
247 if (line_length > 1
248 && (line[0] == '=' || line[0] == '?'))
249 strncpy(gnugo_line, line, 128);
250 if (debug & DEBUG_ORACLE_STREAM) {
251 fprintf(stderr, line);
252 fflush(stderr);
253 }
254 }
255}
256
257
258/* clear the oracle's board and set the boardsize */
259
260void
261oracle_clear_board(int boardsize)
262{
263 TELL_ORACLE("boardsize %d\n", boardsize);
264 ASK_ORACLE;
265}
266
267/*** * *** * *** * *** * *** * *** * *** * *** * *** * *** * *** * ***\
268 * Demonstration: a pattern matcher *
269\*** * *** * *** * *** * *** * *** * *** * *** * *** * *** * *** * ***/
270
271/* Call the pattern matcher */
272
273void
274consult_oracle(int color)
275{
276 do_consult_oracle(color);
277}
278
279void
280do_consult_oracle(int color)
281{
282 struct oracle_move_data oracle_moves[MAX_ORACLE_MOVES];
283 int k;
284
285 for (k = 0; k < MAX_ORACLE_MOVES; k++)
286 oracle_moves[k].value = -1;
287
288 matchpat(oracle_callback, color, &oracle_db, oracle_moves, NULL);
289 for (k = 0; k < MAX_ORACLE_MOVES; k++)
290 if (oracle_moves[k].value > -1) {
291 oracle_trymove(oracle_moves[k].pos, color, oracle_moves[k].reason,
292 0, 0, NO_MOVE);
293 do_consult_oracle(OTHER_COLOR(color));
294 oracle_popgo();
295 }
296}
297
298static void
299oracle_callback(int anchor, int color, struct pattern *pattern,
300 int ll, void *data)
301{
302 int this_move;
303 struct oracle_move_data *moves = data;
304 UNUSED(color);
305
306 this_move = AFFINE_TRANSFORM(pattern->move_offset, ll, anchor);
307 if (within_search_area(this_move))
308 oracle_add_move(moves, this_move, pattern->value, pattern->name);
309 else
310 gprintf("outside the area\n");
311}
312
313/* Add a move to a list */
314
315static void
316oracle_add_move(struct oracle_move_data moves[MAX_ORACLE_MOVES],
317 int this_move, int this_value, const char *this_reason)
318{
319 int k, l;
320
321 for (k = 0; k < MAX_ORACLE_MOVES; k++)
322 if (moves[k].value == -1
323 || this_value >= moves[k].value)
324 break;
325 for (l = MAX_ORACLE_MOVES-1; l > k; l--) {
326 moves[l].pos = moves[l-1].pos;
327 moves[l].value = moves[l-1].value;
328 moves[l].reason = moves[l-1].reason;
329 }
330 moves[k].pos = this_move;
331 moves[k].value = this_value;
332 moves[k].reason = this_reason;
333}
334
335/*** * *** * *** * *** * *** * *** * *** * *** * *** * *** * *** * ***\
336 * Demonstration: metamachine *
337\*** * *** * *** * *** * *** * *** * *** * *** * *** * *** * *** * ***/
338
339#define FIRST_LEVEL_MOVES 3
340#define SECOND_LEVEL_MOVES 2
341
342static int
343do_metamachine_genmove(int color, int width, float *value);
344
345int
346metamachine_genmove(int color, float *value, int limit_search)
347{
348 int move;
349 int pos;
350
351 if (limit_search) {
352 TELL_ORACLE("limit_search 1\n");
353 ASK_ORACLE;
354 for (pos = BOARDMIN; pos < BOARDMAX; pos++)
355 if (within_search_area(pos)) {
356 if (debug & DEBUG_ORACLE_STREAM)
357 gfprintf(stderr, "%oset_search_limit %1m\n", pos);
358 gfprintf(to_gnugo_stream, "%oset_search_limit %1m\n", pos);
359 fflush(to_gnugo_stream);
360 ASK_ORACLE;
361 }
362 }
363 count_variations = 1;
364 move = do_metamachine_genmove(color, search_width(), value);
365 sgffile_enddump(outfilename);
366 count_variations = 0;
367 return move;
368}
369
370static int
371do_metamachine_genmove(int color, int width, float *value)
372{
373 int k, moves_considered;
374 float move_value[10];
375 float best_score = 0.;
376 int best_move = -1;
377 char *token;
378 int moves[10];
379 float score[10];
380 char delimiters[] = " \t\r\n";
381 char buf[100];
382 int i, j;
383
384 if (color == BLACK)
385 TELL_ORACLE("top_moves_black\n");
386 else
387 TELL_ORACLE("top_moves_white\n");
388 ask_oracle();
389 token = strtok(gnugo_line, delimiters);
390 for (k = 0; k < 10; k++) {
391 moves[k] = PASS_MOVE;
392 move_value[k] = 0.0;
393 }
394 moves_considered = width;
395 if (verbose)
396 dump_stack();
397 for (k = 0; k < moves_considered; k++) {
398 token = strtok(NULL, delimiters);
399 if (!token)
400 break;
401 moves[k] = string_to_location(board_size, token);
402 token = strtok(NULL, delimiters);
403 if (!token)
404 break;
405 sscanf(token, "%f", move_value + k);
406 TRACE("move %d: %1m valued %f\n", k, moves[k], move_value[k]);
407 }
408 /* if we left the loop early, k is the number of valid moves */
409 moves_considered = k;
410 if (moves_considered == 0) {
411 *value = 0.0;
412 return PASS_MOVE;
413 }
414 if (moves_considered == 1) {
415 *value = 1.0;
416 return moves[k];
417 }
418 for (k = 0; k < moves_considered; k++) {
419 if (oracle_trymove(moves[k], color, "", 0, 0, NO_MOVE)) {
420 int new_width = search_width();
421
422 if (new_width == 0) {
423 TELL_ORACLE("experimental_score %s\n",
424 color == BLACK ? "black" : "white");
425 ask_oracle();
426 sscanf(gnugo_line, "= %f", score + k);
427 }
428 else {
429 do_metamachine_genmove(OTHER_COLOR(color), new_width, &score[k]);
430 }
431 if (verbose)
432 dump_stack();
433 TRACE("score: %f\n", color == WHITE ? score[k] : -score[k]);
434 sprintf(buf, "value %.2f", color == WHITE ? score[k] : -score[k]);
435 if (sgf_dumptree)
436 sgftreeAddComment(sgf_dumptree, buf);
437 oracle_popgo();
438 }
439 if (best_move == -1
440 || (color == WHITE && score[k] > best_score)
441 || (color == BLACK && score[k] < best_score)) {
442 best_move = k;
443 best_score = score[k];
444 }
445 }
446 TRACE("best: %f at %1m\n", best_score, moves[best_move]);
447 *value = score[best_move];
448 return moves[best_move];
449}
450
451/* decide how wide to search */
452
453static int
454search_width(void)
455{
456 if (stackp == 0)
457 return 3;
458 else if (stackp == 1)
459 return 2;
460 else
461 return 0;
462}
463
464
465#endif
466
467
468
469/*
470 * Local Variables:
471 * tab-width: 8
472 * c-basic-offset: 2
473 * End:
474 */