/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ * This is GNU Go, a Go program. Contact address@hidden, or see * * http://www.gnu.org/software/gnugo/ for more information. * * * * Copyright 1999, 2000, 2001, 2002, 2003 and 2004 * * 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 2 * * * * 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., 59 Temple Place - Suite 330, * * Boston, MA 02111, USA. * \* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "gnugo.h" #include #include #include "sgftree.h" #include "liberty.h" #include "clock.h" #include "gg_utils.h" /* * Initialize the gnugo engine. This needs to be called * once only. */ void init_gnugo(float memory, unsigned int seed) { /* We need a fixed seed when initializing the Zobrist hashing to get * reproducable results. * FIXME: Test the quality of the seed. */ set_random_seed(12345); reading_cache_init(memory * 1024 * 1024); set_random_seed(seed); persistent_cache_init(); clear_board(); transformation_init(); dfa_match_init(); #if EXPERIMENTAL_READING tree_match_init(); #endif clear_approxlib_cache(); clear_accuratelib_cache(); } /* ---------------------------------------------------------------- */ /* * Clear the board. */ void gnugo_clear_board(int boardsize) { gg_assert(MIN_BOARD <= boardsize && boardsize <= MAX_BOARD); board_size = boardsize; clear_board(); #if 0 if (metamachine && oracle_exists) oracle_clear_board(boardsize); #endif } /* Set the komi */ void gnugo_set_komi(float new_komi) { komi = new_komi; } /* Place a stone on the board */ void gnugo_add_stone(int i, int j, int color) { add_stone(POS(i, j), color); } /* Remove a stone from the board */ void gnugo_remove_stone(int i, int j) { remove_stone(POS(i, j)); } /* Return true if (i,j) is PASS_MOVE */ int gnugo_is_pass(int i, int j) { return is_pass(POS(i, j)); } /* Play a move and start the clock */ void gnugo_play_move(int i, int j, int color) { #if ORACLE if (oracle_exists) oracle_play_move(POS(i, j), color); else play_move(POS(i, j), color); #else play_move(POS(i, j), color); #endif clock_push_button(color); } /* Undo n permanent moves. Returns 1 if successful and 0 if it fails. * If n moves cannot be undone, no move is undone. */ int gnugo_undo_move(int n) { return undo_move(n); } /* * Perform the moves and place the stones from the SGF node on the * board. Return the color of the player whose turn it is to move. */ int gnugo_play_sgfnode(SGFNode *node, int to_move) { int i, j; SGFProperty *prop; for (prop = node->props; prop; prop = prop->next) { switch (prop->name) { case SGFAB: /* A black stone. */ get_moveXY(prop, &i, &j, board_size); gnugo_add_stone(i, j, BLACK); break; case SGFAW: /* A white stone. */ get_moveXY(prop, &i, &j, board_size); gnugo_add_stone(i, j, WHITE); break; case SGFPL: /* Player property - who is next to move? */ if (prop->value[0] == 'w' || prop->value[0] == 'W') to_move = WHITE; else to_move = BLACK; break; case SGFW: case SGFB: /* An ordinary move. */ to_move = (prop->name == SGFW) ? WHITE : BLACK; get_moveXY(prop, &i, &j, board_size); gnugo_play_move(i, j, to_move); to_move = OTHER_COLOR(to_move); break; } } return to_move; } /* * Play the moves in ROOT UNTIL movenumber is reached. * Return the color of the player whose turn it is to move. */ int gnugo_play_sgftree(SGFNode *root, int *until, SGFNode **curnode) { SGFNode *node; int next = BLACK; SGFProperty *prop; int i, j; int movenumber = 0; /* Start from the empty board. */ gnugo_clear_board(board_size); for (node = root; node; node = node->child) { for (prop = node->props; prop; prop = prop->next) { switch (prop->name) { case SGFAB: get_moveXY(prop, &i, &j, board_size); gnugo_add_stone(i, j, BLACK); break; case SGFAW: get_moveXY(prop, &i, &j, board_size); gnugo_add_stone(i, j, WHITE); break; case SGFPL: if (prop->value[0] == 'w' || prop->value[0] == 'W') next = WHITE; else next = BLACK; break; case SGFW: case SGFB: next = prop->name == SGFW ? WHITE : BLACK; get_moveXY(prop, &i, &j, board_size); movenumber++; if (movenumber == *until) { *curnode = node->parent; return next; } gnugo_play_move(i, j, next); next = OTHER_COLOR(next); break; } } } *until = movenumber; return next; } /* Interface to is_legal(). */ int gnugo_is_legal(int i, int j, int color) { return is_legal(POS(i, j), color); } /* Interface to is_suicide(). */ int gnugo_is_suicide(int i, int j, int color) { return is_suicide(POS(i, j), color); } /* Interface to placehand. Sets up handicap stones and * returns the number of placed handicap stones. */ int gnugo_placehand(int handicap) { return place_fixed_handicap(handicap); } /* Interface to sgffile_recordboard */ void gnugo_recordboard(SGFNode *root) { sgffile_recordboard(root); } /* Interface to placehand. Sets up handicap stones and * returns the number of placed handicap stones, updating the sgf file. */ int gnugo_sethand(int handicap, SGFNode *node) { int stones = place_fixed_handicap(handicap); sgffile_recordboard(node); return stones; } /* Interface to genmove() */ int gnugo_genmove(int *i, int *j, int color) { return genmove(i, j, color); } /* CHANGE: Interface to genmove_ponder() */ int gnugo_genmove_ponder(int color, int restart, int opponent_move) { return genmove_ponder(color, restart, opponent_move); } /* Interface to attack() */ int gnugo_attack(int m, int n, int *i, int *j) { int retval; int move; retval = attack(POS(m, n), &move); if (i) *i = I(move); if (j) *j = J(move); return retval; } /* Interface to find_defense() */ int gnugo_find_defense(int m, int n, int *i, int *j) { int retval; int move; retval = find_defense(POS(m, n), &move); if (i) *i = I(move); if (j) *j = J(move); return retval; } /* Interface to who_wins */ void gnugo_who_wins(int color, FILE *outfile) { who_wins(color, outfile); } /* Put upper and lower score estimates into *upper, *lower and * return the average. A positive score favors white. In computing * the upper bound, CRITICAL dragons are awarded to white; in * computing the lower bound, they are awarded to black. */ float gnugo_estimate_score(float *upper, float *lower) { silent_examine_position(EXAMINE_DRAGONS); return estimate_score(upper, lower); } /* Accessor functions for internal board state. */ /* Report the komi. */ float gnugo_get_komi() { return komi; } /* Place the board into the b array */ void gnugo_get_board(int b[MAX_BOARD][MAX_BOARD]) { int i, j; for (i = 0; i < board_size; i++) for (j = 0; j < board_size; j++) b[i][j] = BOARD(i, j); } int gnugo_get_boardsize() { return board_size; } int gnugo_get_move_number() { return movenum; } /* ================================================================ */ /* Gameinfo */ /* ================================================================ */ /* * Initialize the structure. */ void gameinfo_clear(Gameinfo *ginfo, int boardsize, float komi) { ginfo->handicap = 0; gnugo_clear_board(boardsize); gnugo_set_komi(komi); ginfo->to_move = BLACK; sgftree_clear(&ginfo->game_record); /* Info relevant to the computer player. */ ginfo->computer_player = WHITE; /* Make an assumption. */ } /* * Print a gameinfo. */ void gameinfo_print(Gameinfo *ginfo) { printf("Board Size: %d\n", board_size); printf("Handicap %d\n", ginfo->handicap); printf("Komi: %.1f\n", komi); printf("Move Number: %d\n", movenum); printf("To Move: %s\n", color_to_string(ginfo->to_move)); printf("Computer player: "); if (ginfo->computer_player == WHITE) printf("White\n"); else if (ginfo->computer_player == BLACK) printf("Black\n"); else if (ginfo->computer_player == EMPTY) printf("Both (solo)\n"); else printf("Nobody\n"); } /* * Reads header info from sgf structure and sets the appropriate variables. */ void gameinfo_load_sgfheader(Gameinfo *gameinfo, SGFNode *head) { int bsize; int handicap; float komi; if (!sgfGetIntProperty(head, "SZ", &bsize)) bsize = 19; if (!sgfGetFloatProperty(head, "KM", &komi)) komi = 5.5; gnugo_clear_board(bsize); gnugo_set_komi(komi); if (!sgfGetIntProperty(head, "HA", &handicap) || handicap < 0) /* Handicap stones should appear as AW, AB properties in the sgf file. */ handicap = 0; gameinfo->handicap = handicap; if (handicap > bsize * bsize - 1 || handicap < 0) { fprintf(stderr, " Handicap HA[%d] is unreasonable.\n", handicap); fprintf(stderr, " Modify SGF file.\n"); exit(EXIT_FAILURE); } } /* * Make a move in the game. Return 1 if the move was legal. In that * case the move is actually done. Otherwise return 0. */ void gameinfo_play_move(Gameinfo *ginfo, int i, int j, int color) { gnugo_play_move(i, j, color); sgftreeAddPlay(&ginfo->game_record, color, i, j); ginfo->to_move = OTHER_COLOR(color); } /* * Play the moves in an SGF tree. Walk the main variation, actioning * the properties into the playing board. * * Returns the color of the next move to be made. * * Head is an sgf tree. * Untilstr is an optional string of the form either 'L12' or '120' * which tells it to stop playing at that move or move-number. * When debugging, this is the location of the move being examined. */ int gameinfo_play_sgftree_rot(Gameinfo *gameinfo, SGFTree *tree, const char *untilstr, int orientation) { int bs, handicap; float komi; int next = BLACK; int untilm = -1, untiln = -1; int until = 9999; if (!sgfGetFloatProperty(tree->root, "KM", &komi)) { if (gameinfo->handicap == 0) komi = 5.5; else komi = 0.5; } if (!sgfGetIntProperty(tree->root, "SZ", &bs)) bs = 19; gnugo_clear_board(bs); gnugo_set_komi(komi); /* Now we can safely parse the until string (which depends on board size). */ if (untilstr) { if (*untilstr > '0' && *untilstr <= '9') { until = atoi(untilstr); DEBUG(DEBUG_LOADSGF, "Loading until move %d\n", until); } else { if ('A' <= *untilstr && *untilstr <= 'Z') untiln = *untilstr - 'A'; else untiln = *untilstr - 'a'; if (untiln >= 'I' - 'A') --untiln; untilm = board_size - atoi(untilstr+1); DEBUG(DEBUG_LOADSGF, "Loading until move at %d,%d (%m)\n", untilm, untiln, untilm, untiln); } } if (sgfGetIntProperty(tree->root, "HA", &handicap) && handicap > 1) { gameinfo->handicap = handicap; next = WHITE; } /* Finally, we iterate over all the properties of all the * nodes, actioning them. We follow only the 'child' pointers, * as we have no interest in variations. * * The sgf routines map AB[aa][bb][cc] into AB[aa]AB[bb]AB[cc] */ for (tree->lastnode = NULL; sgftreeForward(tree);) { SGFProperty *prop; int i, j; for (prop = tree->lastnode->props; prop; prop = prop->next) { DEBUG(DEBUG_LOADSGF, "%c%c[%s]\n", prop->name & 0xff, (prop->name >> 8), prop->value); switch (prop->name) { case SGFAB: get_moveXY(prop, &i, &j, board_size); /* Generally the last move is unknown when the AB or AW * properties are encountered. These are used to set up * a board position (diagram) or to place handicap stones * without reference to the order in which the stones are * placed on the board. */ rotate(i, j, &i, &j, board_size, orientation); if (BOARD(i, j) != EMPTY) gprintf("Illegal SGF! attempt to add a stone at occupied point %m\n", i, j); else gnugo_add_stone(i, j, BLACK); break; case SGFAW: get_moveXY(prop, &i, &j, board_size); rotate(i, j, &i, &j, board_size, orientation); if (BOARD(i, j) != EMPTY) gprintf("Illegal SGF! attempt to add a stone at occupied point %m\n", i, j); else gnugo_add_stone(i, j, WHITE); break; case SGFPL: /* Due to a bad comment in the SGF FF3 definition (in the * "Alphabetical list of properties" section) some * applications encode the colors with 1 for black and 2 for * white. */ if (prop->value[0] == 'w' || prop->value[0] == 'W' || prop->value[0] == '2') next = WHITE; else next = BLACK; /* following really should not be needed for proper sgf file */ if (stones_on_board(GRAY) == 0 && next == WHITE) { gnugo_sethand(gameinfo->handicap, 0); sgfOverwritePropertyInt(tree->root, "HA", gameinfo->handicap); } break; case SGFW: case SGFB: next = prop->name == SGFW ? WHITE : BLACK; /* following really should not be needed for proper sgf file */ if (stones_on_board(GRAY) == 0 && next == WHITE) { gnugo_sethand(gameinfo->handicap, 0); sgfOverwritePropertyInt(tree->root, "HA", gameinfo->handicap); } if (movenum == until - 1) { gameinfo->to_move = next; /* go back so that variant will be added to the proper node */ sgftreeBack(tree); return next; } if (get_moveXY(prop, &i, &j, board_size)) if (i == untilm && j == untiln) { gameinfo->to_move = next; /* go back so that variant will be added to the proper node */ sgftreeBack(tree); return next; } rotate(i, j, &i, &j, board_size, orientation); if ((ON_BOARD2(i, j) && board[POS(i, j)] == EMPTY) || (i == -1 && j == -1)) { gnugo_play_move(i, j, next); next = OTHER_COLOR(next); } else { gprintf("WARNING: Move off board or on occupied position found in sgf-file.\n"); gprintf("Move at %m ignored, trying to proceed.\n", i, j); return next; } break; case SGFIL: /* The IL property is not a standard SGF property but * is used by GNU Go to mark illegal moves. If a move * is found marked with the IL property which is a ko * capture then that ko capture is deemed illegal and * (board_ko_i, board_ko_j) is set to the location of * the ko. */ get_moveXY(prop, &i, &j, board_size); rotate(i, j, &i, &j, board_size, orientation); { int move_color; if (!ON_BOARD2(i, j)) break; if (i > 0) move_color = OTHER_COLOR(BOARD(i-1, j)); else move_color = OTHER_COLOR(BOARD(i+1, j)); if (is_ko(POS(i, j), move_color, NULL)) board_ko_pos = POS(i, j); } break; } } } gameinfo->to_move = next; return next; } /* Same as previous function, using standard orientation */ int gameinfo_play_sgftree(Gameinfo *gameinfo, SGFTree *tree, const char *untilstr) { return gameinfo_play_sgftree_rot(gameinfo, tree, untilstr, 0); } /* * Local Variables: * tab-width: 8 * c-basic-offset: 2 * End: */