Initial commit of GNU Go v3.8.
[sgk-go] / engine / oracle.c
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* 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. *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* The functions in this file implement a mechanism whereby
* GNU Go can fork a second gnugo process, called the oracle.
* The two processes communicate by means of the GTP.
* The functions oracle_trymove() and oracle_popgo() call
* trymove and popgo in the primary gnugo processes but
* actually play and undo the move in the oracle. This
* the oracle can be queried for information which is
* normally only available at the top level.
*/
#include "config.h"
#if ORACLE
#include "gnugo.h"
#include "liberty.h"
#include "patterns.h"
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#define USE_POSIX 1
FILE *to_gnugo_stream, *from_gnugo_stream;
char gnugo_line[128];
int gnugo_line_length;
int pfd_a[2];
int pfd_b[2];
#define TELL_ORACLE(x, args...) do { \
if (debug & DEBUG_ORACLE_STREAM) fprintf(stderr, x, ##args); \
if (fprintf(to_gnugo_stream, x, ##args) < 0) \
error("can't write command in to_gnugo_stream"); \
fflush(to_gnugo_stream); \
} while (0)
#define ASK_ORACLE do { \
gnugo_line_length = 0; \
while (gnugo_line_length != 1) { \
if (!fgets(gnugo_line, 128, from_gnugo_stream)) \
error("can't get response"); \
gnugo_line_length = strlen(gnugo_line); \
if (debug & DEBUG_ORACLE_STREAM) \
fprintf(stderr, gnugo_line); \
} \
} while (0)
#define MAX_ORACLE_MOVES 10
struct oracle_move_data {
int pos; /* move coordinate */
int color; /* color to play */
int value; /* value */
int ab_value; /* alpha-beta value */
const char *reason; /* why this move */
};
static void oracle_callback(int anchor, int color, struct pattern *pattern,
int ll, void *data);
static void oracle_add_move(struct oracle_move_data *moves,
int this_move, int this_value,
const char *this_reason);
void do_consult_oracle(int color);
void error(const char *msg);
static int oracle_trymove(int pos, int color, const char *message, int str,
int komaster, int kom_pos);
static void oracle_popgo(void);
static void tell_oracle(const char *fmt, ...);
static void ask_oracle(void);
static int search_width(void);
/*** * *** * *** * *** * *** * *** * *** * *** * *** * *** * *** * ***\
* Primary Oracle Functions *
\*** * *** * *** * *** * *** * *** * *** * *** * *** * *** * *** * ***/
/* Forks and attaches pipes to a new GNU Go process in gtp mode.
* Loads the sgf file
*/
void
summon_oracle(void)
{
if (pipe(pfd_a) == -1)
error("can't open pipe a");
if (pipe(pfd_b) == -1)
error("can't open pipe b");
switch (fork()) {
case -1:
error("fork failed (try chopsticks)");
case 0:
/* 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);
error("execlp failed");
}
oracle_exists = 1;
/* Attach pipe a to to_gnugo_stream */
to_gnugo_stream = (FILE *) fdopen(pfd_a[1], "w");
/* Attach pipe b to from_gnugo_stream */
from_gnugo_stream = (FILE *) fdopen(pfd_b[0], "r");
}
/* load an sgf file */
void
oracle_loadsgf(char *infilename, char *untilstring)
{
if (untilstring)
TELL_ORACLE("loadsgf %s %s\n", infilename, untilstring);
else
TELL_ORACLE("loadsgf %s\n", infilename);
ASK_ORACLE;
fflush(to_gnugo_stream);
gnugo_line_length = 0;
}
/* Tell the oracle to go away. */
void
dismiss_oracle(void)
{
if (oracle_exists)
TELL_ORACLE("quit\n");
oracle_exists = 0;
}
/* complain and die! */
void
error(const char *msg)
{
fprintf(stderr, "oracle: %s\n", msg);
abort();
}
/* Call trymove in the primary process, and have the oracle actually
* play the move.
*/
static int
oracle_trymove(int pos, int color, const char *message, int str,
int komaster, int kom_pos)
{
if (!trymove(pos, color, message, str))
return 0;
if (debug & DEBUG_ORACLE_STREAM)
gfprintf(stderr, "%o%s %1m\n",
color == BLACK ? "black" : "white", pos);
gfprintf(to_gnugo_stream, "%o%s %1m\n",
color == BLACK ? "black" : "white", pos);
fflush(to_gnugo_stream);
ASK_ORACLE;
return 1;
}
/* Undo the move.
*/
static void
oracle_popgo(void)
{
popgo();
TELL_ORACLE("undo\n");
ASK_ORACLE;
}
/* Play the move.
*/
int
oracle_play_move(int pos, int color)
{
play_move(pos, color);
if (debug & DEBUG_ORACLE_STREAM)
gfprintf(stderr, "%o%s %1m\n",
color == BLACK ? "black" : "white", pos);
gfprintf(to_gnugo_stream, "%o%s %1m\n",
color == BLACK ? "black" : "white", pos);
fflush(to_gnugo_stream);
ASK_ORACLE;
return 1;
}
/* FIXME: Debugging needed. This variadic function doesn't work right if we
* try to pass a const *char argument, like the infilename in
* oracle_loadsgf. So for the time being we stick with the variadic macro
* TELL_ORACLE.
*/
static void
tell_oracle(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
if (debug & DEBUG_ORACLE_STREAM) fprintf(stderr, fmt, ap);
if (fprintf(to_gnugo_stream, fmt, ap) < 0)
error("can't write command in to_gnugo_stream");
fflush(to_gnugo_stream);
va_end(ap);
}
/* FIXME: Debugging needed. This variadic function seems a little more
* reliable than the corresponding variadic macro ASK_ORACLE.
*/
static void
ask_oracle(void)
{
int line_length = 0;
char line[128];
while (line_length != 1) {
if (!fgets(line, 128, from_gnugo_stream))
error("can't get response");
line_length = strlen(line);
if (line_length > 1
&& (line[0] == '=' || line[0] == '?'))
strncpy(gnugo_line, line, 128);
if (debug & DEBUG_ORACLE_STREAM) {
fprintf(stderr, line);
fflush(stderr);
}
}
}
/* clear the oracle's board and set the boardsize */
void
oracle_clear_board(int boardsize)
{
TELL_ORACLE("boardsize %d\n", boardsize);
ASK_ORACLE;
}
/*** * *** * *** * *** * *** * *** * *** * *** * *** * *** * *** * ***\
* Demonstration: a pattern matcher *
\*** * *** * *** * *** * *** * *** * *** * *** * *** * *** * *** * ***/
/* Call the pattern matcher */
void
consult_oracle(int color)
{
do_consult_oracle(color);
}
void
do_consult_oracle(int color)
{
struct oracle_move_data oracle_moves[MAX_ORACLE_MOVES];
int k;
for (k = 0; k < MAX_ORACLE_MOVES; k++)
oracle_moves[k].value = -1;
matchpat(oracle_callback, color, &oracle_db, oracle_moves, NULL);
for (k = 0; k < MAX_ORACLE_MOVES; k++)
if (oracle_moves[k].value > -1) {
oracle_trymove(oracle_moves[k].pos, color, oracle_moves[k].reason,
0, 0, NO_MOVE);
do_consult_oracle(OTHER_COLOR(color));
oracle_popgo();
}
}
static void
oracle_callback(int anchor, int color, struct pattern *pattern,
int ll, void *data)
{
int this_move;
struct oracle_move_data *moves = data;
UNUSED(color);
this_move = AFFINE_TRANSFORM(pattern->move_offset, ll, anchor);
if (within_search_area(this_move))
oracle_add_move(moves, this_move, pattern->value, pattern->name);
else
gprintf("outside the area\n");
}
/* Add a move to a list */
static void
oracle_add_move(struct oracle_move_data moves[MAX_ORACLE_MOVES],
int this_move, int this_value, const char *this_reason)
{
int k, l;
for (k = 0; k < MAX_ORACLE_MOVES; k++)
if (moves[k].value == -1
|| this_value >= moves[k].value)
break;
for (l = MAX_ORACLE_MOVES-1; l > k; l--) {
moves[l].pos = moves[l-1].pos;
moves[l].value = moves[l-1].value;
moves[l].reason = moves[l-1].reason;
}
moves[k].pos = this_move;
moves[k].value = this_value;
moves[k].reason = this_reason;
}
/*** * *** * *** * *** * *** * *** * *** * *** * *** * *** * *** * ***\
* Demonstration: metamachine *
\*** * *** * *** * *** * *** * *** * *** * *** * *** * *** * *** * ***/
#define FIRST_LEVEL_MOVES 3
#define SECOND_LEVEL_MOVES 2
static int
do_metamachine_genmove(int color, int width, float *value);
int
metamachine_genmove(int color, float *value, int limit_search)
{
int move;
int pos;
if (limit_search) {
TELL_ORACLE("limit_search 1\n");
ASK_ORACLE;
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
if (within_search_area(pos)) {
if (debug & DEBUG_ORACLE_STREAM)
gfprintf(stderr, "%oset_search_limit %1m\n", pos);
gfprintf(to_gnugo_stream, "%oset_search_limit %1m\n", pos);
fflush(to_gnugo_stream);
ASK_ORACLE;
}
}
count_variations = 1;
move = do_metamachine_genmove(color, search_width(), value);
sgffile_enddump(outfilename);
count_variations = 0;
return move;
}
static int
do_metamachine_genmove(int color, int width, float *value)
{
int k, moves_considered;
float move_value[10];
float best_score = 0.;
int best_move = -1;
char *token;
int moves[10];
float score[10];
char delimiters[] = " \t\r\n";
char buf[100];
int i, j;
if (color == BLACK)
TELL_ORACLE("top_moves_black\n");
else
TELL_ORACLE("top_moves_white\n");
ask_oracle();
token = strtok(gnugo_line, delimiters);
for (k = 0; k < 10; k++) {
moves[k] = PASS_MOVE;
move_value[k] = 0.0;
}
moves_considered = width;
if (verbose)
dump_stack();
for (k = 0; k < moves_considered; k++) {
token = strtok(NULL, delimiters);
if (!token)
break;
moves[k] = string_to_location(board_size, token);
token = strtok(NULL, delimiters);
if (!token)
break;
sscanf(token, "%f", move_value + k);
TRACE("move %d: %1m valued %f\n", k, moves[k], move_value[k]);
}
/* if we left the loop early, k is the number of valid moves */
moves_considered = k;
if (moves_considered == 0) {
*value = 0.0;
return PASS_MOVE;
}
if (moves_considered == 1) {
*value = 1.0;
return moves[k];
}
for (k = 0; k < moves_considered; k++) {
if (oracle_trymove(moves[k], color, "", 0, 0, NO_MOVE)) {
int new_width = search_width();
if (new_width == 0) {
TELL_ORACLE("experimental_score %s\n",
color == BLACK ? "black" : "white");
ask_oracle();
sscanf(gnugo_line, "= %f", score + k);
}
else {
do_metamachine_genmove(OTHER_COLOR(color), new_width, &score[k]);
}
if (verbose)
dump_stack();
TRACE("score: %f\n", color == WHITE ? score[k] : -score[k]);
sprintf(buf, "value %.2f", color == WHITE ? score[k] : -score[k]);
if (sgf_dumptree)
sgftreeAddComment(sgf_dumptree, buf);
oracle_popgo();
}
if (best_move == -1
|| (color == WHITE && score[k] > best_score)
|| (color == BLACK && score[k] < best_score)) {
best_move = k;
best_score = score[k];
}
}
TRACE("best: %f at %1m\n", best_score, moves[best_move]);
*value = score[best_move];
return moves[best_move];
}
/* decide how wide to search */
static int
search_width(void)
{
if (stackp == 0)
return 3;
else if (stackp == 1)
return 2;
else
return 0;
}
#endif
/*
* Local Variables:
* tab-width: 8
* c-basic-offset: 2
* End:
*/