Index: apps/plugins/viewers.config =================================================================== --- apps/plugins/viewers.config (revision 15606) +++ apps/plugins/viewers.config (working copy) @@ -19,6 +19,7 @@ rsp,viewers/searchengine,8 sok,games/sokoban,1 pgn,games/chessbox,1 +sgf,games/goban,1 ss,games/sudoku,1 wav,viewers/wav2wv,- wav,viewers/mp3_encoder,- Index: apps/plugins/goban/sgf_utils.c =================================================================== --- apps/plugins/goban/sgf_utils.c (revision 0) +++ apps/plugins/goban/sgf_utils.c (revision 0) @@ -0,0 +1,299 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ + * 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 and 2006 * + * 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., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02111, USA. * +\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "plugin.h" + + +#include "sgftree.h" + +extern struct plugin_api* rb; + + +int toupper(int input) +{ + if (input >= 'a' && input <= 'z') + { + return input - 'a' + 'A'; + } + + return input; +} + +/* + * Return the integer X move. + */ + +int +get_moveX(SGFProperty *property, int boardsize) +{ + int i; + if (rb->strlen(property->value) < 2) + return -1; + + i = toupper((int) property->value[1]) - 'A'; + if (i >= boardsize) + return -1; + + return i; +} + +/* + * Return the integer Y move. + */ + +int +get_moveY(SGFProperty *property, int boardsize) +{ + int j; + if (rb->strlen(property->value) < 2) + return -1; + + j = toupper((int) property->value[0]) - 'A'; + if (j >= boardsize) + return -1; + + return j; +} + + +/* Fills (*i, *j) from the property value, in GNU Go co-ords. + * Note that GNU Go uses different conventions from sgf for + * co-ordinates been called. + * + * Returns 1 for a move, 0 for a pass. + */ + +int +get_moveXY(SGFProperty *property, int *i, int *j, int boardsize) +{ + *i = get_moveX(property, boardsize); + *j = get_moveY(property, boardsize); + + if (*i == -1 && *j == -1) + return 0; + + return 1; +} + + +/* + * Debugging function to print properties as they are traversed. + */ + +int +show_sgf_properties(SGFNode *node) +{ + /*SGFProperty *sgf_prop;*/ + int propcount; + (void)node; + + propcount = 0; + return propcount; +} + + +/* + * Recursively traverse each node showing all properties. + */ + +int +show_sgf_tree(SGFNode *node) +{ + int n = 0; /* number of nodes */ + + n++; + show_sgf_properties(node); + + /* must search depth first- siblings are equal! */ + if (node->child) + n += show_sgf_tree(node->child); + + if (node->next) + n += show_sgf_tree(node->next); + + return n; +} + + +/* + * Determine if a node has a mark property in it. + */ + +int +is_markup_node(SGFNode *node) +{ + SGFProperty *sgf_prop; + + /* If the node has no properties, there's nothing to do. + This should have been checked by the caller, but it can't hurt. */ + if (!node->props) + return 0; + + sgf_prop = node->props; + while (sgf_prop) { + switch (sgf_prop->name) { + case SGFCR: + case SGFSQ: /* Square */ + case SGFTR: /* Triangle */ + case SGFMA: /* Mark */ + case SGFBM: /* bad move */ + case SGFDO: /* doubtful move */ + case SGFIT: /* interesting move */ + case SGFTE: /* good move */ + return 1; + break; + default: + break; + } + sgf_prop = sgf_prop->next; + } + + /* No markup property found. */ + return 0; +} + + +/* + * Determine if the node has a move in it. + */ + +int +is_move_node(SGFNode *node) +{ + SGFProperty *sgf_prop; + + /* If the node has no properties, there's nothing to do. + This should have been checked by the caller, but it can't hurt. */ + if (!node->props) + return 0; + + sgf_prop = node->props; + while (sgf_prop) { + switch (sgf_prop->name) { + case SGFB: + case SGFW: + return 1; + break; + default: + break; + } + sgf_prop = sgf_prop->next; + } + + return 0; +} + +SGFProperty * +get_move_prop(SGFNode *node) +{ + SGFProperty *sgf_prop; + + if (!node->props) + return NULL; + + sgf_prop = node->props; + while (sgf_prop) { + switch (sgf_prop->name) { + case SGFB: + case SGFW: + return sgf_prop; + break; + default: + break; + } + sgf_prop = sgf_prop->next; + } + + return NULL; +} + +/* + * Determine if the node has a pass move in it. + */ + +int +is_pass_node(SGFNode *node, int boardsize) +{ + SGFProperty *sgf_prop; + int i, j; + + /* If the node has no properties, there's nothing to do. + This should have been checked by the caller, but it can't hurt. */ + if (!node->props) + return 0; + + sgf_prop = node->props; + while (sgf_prop) { + switch (sgf_prop->name) { + case SGFB: + case SGFW: + return !get_moveXY(sgf_prop, &i, &j, boardsize); + break; + default: + break; + } + sgf_prop = sgf_prop->next; + } + + return 0; +} + + +/* + * Determine whose move is in the node. + */ + +int +find_move(SGFNode *node) +{ + SGFProperty *sgf_prop; + + /* If the node has no properties, there's nothing to do. + This should have been checked by the caller, but it can't hurt. */ + if (!node->props) + return 0; + + sgf_prop = node->props; + while (sgf_prop) { + switch (sgf_prop->name) { + case SGFB: + return BLACK; + break; + case SGFW: + return WHITE; + break; + default: + break; + } + sgf_prop = sgf_prop->next; + } + + return EMPTY; +} + + +/* + * Local Variables: + * tab-width: 8 + * c-basic-offset: 2 + * End: + */ Index: apps/plugins/goban/sgftree.c =================================================================== --- apps/plugins/goban/sgftree.c (revision 0) +++ apps/plugins/goban/sgftree.c (revision 0) @@ -0,0 +1,327 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ + * 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 and 2006 * + * 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., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02111, USA. * +\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + +#include "sgftree.h" + +#define assert(x) + + +void +sgftree_clear(SGFTree *tree) +{ + tree->root = NULL; + tree->lastnode = NULL; +} + +int +sgftree_readfile(SGFTree *tree, const char *infilename) +{ + SGFNode *savetree = tree->root; + + tree->root = readsgffile(infilename); + if (tree->root == NULL) { + tree->root = savetree; + return 0; + } + + sgfFreeNode(savetree); + tree->lastnode = NULL; + return 1; +} + + +/* Go back one node in the tree. If lastnode is NULL, go to the last + * node (the one in main variant which has no children). + */ + +int +sgftreeBack(SGFTree *tree) +{ + if (tree->lastnode) { + if (tree->lastnode->parent) + tree->lastnode = tree->lastnode->parent; + else + return 0; + } + else + while (sgftreeForward(tree)) + ; + + return 1; +} + + +/* Go forward one node in the tree. If lastnode is NULL, go to the + * tree root. + */ + +int +sgftreeForward(SGFTree *tree) +{ + if (tree->lastnode) { + if (tree->lastnode->child) + tree->lastnode = tree->lastnode->child; + else + return 0; + } + else + tree->lastnode = tree->root; + + return 1; +} + + +/* ================================================================ */ +/* High level functions */ +/* ================================================================ */ + +/* + * Returns the node to modify. Use lastnode if available, otherwise + * follow the main variation to the current end of the game. + */ + +SGFNode * +sgftreeNodeCheck(SGFTree *tree) +{ + SGFNode *node = NULL; + assert(tree->root); + + if (tree->lastnode) + node = tree->lastnode; + else { + node = tree->root; + while (node->child) + node = node->child; + } + + return node; +} + + +/* + * Add a stone to the current or the given node. + * Return the node where the stone was added. + */ + +void +sgftreeAddStone(SGFTree *tree, int color, int movex, int movey) +{ + SGFNode *node = sgftreeNodeCheck(tree); + sgfAddStone(node, color, movex, movey); +} + + +/* + * Add a move to the gametree. + */ + +void +sgftreeAddPlay(SGFTree *tree, int color, int movex, int movey) +{ + SGFNode *node = sgftreeNodeCheck(tree); + tree->lastnode = sgfAddPlay(node, color, movex, movey); +} + + +/* + * Add a move to the gametree. New variations are added after the old + * ones rather than before. + */ + +void +sgftreeAddPlayLast(SGFTree *tree, int color, int movex, int movey) +{ + SGFNode *node = sgftreeNodeCheck(tree); + tree->lastnode = sgfAddPlayLast(node, color, movex, movey); +} + + +void +sgftreeCreateHeaderNode(SGFTree *tree, int boardsize, float komi, int handicap) +{ + SGFNode *root = sgfNewNode(); + + sgfAddPropertyInt(root, "SZ", boardsize); + sgfAddPropertyFloat(root, "KM", komi); + sgfAddPropertyInt(root, "HA", handicap); + tree->root = root; + tree->lastnode = root; +} + + +/* + * Add a comment to a gametree. + */ + +void +sgftreeAddComment(SGFTree *tree, const char *comment) +{ + SGFNode *node; + assert(tree && tree->root); + + node = sgftreeNodeCheck(tree); + sgfAddComment(node, comment); +} + + +/* + * Place text on the board at position (i, j). + */ + +void +sgftreeBoardText(SGFTree *tree, int i, int j, const char *text) +{ + SGFNode *node; + assert(tree->root); + + node = sgftreeNodeCheck(tree); + sgfBoardText(node, i, j, text); +} + + +/* + * Place a character on the board at position (i, j). + */ + +void +sgftreeBoardChar(SGFTree *tree, int i, int j, char c) +{ + SGFNode *node; + assert(tree->root); + + node = sgftreeNodeCheck(tree); + sgfBoardChar(node, i, j, c); +} + + +/* + * Place a number on the board at position (i, j). + */ + +void +sgftreeBoardNumber(SGFTree *tree, int i, int j, int number) +{ + SGFNode *node = sgftreeNodeCheck(tree); + sgfBoardNumber(node, i, j, number); +} + + +/* + * Place a circle mark on the board at position (i, j). + */ + +void +sgftreeTriangle(SGFTree *tree, int i, int j) +{ + SGFNode *node = sgftreeNodeCheck(tree); + sgfTriangle(node, i, j); +} + + +/* + * Place a circle mark on the board at position (i, j). + */ + +void +sgftreeCircle(SGFTree *tree, int i, int j) +{ + SGFNode *node = sgftreeNodeCheck(tree); + sgfCircle(node, i, j); +} + + +/* + * Place a square mark on the board at position (i, j). + */ + +void +sgftreeSquare(SGFTree *tree, int i, int j) +{ + SGFNode *node = sgftreeNodeCheck(tree); + sgfSquare(node, i, j); +} + + +/* + * Place a (square) mark on the board at position (i, j). + */ + +void +sgftreeMark(SGFTree *tree, int i, int j) +{ + SGFNode *node = sgftreeNodeCheck(tree); + sgfMark(node, i, j); +} + + +/* + * Start a new variant. + */ + +void +sgftreeStartVariant(SGFTree *tree) +{ + SGFNode *node = sgftreeNodeCheck(tree); + tree->lastnode = sgfStartVariant(node); +} + + +/* + * Start a new variant as first child. + */ + +void +sgftreeStartVariantFirst(SGFTree *tree) +{ + SGFNode *node = sgftreeNodeCheck(tree); + tree->lastnode = sgfStartVariantFirst(node); +} + + +/* + * Write result of the game to the game tree. + */ + +void +sgftreeWriteResult(SGFTree *tree, float score, int overwrite) +{ + assert(tree->root); + + sgfWriteResult(tree->root, score, overwrite); +} + + +void +sgftreeSetLastNode(SGFTree *tree, SGFNode *last_node) +{ + tree->lastnode = last_node; +} + + +/* + * Local Variables: + * tab-width: 8 + * c-basic-offset: 2 + * End: + */ + Index: apps/plugins/goban/sgf_properties.h =================================================================== --- apps/plugins/goban/sgf_properties.h (revision 0) +++ apps/plugins/goban/sgf_properties.h (revision 0) @@ -0,0 +1,179 @@ +/* # */ +/* # This document was taken from the SGF Specfication. See: */ +/* # http://www.red-bean.com/sgf/ */ +/* # */ +/* # [SGF FF[4] - Smart Game Format] */ +/* # */ +/* # FF[4] property index */ +/* # */ +/* # This is an alphabetical index to all properties defined in FF[4]. */ +/* # New properties are marked with '*', changed properties are marked with '!'. */ +/* # */ +/* #ID Description property type property value */ +/* #---- --------------- --------------- -------------------------------------- */ + /* Add Black setup list of stone */ +#define SGFAB 16961 + /* Add Empty setup list of point */ +#define SGFAE 17729 + /* Annotation game-info simpletext */ +#define SGFAN 20033 + /* Application root composed simpletext ':' simpletext */ +#define SGFAP 20545 + /* Arrow - list of composed point ':' point */ +#define SGFAR 21057 + /* Who adds stones - (LOA) simpletext */ +#define SGFAS 21313 + /* Add White setup list of stone */ +#define SGFAW 22337 + /* Black move move */ +#define SGFB 8258 + /* Black time left move real */ +#define SGFBL 19522 + /* Bad move move double */ +#define SGFBM 19778 + /* Black rank game-info simpletext */ +#define SGFBR 21058 + /* Black team game-info simpletext */ +#define SGFBT 21570 + /* Comment - text */ +#define SGFC 8259 + /* Charset root simpletext */ +#define SGFCA 16707 + /* Copyright game-info simpletext */ +#define SGFCP 20547 + /* Circle - list of point */ +#define SGFCR 21059 + /* Dim points - (inherit) elist of point */ +#define SGFDD 17476 + /* Even position - double */ +#define SGFDM 19780 + /* Doubtful move none */ +#define SGFDO 20292 + /* Date game-info simpletext */ +#define SGFDT 21572 + /* Event game-info simpletext */ +#define SGFEV 22085 + /* Fileformat root number (range: 1-4) */ +#define SGFFF 17990 + /* Figure - none | composed number ":" simpletext */ +#define SGFFG 18246 + /* Good for Black - double */ +#define SGFGB 16967 + /* Game comment game-info text */ +#define SGFGC 17223 + /* Game root number (range: 1-5,7-16) */ +#define SGFGM 19783 + /* Game name game-info simpletext */ +#define SGFGN 20039 + /* Good for White - double */ +#define SGFGW 22343 + /* Handicap game-info (Go) number */ +#define SGFHA 16712 + /* Hotspot - double */ +#define SGFHO 20296 + /* Initial pos. game-info (LOA) simpletext */ +#define SGFIP 20553 + /* Interesting move none */ +#define SGFIT 21577 + /* Invert Y-axis game-info (LOA) simpletext */ +#define SGFIY 22857 + /* Komi game-info (Go) real */ +#define SGFKM 19787 + /* Ko move none */ +#define SGFKO 20299 + /* Label - list of composed point ':' simpletext */ +#define SGFLB 16972 + /* Line - list of composed point ':' point */ +#define SGFLN 20044 + /* Mark - list of point */ +#define SGFMA 16717 + /* set move number move number */ +#define SGFMN 20045 + /* Nodename - simpletext */ +#define SGFN 8270 + /* OtStones Black move number */ +#define SGFOB 16975 + /* Opening game-info text */ +#define SGFON 20047 + /* Overtime game-info simpletext */ +#define SGFOT 21583 + /* OtStones White move number */ +#define SGFOW 22351 + /* Player Black game-info simpletext */ +#define SGFPB 16976 + /* Place game-info simpletext */ +#define SGFPC 17232 + /* Player to play setup color */ +#define SGFPL 19536 + /* Print move mode - (inherit) number */ +#define SGFPM 19792 + /* Player White game-info simpletext */ +#define SGFPW 22352 + /* Result game-info simpletext */ +#define SGFRE 17746 + /* Round game-info simpletext */ +#define SGFRO 20306 + /* Rules game-info simpletext */ +#define SGFRU 21842 + /* Markup - (LOA) point */ +#define SGFSE 17747 + /* Selected - list of point */ +#define SGFSL 19539 + /* Source game-info simpletext */ +#define SGFSO 20307 + /* Square - list of point */ +#define SGFSQ 20819 + /* Style root number (range: 0-3) */ +#define SGFST 21587 + /* Setup type game-info (LOA) simpletext */ +#define SGFSU 21843 + /* Size root (number | composed number ':' number) */ +#define SGFSZ 23123 + /* Territory Black - (Go) elist of point */ +#define SGFTB 16980 + /* Tesuji move double */ +#define SGFTE 17748 + /* Timelimit game-info real */ +#define SGFTM 19796 + /* Triangle - list of point */ +#define SGFTR 21076 + /* Territory White - (Go) elist of point */ +#define SGFTW 22356 + /* Unclear pos - double */ +#define SGFUC 17237 + /* User game-info simpletext */ +#define SGFUS 21333 + /* Value - real */ +#define SGFV 8278 + /* View - (inherit) elist of point */ +#define SGFVW 22358 + /* White move move */ +#define SGFW 8279 + /* White time left move real */ +#define SGFWL 19543 + /* White rank game-info simpletext */ +#define SGFWR 21079 + /* White team game-info simpletext */ +#define SGFWT 21591 +/* # */ +/* # These are additions to the SGF spec- old commands and some others */ +/* # */ + +/* # */ +/* # outdated FF3 properties */ +/* # */ +#define SGFBS 21314 +#define SGFWS 21335 +#define SGFID 17481 +#define SGFRG 18258 +#define SGFSC 17235 + +/* # */ +/* # some random ones used by CGoban */ +/* # */ +#define SGFSY 22867 + +/* # */ +/* # nonstandard SGF property used by GNU Go to mark illegal moves */ +/* # */ +#define SGFIL 19529 Index: apps/plugins/goban/interface.c =================================================================== --- apps/plugins/goban/interface.c (revision 0) +++ apps/plugins/goban/interface.c (revision 0) @@ -0,0 +1,502 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ + * 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 and 2006 * + * 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., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02111, USA. * +\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "plugin.h" + +extern struct plugin_api * rb; + +#include "gnugo.h" + +#include "sgftree.h" +#include "liberty.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. + */ + +(void)memory; +(void)seed; + +/* + set_random_seed(HASH_RANDOM_SEED); + + reading_cache_init(memory * 1024 * 1024); + set_random_seed(seed); + persistent_cache_init(); +*/ + clear_board(); + +/* + transformation_init(); + dfa_match_init(); + + clear_approxlib_cache(); + clear_accuratelib_cache(); +*/ +} + + +/* ---------------------------------------------------------------- */ + +/* + * Clear the board. + */ +void +gnugo_clear_board(int boardsize) +{ + board_size = boardsize; + clear_board(); + /*init_timers();*/ +} + +/* Play a move and start the clock */ + +void +gnugo_play_move(int move, int color) +{ + play_move(move, color); +} + + +/* + * 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) +{ + SGFProperty *prop; + + for (prop = node->props; prop; prop = prop->next) { + switch (prop->name) { + case SGFAB: + /* A black stone. */ + add_stone(get_sgfmove(prop), BLACK); + break; + + case SGFAW: + /* A white stone. */ + add_stone(get_sgfmove(prop), 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; + gnugo_play_move(get_sgfmove(prop), to_move); + to_move = OTHER_COLOR(to_move); + break; + } + } + + return to_move; +} + + +/* Interface to place_fixed_handicap. Sets up handicap stones and + * updates the sgf file. + */ +int +gnugo_sethand(int desired_handicap, SGFNode *node) +{ + place_fixed_handicap(desired_handicap); + sgffile_recordboard(node); + return handicap; +} + + + +void +sgffile_recordboard(SGFNode *node) +{ + int i, j; + + if (node) + for (i = 0; i < board_size; i++) + for (j = 0; j < board_size; j++) + if (BOARD(i, j) == BLACK) + sgfAddStone(node, BLACK, i, j); +} + + + + +int +get_sgfmove(SGFProperty *property) +{ + return POS(get_moveX(property, board_size), get_moveY(property, board_size)); +} + + + + +/* ================================================================ */ +/* Gameinfo */ +/* ================================================================ */ + + +/* + * Initialize the structure. + */ + +void +gameinfo_clear(Gameinfo *gameinfo) +{ + gnugo_clear_board(board_size); + gameinfo->handicap = 0; + gameinfo->to_move = BLACK; + sgftree_clear(&gameinfo->game_record); + + /* Info relevant to the computer player. */ + gameinfo->computer_player = WHITE; /* Make an assumption. */ +} + + +/* + * Print a gameinfo. + */ + +void +gameinfo_print(Gameinfo *gameinfo) +{ + DEBUGF("Board Size: %d\n", board_size); + DEBUGF("Handicap %d\n", gameinfo->handicap); + DEBUGF("Komi: %.1f\n", komi); + DEBUGF("Move Number: %d\n", movenum); + DEBUGF("To Move: %d\n", gameinfo->to_move); + + DEBUGF("Computer player: "); + if (gameinfo->computer_player == WHITE) + { + DEBUGF("White\n"); + + } else if (gameinfo->computer_player == BLACK) + { + DEBUGF("Black\n"); + } + else if (gameinfo->computer_player == EMPTY) + { + DEBUGF("Both (solo)\n"); + } + else + { + DEBUGF("Nobody\n"); + } +} + + +/* Reorientation of point (i, j) into (*ri, *rj) */ +void +sgf_rotate(int i, int j, int *ri, int *rj, int bs, int rot) +{ + int bs1; +/* + assert(bs > 0); + assert(ri != NULL && rj != NULL); + assert(rot >= 0 && rot < 8); +*/ + /* PASS case */ + if (i == -1 && j == -1) { + *ri = i; + *rj = j; + return; + } + +/* + assert(i >= 0 && i < bs); + assert(j >= 0 && j < bs); +*/ + bs1 = bs - 1; + if (rot == 0) { + /* identity map */ + *ri = i; + *rj = j; + } + else if (rot == 1) { + /* rotation over 90 degrees */ + *ri = bs1 - j; + *rj = i; + } + else if (rot == 2) { + /* rotation over 180 degrees */ + *ri = bs1 - i; + *rj = bs1 - j; + } + else if (rot == 3) { + /* rotation over 270 degrees */ + *ri = j; + *rj = bs1 - i; + } + else if (rot == 4) { + /* flip along diagonal */ + *ri = j; + *rj = i; + } + else if (rot == 5) { + /* flip */ + *ri = bs1 - i; + *rj = j; + } + else if (rot == 6) { + /* flip along diagonal */ + *ri = bs1 - j; + *rj = bs1 - i; + } + else if (rot == 7) { + /* flip */ + *ri = i; + *rj = bs1 - j; + } +} + + + +/* 1D interface to rotate(). */ +int +sgf_rotate1(int pos, int orientation) +{ + int ri, rj; + sgf_rotate(I(pos), J(pos), &ri, &rj, board_size, orientation); + return POS(ri, rj); +} + + + + +/* + * 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. The returned color + * being EMPTY signals a failure to load the file. + * + * 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; + int next = BLACK; + int untilmove = -1; /* Neither a valid move nor pass. */ + int until = 9999; + + if (!sgfGetIntProperty(tree->root, "SZ", &bs)) + bs = 19; + + if (bs < MIN_BOARD || bs > MAX_BOARD) { + if (bs < MIN_BOARD) + { + DEBUGF("Boardsize too small.\n"); + } else + { + DEBUGF("Boardsize too large.\n"); + } + + return EMPTY; + } + + handicap = 0; + if (sgfGetIntProperty(tree->root, "HA", &handicap) && handicap > 1) + next = WHITE; + gameinfo->handicap = handicap; + + if (handicap > bs * bs - 1 || handicap < 0) { + DEBUGF(" Handicap HA[%d] is unreasonable.\n Modify SGF file.\n", + handicap); + return EMPTY; + } + + gnugo_clear_board(bs); + + if (!sgfGetFloatProperty(tree->root, "KM", &komi)) { + if (gameinfo->handicap == 0) + komi = 6.5; + else + komi = 0.5; + } + + /* Now we can safely parse the until string (which depends on board size). */ + if (untilstr) { + if (*untilstr > '0' && *untilstr <= '9') { + until = rb->atoi(untilstr); + DEBUGF("Loading until move %d\n", until); + } + else { + /* don't feel like porting string_to_location */ + return EMPTY; + /* + untilmove = string_to_location(board_size, untilstr); + DEBUGF("Loading until move\n"); + */ + } + } + + /* 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 move; + + for (prop = tree->lastnode->props; prop; prop = prop->next) { + DEBUGF("%c%c[%s]\n", + prop->name & 0xff, (prop->name >> 8), prop->value); + switch (prop->name) { + case SGFAB: + case SGFAW: + /* 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. + */ + move = sgf_rotate1(get_sgfmove(prop), orientation); + if (board[move] != EMPTY) + DEBUGF("Illegal SGF! attempt to add a stone at occupied point\n"); + else + add_stone(move, prop->name == SGFAB ? BLACK : 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) { + place_fixed_handicap(gameinfo->handicap); + sgfOverwritePropertyInt(tree->root, "HA", 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) { + place_fixed_handicap(gameinfo->handicap); + sgfOverwritePropertyInt(tree->root, "HA", handicap); + } + + move = get_sgfmove(prop); + if (move == untilmove || movenum == until - 1) { + gameinfo->to_move = next; + /* go back so that variant will be added to the proper node */ + sgftreeBack(tree); + return next; + } + + move = sgf_rotate1(move, orientation); + if (move == PASS_MOVE || board[move] == EMPTY) { + gnugo_play_move(move, next); + next = OTHER_COLOR(next); + } + else { + DEBUGF("WARNING: Move off board or on occupied position found in sgf-file.\n"); + DEBUGF("Move ignored, trying to proceed.\n"); + gameinfo->to_move = next; + 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. + */ + move = sgf_rotate1(get_sgfmove(prop), orientation); + + if (board_size > 1) + { + int move_color; + + if (ON_BOARD(NORTH(move))) + move_color = OTHER_COLOR(board[NORTH(move)]); + else + move_color = OTHER_COLOR(board[SOUTH(move)]); + if (is_ko(move, move_color, NULL)) + board_ko_pos = move; + } + 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: + */ Index: apps/plugins/goban/SOURCES =================================================================== --- apps/plugins/goban/SOURCES (revision 0) +++ apps/plugins/goban/SOURCES (revision 0) @@ -0,0 +1,11 @@ +goban.c +sgf_malloc.c +sgfnode.c +sgftree.c +sgf_utils.c +hash.c +board.c +globals.c +boardlib.c +interface.c +handicap.c Index: apps/plugins/goban/sgftree.h =================================================================== --- apps/plugins/goban/sgftree.h (revision 0) +++ apps/plugins/goban/sgftree.h (revision 0) @@ -0,0 +1,181 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ + * 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 and 2006 * + * 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., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02111, USA. * +\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _SGFTREE_H_ +#define _SGFTREE_H_ + +#include + +#include "sgf_properties.h" + + +#ifndef _BOARD_H_ +/* + * NOTE: These MUST coincide with the definitions for the engine that we + * are using. In this case they are defined in engine/gnugo.h. + * + * The reason that we put them here within the #ifndef clause is because + * we want to decouple the GNU Go engine from SGF library, but we don't + * want to redefine these symbols if we include this file into board.h. + */ + +#define EMPTY 0 +#define WHITE 1 +#define BLACK 2 +#endif + + +void *xalloc(unsigned int); + +/* + * A property of an SGF node. An SGF node is described by a linked + * list of these. + */ + +typedef struct SGFProperty_t { + struct SGFProperty_t *next; + short name; + char *value; +} SGFProperty; + + +typedef struct SGFNode_t { + SGFProperty *props; + struct SGFNode_t *parent; + struct SGFNode_t *child; + struct SGFNode_t *next; +} SGFNode; + + +/* low level functions */ +SGFNode *sgfPrev(SGFNode *node); +SGFNode *sgfRoot(SGFNode *node); +SGFNode *sgfNewNode(void); +void sgfFreeNode(SGFNode *node); + +int sgfGetIntProperty(SGFNode *node, const char *name, int *value); +int sgfGetFloatProperty(SGFNode *node, const char *name, float *value); +int sgfGetCharProperty(SGFNode *node, const char *name, char **value); +void sgfAddProperty(SGFNode *node, const char *name, const char *value); +void sgfAddPropertyInt(SGFNode *node, const char *name, long val); +void sgfAddPropertyFloat(SGFNode *node, const char *name, float val); +void sgfOverwriteProperty(SGFNode *node, const char *name, const char *text); +void sgfOverwritePropertyFloat(SGFNode *node, const char *name, float val); +void sgfOverwritePropertyInt(SGFNode *node, const char *name, int val); +void *xrealloc(void *pt, unsigned int size); +SGFProperty *sgfMkProperty(const char *name, const char *value, + SGFNode *node, SGFProperty *last); +void sgfFreeProperty(SGFProperty *prop); + +SGFNode *sgfAddStone(SGFNode *node, int color, int movex, int movey); +SGFNode *sgfAddPlay(SGFNode *node, int who, int movex, int movey); +SGFNode *sgfAddPlayLast(SGFNode *node, int who, int movex, int movey); + +void sgfWriteResult(SGFNode *node, float score, int overwrite); +void sgf_write_header(SGFNode *root, int overwrite, int seed, float komi, + int handicap, int level, int rules); + +SGFNode *sgfLabel(SGFNode *node, const char *label, int i, int j); +SGFNode *sgfLabelInt(SGFNode *node, int num, int i, int j); +SGFNode *sgfCircle(SGFNode *node, int i, int j); +SGFNode *sgfSquare(SGFNode *node, int i, int j); +SGFNode *sgfTriangle(SGFNode *node, int i, int j); +SGFNode *sgfMark(SGFNode *node, int i, int j); +SGFNode *sgfAddComment(SGFNode *node, const char *comment); +SGFNode *sgfBoardText(SGFNode *node, int i, int j, const char *text); +SGFNode *sgfBoardChar(SGFNode *node, int i, int j, char c); +SGFNode *sgfBoardNumber(SGFNode *node, int i, int j, int number); +SGFNode *sgfStartVariant(SGFNode *node); +SGFNode *sgfStartVariantFirst(SGFNode *node); +SGFNode *sgfAddChild(SGFNode *node); + +SGFNode *sgfCreateHeaderNode(int boardsize, float komi, int handicap); + +/* Read SGF tree from file. */ +SGFNode *readsgffile(const char *filename); +/* Specific solution for fuseki */ +SGFNode *readsgffilefuseki(const char *filename, int moves_per_game); + +/* Write SGF tree to a file. */ +int writesgf(SGFNode *root, const char *filename); + + +/* ---------------------------------------------------------------- */ +/* --- SGFTree --- */ +/* ---------------------------------------------------------------- */ + + +typedef struct SGFTree_t { + SGFNode *root; + SGFNode *lastnode; +} SGFTree; + + +void sgftree_clear(SGFTree *tree); +int sgftree_readfile(SGFTree *tree, const char *infilename); + +int sgftreeBack(SGFTree *tree); +int sgftreeForward(SGFTree *tree); + +void sgftreeAddPlay(SGFTree *tree, int color, int movex, int movey); +void sgftreeAddPlayLast(SGFTree *tree, int color, int movex, int movey); +void sgftreeAddStone(SGFTree *tree, int color, int movex, int movey); +void sgftreeWriteResult(SGFTree *tree, float score, int overwrite); +SGFNode *sgftreeNodeCheck(SGFTree *tree); + +void sgftreeCircle(SGFTree *tree, int i, int j); +void sgftreeSquare(SGFTree *tree, int i, int j); +void sgftreeTriangle(SGFTree *tree, int i, int j); +void sgftreeMark(SGFTree *tree, int i, int j); +void sgftreeAddComment(SGFTree *tree, const char *comment); +void sgftreeBoardText(SGFTree *tree, int i, int j, const char *text); +void sgftreeBoardChar(SGFTree *tree, int i, int j, char c); +void sgftreeBoardNumber(SGFTree *tree, int i, int j, int number); +void sgftreeStartVariant(SGFTree *tree); +void sgftreeStartVariantFirst(SGFTree *tree); +void sgftreeCreateHeaderNode(SGFTree *tree, int boardsize, float komi, + int handicap); +void sgftreeSetLastNode(SGFTree *tree, SGFNode *lastnode); + + +/* ---------------------------------------------------------------- */ +/* --- Utilities --- */ +/* ---------------------------------------------------------------- */ + + +int get_moveX(SGFProperty *property, int boardsize); +int get_moveY(SGFProperty *property, int boardsize); +int get_moveXY(SGFProperty *property, int *i, int *j, int boardsize); + +int show_sgf_properties(SGFNode *node); +int show_sgf_tree(SGFNode *node); +int is_markup_node(SGFNode *node); +int is_move_node(SGFNode *node); +int is_pass_node(SGFNode *node, int boardsize); +int find_move(SGFNode *node); + + +SGFProperty * get_move_prop(SGFNode *node); + + +#endif + Index: apps/plugins/goban/sgf_malloc.c =================================================================== --- apps/plugins/goban/sgf_malloc.c (revision 0) +++ apps/plugins/goban/sgf_malloc.c (revision 0) @@ -0,0 +1,212 @@ +/***************************************************************************** +Simple malloc +Chris Giese http://www.execpc.com/~geezer +Release date: Oct 30, 2002 +This code is public domain (no copyright). +You can do whatever you want with it. + +Features: +- First-fit +- free() coalesces adjacent free blocks + +Modified by Joshua Simmons for use with Rockbox + + +*****************************************************************************/ +#include "plugin.h" + +extern struct plugin_api* rb; + +MEM_FUNCTION_WRAPPERS(rb) + + +#define MALLOC_MAGIC 0x6D92 + +typedef struct _malloc +{ + size_t size; + struct _malloc *next; + unsigned int magic; + unsigned int used; +} malloc_t; + +static char *g_heap_bot = NULL; +static char *g_heap_top = NULL; + + +void sgf_malloc_init_heap(void * buffer, size_t size) +{ + while ((unsigned int) buffer & 0x7) + { + buffer = (char *) buffer + 1; + --size; + } + + g_heap_bot = buffer; + g_heap_top = (char *) buffer + size; + + malloc_t *m = buffer; + + m->size = size - sizeof(malloc_t); + m->next = NULL; + m->magic = MALLOC_MAGIC; + m->used = 0; + /* fuck you gcc */ + DEBUGF("heap size: %d", (int) m->size); +} + + + +/***************************************************************************** +sgf_malloc() and sgf_free() use g_heap_bot, but not g_heap_top +*****************************************************************************/ +void * sgf_malloc(size_t size) +{ + unsigned total_size; + malloc_t *m, *n; + + if(size == 0) + return NULL; + + while (size & 0x7) + { + ++size; + } + + total_size = size + sizeof(malloc_t); +/* search heap for free block (FIRST FIT) */ + m = (malloc_t *)g_heap_bot; +/* g_heap_bot == 0 == NULL if heap does not yet exist */ + if(m != NULL) + { + if(m->magic != MALLOC_MAGIC) + { + return NULL; + } + for(;;) + { + if(m->used) + { + if (m->next) + { + m = m->next; + continue; + } else + { + break; + } + } +/* size == m->size is a perfect fit */ + if(size == m->size) + m->used = 1; + else + { +/* otherwise, we need an extra sizeof(malloc_t) bytes for the header +of a second, free block */ + if(total_size > m->size) + { + if (m->next) + { + m = m->next; + continue; + } else + { + break; + } + } +/* create a new, smaller free block after this one */ + n = (malloc_t *)((char *)m + total_size); + n->size = m->size - total_size; + n->next = m->next; + n->magic = MALLOC_MAGIC; + n->used = 0; +/* reduce the size of this block and mark it used */ + m->size = size; + m->next = n; + m->used = 1; + } + return (char *)m + sizeof(malloc_t); + } + } + return NULL; +} +/***************************************************************************** +*****************************************************************************/ +void sgf_free(void *blk) +{ + malloc_t *m, *n; + +/* get address of header */ + m = (malloc_t *)((char *)blk - sizeof(malloc_t)); + if(m->magic != MALLOC_MAGIC) + { + return; + } +/* find this block in the heap */ + n = (malloc_t *)g_heap_bot; + if(n->magic != MALLOC_MAGIC) + { + return; + } + for(; n != NULL; n = n->next) + { + if(n == m) + break; + } +/* not found? bad pointer or no heap or something else? */ + if(n == NULL) + { + return; + } +/* free the block */ + m->used = 0; +/* coalesce adjacent free blocks +Hard to spell, hard to do */ + for(m = (malloc_t *)g_heap_bot; m != NULL; m = m->next) + { + while(!m->used && m->next != NULL && !m->next->used) + { +/* resize this block */ + m->size += sizeof(malloc_t) + m->next->size; +/* merge with next block */ + m->next = m->next->next; + } + } +} +/***************************************************************************** +*****************************************************************************/ +void *sgf_realloc(void *blk, size_t size) +{ + void *new_blk; + malloc_t *m; + +/* size == 0: free block */ + if(size == 0) + { + if(blk != NULL) + sgf_free(blk); + new_blk = NULL; + } + else + { +/* allocate new block */ + new_blk = sgf_malloc(size); +/* if allocation OK, and if old block exists, copy old block to new */ + if(new_blk != NULL && blk != NULL) + { + m = (malloc_t *)((char *)blk - sizeof(malloc_t)); + if(m->magic != MALLOC_MAGIC) + { + return NULL; + } +/* copy minimum of old and new block sizes */ + if(size > m->size) + size = m->size; + memcpy(new_blk, blk, size); +/* free the old block */ + sgf_free(blk); + } + } + return new_blk; +} + Index: apps/plugins/goban/handicap.c =================================================================== --- apps/plugins/goban/handicap.c (revision 0) +++ apps/plugins/goban/handicap.c (revision 0) @@ -0,0 +1,214 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ + * 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 and 2006 * + * 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., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02111, USA. * +\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "gnugo.h" + +#include "plugin.h" + +extern struct plugin_api * rb; + +#include "liberty.h" + +/* ================================================================ */ +/* Set up fixed placement handicap stones for black side */ +/* ================================================================ */ + + +/* Handicap stones are set up according to the following diagrams: + * + * 2 stones: 3 stones: + * + * A B C D E F G H J A B C D E F G H J + * 9 . . . . . . . . . 9 9 . . . . . . . . . 9 + * 8 . . . . . . . . . 8 8 . . . . . . . . . 8 + * 7 . . + . . . X . . 7 7 . . X . . . X . . 7 + * 6 . . . . . . . . . 6 6 . . . . . . . . . 6 + * 5 . . . . + . . . . 5 5 . . . . + . . . . 5 + * 4 . . . . . . . . . 4 4 . . . . . . . . . 4 + * 3 . . X . . . + . . 3 3 . . X . . . + . . 3 + * 2 . . . . . . . . . 2 2 . . . . . . . . . 2 + * 1 . . . . . . . . . 1 1 . . . . . . . . . 1 + * A B C D E F G H J A B C D E F G H J + * + * 4 stones: 5 stones: + * + * A B C D E F G H J A B C D E F G H J + * 9 . . . . . . . . . 9 9 . . . . . . . . . 9 + * 8 . . . . . . . . . 8 8 . . . . . . . . . 8 + * 7 . . X . . . X . . 7 7 . . X . . . X . . 7 + * 6 . . . . . . . . . 6 6 . . . . . . . . . 6 + * 5 . . . . + . . . . 5 5 . . . . X . . . . 5 + * 4 . . . . . . . . . 4 4 . . . . . . . . . 4 + * 3 . . X . . . X . . 3 3 . . X . . . X . . 3 + * 2 . . . . . . . . . 2 2 . . . . . . . . . 2 + * 1 . . . . . . . . . 1 1 . . . . . . . . . 1 + * A B C D E F G H J A B C D E F G H J + * + * 6 stones: 7 stones: + * + * A B C D E F G H J A B C D E F G H J + * 9 . . . . . . . . . 9 9 . . . . . . . . . 9 + * 8 . . . . . . . . . 8 8 . . . . . . . . . 8 + * 7 . . X . . . X . . 7 7 . . X . . . X . . 7 + * 6 . . . . . . . . . 6 6 . . . . . . . . . 6 + * 5 . . X . + . X . . 5 5 . . X . X . X . . 5 + * 4 . . . . . . . . . 4 4 . . . . . . . . . 4 + * 3 . . X . . . X . . 3 3 . . X . . . X . . 3 + * 2 . . . . . . . . . 2 2 . . . . . . . . . 2 + * 1 . . . . . . . . . 1 1 . . . . . . . . . 1 + * A B C D E F G H J A B C D E F G H J + * + * 8 stones: 9 stones: + * + * A B C D E F G H J A B C D E F G H J + * 9 . . . . . . . . . 9 9 . . . . . . . . . 9 + * 8 . . . . . . . . . 8 8 . . . . . . . . . 8 + * 7 . . X . X . X . . 7 7 . . X . X . X . . 7 + * 6 . . . . . . . . . 6 6 . . . . . . . . . 6 + * 5 . . X . + . X . . 5 5 . . X . X . X . . 5 + * 4 . . . . . . . . . 4 4 . . . . . . . . . 4 + * 3 . . X . X . X . . 3 3 . . X . X . X . . 3 + * 2 . . . . . . . . . 2 2 . . . . . . . . . 2 + * 1 . . . . . . . . . 1 1 . . . . . . . . . 1 + * A B C D E F G H J A B C D E F G H J + * + * For odd-sized boards larger than 9x9, the same pattern is followed, + * except that the edge stones are moved to the fourth line for 13x13 + * boards and larger. + * + * For even-sized boards at least 8x8, only the four first diagrams + * are used, because there is no way to place the center stones + * symmetrically. As for odd-sized boards, the edge stones are moved + * to the fourth line for boards larger than 11x11. + * + * At most four stones are placed on 7x7 boards too (this size may or + * may not be supported by the rest of the engine). No handicap stones + * are ever placed on smaller boards. + * + * Notice that this function only deals with fixed handicap placement. + * Larger handicaps can be added by free placement if the used + * interface supports it. + */ + + +/* This table contains the (coded) positions of the stones. + * 2 maps to 2 or 3, depending on board size + * 0 maps to center + * -ve numbers map to board_size - number + * + * The stones are placed in this order, *except* if there are + * 5 or 7 stones, in which case center ({0, 0}) is placed, and + * then as for 4 or 6. + */ + +static const int places[][2] = { + + {2, -2}, {-2, 2}, {2, 2}, {-2, -2}, /* first 4 are easy */ + /* for 5, {0,0} is explicitly placed */ + + {0, 2}, {0, -2}, /* for 6 these two are placed */ + /* for 7, {0,0} is explicitly placed */ + + {2, 0}, {-2, 0}, /* for 8, these two are placed */ + + {0, 0}, /* finally tengen for 9 */ +}; + + +/* + * Sets up fixed handicap placement stones, returning the number of + * placed handicap stones and also setting the global variable + * handicap to the same value. + */ + +int +place_fixed_handicap(int desired_handicap) +{ + int r; + int max_handicap; + int remaining_stones; + int three = board_size > 11 ? 3 : 2; + int mid = board_size/2; + + /* A handicap of 1 just means that B plays first, no komi. + * Black is not told where to play the first stone so no handicap + * is set. + */ + if (desired_handicap < 2) { + handicap = 0; + return 0; + } + + if ((board_size % 2 == 1) && (board_size >= 9)) + max_handicap = 9; + else if (board_size >= 7) + max_handicap = 4; + else + max_handicap = 0; + + /* It's up to the caller of this function to notice if the handicap + * was too large for fixed placement and act upon that. + */ + if (desired_handicap > max_handicap) + handicap = max_handicap; + else + handicap = desired_handicap; + + remaining_stones = handicap; + /* special cases: 5 and 7 */ + if (desired_handicap == 5 || desired_handicap == 7) { + add_stone(POS(mid, mid), BLACK); + remaining_stones--; + } + + for (r = 0; r < remaining_stones; r++) { + int i = places[r][0]; + int j = places[r][1]; + + /* Translate the encoded values to board co-ordinates. */ + if (i == 2) + i = three; /* 2 or 3 */ + else if (i == 0) + i = mid; + else if (i == -2) + i = board_size - 1 - three; + + if (j == 2) + j = three; + else if (j == 0) + j = mid; + else if (j == -2) + j = board_size - 1 - three; + + add_stone(POS(i, j), BLACK); + } + + return handicap; +} + + +/* + * Local Variables: + * tab-width: 8 + * c-basic-offset: 2 + * End: + */ Index: apps/plugins/goban/sgf_malloc.h =================================================================== --- apps/plugins/goban/sgf_malloc.h (revision 0) +++ apps/plugins/goban/sgf_malloc.h (revision 0) @@ -0,0 +1,4 @@ +void sgf_malloc_init_heap(void * buffer, size_t size); +void * sgf_malloc(size_t size); +void sgf_free(void *blk); +void * sgf_realloc(void *blk, size_t size); Index: apps/plugins/goban/boardlib.c =================================================================== --- apps/plugins/goban/boardlib.c (revision 0) +++ apps/plugins/goban/boardlib.c (revision 0) @@ -0,0 +1,65 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ + * 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 and 2006 * + * 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., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02111, USA. * +\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + +/* This file contains the global functions of the board library libboard.a. */ + +#include "board.h" +#include "hash.h" + +/* The board state itself. */ +int board_size = DEFAULT_BOARD_SIZE; /* board size */ +Intersection board[BOARDSIZE]; +int board_ko_pos; +int white_captured; /* number of black and white stones captured */ +int black_captured; + +Intersection initial_board[BOARDSIZE]; +int initial_board_ko_pos; +int initial_white_captured; +int initial_black_captured; +int move_history_color[MAX_MOVE_HISTORY]; +int move_history_pos[MAX_MOVE_HISTORY]; +Hash_data move_history_hash[MAX_MOVE_HISTORY]; +int move_history_pointer; + +float komi = 0.0; +int handicap = 0; +int movenum; +enum suicide_rules suicide_rule = FORBIDDEN; +enum ko_rules ko_rule = SIMPLE; + + +signed char shadow[BOARDMAX]; + +/* Hashing of positions. */ +Hash_data board_hash; + +int stackp; /* stack pointer */ +int position_number; /* position number */ + +/* Some statistics gathered partly in board.c and hash.c */ +struct stats_data stats; + +/* Variation tracking in SGF trees: */ +int count_variations = 0; +SGFTree *sgf_dumptree = NULL; Index: apps/plugins/goban/sgffile.c =================================================================== --- apps/plugins/goban/sgffile.c (revision 0) +++ apps/plugins/goban/sgffile.c (revision 0) @@ -0,0 +1,248 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ + * 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 and 2006 * + * 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., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02111, USA. * +\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/* + * sgffile.c + * + * This file used to contain functions that create an SGF file on the fly. + * + * Today it contains supporting code around the more general SGF library + * found in the sgf/ directory. + */ + +#include "gnugo.h" + +#include +#include +#include +#include + +#include "liberty.h" +#include "sgftree.h" +#include "gg_utils.h" + +/* + * Add debug information to a node if user requested it from command + * line. + */ + +void +sgffile_add_debuginfo(SGFNode *node, float value) +{ + int pos; + char comment[24]; + + if (!outfilename[0]) + return; + + for (pos = BOARDMIN; pos < BOARDMAX; pos++) { + if (!ON_BOARD(pos)) + continue; + + if (IS_STONE(board[pos]) && (output_flags & OUTPUT_MARKDRAGONS)) { + if (dragon[pos].crude_status == DEAD) + sgfLabel(node, "X", I(pos), J(pos)); + else if (dragon[pos].crude_status == CRITICAL) + sgfLabel(node, "!", I(pos), J(pos)); + } + + if (potential_moves[pos] > 0.0 && (output_flags & OUTPUT_MOVEVALUES)) { + if (potential_moves[pos] < 1.0) + sgfLabel(node, "<1", I(pos), J(pos)); + else + sgfLabelInt(node, (int) potential_moves[pos], I(pos), J(pos)); + } + } + + if (value > 0.0 && (output_flags & OUTPUT_MOVEVALUES)) { + sprintf(comment, "Value of move: %.2f", value); + sgfAddComment(node, comment); + } +} + + +/* + * Write sgf tree to output file specified with -o option. + * This can safely be done multiple times. + */ + +void +sgffile_output(SGFTree *tree) +{ + if (outfilename[0]) + writesgf(tree->root, outfilename); +} + + +/* ================================================================ + * Dumping of information about a position into an sgftree. + * Used by sgffile_decideposition, etc. + * ================================================================ */ + + +/* + * sgffile_begindump begins storing all moves considered by + * trymove and tryko in an sgf tree in memory. + * + * The caller only has to provide an own SGFTree pointer if he wants + * to do something more with the tree than writing it to file as done + * by sgffile_enddump(). + */ + +void +sgffile_begindump(SGFTree *tree) +{ + static SGFTree local_tree; + gg_assert(sgf_dumptree == NULL); + + if (tree == NULL) + sgf_dumptree = &local_tree; + else + sgf_dumptree = tree; + + sgftree_clear(sgf_dumptree); + sgftreeCreateHeaderNode(sgf_dumptree, board_size, komi, handicap); + sgffile_printboard(sgf_dumptree); +} + + +/* + * sgffile_enddump ends the dump and writes the sgf tree to file. + */ + +void +sgffile_enddump(const char *filename) +{ + /* Check if we have a valid filename and a tree. */ + if (filename && *filename && sgf_dumptree) { + if (writesgf(sgf_dumptree->root, filename)) { + /* Only delete the tree if writesgf() succeeds. If it doesn't, one + * will most likely wish to save into another (writable) file. + */ + sgfFreeNode(sgf_dumptree->root); + sgf_dumptree = NULL; + } + } +} + + +/* + * sgffile_printsgf creates an sgf of the current board position + * (without any move history). It also adds information about who is + * to play and marks illegal moves with the private sgf property IL. + */ + +void +sgffile_printsgf(int color_to_play, const char *filename) +{ + SGFTree sgftree; + int m, n; + char pos[3]; + char str[128]; + float relative_komi; + + relative_komi = komi + black_captured - white_captured; + + sgftree_clear(&sgftree); + sgftreeCreateHeaderNode(&sgftree, board_size, relative_komi, handicap); + sgf_write_header(sgftree.root, 1, get_random_seed(), relative_komi, + handicap, get_level(), chinese_rules); + gg_snprintf(str, 128, "GNU Go %s load and print", gg_version()); + sgfOverwriteProperty(sgftree.root, "GN", str); + + sgffile_printboard(&sgftree); + + if (color_to_play != EMPTY) { + sgfAddProperty(sgftree.lastnode, "PL", + (color_to_play == WHITE ? "W" : "B")); + + for (m = 0; m < board_size; ++m) + for (n = 0; n < board_size; ++n) + if (BOARD(m, n) == EMPTY && !is_legal(POS(m, n), color_to_play)) { + gg_snprintf(pos, 3, "%c%c", 'a' + n, 'a' + m); + sgfAddProperty(sgftree.lastnode, "IL", pos); + } + } + + writesgf(sgftree.root, filename); +} + + +/* + * sgffile_printboard adds the current board position to the tree. + */ + +void +sgffile_printboard(SGFTree *tree) +{ + int i, j; + SGFNode *node; + + gg_assert(tree); + node = tree->lastnode; + + /* Write the white stones to the file. */ + for (i = 0; i < board_size; i++) { + for (j = 0; j < board_size; j++) { + if (BOARD(i, j) == WHITE) + sgfAddStone(node, WHITE, i, j); + } + } + + /* Write the black stones to the file. */ + for (i = 0; i < board_size; i++) { + for (j = 0; j < board_size; j++) { + if (BOARD(i, j) == BLACK) + sgfAddStone(node, BLACK, i, j); + } + } + + sgftreeSetLastNode(tree, node); +} + + +void +sgffile_recordboard(SGFNode *node) +{ + int i, j; + + if (node) + for (i = 0; i < board_size; i++) + for (j = 0; j < board_size; j++) + if (BOARD(i, j) == BLACK) + sgfAddStone(node, BLACK, i, j); +} + + +int +get_sgfmove(SGFProperty *property) +{ + return POS(get_moveX(property, board_size), get_moveY(property, board_size)); +} + + +/* + * Local Variables: + * tab-width: 8 + * c-basic-offset: 2 + * End: + */ Index: apps/plugins/goban/liberty.h =================================================================== --- apps/plugins/goban/liberty.h (revision 0) +++ apps/plugins/goban/liberty.h (revision 0) @@ -0,0 +1,1042 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ + * 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 and 2006 * + * 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., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02111, USA. * +\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _LIBERTY_H_ +#define _LIBERTY_H_ + +#include "plugin.h" + +#include "board.h" +#include "hash.h" +#include "gnugo.h" + +/* ================================================================ */ +/* public variables */ +/* ================================================================ */ + + +/* ================================================================ */ + + +#define FALSE_EYE 1 +#define HALF_EYE 2 + + +#define REVERSE_RESULT(result) (WIN - result) + + +void start_timer(int n); +double time_report(int n, const char *occupation, int move, double mintime); +void showstats(void); +void clearstats(void); + +void transformation_init(void); + +void ascii_report_worm(char *string); +/* +void report_dragon(FILE *outfile, int pos); +*/ +void ascii_report_dragon(char *string); +struct dragon_data2 *dragon2_func(int pos); + +/* Routine names used by persistent and non-persistent caching schemes. */ +enum routine_id { + OWL_ATTACK, + OWL_DEFEND, + SEMEAI, + FIND_DEFENSE, + ATTACK, + CONNECT, + DISCONNECT, + BREAK_IN, + BLOCK_OFF, + OWL_THREATEN_ATTACK, + OWL_THREATEN_DEFENSE, + OWL_DOES_DEFEND, + OWL_DOES_ATTACK, + OWL_CONNECTION_DEFENDS, + OWL_SUBSTANTIAL, + OWL_CONFIRM_SAFETY, + ANALYZE_SEMEAI, + NUM_CACHE_ROUTINES +}; + +#define ROUTINE_NAMES \ + "owl_attack", \ + "owl_defend", \ + "semeai", \ + "find_defense", \ + "attack", \ + "connect", \ + "disconnect", \ + "break_in", \ + "block_off", \ + "owl_threaten_attack", \ + "owl_threatend_defense", \ + "owl_does_defend", \ + "owl_does_attack", \ + "owl_connection_defends", \ + "owl_substantial", \ + "owl_confirm_safety", \ + "analyze_semeai" + +/* To prioritize between different types of reading, we give a cost + * ranking to each of the routines above: + * + * 4 semeai + * 3 owl + * 2 break-in + * 1 connection + * 0 tactical reading + * + * -1 is left at the end for a consistency check. + */ +#define ROUTINE_COSTS \ + 3, 3, 4, 0, 0, 1, 1, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, -1 + + +const char *routine_id_to_string(enum routine_id routine); + + +/* This is used for both the dragon status and safety fields. + * Also used for unconditional status in struct worm_data and for the + * final status computed by the aftermath code. + */ +enum dragon_status { + DEAD, + ALIVE, + CRITICAL, + UNKNOWN, + UNCHECKED, + CAN_THREATEN_ATTACK, + CAN_THREATEN_DEFENSE, + INESSENTIAL, + TACTICALLY_DEAD, + ALIVE_IN_SEKI, + STRONGLY_ALIVE, + INVINCIBLE, + INSUBSTANTIAL, + WHITE_TERRITORY, + BLACK_TERRITORY, + DAME, + NUM_DRAGON_STATUS +}; + +#define DRAGON_STATUS_NAMES \ + "dead", \ + "alive", \ + "critical", \ + "unknown", \ + "unchecked", \ + "can threaten attack", \ + "can threaten defense", \ + "inessential", \ + "tactically dead", \ + "alive in seki", \ + "strongly alive", \ + "invincible", \ + "insubstantial", \ + "white_territory", \ + "black_territory", \ + "dame" + +const char *status_to_string(enum dragon_status status); + + +/* Forward struct declarations. */ +struct pattern; +struct pattern_db; +struct fullboard_pattern; +struct corner_pattern; +struct corner_db; +struct half_eye_data; +struct movelist; + +/* + * Try to match a pattern in the database to the board. Callbacks for + * each match. + */ +typedef void (*matchpat_callback_fn_ptr)(int anchor, int color, + struct pattern *, int rotation, + void *data); +typedef void (*fullboard_matchpat_callback_fn_ptr)(int move, + struct fullboard_pattern *, + int rotation); +typedef void (*corner_matchpat_callback_fn_ptr)(int move, int color, + struct corner_pattern *pattern, + int trans, + int *stones, int num_stones); +void matchpat(matchpat_callback_fn_ptr callback, int color, + struct pattern_db *pdb, void *callback_data, + signed char goal[BOARDMAX]); +void matchpat_goal_anchor(matchpat_callback_fn_ptr callback, int color, + struct pattern_db *pdb, void *callback_data, + signed char goal[BOARDMAX], int anchor_in_goal); +void fullboard_matchpat(fullboard_matchpat_callback_fn_ptr callback, + int color, struct fullboard_pattern *pattern); +void corner_matchpat(corner_matchpat_callback_fn_ptr callback, int color, + struct corner_db *database); +void dfa_match_init(void); + +void reading_cache_init(int bytes); +void reading_cache_clear(void); +float reading_cache_default_size(void); + +/* reading.c */ +int attack(int str, int *move); +int find_defense(int str, int *move); +int attack_and_defend(int str, + int *attack_code, int *attack_point, + int *defend_code, int *defense_point); +int attack_either(int astr, int bstr); +int defend_both(int astr, int bstr); +int break_through(int apos, int bpos, int cpos); +int attack_threats(int pos, int max_points, int moves[], int codes[]); + +int restricted_defend1(int str, int *move, + int num_forbidden_moves, int *forbidden_moves); +int restricted_attack2(int str, int *move, + int num_forbidden_moves, int *forbidden_moves); + +int simple_ladder(int str, int *move); +#define MOVE_ORDERING_PARAMETERS 67 +void tune_move_ordering(int params[MOVE_ORDERING_PARAMETERS]); +void draw_reading_shadow(void); + +/* persistent.c */ +void persistent_cache_init(void); +void purge_persistent_caches(void); +void clear_persistent_caches(void); + +int search_persistent_reading_cache(enum routine_id routine, int str, + int *result, int *move); +void store_persistent_reading_cache(enum routine_id routine, int str, + int result, int move, int nodes); +void reading_hotspots(float values[BOARDMAX]); +int search_persistent_connection_cache(enum routine_id routine, + int str1, int str2, + int *result, int *move); +void store_persistent_connection_cache(enum routine_id routine, + int str1, int str2, + int result, int move, + int tactical_nodes, + signed char connection_shadow[BOARDMAX]); +int search_persistent_breakin_cache(enum routine_id routine, + int str, Hash_data *goal_hash, + int breakin_node_limit, + int *result, int *move); +void store_persistent_breakin_cache(enum routine_id routine, + int str, Hash_data *goal_hash, + int result, int move, + int tactical_nodes, + int breakin_node_limit, + signed char breakin_shadow[BOARDMAX]); +int search_persistent_owl_cache(enum routine_id routine, + int apos, int bpos, int cpos, + int *result, int *move, int *move2, + int *certain); +void store_persistent_owl_cache(enum routine_id routine, + int apos, int bpos, int cpos, + int result, int move, int move2, int certain, + int tactical_nodes, signed char goal[BOARDMAX], + int goal_color); +void owl_hotspots(float values[BOARDMAX]); +int search_persistent_semeai_cache(enum routine_id routine, + int apos, int bpos, int cpos, int color, + Hash_data *goal_hash, + int *resulta, int *resultb, + int *move, int *certain); +void store_persistent_semeai_cache(enum routine_id routine, + int apos, int bpos, int cpos, int color, + Hash_data *goal_hash, + int resulta, int resultb, + int move, int certain, int tactical_nodes, + signed char goala[BOARDMAX], + signed char goalb[BOARDMAX]); + + +/* readconnect.c */ +int string_connect(int str1, int str2, int *move); +int disconnect(int str1, int str2, int *move); +int fast_disconnect(int str1, int str2, int *move); +int non_transitivity(int str1, int str2, int str3, int *move); + +int break_in(int str, const signed char goal[BOARDMAX], int *move); +int block_off(int str1, const signed char goal[BOARDMAX], int *move); + +int obvious_false_eye(int pos, int color); +void estimate_lunch_eye_value(int lunch, int *min, int *probable, int *max, + int appreciate_one_two_lunches); +int owl_topological_eye(int pos, int color); +int vital_chain(int pos); +int confirm_safety(int move, int color, int *defense_point, + signed char safe_stones[BOARDMAX]); +int dragon_weak(int pos); +float dragon_weakness(int pos, int ignore_dead_dragons); +int size_of_biggest_critical_dragon(void); +float blunder_size(int move, int color, int *defense_point, + signed char safe_stones[BOARDMAX]); +void set_depth_values(int level, int report_levels); +void modify_depth_values(int n); +void increase_depth_values(void); +void decrease_depth_values(void); +int get_depth_modification(void); + +int safe_move(int move, int color); +int does_secure(int color, int move, int pos); + +void compute_new_dragons(int dragon_origins[BOARDMAX]); +void join_dragons(int d1, int d2); +int dragon_escape(signed char goal[BOARDMAX], int color, + signed char escape_value[BOARDMAX]); +void compute_refined_dragon_weaknesses(void); +void compute_strategic_sizes(void); + +struct eyevalue; +void compute_dragon_genus(int d, struct eyevalue *genus, int eye_to_exclude); +float crude_dragon_weakness(int safety, struct eyevalue *genus, int has_lunch, + float moyo_value, float escape_route); + +int is_same_dragon(int d1, int d2); +int are_neighbor_dragons(int d1, int d2); +void mark_dragon(int pos, signed char mx[BOARDMAX], signed char mark); +int first_worm_in_dragon(int d); +int next_worm_in_dragon(int w); +int lively_dragon_exists(int color); +void compute_dragon_influence(void); +void set_strength_data(int color, signed char safe_stones[BOARDMAX], + float strength[BOARDMAX]); +void mark_inessential_stones(int color, signed char safe_stones[BOARDMAX]); + +void get_lively_stones(int color, signed char safe_stones[BOARDMAX]); +int is_same_worm(int w1, int w2); +int is_worm_origin(int w, int pos); +void propagate_worm(int pos); +void find_cuts(void); +void find_connections(void); + +/* movelist.c */ +int movelist_move_known(int move, int max_points, int points[], int codes[]); +void movelist_change_point(int move, int code, int max_points, + int points[], int codes[]); + +/* surround.c */ +int compute_surroundings(int pos, int apos, int showboard, + int *surround_size); +int is_surrounded(int pos); +int does_surround(int move, int dragon); +void reset_surround_data(void); +int surround_map(int dr, int pos); + +/* functions to add (or remove) move reasons */ +void collect_move_reasons(int color); + +void clear_move_reasons(void); +void add_lunch(int eater, int food); +void add_attack_move(int pos, int ww, int code); +void add_defense_move(int pos, int ww, int code); +void add_attack_threat_move(int pos, int ww, int code); +void remove_attack_threat_move(int pos, int ww); +void add_defense_threat_move(int pos, int ww, int code); +void add_connection_move(int pos, int dr1, int dr2); +void add_cut_move(int pos, int dr1, int dr2); +void add_antisuji_move(int pos); +void add_semeai_move(int pos, int dr); +void add_potential_semeai_attack(int pos, int dr1, int dr2); +void add_potential_semeai_defense(int pos, int dr1, int dr2); +void add_semeai_threat(int pos, int dr); + +void add_owl_attack_move(int pos, int dr, int kworm, int code); +void add_owl_defense_move(int pos, int dr, int code); +void add_owl_attack_threat_move(int pos, int dr, int code); +void add_owl_defense_threat_move(int pos, int dr, int code); +void add_owl_prevent_threat_move(int pos, int dr); +void add_owl_uncertain_defense_move(int pos, int dr); +void add_owl_uncertain_attack_move(int pos, int dr); +void add_gain_move(int pos, int target1, int target2); +void add_loss_move(int pos, int target1, int target2); + +void add_my_atari_atari_move(int pos, int size); +void add_your_atari_atari_move(int pos, int size); +void add_vital_eye_move(int pos, int eyespace, int color); +void add_invasion_move(int pos); +void add_expand_territory_move(int pos); +void add_expand_moyo_move(int pos); +void add_strategical_attack_move(int pos, int dr); +void add_strategical_defense_move(int pos, int dr); +void add_worthwhile_threat_move(int pos); +void add_replacement_move(int from, int to); + +/* Parameters to add_either_move and add_all_move */ +#define ATTACK_STRING 1 +#define DEFEND_STRING 2 +void add_either_move(int pos, int reason1, int target1, + int reason2, int target2); +void add_all_move(int pos, int reason1, int target1, + int reason2, int target2); + +int set_minimum_move_value(int pos, float value); +void set_maximum_move_value(int pos, float value); +void set_minimum_territorial_value(int pos, float value); +void set_maximum_territorial_value(int pos, float value); +void add_shape_value(int pos, float value); +void add_followup_value(int pos, float value); +void add_reverse_followup_value(int pos, float value); +/* +int list_move_reasons(FILE *out, int pos); +void print_all_move_values(FILE *output); +*/ +void record_top_move(int move, float val); +void remove_top_move(int move); +void scale_randomness(int pos, float scaling); +void compute_move_probabilities(float probabilities[BOARDMAX]); + +void register_good_attack_threat(int move, int target); +int is_known_good_attack_threat(int move, int target); + +int get_attack_threats(int pos, int max_strings, int strings[]); +int get_defense_threats(int pos, int max_strings, int strings[]); +void get_saved_worms(int pos, signed char saved[BOARDMAX]); +void get_saved_dragons(int pos, signed char saved[BOARDMAX]); +void mark_safe_stones(int color, int move_pos, + const signed char saved_dragons[BOARDMAX], + const signed char saved_worms[BOARDMAX], + signed char safe_stones[BOARDMAX]); + + +int owl_lively(int pos); +int owl_escape_value(int pos); +int owl_goal_dragon(int pos); +int owl_eyespace(int pos); +int owl_big_eyespace(int pos); +int owl_mineye(int pos); +int owl_maxeye(int pos); +int owl_proper_eye(int pos); +int owl_eye_size(int pos); +int owl_lunch(int str); +int owl_strong_dragon(int pos); +void owl_reasons(int color); + +void unconditional_life(int unconditional_territory[BOARDMAX], int color); +void find_superstring(int str, int *num_stones, int *stones); +void find_superstring_conservative(int str, int *num_stones, int *stones); +void find_superstring_liberties(int str, int *liberties, int *libs, + int liberty_cap); +void find_proper_superstring_liberties(int str, int *liberties, int *libs, + int liberty_cap); +void find_superstring_stones_and_liberties(int str, int *num_stones, + int *stones, int *liberties, + int *libs, int liberty_cap); +void superstring_chainlinks(int str, int *num_adj, int adj[MAXCHAIN], + int liberty_cap); +void proper_superstring_chainlinks(int str, int *num_adj, + int adj[MAXCHAIN], int liberty_cap); + +int place_fixed_handicap(int handicap); /* place stones on board only */ +int place_free_handicap(int handicap); /* place stones on board only */ +int free_handicap_remaining_stones(void); +int free_handicap_total_stones(void); + + +/* Various different strategies for finding a move */ +void fuseki(int color); +void semeai(void); +void semeai_move_reasons(int color); +void shapes(int color); +void endgame(int color); +void endgame_shapes(int color); + +void combinations(int color); +int atari_atari(int color, int *attack_move, + signed char defense_moves[BOARDMAX], + int save_verbose); +int atari_atari_confirm_safety(int color, int tpos, int *move, int minsize, + const signed char saved_dragons[BOARDMAX], + const signed char saved_worms[BOARDMAX]); + +int atari_atari_blunder_size(int color, int tpos, + signed char defense_moves[BOARDMAX], + const signed char safe_stones[BOARDMAX]); + +int review_move_reasons(int *move, float *value, int color, + float pure_threat_value, float our_score, + int allowed_moves[BOARDMAX], + int use_thrashing_dragon_heuristics); +void prepare_move_influence_debugging(int pos, int color); +int fill_liberty(int *move, int color); +int aftermath_genmove(int color, int do_capture_dead_stones, + int allowed_moves[BOARDMAX]); +enum dragon_status aftermath_final_status(int color, int pos); + +int owl_attack(int target, int *attack_point, int *certain, int *kworm); +int owl_defend(int target, int *defense_point, int *certain, int *kworm); +int owl_threaten_attack(int target, int *attack1, int *attack2); +int owl_threaten_defense(int target, int *defend1, int *defend2); +int owl_does_defend(int move, int target, int *kworm); +int owl_confirm_safety(int move, int target, int *defense_point, int *kworm); +int owl_does_attack(int move, int target, int *kworm); +int owl_connection_defends(int move, int target1, int target2); +int owl_substantial(int str); +void owl_analyze_semeai(int apos, int bpos, + int *resulta, int *resultb, int *semeai_move, + int owl, int *semeai_result_certain); +void owl_analyze_semeai_after_move(int move, int color, int apos, int bpos, + int *resulta, int *resultb, + int *semeai_move, int owl, + int *semeai_result_certain, + int recompute_dragons); + +void set_limit_search(int value); +void set_search_diamond(int pos); +void reset_search_mask(void); +void set_search_mask(int pos, int value); +int oracle_play_move(int pos, int color); +void consult_oracle(int color); +void summon_oracle(void); +void oracle_loadsgf(char *infilename, char *untilstring); +int oracle_threatens(int move, int target); +int within_search_area(int pos); +int metamachine_genmove(int color, float *value); +void draw_search_area(void); + +int genmove_restricted(int color, int allowed_moves[BOARDMAX]); + +void change_attack(int str, int move, int acode); +void change_defense(int str, int move, int dcode); +void change_attack_threat(int str, int move, int acode); +void change_defense_threat(int str, int move, int dcode); +int attack_move_known(int move, int str); +int defense_move_known(int move, int str); +int attack_threat_move_known(int move, int str); +int defense_threat_move_known(int move, int str); +void worm_reasons(int color); + +int semeai_move_reason_known(int move, int dr); + +int does_attack(int move, int str); +int does_defend(int move, int str); +int double_atari(int move, int color, float *value, + signed char safe_stones[BOARDMAX]); +int send_two_return_one(int move, int color); +int playing_into_snapback(int move, int color); +int play_attack_defend_n(int color, int do_attack, int num_moves, ...); +int play_attack_defend2_n(int color, int do_attack, int num_moves, ...); +int play_break_through_n(int color, int num_moves, ...); +int play_connect_n(int color, int do_connect, int num_moves, ...); +int play_lib_n(int color, int num_moves, ...); +int cut_possible(int pos, int color); +int defend_against(int move, int color, int apos); +int somewhere(int color, int check_alive, int num_moves, ...); +int visible_along_edge(int color, int apos, int bpos); +int test_symmetry_after_move(int move, int color, int strict); + +/* Printmoyo values, specified by -m flag. */ +#define PRINTMOYO_TERRITORY 0x01 +#define PRINTMOYO_MOYO 0x02 +#define PRINTMOYO_AREA 0x04 +/* The following have been borrowed by the influence functions below. */ +#define PRINTMOYO_INITIAL_INFLUENCE 0x08 +#define PRINTMOYO_PRINT_INFLUENCE 0x10 +#define PRINTMOYO_NUMERIC_INFLUENCE 0x20 +#define PRINTMOYO_PERMEABILITY 0x40 +#define PRINTMOYO_STRENGTH 0x80 +#define PRINTMOYO_ATTENUATION 0x100 +#define PRINTMOYO_VALUE_TERRITORY 0x200 + +/* These values are used to communicate whether stones are safe or + * have been saved, when computing influence. + */ +#define INFLUENCE_SAFE_STONE 1 +#define INFLUENCE_SAVED_STONE 2 + +/* These values are used to communicate the status of stones when analyzing + * a move for potentially being a blunder. + */ +/* dead 0 */ +#define SAFE_STONE 1 +#define OWL_SAVED_STONE 2 + +/* This format is used when exporting the moyo segmentation. */ +#define MAX_MOYOS MAX_BOARD*MAX_BOARD + +struct moyo_data +{ + int number; /* Number of moyos. */ + int segmentation[BOARDMAX]; /* Numbers the moyos. */ + int size[MAX_MOYOS]; + int owner[MAX_MOYOS]; + float territorial_value[MAX_MOYOS]; +}; + +/* We use a forward declaration of influence_data so that the rest + * of the engine can reference influence data. It can only be accessed + * in influence.c, however! + */ +struct influence_data; +extern struct influence_data initial_black_influence; +extern struct influence_data initial_white_influence; +extern struct influence_data move_influence; +extern struct influence_data followup_influence; + +#define INITIAL_INFLUENCE(color) ((color) == WHITE ? \ + &initial_white_influence \ + : &initial_black_influence) +#define OPPOSITE_INFLUENCE(color) (INITIAL_INFLUENCE(OTHER_COLOR(color))) + +#define DEFAULT_STRENGTH 100.0 + +/* Influence functions. */ +void compute_influence(int color, const signed char safe_stones[BOARDMAX], + const float strength[BOARDMAX], + struct influence_data *q, + int move, const char *trace_message); +void compute_followup_influence(const struct influence_data *base, + struct influence_data *q, + int move, const char *trace_message); +void compute_escape_influence(int color, + const signed char safe_stones[BOARDMAX], + const signed char goal[BOARDMAX], + const float strength[BOARDMAX], + signed char escape_value[BOARDMAX]); + +float influence_delta_territory(const struct influence_data *base, + const struct influence_data *q, int color, + int move); +int retrieve_delta_territory_cache(int pos, int color, float *move_value, + float *followup_value, + const struct influence_data *base, + Hash_data safety_hash); +void store_delta_territory_cache(int pos, int color, float move_value, + float followup_value, + const struct influence_data *base, + Hash_data safety_hash); + +int whose_territory(const struct influence_data *q, int pos); +int whose_moyo(const struct influence_data *q, int pos); +int whose_moyo_restricted(const struct influence_data *q, int pos); +int whose_area(const struct influence_data *q, int pos); +float influence_territory(const struct influence_data *q, int pos, int color); +void influence_get_territory_segmentation(struct influence_data *q, + struct moyo_data *moyo); +void get_influence(const struct influence_data *q, + float white_influence[BOARDMAX], + float black_influence[BOARDMAX], + float white_strength[BOARDMAX], + float black_strength[BOARDMAX], + float white_attenuation[BOARDMAX], + float black_attenuation[BOARDMAX], + float white_permeability[BOARDMAX], + float black_permeability[BOARDMAX], + float territory_value[BOARDMAX], + int influence_regions[BOARDMAX], + int non_territory[BOARDMAX]); +float influence_score(const struct influence_data *q, int chinese_rules); +float game_status(int color); +void influence_mark_non_territory(int pos, int color); +int influence_considered_lively(const struct influence_data *q, int pos); +void influence_erase_territory(struct influence_data *q, int pos, int color); + +void break_territories(int color_to_move, struct influence_data *q, + int store, int pos); +void clear_break_in_list(void); +void break_in_move_reasons(int color); + +void choose_strategy(int color, float our_score, float game_status); + +/* Eye space functions. */ +int is_eye_space(int pos); +int is_proper_eye_space(int pos); +int is_marginal_eye_space(int pos); +int max_eye_value(int pos); +void test_eyeshape(int eyesize, int *eye_vertices); +int analyze_eyegraph(const char *coded_eyegraph, struct eyevalue *value, + char *analyzed_eyegraph); + + +/* debugging support */ +void goaldump(const signed char goal[BOARDMAX]); +void move_considered(int move, float value); + + +/* Transformation stuff. */ +#define MAX_OFFSET (2*MAX_BOARD - 1) * (2*MAX_BOARD - 1) +#define OFFSET(dx, dy)\ + ((dy + MAX_BOARD - 1) * (2*MAX_BOARD - 1) + (dx + MAX_BOARD - 1)) +#define OFFSET_DELTA(dx, dy) (OFFSET(dx, dy) - OFFSET(0, 0)) +#define CENTER_OFFSET(offset) (offset - OFFSET(0, 0)) +#define TRANSFORM(offset, trans) (transformation[offset][trans]) +#define AFFINE_TRANSFORM(offset, trans, delta)\ + (transformation[offset][trans] + delta) +#define TRANSFORM2(x, y, tx, ty, trans)\ + do {\ + *tx = transformation2[trans][0][0] * (x) + transformation2[trans][0][1] * (y);\ + *ty = transformation2[trans][1][0] * (x) + transformation2[trans][1][1] * (y);\ + } while (0) + + +/* ================================================================ */ +/* global variables */ +/* ================================================================ */ + +extern int disable_threat_computation; +extern int disable_endgame_patterns; +extern int doing_scoring; + +/* Reading parameters */ +extern int depth; /* deep reading cutoff */ +extern int backfill_depth; /* deep reading cutoff */ +extern int backfill2_depth; /* deep reading cutoff */ +extern int break_chain_depth; /* deep reading cutoff */ +extern int superstring_depth; /* deep reading cutoff */ +extern int branch_depth; /* deep reading cutoff */ +extern int fourlib_depth; /* deep reading cutoff */ +extern int ko_depth; /* deep ko reading cutoff */ +extern int aa_depth; /* deep global reading cutoff */ +extern int depth_offset; /* keeps track of temporary depth changes */ +extern int owl_distrust_depth; /* below this owl trusts the optics code */ +extern int owl_branch_depth; /* below this owl tries only one variation */ +extern int owl_reading_depth; /* owl does not read below this depth */ +extern int owl_node_limit; /* maximum number of nodes considered */ +extern int semeai_branch_depth; +extern int semeai_branch_depth2; +extern int semeai_node_limit; +extern int connect_depth; +extern int connect_depth2; +extern int connection_node_limit; +extern int breakin_depth; +extern int breakin_node_limit; +extern int semeai_variations; /* max variations considered reading semeai */ +extern float best_move_values[10]; +extern int best_moves[10]; + +extern int experimental_owl_ext; /* use experimental owl (GAIN/LOSS) */ +extern int experimental_semeai; /* use experimental semeai module */ +extern int experimental_connections; /* use experimental connection module */ +extern int alternate_connections; /* use alternate connection module */ +extern int owl_threats; /* compute owl threats */ +extern int experimental_break_in; /* use experimental module breakin.c */ +extern int cosmic_gnugo; /* use center oriented influence */ +extern int large_scale; /* seek large scale captures */ + +extern int thrashing_dragon; /* Dead opponent's dragon trying to live */ +extern signed char thrashing_stone[BOARDMAX]; /* All thrashing stones. */ + +extern int transformation[MAX_OFFSET][8]; +extern const int transformation2[8][2][2]; + + +/* Arrays pointing out the closest worms from each vertex. The first + * one is the closest worms of either color, the last two ones ignore + * worms of the other color. Beyond a certain distance from any worm + * no close worm is listed at all. Only the closest worm is listed + * and if more than one are equally close they are all listed. The + * number of equally close worms is given in the number_*_worms + * arrays. If more than MAX_CLOSE_WORMS are equally close, none is + * listed. + * + * See compute_effective_worm_sizes() in worm.c for details. + */ +#define MAX_CLOSE_WORMS 4 +extern int close_worms[BOARDMAX][MAX_CLOSE_WORMS]; +extern int number_close_worms[BOARDMAX]; +extern int close_black_worms[BOARDMAX][MAX_CLOSE_WORMS]; +extern int number_close_black_worms[BOARDMAX]; +extern int close_white_worms[BOARDMAX][MAX_CLOSE_WORMS]; +extern int number_close_white_worms[BOARDMAX]; + +extern int false_eye_territory[BOARDMAX]; +extern int forced_backfilling_moves[BOARDMAX]; + +extern double slowest_time; /* Timing statistics */ +extern int slowest_move; +extern int slowest_movenum; +extern double total_time; + + +struct eyevalue { + unsigned char a; /* number of eyes if attacker plays first twice */ + unsigned char b; /* number of eyes if attacker plays first */ + unsigned char c; /* number of eyes if defender plays first */ + unsigned char d; /* number of eyes if defender plays first twice */ +}; + + +struct half_eye_data { + float value; /* Topological eye value. */ + unsigned char type; /* HALF_EYE or FALSE_EYE; */ + int num_attacks; /* number of attacking points */ + int attack_point[4]; /* the moves to attack a topological halfeye */ + int num_defenses; /* number of defending points */ + int defense_point[4]; /* the moves to defend a topological halfeye */ +}; + +/* array of half-eye data */ +extern struct half_eye_data half_eye[BOARDMAX]; + +/* + * data concerning a worm. A copy is kept at each vertex of the worm. + */ + +#define MAX_TACTICAL_POINTS 10 + +struct worm_data { + int color; /* its color */ + int size; /* its cardinality */ + float effective_size; /* stones and surrounding spaces */ + int origin; /* the origin of the string. Two vertices are in */ + /* the same worm iff they have same origin. */ + int liberties; /* number of liberties */ + int liberties2; /* number of second order liberties */ + int liberties3; /* third order liberties (empty vertices at distance 3) */ + int liberties4; /* fourth order liberties */ + int lunch; /* if lunch != 0 then lunch points to a boundary */ + /* worm which can be captured easily. */ + int cutstone; /* 1=potential cutting stone; 2=cutting stone */ + int cutstone2; /* Number of potential cuts involving the worm. */ + int genus; /* number of connected components of the complement, less one */ + int inessential; /* 1=inessential worm */ + int invincible; /* 1=strongly unconditionally non-capturable */ + enum dragon_status unconditional_status; /* ALIVE, DEAD, WHITE_TERRITORY, + BLACK_TERRITORY, UNKNOWN */ + + /* The following arrays keeps track of up to MAX_TACTICAL_POINTS + * different attack, defense, attack threat, and defense threat + * points with corresponding result codes. (0 = loss, 1 = bad ko, 2 + * = good ko, 3 = win). The arrays are guaranteed to be sorted with + * respect to the codes so that the first element contains the best + * result. + */ + int attack_points[MAX_TACTICAL_POINTS]; + int attack_codes[MAX_TACTICAL_POINTS]; + int defense_points[MAX_TACTICAL_POINTS]; + int defense_codes[MAX_TACTICAL_POINTS]; + int attack_threat_points[MAX_TACTICAL_POINTS]; + int attack_threat_codes[MAX_TACTICAL_POINTS]; + int defense_threat_points[MAX_TACTICAL_POINTS]; + int defense_threat_codes[MAX_TACTICAL_POINTS]; +}; + +extern struct worm_data worm[BOARDMAX]; + +/* Surround cache (see surround.c) */ + +#define MAX_SURROUND 10 + +struct surround_data { + int dragon_number; /* number of the (surrounded) beast */ + signed char surround_map[BOARDMAX]; /* surround map */ +}; + +extern struct surround_data surroundings[MAX_SURROUND]; +extern int surround_pointer; + +/* + * data concerning a dragon. A copy is kept at each stone of the string. + */ + +struct dragon_data { + int color; /* its color */ + int id; /* the index into the dragon2 array */ + int origin; /* the origin of the dragon. Two vertices are in the same */ + /* dragon iff they have same origin. */ + int size; /* size of the dragon */ + float effective_size; /* stones and surrounding spaces */ + enum dragon_status crude_status; /* (ALIVE, DEAD, UNKNOWN, CRITICAL) */ + enum dragon_status status; /* best trusted status */ +}; + +extern struct dragon_data dragon[BOARDMAX]; + +/* Supplementary data concerning a dragon. Only one copy is stored per + * dragon in the dragon2 array. + */ + +#define MAX_NEIGHBOR_DRAGONS 10 + +struct dragon_data2 { + int origin; /* the origin of the dragon */ + int adjacent[MAX_NEIGHBOR_DRAGONS]; /* adjacent dragons */ + int neighbors; /* number of adjacent dragons */ + int hostile_neighbors; /* neighbors of opposite color */ + + int moyo_size; /* size of surrounding influence moyo, */ + float moyo_territorial_value; /* ...and its territorial value */ + enum dragon_status safety; /* a more detailed status estimate */ + float weakness; /* A new (3.4) continuous estimate of the dragon's safety */ + float weakness_pre_owl; /* Dragon safety based on pre-owl computations */ + float strategic_size; /* An effective size including weakness of neighbors */ + int escape_route; /* a measurement of likelihood of escape */ + struct eyevalue genus; /* the number of eyes (approximately) */ + int heye; /* coordinates of a half eye */ + int lunch; /* if lunch != 0 then lunch points to a boundary worm which */ + /* can be captured easily. */ + int surround_status; /* Is it surrounded? */ + int surround_size; /* Size of the surrounding area */ + + int semeais; /* number of semeais in which the dragon is involved */ + int semeai_defense_code ;/* Result code for semeai defense. */ + int semeai_defense_point;/* Move found by semeai code to rescue dragon */ + int semeai_defense_certain; + int semeai_defense_target; /* The opponent dragon involved in the semeai */ + int semeai_attack_code ; /* Result code for semeai attack. */ + int semeai_attack_point; /* Move found by semeai code to kill dragon */ + int semeai_attack_certain; + int semeai_attack_target; /* The opponent dragon involved in the semeai */ + enum dragon_status owl_threat_status; /* CAN_THREATEN_ATTACK/DEFENSE */ + enum dragon_status owl_status; /* (ALIVE, DEAD, UNKNOWN, CRITICAL, UNCHECKED) */ + int owl_attack_point; /* vital point for attack */ + int owl_attack_code; /* ko result code */ + int owl_attack_certain; /* 0 if owl reading node limit is reached */ + int owl_attack_node_count; + int owl_second_attack_point;/* if attacker gets both attack points, wins */ + int owl_defense_point; /* vital point for defense */ + int owl_defense_code; /* ko result code */ + int owl_defense_certain; /* 0 if owl reading node limit is reached */ + int owl_second_defense_point;/* if defender gets both attack points, wins */ + int owl_attack_kworm; /* only valid when owl_attack_code is GAIN */ + int owl_defense_kworm; /* only valid when owl_defense_code is LOSS */ +}; + +/* dragon2 is dynamically allocated */ +extern int number_of_dragons; +extern struct dragon_data2 *dragon2; + +/* Macros for accessing the dragon2 data with board coordinates and + * the dragon data with a dragon id. + */ +#if 1 /* Trust DRAGON2 accesses */ +#define DRAGON2(pos) dragon2[dragon[pos].id] +#else +struct dragon_data2 *dragon2_func(int pos); +#define DRAGON2(pos) (*dragon2_func(pos)) +#endif + +#define DRAGON(d) dragon[dragon2[d].origin] + +extern float white_score, black_score; + +/* Global variables to tune strategy. */ + +extern float minimum_value_weight; +extern float maximum_value_weight; +extern float invasion_malus_weight; +extern float strategical_weight; +extern float territorial_weight; +extern float attack_dragon_weight; +extern float followup_weight; + +struct aftermath_data { + int white_captured; + int black_captured; + int white_prisoners; + int black_prisoners; + int white_territory; + int black_territory; + int white_area; + int black_area; + int white_control[BOARDMAX]; + int black_control[BOARDMAX]; + enum dragon_status final_status[BOARDMAX]; +}; + +#define MAX_EYE_ATTACKS 3 + +struct eye_data { + int color; /* BLACK, WHITE, or GRAY */ + int esize; /* size of the eyespace */ + int msize; /* number of marginal vertices */ + int origin; /* The origin */ + struct eyevalue value; /* Number of eyes. */ + + /* The above fields are constant on the whole eyespace. */ + /* ---------------------------------------------------------------- */ + /* The below fields are not. */ + + unsigned char marginal; /* This vertex is marginal */ + unsigned char neighbors; /* number of neighbors in eyespace */ + unsigned char marginal_neighbors; /* number of marginal neighbors */ +}; + +struct vital_eye_points { + int attack_points[MAX_EYE_ATTACKS]; + int defense_points[MAX_EYE_ATTACKS]; +}; + +extern struct vital_eye_points black_vital_points[BOARDMAX]; +extern struct vital_eye_points white_vital_points[BOARDMAX]; + +extern struct eye_data white_eye[BOARDMAX]; +extern struct eye_data black_eye[BOARDMAX]; + +/* Array with the information which was previously stored in the cut + * field and in the INHIBIT_CONNECTION bit of the type field in struct + * eye_data. + */ +extern int cutting_points[BOARDMAX]; + +/* The following declarations have to be postponed until after the + * definition of struct eye_data or struct half_eye_data. + */ + +void compute_eyes(int pos, struct eyevalue *value, + int *attack_point, int *defense_point, + struct eye_data eye[BOARDMAX], + struct half_eye_data heye[BOARDMAX], + int add_moves); +void compute_eyes_pessimistic(int pos, struct eyevalue *value, + int *pessimistic_min, + int *attack_point, int *defense_point, + struct eye_data eye[BOARDMAX], + struct half_eye_data heye[BOARDMAX]); +void propagate_eye(int pos, struct eye_data eye[BOARDMAX]); +int find_eye_dragons(int origin, struct eye_data eye[BOARDMAX], int eye_color, + int dragons[], int max_dragons); +void make_domains(struct eye_data b_eye[BOARDMAX], + struct eye_data w_eye[BOARDMAX], + int owl_call); +void partition_eyespaces(struct eye_data eye[BOARDMAX], int color); +void find_half_and_false_eyes(int color, struct eye_data eye[BOARDMAX], + struct half_eye_data heye[BOARDMAX], + int find_mask[BOARDMAX]); + +void set_eyevalue(struct eyevalue *e, int a, int b, int c, int d); +int min_eye_threat(struct eyevalue *e); +int min_eyes(struct eyevalue *e); +int max_eyes(struct eyevalue *e); +int max_eye_threat(struct eyevalue *e); +void add_eyevalues(struct eyevalue *e1, struct eyevalue *e2, + struct eyevalue *sum); +int eye_move_urgency(struct eyevalue *e); +char *eyevalue_to_string(struct eyevalue *e); + +int is_halfeye(struct half_eye_data heye[BOARDMAX], int pos); +int is_false_eye(struct half_eye_data heye[BOARDMAX], int pos); + +#endif /* _LIBERTY_H_ */ + + +/* + * Local Variables: + * tab-width: 8 + * c-basic-offset: 2 + * End: + */ Index: apps/plugins/goban/hash.c =================================================================== --- apps/plugins/goban/hash.c (revision 0) +++ apps/plugins/goban/hash.c (revision 0) @@ -0,0 +1,220 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ + * 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 and 2006 * + * 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., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02111, USA. * +\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "plugin.h" + +#include "board.h" +#include "hash.h" + +extern struct plugin_api * rb; + + +/* + * This file, together with engine/hash.h implements hashing of go positions + * using a method known as Zobrist hashing. See the Texinfo documentation + * (Reading/Hashing) for more information. + */ + + +/* ================================================================ */ + + + + +/* Random values for the board hash function. For stones and ko position. */ +static Hash_data white_hash[BOARDMAX]; +static Hash_data black_hash[BOARDMAX]; +static Hash_data ko_hash[BOARDMAX]; +static Hash_data komaster_hash[NUM_KOMASTER_STATES]; +static Hash_data kom_pos_hash[BOARDMAX]; +static Hash_data goal_hash[BOARDMAX]; + + +/* Get a random Hashvalue, where all bits are used. */ +static Hashvalue +hash_rand(void) +{ + int i; + Hashvalue h = 0; + + for (i = 0; 32*i < (int) (CHAR_BIT*sizeof(Hashvalue)); i++) + h |= (Hashvalue) rb->rand() << 32*i; + +/*gg_urand*/ + + return h; +} + +/* Fill an array with random numbers for Zobrist hashing. */ +void +hash_init_zobrist_array(Hash_data *array, int size) +{ + int i, j; + for (i = 0; i < size; i++) + for (j = 0; j < NUM_HASHVALUES; j++) + array[i].hashval[j] = hash_rand(); +} + +/* + * Initialize the board hash system. + */ + +void +hash_init(void) +{ + static int is_initialized = 0; + if (is_initialized) + return; + + INIT_ZOBRIST_ARRAY(black_hash); + INIT_ZOBRIST_ARRAY(white_hash); + INIT_ZOBRIST_ARRAY(ko_hash); + INIT_ZOBRIST_ARRAY(komaster_hash); + INIT_ZOBRIST_ARRAY(kom_pos_hash); + INIT_ZOBRIST_ARRAY(goal_hash); + + is_initialized = 1; +} + + +/* ---------------------------------------------------------------- */ + +/* Calculate the compactboard and the hashvalues in one function. + * They are always used together and it saves us a loop and a function + * call. + */ + +void +hashdata_recalc(Hash_data *target, Intersection *p, int ko_pos) +{ + int pos; + + hashdata_clear(target); + + for (pos = BOARDMIN; pos < BOARDMAX; pos++) { + if (p[pos] == WHITE) + hashdata_xor(*target, white_hash[pos]); + else if (p[pos] == BLACK) + hashdata_xor(*target, black_hash[pos]); + } + + if (ko_pos != 0) + hashdata_xor(*target, ko_hash[ko_pos]); +} + +/* Clear hashdata. */ +void +hashdata_clear(Hash_data *hd) +{ + int i; + for (i = 0; i < NUM_HASHVALUES; i++) + hd->hashval[i] = 0; +} + +/* Set or remove ko in the hash value and hash position. */ +void +hashdata_invert_ko(Hash_data *hd, int pos) +{ + hashdata_xor(*hd, ko_hash[pos]); +} + + +/* Set or remove a stone of COLOR at pos in a Hash_data. */ +void +hashdata_invert_stone(Hash_data *hd, int pos, int color) +{ + if (color == BLACK) + hashdata_xor(*hd, black_hash[pos]); + else if (color == WHITE) + hashdata_xor(*hd, white_hash[pos]); +} + + +/* Set or remove the komaster value in the hash data. */ +void +hashdata_invert_komaster(Hash_data *hd, int komaster) +{ + hashdata_xor(*hd, komaster_hash[komaster]); +} + +/* Set or remove the komaster position in the hash data. */ +void +hashdata_invert_kom_pos(Hash_data *hd, int kom_pos) +{ + hashdata_xor(*hd, kom_pos_hash[kom_pos]); +} + +/* Compute hash value to identify the goal area. */ +Hash_data +goal_to_hashvalue(const signed char *goal) +{ + int pos; + Hash_data return_value; + + hashdata_clear(&return_value); + + for (pos = BOARDMIN; pos < BOARDMAX; pos++) + if (ON_BOARD(pos) && goal[pos]) + hashdata_xor(return_value, goal_hash[pos]); + + return return_value; +} + + +#define HASHVALUE_NUM_DIGITS (1 + (CHAR_BIT * SIZEOF_HASHVALUE - 1) / 4) +#define BUFFER_SIZE (1 + NUM_HASHVALUES * HASHVALUE_NUM_DIGITS) +/*char * +hashdata_to_string(Hash_data *hashdata) +{ + static char buffer[BUFFER_SIZE]; + int n = 0; + int k; + + for (k = NUM_HASHVALUES - 1; k >= 0; k--) { + n += rb->sprintf(buffer + n, HASHVALUE_PRINT_FORMAT, + HASHVALUE_NUM_DIGITS, hashdata->hashval[k]); + gg_assert(n < BUFFER_SIZE); + } + + return buffer; +} +*/ +#if NUM_HASHVALUES > 2 +int +hashdata_is_equal_func(Hash_data *hd1, Hash_data *hd2) +{ + int i; + for (i = 0; i < NUM_HASHVALUES; i++) + if (hd1->hashval[i] != hd2->hashval[i]) + return 0; + + return 1; +} +#endif + + +/* + * Local Variables: + * tab-width: 8 + * c-basic-offset: 2 + * End: + */ Index: apps/plugins/goban/globals.c =================================================================== --- apps/plugins/goban/globals.c (revision 0) +++ apps/plugins/goban/globals.c (revision 0) @@ -0,0 +1,158 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ + * 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 and 2006 * + * 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., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02111, USA. * +\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "gnugo.h" + +#include + +#include "sgftree.h" +#include "liberty.h" + +/* + * Define all global variables used within the engine. + */ + +#if 0 + +int thrashing_dragon = NO_MOVE; /* Dead opponent's dragon trying to live. */ +signed char thrashing_stone[BOARDMAX]; /* All thrashing stones. */ + +float potential_moves[BOARDMAX]; + +/* Used by reading. */ +int depth; /* deep reading cut off */ +int backfill_depth; /* deep reading cut off */ +int backfill2_depth; /* deep reading cut off */ +int break_chain_depth; /* deep reading cut off */ +int superstring_depth; /* deep reading cut off */ +int fourlib_depth; /* deep reading cut off */ +int ko_depth; /* deep reading cut off */ +int branch_depth; /* deep reading cut off */ +int aa_depth; +int depth_offset; /* keeps track of temporary depth changes */ +int owl_distrust_depth; /* below this owl trusts the optics code */ +int owl_branch_depth; /* below this owl tries only one variation */ +int owl_reading_depth; /* owl does not read below this depth */ +int owl_node_limit; /* maximum number of nodes considered */ +int semeai_branch_depth; +int semeai_branch_depth2; +int semeai_node_limit; +int connect_depth; /* Used by Tristan Cazenave's connection reader. */ +int connect_depth2; /* Used by alternater connection reader. */ +int connection_node_limit; +int breakin_node_limit; /* Reading limits for break_in/block_off reading */ +int breakin_depth; +/* Mandated values for deep reading cutoffs. */ +int mandated_depth = -1; +int mandated_backfill_depth = -1; +int mandated_backfill2_depth = -1; +int mandated_break_chain_depth = -1; +int mandated_superstring_depth = -1; +int mandated_fourlib_depth = -1; +int mandated_ko_depth = -1; +int mandated_branch_depth = -1; +int mandated_aa_depth = -1; +int mandated_owl_distrust_depth = -1; +int mandated_owl_branch_depth = -1; +int mandated_owl_reading_depth = -1; +int mandated_owl_node_limit = -1; +int mandated_semeai_node_limit = -1; + + +/* Miscellaneous. */ +int quiet = 0; /* minimal output */ +int showstatistics = 0; /* print statistics */ +int profile_patterns = 0; /* print statistics of pattern usage */ +int allpats = 0; /* generate all patterns, even small ones */ +int printworms = 0; /* print full data on each string */ +int printmoyo = 0; /* print moyo board each move */ +int printboard = 0; /* print board each move */ +int fusekidb = 1; /* use fuseki database */ +int disable_fuseki = 0; /* do not generate fuseki moves */ +int josekidb = 1; /* use joseki database */ +int showtime = 0; /* print time to find move */ +int showscore = 0; /* print estimated score */ +int debug = 0; /* controls debug output */ +int verbose = 0; /* trace level */ +char outfilename[128] = ""; /* output file (-o option) */ +int output_flags = OUTPUT_DEFAULT; /* amount of output to outfile */ +int metamachine = 0; /* use metamachine_genmove */ +int oracle_exists = 0; /* oracle is available for consultation */ +int autolevel_on = 0; /* Adjust level in GMP or ASCII mode. */ + +int disable_threat_computation = 0; +int disable_endgame_patterns = 0; +int doing_scoring = 0; + +int capture_all_dead = 0; /* capture all dead opponent stones */ +int play_out_aftermath = 0; /* make everything unconditionally settled */ + +int play_mirror_go = 0; /* try to play mirror go if possible */ +int mirror_stones_limit = -1; /* but stop at this number of stones */ + +int gtp_version = 2; /* Use GTP version 2 by default. */ + +float best_move_values[10]; +int best_moves[10]; +float white_score; +float black_score; +#endif +/* +int close_worms[BOARDMAX][4]; +int number_close_worms[BOARDMAX]; +int close_black_worms[BOARDMAX][4]; +int number_close_black_worms[BOARDMAX]; +int close_white_worms[BOARDMAX][4]; +int number_close_white_worms[BOARDMAX]; + +int false_eye_territory[BOARDMAX]; +int forced_backfilling_moves[BOARDMAX]; + +struct worm_data worm[BOARDMAX]; +struct dragon_data dragon[BOARDMAX]; +int number_of_dragons; +struct dragon_data2 *dragon2 = NULL; +struct half_eye_data half_eye[BOARDMAX]; +struct eye_data black_eye[BOARDMAX]; +struct eye_data white_eye[BOARDMAX]; +struct vital_eye_points black_vital_points[BOARDMAX]; +struct vital_eye_points white_vital_points[BOARDMAX]; +struct surround_data surroundings[MAX_SURROUND]; +int surround_pointer; + +int cutting_points[BOARDMAX]; + +double slowest_time = 0.0; +int slowest_move = NO_MOVE; +int slowest_movenum = 0; +double total_time = 0.0; + + +float minimum_value_weight = 1.0; +float maximum_value_weight = 1.0; +float invasion_malus_weight = 1.0; +float territorial_weight = 1.0; +float strategical_weight = 1.0; +float attack_dragon_weight = 1.0; +float followup_weight = 1.0; +*/ + Index: apps/plugins/goban/gnugo.h =================================================================== --- apps/plugins/goban/gnugo.h (revision 0) +++ apps/plugins/goban/gnugo.h (revision 0) @@ -0,0 +1,402 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ + * 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 and 2006 * + * 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., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02111, USA. * +\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/* ---------------------------------------------------------------- * + * gnugo.h + * This file contains the public interface to the GNU Go engine. + * ---------------------------------------------------------------- */ + + +#ifndef _GNUGO_H_ +#define _GNUGO_H_ + +#include "board.h" +#include "plugin.h" + + +#include "sgftree.h" + +/* interface.c */ +/* Initialize the whole thing. Should be called once. */ +void init_gnugo(float memory, unsigned int random_seed); + + +/* ================================================================ */ +/* some public macros used everywhere */ +/* ================================================================ */ + + +/* Used in matchpat.c. Have to be different from WHITE, BLACK. */ +#define ANCHOR_COLOR 6 +#define ANCHOR_OTHER 7 + +/* Return codes for reading functions */ + +#define WIN 5 +#define KO_A 4 +#define GAIN 3 +#define LOSS 2 +#define KO_B 1 +#define LOSE 0 + +const char *result_to_string(int result); + +/* Used by break_through(). Must be different from 0 and WIN. */ +#define CUT 2 + + +/* Surrounded */ + +#define SURROUNDED 1 +#define WEAKLY_SURROUNDED 2 + +/* ================================================================ */ +/* Board manipulation */ +/* ================================================================ */ + + +void gnugo_clear_board(int boardsize); +void gnugo_play_move(int move, int color); +int gnugo_play_sgfnode(SGFNode *node, int to_move); +int gnugo_sethand(int desired_handicap, SGFNode *node); +#if 0 +float gnugo_estimate_score(float *upper, float *lower); +#endif +/* ================================================================ */ +/* Game handling */ +/* ================================================================ */ + + +typedef struct { + int handicap; + int to_move; /* whose move it currently is */ + SGFTree game_record; /* Game record in sgf format. */ + int computer_player; /* BLACK, WHITE, or EMPTY (used as BOTH) */ +} Gameinfo; + +void gameinfo_clear(Gameinfo *ginfo); +void gameinfo_print(Gameinfo *ginfo); +int gameinfo_play_sgftree_rot(Gameinfo *gameinfo, SGFTree *tree, + const char *untilstr, int orientation); +int gameinfo_play_sgftree(Gameinfo *gameinfo, SGFTree *tree, + const char *untilstr); + + +/* ================================================================ */ +/* global variables */ +/* ================================================================ */ + +#if 0 +/* Miscellaneous debug options. */ +extern int quiet; /* Minimal output. */ +extern int verbose; /* Bore the opponent. */ +extern int allpats; /* generate all patterns, even small ones */ +extern int printworms; /* print full data on each string */ +extern int printmoyo; /* print moyo board each move */ +extern int printdragons; /* print full data on each dragon */ +extern int printboard; /* print board each move */ +extern int showstatistics; /* print statistics */ +extern int profile_patterns; /* print statistics of pattern usage */ +extern char outfilename[128]; /* output file (-o option) */ +extern int output_flags; /* amount of output to outfile */ +#endif + +/* output flag bits */ +#define OUTPUT_MARKDRAGONS 0x0001 /* mark dead and critical dragons */ +#define OUTPUT_MOVEVALUES 0x0002 /* output values of all moves in list */ + +#define OUTPUT_DEFAULT 0 /* no debug output by default */ + +/* debug flag bits */ +/* NOTE : can specify -d0x... */ +/* Please keep this list in sync with the DEBUG_FLAGS string below. */ +#define DEBUG_INFLUENCE 0x0001 +#define DEBUG_EYES 0x0002 +#define DEBUG_OWL 0x0004 +#define DEBUG_ESCAPE 0x0008 +#define DEBUG_MATCHER 0x0010 +#define DEBUG_DRAGONS 0x0020 +#define DEBUG_SEMEAI 0x0040 +#define DEBUG_LOADSGF 0x0080 +#define DEBUG_HELPER 0x0100 +#define DEBUG_READING 0x0200 +#define DEBUG_WORMS 0x0400 +#define DEBUG_MOVE_REASONS 0x0800 +#define DEBUG_OWL_PERFORMANCE 0x1000 +#define DEBUG_BREAKIN 0x2000 +#define DEBUG_FILLLIB 0x4000 +#define DEBUG_READING_PERFORMANCE 0x8000 +#define DEBUG_SCORING 0x010000 +#define DEBUG_AFTERMATH 0x020000 +#define DEBUG_ATARI_ATARI 0x040000 +#define DEBUG_READING_CACHE 0x080000 +#define DEBUG_TERRITORY 0x100000 +#define DEBUG_PERSISTENT_CACHE 0x200000 +#define DEBUG_TOP_MOVES 0x400000 +#define DEBUG_MISCELLANEOUS 0x800000 +#define DEBUG_ORACLE_STREAM 0x1000000 +#define DEBUG_LARGE_SCALE 0x1000000 +#define DEBUG_SPLIT_OWL 0x2000000 +#define DEBUG_TIME 0x4000000 + + +#define DEBUG_FLAGS "\ +DEBUG_INFLUENCE 0x0001\n\ +DEBUG_EYES 0x0002\n\ +DEBUG_OWL 0x0004\n\ +DEBUG_ESCAPE 0x0008\n\ +DEBUG_MATCHER 0x0010\n\ +DEBUG_DRAGONS 0x0020\n\ +DEBUG_SEMEAI 0x0040\n\ +DEBUG_LOADSGF 0x0080\n\ +DEBUG_HELPER 0x0100\n\ +DEBUG_READING 0x0200\n\ +DEBUG_WORMS 0x0400\n\ +DEBUG_MOVE_REASONS 0x0800\n\ +DEBUG_OWL_PERFORMANCE 0x1000\n\ +DEBUG_BREAKIN 0x2000\n\ +DEBUG_FILLLIB 0x4000\n\ +DEBUG_READING_PERFORMANCE 0x8000\n\ +DEBUG_SCORING 0x010000\n\ +DEBUG_AFTERMATH 0x020000\n\ +DEBUG_ATARI_ATARI 0x040000\n\ +DEBUG_READING_CACHE 0x080000\n\ +DEBUG_TERRITORY 0x100000\n\ +DEBUG_PERSISTENT_CACHE 0x200000\n\ +DEBUG_TOP_MOVES 0x400000\n\ +DEBUG_MISCELLANEOUS 0x800000\n\ +DEBUG_ORACLE_STREAM 0x1000000\n\ +DEBUG_LARGE_SCALE 0x1000000\n\ +DEBUG_SPLIT_OWL 0x2000000\n\ +DEBUG_TIME 0x4000000\n\ +" + +#if 0 +extern int debug; /* debug flags */ +extern int fusekidb; /* use fuseki database */ +extern int disable_fuseki; /* do not generate fuseki moves */ +extern int josekidb; /* use joseki database */ +extern int semeai_variations; /* max variations considered reading semeai */ +extern int showtime; /* print genmove time */ +extern int showscore; /* print score */ +extern int chinese_rules; /* use chinese (area) rules for counting */ +extern int experimental_owl_ext; /* use experimental owl (GAIN/LOSS) */ +extern int experimental_connections; /* use experimental connection module */ +extern int alternate_connections; /* use alternate connection module */ +extern int owl_threats; /* compute owl threats */ +extern int capture_all_dead; /* capture all dead opponent stones */ +extern int play_out_aftermath; /* make everything unconditionally settled */ +extern int resign_allowed; /* allows GG to resign hopeless games */ +extern int play_mirror_go; /* try to play mirror go if possible */ +extern int mirror_stones_limit; /* but stop at this number of stones */ +extern int gtp_version; /* version of Go Text Protocol */ + +/* Mandatory values of reading parameters. Normally -1, if set at + * these override the values derived from the level. */ +extern int mandated_depth; +extern int mandated_backfill_depth; +extern int mandated_backfill2_depth; +extern int mandated_break_chain_depth; +extern int mandated_superstring_depth; +extern int mandated_fourlib_depth; +extern int mandated_ko_depth; +extern int mandated_branch_depth; +extern int mandated_aa_depth; +extern int mandated_owl_distrust_depth; +extern int mandated_owl_branch_depth; +extern int mandated_owl_reading_depth; +extern int mandated_owl_node_limit; +extern int mandated_semeai_node_limit; + +extern int autolevel_on; + +extern float potential_moves[BOARDMAX]; + +extern int oracle_exists; /* oracle is available for consultation */ +extern int metamachine; /* use metamachine_genmove */ +#endif +/* ================================================================ */ +/* tracing and debugging functions */ +/* ================================================================ */ + +/* Colors. */ +#define GG_COLOR_BLACK 0 +#define GG_COLOR_RED 1 +#define GG_COLOR_GREEN 2 +#define GG_COLOR_YELLOW 3 +#define GG_COLOR_BLUE 4 +#define GG_COLOR_MAGENTA 5 +#define GG_COLOR_CYAN 6 +#define GG_COLOR_WHITE 7 + +#if 0 +/* showbord.c */ +void start_draw_board(void); +void draw_color_char(int m, int n, int c, int color); +void draw_char(int m, int n, int c); +void end_draw_board(void); +void showboard(int xo); /* ascii rep. of board to stderr */ + +double gg_gettimeofday(void); +#endif + +#if 0 +/* influence.c */ +void debug_influence_move(int move); +#endif + +#define TRACE (!(verbose)) ? (void)0 : (void)gprintf + +#ifdef HAVE_VARIADIC_DEFINE + +/* gnuc allows variadic macros, so the tests can be done inline */ +#define DEBUG(level, fmt, args...) \ + do { if ((debug & (level))) DEBUGF(fmt, ##args); } while (0) + +#else /*HAVE_VARIADIC_DEFINE*/ + +/* if debug == 0, then can skip the function call. */ +#define DEBUG (!(debug)) ? (void)0 : (void)DEBUG_func +int DEBUG_func(int level, const char *fmt, ...); + +#endif /*HAVE_VARIADIC_DEFINE*/ + +#if 0 +/* genmove.c */ +#define EXAMINE_WORMS 1 +#define EXAMINE_INITIAL_INFLUENCE 2 +#define EXAMINE_DRAGONS_WITHOUT_OWL 3 +#define EXAMINE_DRAGONS 4 +#define EXAMINE_OWL_REASONS 5 +#define EXAMINE_INITIAL_INFLUENCE2 6 +#define FULL_EXAMINE_DRAGONS 7 + +#define EXAMINE_ALL 99 + +#endif + +#if 0 +void reset_engine(void); +void examine_position(int how_much, int aftermath_play); +void silent_examine_position(int how_much); +#endif + +/* ================================================================ */ +/* statistics functions */ +/* ================================================================ */ + +#if 0 +/* These are mostly used for GTP examination. */ +void reset_owl_node_counter(void); +int get_owl_node_counter(void); +void reset_reading_node_counter(void); +int get_reading_node_counter(void); +void reset_connection_node_counter(void); +int get_connection_node_counter(void); +#endif + + +/* ================================================================ */ +/* Low level functions */ +/* ================================================================ */ + +/* utils.c */ +/* +void change_dragon_status(int dr, int status); +void who_wins(int color, FILE *outfile); +*/ +/* high-level routine to generate the best move for the given color */ +#if 0 +int genmove(int color, float *value, int *resign); +int genmove_conservative(int color, float *value); + +/* Play through the aftermath. */ +float aftermath_compute_score(int color, SGFTree *tree); +#endif + + +#if 0 + +/* Basic information gathering. */ +/* worm.c */ +void make_worms(void); +void compute_worm_influence(void); + +/* dragon.c */ +void make_dragons(int stop_before_owl); +void initialize_dragon_data(void); +void show_dragons(void); +int crude_status(int pos); +int dragon_status(int pos); +int same_dragon(int dr1, int dr2); + +/* debugging functions */ +void prepare_pattern_profiling(void); +void report_pattern_profiling(void); + +/* sgffile.c */ +void sgffile_add_debuginfo(SGFNode *node, float value); +void sgffile_output(SGFTree *tree); + +#endif + +void sgffile_printsgf(int color_to_play, const char *filename); +void sgffile_printboard(SGFTree *tree); +void sgffile_recordboard(SGFNode *node); +int get_sgfmove(SGFProperty *property); + + +int sgf_rotate1(int pos, int orientation); + + +/* Reorientation of point (i, j) into (*ri, *rj) */ +void sgf_rotate(int i, int j, int *ri, int *rj, int bs, int rot); + +#if 0 +/* sgfdecide.c */ +void decide_string(int pos); +void decide_connection(int apos, int bpos); +void decide_owl(int pos); +void decide_dragon_data(int pos); +void decide_semeai(int apos, int bpos); +void decide_tactical_semeai(int apos, int bpos); +void decide_position(void); +void decide_eye(int pos); +void decide_combination(int color); +void decide_surrounded(int pos); +void decide_oracle(Gameinfo *gameinfo, char *infilename, char *untilstring); + +/*oracle.c*/ +void dismiss_oracle(void); +void oracle_clear_board(int boardsize); +#endif + + +#endif /* _GNUGO_H_ */ + + +/* + * Local Variables: + * tab-width: 8 + * c-basic-offset: 2 + * End: + */ Index: apps/plugins/goban/sgfnode.c =================================================================== --- apps/plugins/goban/sgfnode.c (revision 0) +++ apps/plugins/goban/sgfnode.c (revision 0) @@ -0,0 +1,1664 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ + * 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 and 2006 * + * 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., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02111, USA. * +\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/* Parts of this code were given to us by Tommy Thorn */ + + +#include "plugin.h" + +#include "sgf_malloc.h" + + +#include "sgftree.h" + + + +extern struct plugin_api* rb; + + + +#define STRICT_SGF 's' +#define LAX_SGF 'l' + +#define assert(x) + +/* Set this to 1 if you want warnings for missing GM and FF properties. */ +#define VERBOSE_WARNINGS 0 + +/* ================================================================ */ +/* Some utility functions. */ +/* ================================================================ */ + +/* + * Utility: a checking, initializing malloc + */ + +void * +xalloc(unsigned int size) +{ + void *pt = sgf_malloc(size); + + rb->memset(pt, 0, (unsigned long) size); + return pt; +} + +void * +xrealloc(void *pt, unsigned int size) +{ + void *ptnew = sgf_realloc(pt, size); + rb->memset(pt, 0, (unsigned long) size); + return ptnew; +} + + +/* ================================================================ */ +/* SGF Nodes */ +/* ================================================================ */ + + +/* + * Allocate memory for a new SGF node. + */ + +SGFNode * +sgfNewNode() +{ + SGFNode *newnode; + newnode = xalloc(sizeof(SGFNode)); + newnode->next = NULL; + newnode->props = NULL; + newnode->parent = NULL; + newnode->child = NULL; + return newnode; +} + +/* + * Recursively free an sgf node + */ + +void +sgfFreeNode(SGFNode *node) +{ + if (node == NULL) + return; + sgfFreeNode(node->next); + sgfFreeNode(node->child); + sgfFreeProperty(node->props); + sgf_free(node); +} + + +/* + * Add a generic text property to an SGF node. + */ + +void +sgfAddProperty(SGFNode *node, const char *name, const char *value) +{ + SGFProperty *prop = node->props; + + if (prop) + while (prop->next) + prop = prop->next; + + sgfMkProperty(name, value, node, prop); +} + + +/* + * Add an integer property to an SGF node. + */ + +void +sgfAddPropertyInt(SGFNode *node, const char *name, long val) +{ + char buffer[10]; + + rb->snprintf(buffer, 10, "%ld", val); + sgfAddProperty(node, name, buffer); +} + + +/* + * Add a float property to an SGF node. + */ + +void +sgfAddPropertyFloat(SGFNode *node, const char *name, float val) +{ + char buffer[10]; + + rb->snprintf(buffer, 10, "%3.1f", val); + sgfAddProperty(node, name, buffer); +} + + +/* + * Read a property as int from an SGF node. + */ + +int +sgfGetIntProperty(SGFNode *node, const char *name, int *value) +{ + SGFProperty *prop; + short nam = name[0] | name[1] << 8; + + for (prop = node->props; prop; prop = prop->next) + if (prop->name == nam) { + *value = rb->atoi(prop->value); + return 1; + } + + return 0; +} + + +/* + * Read a property as float from an SGF node. + */ + +int +sgfGetFloatProperty(SGFNode *node, const char *name, float *value) +{ + SGFProperty *prop; + short nam = name[0] | name[1] << 8; + + for (prop = node->props; prop; prop = prop->next) + if (prop->name == nam) { + /* pretty major hack, assumes all komi is fractional */ + *value = rb->atoi(prop->value) + 0.5f; + return 1; + } + + return 0; +} + + +/* + * Read a property as text from an SGF node. + */ + +int +sgfGetCharProperty(SGFNode *node, const char *name, char **value) +{ + SGFProperty *prop; + short nam = name[0] | name[1] << 8; + + for (prop = node->props; prop; prop = prop->next) + if (prop->name == nam) { + *value = prop->value; + return 1; + } + + return 0; +} + + +/* + * Is there a property of this type in the node? + */ + +static int +sgfHasProperty(SGFNode *node, const char *name) +{ + SGFProperty *prop; + short nam = name[0] | name[1] << 8; + + for (prop = node->props; prop; prop = prop->next) + if (prop->name == nam) + return 1; + + return 0; +} + + +/* + * Overwrite a property from an SGF node with text or create a new + * one if it does not exist. + */ + +void +sgfOverwriteProperty(SGFNode *node, const char *name, const char *text) +{ + SGFProperty *prop; + short nam = name[0] | name[1] << 8; + + for (prop = node->props; prop; prop = prop->next) + if (prop->name == nam) { + prop->value = xrealloc(prop->value, rb->strlen(text)+1); + rb->strcpy(prop->value, text); + return; + } + + sgfAddProperty(node, name, text); +} + + +/* + * Overwrite an int property in an SGF node with val or create a new + * one if it does not exist. + */ + +void +sgfOverwritePropertyInt(SGFNode *node, const char *name, int val) +{ + SGFProperty *prop; + short nam = name[0] | name[1] << 8; + + for (prop = node->props; prop; prop = prop->next) + if (prop->name == nam) { + prop->value = xrealloc(prop->value, 12); + rb->snprintf(prop->value, 12, "%d", val); + return; + } + + sgfAddPropertyInt(node, name, val); +} + + +/* + * Overwrite a float property in the gametree with val or create + * a new one if it does not exist. + */ + +void +sgfOverwritePropertyFloat(SGFNode *node, const char *name, float val) +{ + SGFProperty *prop; + short nam = name[0] | name[1] << 8; + + for (prop = node->props; prop; prop = prop->next) + if (prop->name == nam) { + prop->value = xrealloc(prop->value, 15); + rb->snprintf(prop->value, 15, "%3.1f", val); + return; + } + + sgfAddPropertyFloat(node, name, val); +} + + +/* + * Goto previous node. + */ + +SGFNode * +sgfPrev(SGFNode *node) +{ + SGFNode *q; + SGFNode *prev; + + if (!node->parent) + return NULL; + + q = node->parent->child; + prev = NULL; + while (q && q != node) { + prev = q; + q = q->next; + } + + return prev; +} + + +/* + * Goto root node. + */ + +SGFNode * +sgfRoot(SGFNode *node) +{ + while (node->parent) + node = node->parent; + + return node; +} + + +/* ================================================================ */ +/* SGF Properties */ +/* ================================================================ */ + + +/* + * Make an SGF property. + */ +static SGFProperty * +do_sgf_make_property(short sgf_name, const char *value, + SGFNode *node, SGFProperty *last) +{ + SGFProperty *prop; + + prop = (SGFProperty *) xalloc(sizeof(SGFProperty)); + prop->name = sgf_name; + prop->value = xalloc(rb->strlen(value) + 1); + rb->strcpy(prop->value, value); + prop->next = NULL; + + if (last == NULL) + node->props = prop; + else + last->next = prop; + + return prop; +} + + +/* Make an SGF property. In case of a property with a range it + * expands it and makes several properties instead. + */ +SGFProperty * +sgfMkProperty(const char *name, const char *value, + SGFNode *node, SGFProperty *last) +{ + static const short properties_allowing_ranges[12] = { + /* Board setup properties. */ + SGFAB, SGFAW, SGFAE, + + /* Markup properties. */ + SGFCR, SGFMA, SGFSQ, SGFTR, SGFDD, SGFSL, + + /* Miscellaneous properties. */ + SGFVW, + + /* Go-specific properties. */ + SGFTB, SGFTW + }; + + int k; + short sgf_name; + + if (rb->strlen(name) == 1) + sgf_name = name[0] | (short) (' ' << 8); + else + sgf_name = name[0] | name[1] << 8; + + for (k = 0; k < 12; k++) { + if (properties_allowing_ranges[k] == sgf_name) + break; + } + + if (k < 12 + && rb->strlen(value) == 5 + && value[2] == ':') { + char x1 = value[0]; + char y1 = value[1]; + char x2 = value[3]; + char y2 = value[4]; + char new_value[] = "xy"; + + if (x1 <= x2 && y1 <= y2) { + for (new_value[0] = x1; new_value[0] <= x2; new_value[0]++) { + for (new_value[1] = y1; new_value[1] <= y2; new_value[1]++) + last = do_sgf_make_property(sgf_name, new_value, node, last); + } + + return last; + } + } + + /* Not a range property. */ + return do_sgf_make_property(sgf_name, value, node, last); +} + + +/* + * Recursively free an SGF property. + * + */ + +void +sgfFreeProperty(SGFProperty *prop) +{ + if (prop == NULL) + return; + sgfFreeProperty(prop->next); + sgf_free(prop->value); + sgf_free(prop); +} + + +/* ================================================================ */ +/* High level functions */ +/* ================================================================ */ + + +/* + * Add a stone to the current or the given node. + * Return the node where the stone was added. + */ + +SGFNode * +sgfAddStone(SGFNode *node, int color, int movex, int movey) +{ + char move[3]; + + rb->snprintf(move, 3, "%c%c", movey + 'a', movex + 'a'); + sgfAddProperty(node, (color == BLACK) ? "AB" : "AW", move); + + return node; +} + + +/* + * Add a move to the gametree. + */ + +SGFNode * +sgfAddPlay(SGFNode *node, int who, int movex, int movey) +{ + char move[3]; + SGFNode *new; + + /* a pass move? */ + if (movex == -1 && movey == -1) + move[0] = 0; + else + rb->snprintf(move, 3, "%c%c", movey + 'a', movex + 'a'); + + if (node->child) + new = sgfStartVariantFirst(node->child); + else { + new = sgfNewNode(); + node->child = new; + new->parent = node; + } + + sgfAddProperty(new, (who == BLACK) ? "B" : "W", move); + + return new; +} + + +/* + * Add a move to the gametree. New variations are added after the old + * ones rather than before. + */ + +SGFNode * +sgfAddPlayLast(SGFNode *node, int who, int movex, int movey) +{ + char move[3]; + SGFNode *new; + + /* a pass move? */ + if (movex == -1 && movey == -1) + move[0] = 0; + else + rb->snprintf(move, 3, "%c%c", movey + 'a', movex + 'a'); + + new = sgfAddChild(node); + sgfAddProperty(new, (who == BLACK) ? "B" : "W", move); + + return new; +} + + +SGFNode * +sgfCreateHeaderNode(int boardsize, float komi, int handicap) +{ + SGFNode *root = sgfNewNode(); + + sgfAddPropertyInt(root, "SZ", boardsize); + sgfAddPropertyFloat(root, "KM", komi); + sgfAddPropertyInt(root, "HA", handicap); + + return root; +} + + +/* + * Add a comment to an SGF node. + */ + +SGFNode * +sgfAddComment(SGFNode *node, const char *comment) +{ + sgfAddProperty(node, "C ", comment); + + return node; +} + + +/* + * Place text on the board at position (i, j). + */ + +SGFNode * +sgfBoardText(SGFNode *node, int i, int j, const char *text) +{ + void *str = xalloc(rb->strlen(text) + 3); + + rb->snprintf(str, rb->strlen(text) + 3, "%c%c:%s", j+'a', i+'a', text); + sgfAddProperty(node, "LB", str); + sgf_free(str); + + return node; +} + + +/* + * Place a character on the board at position (i, j). + */ + +SGFNode * +sgfBoardChar(SGFNode *node, int i, int j, char c) +{ + char text[2] = ""; + + text[0] = c; + text[1] = 0; + + return sgfBoardText(node, i, j, text); +} + + +/* + * Place a number on the board at position (i, j). + */ + +SGFNode * +sgfBoardNumber(SGFNode *node, int i, int j, int number) +{ + char text[10]; + + rb->snprintf(text, 10, "%c%c:%i", j+'a', i+'a', number); + sgfAddProperty(node, "LB", text); + + return node; +} + + +/* + * Place a triangle mark on the board at position (i, j). + */ + +SGFNode * +sgfTriangle(SGFNode *node, int i, int j) +{ + char text[3]; + + rb->snprintf(text, 3, "%c%c", j+'a', i+'a'); + sgfAddProperty(node, "TR", text); + + return node; +} + + +/* + * Place a label on the board at position (i, j). + */ + +SGFNode * +sgfLabel(SGFNode *node, const char *label, int i, int j) +{ + /* allows 12 chars labels - more than enough */ + char text[16]; + + rb->snprintf(text, 16, "%c%c:%s", j+'a', i+'a', label); + sgfAddProperty(node, "LB", text); + + return node; +} + + +/* + * Place a numeric label on the board at position (i, j). + */ + +SGFNode * +sgfLabelInt(SGFNode *node, int num, int i, int j) +{ + char text[16]; + + rb->snprintf(text, 16, "%c%c:%d", j+'a', i+'a', num); + sgfAddProperty(node, "LB", text); + + return node; +} + + +/* + * Place a circle mark on the board at position (i, j). + */ + +SGFNode * +sgfCircle(SGFNode *node, int i, int j) +{ + char text[3]; + + rb->snprintf(text, 3, "%c%c", j+'a', i+'a'); + sgfAddProperty(node, "CR", text); + + return node; +} + + +/* + * Place a square mark on the board at position (i, j). + */ + +SGFNode * +sgfSquare(SGFNode *node, int i, int j) +{ + return sgfMark(node, i, j); /* cgoban 1.9.5 does not understand SQ */ +} + + +/* + * Place a (square) mark on the board at position (i, j). + */ + +SGFNode * +sgfMark(SGFNode *node, int i, int j) +{ + char text[3]; + + rb->snprintf(text, 3, "%c%c", j+'a', i+'a'); + sgfAddProperty(node, "MA", text); + + return node; +} + + +/* + * Start a new variant. Returns a pointer to the new node. + */ + +SGFNode * +sgfStartVariant(SGFNode *node) +{ + assert(node); + assert(node->parent); + + while (node->next) + node = node->next; + node->next = sgfNewNode(); + node->next->parent = node->parent; + + return node->next; +} + + +/* + * Start a new variant as first child. Returns a pointer to the new node. + */ + +SGFNode * +sgfStartVariantFirst(SGFNode *node) +{ + SGFNode *old_first_child = node; + SGFNode *new_first_child = sgfNewNode(); + + assert(node); + assert(node->parent); + + new_first_child->next = old_first_child; + new_first_child->parent = old_first_child->parent; + + new_first_child->parent->child = new_first_child; + + return new_first_child; +} + + +/* + * If no child exists, add one. Otherwise add a sibling to the + * existing children. Returns a pointer to the new node. + */ + +SGFNode * +sgfAddChild(SGFNode *node) +{ + SGFNode *new_node = sgfNewNode(); + assert(node); + + new_node->parent = node; + + if (!node->child) + node->child = new_node; + else { + node = node->child; + while (node->next) + node = node->next; + node->next = new_node; + } + + return new_node; +} + + +/* + * Write result of the game to the game tree. + */ + +void +sgfWriteResult(SGFNode *node, float score, int overwrite) +{ + char text[8]; + char winner; + float s; + int dummy; + + /* If not writing to the SGF file, skip everything and return now. */ + if (!node) + return; + + /* If not overwriting and there already is a result property, return. */ + if (!overwrite) + if (sgfGetIntProperty(node, "RE", &dummy)) + return; + + if (score > 0.0) { + winner = 'W'; + s = score; + } + else if (score < 0.0) { + winner = 'B'; + s = -score; + } + else { + winner = '0'; + s = 0; + } + + if (winner == '0') + rb->snprintf(text, 8, "0"); + else if (score < 1000.0 && score > -1000.0) + rb->snprintf(text, 8, "%c+%3.1f", winner, s); + else + rb->snprintf(text, 8, "%c+%c", winner, 'R'); + sgfOverwriteProperty(node, "RE", text); +} + + +static void +sgf_write_header_reduced(SGFNode *root, int overwrite) +{ + //time_t curtime = rb->time(NULL); + struct tm *loctime = rb->get_time(); + char str[128]; + int dummy; + + rb->snprintf(str, 128, "%4.4i-%2.2i-%2.2i", + loctime->tm_year+1900, loctime->tm_mon+1, loctime->tm_mday); + if (overwrite || !sgfGetIntProperty(root, "DT", &dummy)) + sgfOverwriteProperty(root, "DT", str); + if (overwrite || !sgfGetIntProperty(root, "AP", &dummy)) + sgfOverwriteProperty(root, "AP", "GNU Go:""BLAH"); + sgfOverwriteProperty(root, "FF", "4"); +} + + +void +sgf_write_header(SGFNode *root, int overwrite, int seed, float komi, + int handicap, int level, int rules) +{ + char str[128]; + int dummy; + + rb->snprintf(str, 128, "GNU Go %s Random Seed %d level %d", + "fake", seed, level); + if (overwrite || !sgfGetIntProperty(root, "GN", &dummy)) + sgfOverwriteProperty(root, "GN", str); + if (overwrite || !sgfGetIntProperty(root, "RU", &dummy)) + sgfOverwriteProperty(root, "RU", rules ? "Chinese" : "Japanese"); + sgfOverwritePropertyFloat(root, "KM", komi); + sgfOverwritePropertyInt(root, "HA", handicap); + + sgf_write_header_reduced(root, overwrite); +} + + +/* ================================================================ */ +/* Read SGF tree */ +/* ================================================================ */ + + + + + +/***************************** + * fakes + *****************************/ + +#define is_upper(x) ('A' <= x && 'Z' >= x) +#define is_lower(x) ('a' <= x && 'z' >= x) +#define is_alpha(x) (is_upper(x) || is_lower(x)) + +#define is_space(x) (x == ' ' || x == '\t' || x == '\n' || x == '\r') + +#define FILE int + +#define FALSE 0 +#define TRUE -1 + + + +int fake_getc(FILE * stream) +{ + int result; + char buffer; + + if (!stream) + { + return -1; + } + result = rb->read(*stream, &buffer, 1); + + if (result != 1) + { + return -1; + } + + return buffer; +} + +int fake_fputc(int character, FILE * stream) +{ + if (!stream) + { + return -1; + } + + rb->fdprintf(*stream, "%c", (char) character); + + return character; +} + +int fake_fputs(const char * str, FILE * stream) +{ + if (!stream || !str) + { + return -1; + } + + rb->fdprintf(*stream, "%s", str); + + return 0; +} + + +FILE * fake_fopen(const char * filename, const char * flags) +{ + int mode = 0; + bool read = FALSE; + bool write = FALSE; + + int * fd; + + if (flags == 0 || filename == 0) + { + return NULL; + } + + while (*flags) + { + if (*flags == 'r') + { + read = TRUE; + } else if (*flags == 'w') + { + write = TRUE; + } + ++flags; + } + + if (read && write) + { + mode = O_RDWR; + } else if (read) + { + mode = O_RDONLY; + } else if (write) + { + mode = O_WRONLY; + } else + { + return NULL; + } + + if (write) + { + mode |= O_CREAT; + } + + fd = sgf_malloc(sizeof(int)); + + if (!fd) + { + return NULL; + } + + *fd = rb->open(filename, mode); + + if (*fd < 0) + { + sgf_free(fd); + return NULL; + } + + return fd; +} + +int fake_fclose(FILE * file) +{ + int result; + if (!file) + { + return 0; + } + + result = rb->close(*file); + sgf_free(file); + + return result; +} + + + + +/* + * SGF grammar: + * + * Collection = GameTree { GameTree } + * GameTree = "(" Sequence { GameTree } ")" + * Sequence = Node { Node } + * Node = ";" { Property } + * Property = PropIdent PropValue { PropValue } + * PropIdent = UcLetter { UcLetter } + * PropValue = "[" CValueType "]" + * CValueType = (ValueType | Compose) + * ValueType = (None | Number | Real | Double | Color | SimpleText | + * Text | Point | Move | Stone) + * + * The above grammar has a number of simple properties which enables us + * to write a simpler parser: + * 1) There is never a need for backtracking + * 2) The only recursion is on gametree. + * 3) Tokens are only one character + * + * We will use a global state to keep track of the remaining input + * and a global char variable, `lookahead' to hold the next token. + * The function `nexttoken' skips whitespace and fills lookahead with + * the new token. + */ + + +static void parse_error(const char *msg, int arg); +static void nexttoken(void); +static void match(int expected); + + +static FILE *sgffile; + + +#define sgf_getch() (fake_getc(sgffile)) + + +static char *sgferr; +#ifdef TEST_SGFPARSER +static int sgferrarg; +#endif +/*static int sgferrpos;*/ + +static int lookahead; + + +/* ---------------------------------------------------------------- */ +/* Parsing primitives */ +/* ---------------------------------------------------------------- */ + + +static void +parse_error(const char *msg, int arg) +{ + (void)msg; + (void)arg; +} + + +static void +nexttoken() +{ + do + lookahead = sgf_getch(); + while (is_space(lookahead)); +} + + +static void +match(int expected) +{ + if (lookahead != expected) + parse_error("expected: %c", expected); + else + nexttoken(); +} + + +/* ---------------------------------------------------------------- */ +/* The parser proper */ +/* ---------------------------------------------------------------- */ + + +static void +propident(char *buffer, int size) +{ + if (lookahead == EOF || !is_upper(lookahead)) + parse_error("Expected an upper case letter.", 0); + + while (lookahead != EOF && is_alpha(lookahead)) { + if (is_upper(lookahead) && size > 1) { + *buffer++ = lookahead; + size--; + } + nexttoken(); + } + *buffer = '\0'; +} + + +static void +propvalue(char *buffer, int size) +{ + char *p = buffer; + + match('['); + while (lookahead != ']' && lookahead != EOF) { + if (lookahead == '\\') { + lookahead = sgf_getch(); + /* Follow the FF4 definition of backslash */ + if (lookahead == '\r') { + lookahead = sgf_getch(); + if (lookahead == '\n') + lookahead = sgf_getch(); + } + else if (lookahead == '\n') { + lookahead = sgf_getch(); + if (lookahead == '\r') + lookahead = sgf_getch(); + } + } + if (size > 1) { + *p++ = lookahead; + size--; + } + lookahead = sgf_getch(); + } + match(']'); + + /* Remove trailing whitespace */ + --p; + while (p > buffer && is_space((int) *p)) + --p; + *++p = '\0'; +} + + +static SGFProperty * +property(SGFNode *n, SGFProperty *last) +{ + char name[3]; + char * buffer = sgf_malloc(4000); + + if (!buffer) + { + return NULL; + } + propident(name, sizeof(name)); + do { + propvalue(buffer, 4000); + last = sgfMkProperty(name, buffer, n, last); + } while (lookahead == '['); + + sgf_free(buffer); + return last; +} + + +static void +node(SGFNode *n) +{ + SGFProperty *last = NULL; + match(';'); + while (lookahead != EOF && is_upper(lookahead)) + last = property(n, last); +} + + +static SGFNode * +sequence(SGFNode *n) +{ + node(n); + while (lookahead == ';') { + SGFNode *new = sgfNewNode(); + new->parent = n; + n->child = new; + n = new; + node(n); + } + return n; +} + + +static void +gametree(SGFNode **p, SGFNode *parent, int mode) +{ + if (mode == STRICT_SGF) + match('('); + else + for (;;) { + if (lookahead == EOF) { + parse_error("Empty file?", 0); + break; + } + if (lookahead == '(') { + while (lookahead == '(') + nexttoken(); + if (lookahead == ';') + break; + } + nexttoken(); + } + + /* The head is parsed */ + { + SGFNode *head = sgfNewNode(); + SGFNode *last; + + head->parent = parent; + *p = head; + + last = sequence(head); + p = &last->child; + while (lookahead == '(') { + gametree(p, last, STRICT_SGF); + p = &((*p)->next); + } + if (mode == STRICT_SGF) + match(')'); + } +} + + +/* + * Fuseki readers + * Reads an SGF file for extract_fuseki in a compact way + */ + +static void +gametreefuseki(SGFNode **p, SGFNode *parent, int mode, + int moves_per_game, int i) +{ + if (mode == STRICT_SGF) + match('('); + else + for (;;) { + if (lookahead == EOF) { + parse_error("Empty file?", 0); + break; + } + if (lookahead == '(') { + while (lookahead == '(') + nexttoken(); + if (lookahead == ';') + break; + } + nexttoken(); + } + + /* The head is parsed */ + { + + SGFNode *head = sgfNewNode(); + SGFNode *last; + head->parent = parent; + *p = head; + + last = sequence(head); + p = &last->child; + while (lookahead == '(') { + if (last->props + && (last->props->name == SGFB || last->props->name == SGFW)) + i++; + /* break after number_of_moves moves in SGF file */ + if (i >= moves_per_game) { + last->child = NULL; + last->next = NULL; + break; + } + else { + gametreefuseki(p, last, mode, moves_per_game, i); + p = &((*p)->next); + } + } + if (mode == STRICT_SGF) + match(')'); + } +} + +SGFNode * +readsgffilefuseki(const char *filename, int moves_per_game) +{ + SGFNode *root; + int tmpi = 0; + + sgffile = fake_fopen(filename, "r"); + + if (!sgffile) + return NULL; + + + nexttoken(); + gametreefuseki(&root, NULL, LAX_SGF, moves_per_game, 0); + + fake_fclose(sgffile); + + if (sgferr) { + sgfFreeNode(root); + return NULL; + } + + /* perform some simple checks on the file */ + if (!sgfGetIntProperty(root, "GM", &tmpi)) { + } + else if (tmpi != 1) { + } + + if (!sgfGetIntProperty(root, "FF", &tmpi)) { + } + else if ((tmpi < 3 || tmpi > 4) && VERBOSE_WARNINGS) + { + } + + return root; +} + + + + + +/* + * Wrapper around readsgf which reads from a file rather than a string. + * Returns NULL if file will not open, or some other parsing error. + * Filename "-" means read from stdin, and leave it open when done. + */ + +SGFNode * +readsgffile(const char *filename) +{ + SGFNode *root; + int tmpi = 0; + + sgffile = fake_fopen(filename, "r"); + + if (!sgffile) + return NULL; + + + nexttoken(); + + gametree(&root, NULL, LAX_SGF); + + fake_fclose(sgffile); + + if (sgferr) { + sgfFreeNode(root); + return NULL; + } + + /* perform some simple checks on the file */ + if (!sgfGetIntProperty(root, "GM", &tmpi)) { + } + else if (tmpi != 1) { + } + + if (!sgfGetIntProperty(root, "FF", &tmpi)) { + } + else if ((tmpi < 3 || tmpi > 4) && VERBOSE_WARNINGS) + { + + } + + return root; +} + + + +/* ================================================================ */ +/* Write SGF tree */ +/* ================================================================ */ + + +#define OPTION_STRICT_FF4 0 + +static int sgf_column = 0; + +static void +sgf_putc(int c, FILE *file) +{ + if (c == '\n' && sgf_column == 0) + return; + + fake_fputc(c, file); + + if (c == '\n') + sgf_column = 0; + else + sgf_column++; + + if (c == ']' && sgf_column > 60) { + fake_fputc('\n', file); + sgf_column = 0; + } +} + +static void +sgf_puts(const char *s, FILE *file) +{ + for (; *s; s++) { + if (*s == '[' || *s == ']' || *s == '\\') { + fake_fputc('\\', file); + sgf_column++; + } + fake_fputc((int) *s, file); + sgf_column++; + } +} + +/* Print all properties with the given name in a node to file and mark + * them as printed. + * + * If is_comment is 1, multiple properties are concatenated with a + * newline. I.e. we write + * + * C[comment1 + * comment2] + * + * instead of + * + * C[comment1][comment2] + * + * Most other property types should be written in the latter style. + */ + +static void +sgf_print_name(FILE *file, short name) +{ + sgf_putc(name & 0xff, file); + if (name >> 8 != ' ') + sgf_putc(name >> 8, file); +} + +static void +sgf_print_property(FILE *file, SGFNode *node, short name, int is_comment) +{ + int n = 0; + SGFProperty *prop; + + for (prop = node->props; prop; prop = prop->next) { + if (prop->name == name) { + prop->name |= 0x20; /* Indicate already printed. */ + if (n == 0) { + sgf_print_name(file, name); + sgf_putc('[', file); + } + else if (is_comment) + sgf_putc('\n', file); + else { + sgf_putc(']', file); + sgf_putc('[', file); + } + + sgf_puts(prop->value, file); + n++; + } + } + + if (n > 0) + sgf_putc(']', file); + + /* Add a newline after certain properties. */ + if (name == SGFAB || name == SGFAW || name == SGFAE || (is_comment && n > 1)) + sgf_putc('\n', file); +} + +/* + * Print all remaining unprinted property values at node N to file. + */ + +static void +sgfPrintRemainingProperties(FILE *file, SGFNode *node) +{ + SGFProperty *prop; + + for (prop = node->props; prop; prop = prop->next) + if (!(prop->name & 0x20)) + sgf_print_property(file, node, prop->name, 0); +} + + +/* + * Print the property values of NAME at node N and mark it as printed. + */ + +static void +sgfPrintCharProperty(FILE *file, SGFNode *node, const char *name) +{ + short nam = name[0] | name[1] << 8; + + sgf_print_property(file, node, nam, 0); +} + + +/* + * Print comments from Node node. + * + * NOTE: cgoban does not print "C[comment1][comment2]" and I don't know + * what the sgfspec says. + */ + +static void +sgfPrintCommentProperty(FILE *file, SGFNode *node, const char *name) +{ + short nam = name[0] | name[1] << 8; + + sgf_print_property(file, node, nam, 1); +} + + +static void +unparse_node(FILE *file, SGFNode *node) +{ + sgf_putc(';', file); + sgfPrintCharProperty(file, node, "B "); + sgfPrintCharProperty(file, node, "W "); + sgfPrintCommentProperty(file, node, "N "); + sgfPrintCommentProperty(file, node, "C "); + sgfPrintRemainingProperties(file, node); +} + + +static void +unparse_root(FILE *file, SGFNode *node) +{ + sgf_putc(';', file); + + if (sgfHasProperty(node, "GM")) + sgfPrintCharProperty(file, node, "GM"); + else { + fake_fputs("GM[1]", file); + sgf_column += 5; + } + + sgfPrintCharProperty(file, node, "FF"); + sgf_putc('\n', file); + + sgfPrintCharProperty(file, node, "SZ"); + sgf_putc('\n', file); + + sgfPrintCharProperty(file, node, "GN"); + sgf_putc('\n', file); + + sgfPrintCharProperty(file, node, "DT"); + sgf_putc('\n', file); + + sgfPrintCommentProperty(file, node, "PB"); + sgfPrintCommentProperty(file, node, "BR"); + sgf_putc('\n', file); + + sgfPrintCommentProperty(file, node, "PW"); + sgfPrintCommentProperty(file, node, "WR"); + sgf_putc('\n', file); + + sgfPrintCommentProperty(file, node, "N "); + sgfPrintCommentProperty(file, node, "C "); + sgfPrintRemainingProperties(file, node); + + sgf_putc('\n', file); +} + + +/* + * p->child is the next move. + * p->next is the next variation + */ + +static void +unparse_game(FILE *file, SGFNode *node, int root) +{ + if (!root) + sgf_putc('\n', file); + sgf_putc('(', file); + if (root) + unparse_root(file, node); + else + unparse_node(file, node); + + node = node->child; + while (node != NULL && node->next == NULL) { + unparse_node(file, node); + node = node->child; + } + + while (node != NULL) { + unparse_game(file, node, 0); + node = node->next; + } + sgf_putc(')', file); + if (root) + sgf_putc('\n', file); +} + +/* Printed properties are marked by adding the 0x20 bit to the + * property name (changing an upper case letter to lower case). This + * function removes this mark so that we can print the property next + * time too. It recurses to all properties in the linked list. + */ +static void +restore_property(SGFProperty *prop) +{ + if (prop) { + restore_property(prop->next); + prop->name &= ~0x20; + } +} + +/* When called with the tree root, recurses to all properties in the + * tree and removes all print marks. + */ +static void +restore_node(SGFNode *node) +{ + if (node) { + restore_property(node->props); + restore_node(node->child); + restore_node(node->next); + } +} + + +/* + * Opens filename and writes the game stored in the sgf structure. + */ + +int +writesgf(SGFNode *root, const char *filename) +{ + FILE *outfile; + outfile = fake_fopen(filename, "w"); + + if (!outfile) { + return 0; + } + + sgf_write_header_reduced(root, 0); + + sgf_column = 0; + unparse_game(outfile, root, 1); + fake_fclose(outfile); + + /* Remove "printed" marks so that the tree can be written multiple + * times. + */ + restore_node(root); + + return 1; +} + + +#ifdef TEST_SGFPARSER +int +main() +{ + static char buffer[25000]; + static char output[25000]; + SGFNode *game; + + sgffile = stdin; + + nexttoken(); + gametree(&game, LAX_SGF); + if (sgferr) { + /* fprintf(stderr, "Parse error:"); + fprintf(stderr, sgferr, sgferrarg); + fprintf(stderr, " at position %d\n", sgferrpos);*/ + } + else { + /* + unparse_game(stdin, game, 1); + write(1, output, outputp - output); + */ + } +} +#endif + + + +/* + * Local Variables: + * tab-width: 8 + * c-basic-offset: 2 + * End: + */ Index: apps/plugins/goban/goban.c =================================================================== --- apps/plugins/goban/goban.c (revision 0) +++ apps/plugins/goban/goban.c (revision 0) @@ -0,0 +1,1172 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007 Joshua Simmons * + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "plugin.h" + +#include "board.h" +#include "liberty.h" +#include "gnugo.h" +#include "sgf_malloc.h" +#include "sgftree.h" + +#include "lib/fixedpoint.h" + +PLUGIN_HEADER +#include "goban.h" +struct plugin_api *rb; + +static int intersection_size = 0; + +#define LINE_OFFSET (intersection_size / 2) + +/* pixel offsets for the board on the LCD */ +static int board_x = 0; +static int board_y = 0; + + +/* current cursor position in game coordinates */ +static int cursor_x = 0; +static int cursor_y = 0; + + +static bool draw_variations = true; + + + +/* function prototypes */ + +static int sgf_pixel_x (int x, int y); +static int sgf_pixel_y (int x, int y); + +static int sgf_pixel_x_no_rot (int x, int y); +static int sgf_pixel_y_no_rot (int x, int y); + +static void sgf_circle_precalculate (void); +static void sgf_setup_graphics (void); + +static void sgf_draw_cursor (int x, int y); +static void sgf_draw_stone_raw (int pixel_x, int pixel_y, bool black); +static void sgf_draw_stone (int x, int y, bool black); +static void sgf_draw_hoshi (int x, int y); +static void sgf_draw_mark (int x, int y, int type); +static void sgf_draw_footer (void); +static void sgf_draw_all_variations (void); +static void sgf_draw_all_hoshi (void); +static void sgf_draw_board (void); + +static int sgf_setup_game (char *filename); +static int sgf_save_game (bool save_as, bool panic); + +static void *sgf_get_next_variation (void *bookmark, int *move); +static int sgf_follow_or_play (int move); + + + + + + + +static int +sgf_pixel_x (int x, int y) +{ + int x1, y1; + sgf_rotate (x, y, &x1, &y1, board_size, BOARD_ORIENTATION); + return board_x + x1 * intersection_size; +} + +static int +sgf_pixel_y (int x, int y) +{ + int x1, y1; + sgf_rotate (x, y, &x1, &y1, board_size, BOARD_ORIENTATION); + return board_y + y1 * intersection_size; +} + +static int +sgf_pixel_x_no_rot (int x, int y) +{ + (void) y; + return board_x + x * intersection_size; +} + +static int +sgf_pixel_y_no_rot (int x, int y) +{ + (void) x; + return board_y + y * intersection_size; +} + + +static unsigned char circle_x_precalc[MAX_CIRCLE_SIZE]; +static int circle_size = 0; + +static Gameinfo curr_game; + +/* TODO: this should not be dynamically allocated, really dumb */ +static char *save_file = NULL; +static bool game_dirty = false; + + +/* x**2 + y**2 = r**2 + * solve for x, and x = sqrt( r**2 - y**2 ) + * + * we precalculate these so stone drawing doesn't involve lots of + * square roots it only needs to be done once initially and then on + * board resize + * + * this is probably unnecessary, but doing thousands + * of square roots on each board redraw seems stupid + * + * should probably just change this to a table of square roots + */ +static void +sgf_circle_precalculate (void) +{ + int i; + int current_y_coord = 0; + + // Radius = diameter^2 / 4 + long radius_squared = (intersection_size * intersection_size) / 4; + + circle_size = intersection_size; + + i = circle_size / 2; + + while (i >= 0) + { + circle_x_precalc[i] = fsqrt (radius_squared - + (current_y_coord * current_y_coord), + 0); + ++current_y_coord; + --i; + }; + + for (i = 0; i <= circle_size / 2; ++i) + { + circle_x_precalc[circle_size - i - 1] = circle_x_precalc[i]; + } + +} + + +/* Call every time the board size might have changed! */ +static void +sgf_setup_graphics (void) +{ + + intersection_size = LCD_MIN_DIMENSION / board_size; + + /* intersections are easier to draw if they are an odd size + * should probably not require this as on some players the + * screen is already too small without wasting up to 19 pixels */ + if (!(intersection_size & 1)) + { + --intersection_size; + } + /* IMPORTANT: YOU CAN'T JUST COMMENT THAT OUT! + * things will probably go wonky if drawing happens outside the screen + * or something like that, because all of my other code assumes + * that intersections are odd sized + */ + + board_x = (LCD_WIDTH - (intersection_size * board_size)) / 2; + board_y = 0; + + sgf_circle_precalculate (); + + /* cursor starts on tengen (middle of the board) */ + cursor_x = cursor_y = board_size / 2; +} + +#define CURSOR_WIDTH (intersection_size / 2) +static void +sgf_draw_cursor (int x, int y) +{ +/* int saved_draw_mode = rb->lcd_get_drawmode();*/ + + if (x >= board_size || y >= board_size) + { + return; + } + + rb->lcd_set_foreground (CURSOR_COLOR); + + rb->lcd_drawrect (sgf_pixel_x_no_rot (x, y), + sgf_pixel_y_no_rot (x, y), + intersection_size, intersection_size); + //rb->lcd_set_drawmode(DRMODE_COMPLEMENT); + /* + rb->lcd_drawrect(sgf_pixel_x(x, y) + + (intersection_size - CURSOR_WIDTH) / 2, + sgf_pixel_y(x, y) + + (intersection_size - CURSOR_WIDTH) / 2, + CURSOR_WIDTH, + CURSOR_WIDTH); + */ + /* + rb->lcd_hline(sgf_pixel_x(x, y), + sgf_pixel_x(x, y) + intersection_size - 1, + sgf_pixel_y(x, y) + LINE_OFFSET); + + rb->lcd_vline(sgf_pixel_x(x, y) + LINE_OFFSET, + sgf_pixel_y(x, y), + sgf_pixel_y(x, y) + intersection_size - 1); + + rb->lcd_set_drawmode(saved_draw_mode); + */ +} + +static void +sgf_draw_stone_raw (int pixel_x, int pixel_y, bool black) +{ + int i; + + rb->lcd_set_foreground (black ? BLACK_COLOR : WHITE_COLOR); + + for (i = 0; i < circle_size; ++i) + { + rb->lcd_hline (LINE_OFFSET + pixel_x - circle_x_precalc[i], + LINE_OFFSET + pixel_x + circle_x_precalc[i], + pixel_y + i); + + /*********************************************************** + * This is the code to add an outline around white circles * + * * + * READ ONLY CODE FOLLOWS, CONTINUE AT YOUR OWN PERIL * + ***********************************************************/ +#if OUTLINE_STONES + if (!black) + { + rb->lcd_set_foreground (BLACK_COLOR); + if (i == 0 || i == circle_size - 1) + { + rb->lcd_hline (LINE_OFFSET + pixel_x - + circle_x_precalc[i], + LINE_OFFSET + pixel_x + + circle_x_precalc[i], pixel_y + i); + } + else + { + int min_off = min (circle_x_precalc[i - 1], + circle_x_precalc[i + 1]); + + + rb->lcd_drawpixel (LINE_OFFSET + pixel_x - + circle_x_precalc[i], pixel_y + i); + rb->lcd_drawpixel (LINE_OFFSET + pixel_x + + circle_x_precalc[i], pixel_y + i); + + if (min_off != circle_x_precalc[i]) + { + ++min_off; + rb->lcd_hline (LINE_OFFSET + pixel_x - + circle_x_precalc[i], + LINE_OFFSET + pixel_x - + min_off, pixel_y + i); + rb->lcd_hline (LINE_OFFSET + pixel_x + + circle_x_precalc[i], + LINE_OFFSET + pixel_x + + min_off, pixel_y + i); + } + } + rb->lcd_set_foreground (WHITE_COLOR); + } +#endif /* OUTLINE_STONES */ + /**************************************************** + * end of outline drawing code * + * * + * END OF READ ONLY CODE * + ****************************************************/ + } + +} + + +static void +sgf_draw_stone (int x, int y, bool black) +{ + if (x >= board_size || y >= board_size) + { + return; + } + + sgf_draw_stone_raw (sgf_pixel_x (x, y), sgf_pixel_y (x, y), black); +} + + +static void +sgf_draw_all_stones (void) +{ + int x, y; + + for (x = 0; x < board_size; ++x) + { + for (y = 0; y < board_size; ++y) + { + if (BOARD (x, y) == EMPTY) + { + continue; + } + + sgf_draw_stone (x, y, BOARD (x, y) == BLACK); + } + } +} + +static void +sgf_draw_hoshi (int x, int y) +{ + if (x >= board_size || y >= board_size) + { + return; + } + if (intersection_size > 8) + { + rb->lcd_fillrect (sgf_pixel_x (x, y) + LINE_OFFSET - 1, + sgf_pixel_y (x, y) + LINE_OFFSET - 1, 3, 3); + } + else + { + rb->lcd_drawpixel (sgf_pixel_x (x, y) + LINE_OFFSET - 1, + sgf_pixel_y (x, y) + LINE_OFFSET - 1); + rb->lcd_drawpixel (sgf_pixel_x (x, y) + LINE_OFFSET + 1, + sgf_pixel_y (x, y) + LINE_OFFSET + 1); + } +} + + +static void * +sgf_get_next_variation (void *bookmark, int *move) +{ + SGFNode *curr_child = (SGFNode *) bookmark; + + if (curr_child == NULL) + { + if (!curr_game.game_record.lastnode || + !curr_game.game_record.lastnode->child) + { + *move = PASS_MOVE; + return NULL; + } + curr_child = curr_game.game_record.lastnode->child; + } + + do + { + SGFProperty *temp_prop = get_move_prop (curr_child); + if (temp_prop == NULL) + { + continue; + } + else + { + if (is_pass (get_sgfmove (temp_prop))) + { + continue; + } + + *move = get_sgfmove (temp_prop); + return curr_child->next; + } + } + while ((curr_child = curr_child->next)); + + *move = PASS_MOVE; + return NULL; +} + + +/* TODO: add other mark types */ +static void +sgf_draw_mark (int x, int y, int type) +{ + switch (type) + { + default: + rb->lcd_set_foreground (CURSOR_COLOR); + if (intersection_size > 8) + { + rb->lcd_fillrect (sgf_pixel_x (x, y) + LINE_OFFSET - 1, + sgf_pixel_y (x, y) + LINE_OFFSET - 1, 3, 3); + } + else + { + rb->lcd_drawpixel (sgf_pixel_x (x, y) + LINE_OFFSET - 1, + sgf_pixel_y (x, y) + LINE_OFFSET - 1); + rb->lcd_drawpixel (sgf_pixel_x (x, y) + LINE_OFFSET + 1, + sgf_pixel_y (x, y) + LINE_OFFSET + 1); + } + }; +} + +static void +sgf_draw_all_variations (void) +{ + void *bookmark = NULL; + int move; + + do + { + bookmark = sgf_get_next_variation (bookmark, &move); + + if (!is_pass (move)) + { + sgf_draw_mark (I (move), J (move), MARK_VARIATION); + } + } + while (bookmark); +} + +static void +sgf_draw_all_hoshi (void) +{ + if (board_size == 19) + { + sgf_draw_hoshi (3, 3); + sgf_draw_hoshi (3, 9); + sgf_draw_hoshi (3, 15); + + sgf_draw_hoshi (9, 3); + sgf_draw_hoshi (9, 9); + sgf_draw_hoshi (9, 15); + + sgf_draw_hoshi (15, 3); + sgf_draw_hoshi (15, 9); + sgf_draw_hoshi (15, 15); + } + else if (board_size == 9) + { + sgf_draw_hoshi (2, 2); + sgf_draw_hoshi (2, 6); + + sgf_draw_hoshi (4, 4); + + sgf_draw_hoshi (6, 2); + sgf_draw_hoshi (6, 6); + } + else if (board_size == 13) + { + sgf_draw_hoshi (3, 3); + sgf_draw_hoshi (3, 9); + + sgf_draw_hoshi (6, 6); + + sgf_draw_hoshi (9, 3); + sgf_draw_hoshi (9, 9); + + } +} + + +static int +sgf_save_game (bool save_as, bool panic) +{ + char *save_as_buffer; + if (save_as) + { + save_as_buffer = sgf_malloc (22); + + if (!save_as_buffer) + { + rb->splash (2 * HZ, "malloc failed for save as!"); + return 0; + } + if (rb->kbd_input (save_as_buffer + 1, 17)) + /* save room for '.sgf' */ + { + save_as_buffer[0] = '/'; + rb->strcat (save_as_buffer, ".sgf"); + sgf_free (save_file); + save_file = save_as_buffer; + } + else + { + return 0; + } + } + + /* now we want to save to save_file */ + + if (curr_game.game_record.root == NULL) + { + /* technically this is kind of success, everything is saved, + * there's just nothing to save + * + * actually i don't think this can ever happen... */ + DEBUGF ("How did you find me?\n"); + rb->splash (5 * HZ, "How did you find me?"); + return 1; + } + + if (writesgf + (curr_game.game_record.root, panic ? DEFAULT_SAVE : save_file)) + { + game_dirty = false; + return 1; + } + else + { + return 0; + } +} + +static void +sgf_draw_footer (void) +{ + char captures_buffer[6]; + char end_of_game_mark = ' '; + int size_x, size_y; + + rb->lcd_set_background (BACKGROUND_COLOR); + + rb->lcd_set_foreground (BLACK_COLOR); + + rb->snprintf (captures_buffer, sizeof (captures_buffer), + "%d", black_captured); + + + sgf_draw_stone_raw (1, LCD_HEIGHT - circle_size - 1, true); + + rb->lcd_getstringsize (captures_buffer, &size_x, &size_y); + + rb->lcd_putsxy (circle_size + 1, LCD_HEIGHT - size_y - 1, + captures_buffer); + + + rb->lcd_set_foreground (WHITE_COLOR); + + rb->snprintf (captures_buffer, sizeof (captures_buffer), + "%d", white_captured); + + rb->lcd_getstringsize (captures_buffer, &size_x, &size_y); + + + sgf_draw_stone_raw (LCD_WIDTH - circle_size - 1, + LCD_HEIGHT - circle_size - 1, false); + + rb->lcd_putsxy (LCD_WIDTH - circle_size - 2 - size_x, + LCD_HEIGHT - size_y - 1, captures_buffer); + + + + rb->lcd_set_foreground (BLACK_COLOR); + + if (!curr_game.game_record.lastnode || + !curr_game.game_record.lastnode->child) + { + end_of_game_mark = ' '; + } + else + { + end_of_game_mark = '+'; + } + + rb->snprintf (captures_buffer, sizeof (captures_buffer), + "%d%c", movenum, end_of_game_mark); + + rb->lcd_getstringsize (captures_buffer, &size_x, &size_y); + + rb->lcd_putsxy ((LCD_WIDTH - size_x) / 2, + LCD_HEIGHT - size_y - 1, captures_buffer); + +} + +static void +sgf_draw_board (void) +{ + int i; + + rb->lcd_set_background (BACKGROUND_COLOR); + rb->lcd_set_foreground (BOARD_COLOR); + + rb->lcd_set_drawmode (DRMODE_FG); + + + rb->lcd_clear_display (); + + /* HACK! + * + * why doesn't clear display fill the LCD with the background??? + * (even with draw_mode = DRMODE_SOLID) */ + + rb->lcd_set_foreground (BACKGROUND_COLOR); + + rb->lcd_fillrect (0, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1); + + rb->lcd_set_foreground (BOARD_COLOR); + + + rb->lcd_fillrect (board_x, board_y, + intersection_size * board_size, + intersection_size * board_size); + + rb->lcd_set_foreground (LINE_COLOR); + + for (i = 0; i < board_size; ++i) + { + rb->lcd_hline (board_x + LINE_OFFSET, + board_x + LINE_OFFSET + + intersection_size * (board_size - 1), + board_y + LINE_OFFSET + i * intersection_size); + } + + for (i = 0; i < board_size; ++i) + { + rb->lcd_vline (board_x + LINE_OFFSET + i * intersection_size, + board_y + LINE_OFFSET, + board_y + LINE_OFFSET + + intersection_size * (board_size - 1)); + } + + sgf_draw_all_hoshi (); + sgf_draw_all_stones (); + sgf_draw_cursor (cursor_x, cursor_y); + if (draw_variations) + { + sgf_draw_all_variations (); + } + sgf_draw_footer (); + rb->lcd_update (); +} + + +static int +sgf_setup_game (char *filename) +{ + SGFTree temp_tree; + sgftree_clear (&temp_tree); + + if (filename != NULL) + { + /* if we can't read the file, nothing is setup and any current + * game isn't cleared. this means that if the initial setup + * fails, the program should exit or try again! */ + + if (!sgftree_readfile (&(temp_tree), filename)) + { + return 0; + } + + if (curr_game.game_record.root) + { + sgfFreeNode (curr_game.game_record.root); + } + + gameinfo_clear (&curr_game); + clear_board (); + + curr_game.game_record.root = temp_tree.root; + curr_game.game_record.lastnode = temp_tree.lastnode; + + game_dirty = false; + + if ((curr_game.to_move = + gameinfo_play_sgftree (&curr_game, + &curr_game.game_record, "1")) == EMPTY) + { + if (curr_game.game_record.root) + { + sgfFreeNode (curr_game.game_record.root); + } + gameinfo_clear (&curr_game); + return 0; + } + + gameinfo_print (&curr_game); + + } + else + { + if (save_file) + { + sgf_free (save_file); + } + + save_file = sgf_malloc (rb->strlen (DEFAULT_SAVE) + 1); + + if (save_file) + { + rb->strcpy (save_file, DEFAULT_SAVE); + } + else + { + rb->splash (4 * HZ, "Malloc failed for save file!"); + } + + if (curr_game.game_record.root) + { + sgfFreeNode (curr_game.game_record.root); + } + + gameinfo_clear (&curr_game); + clear_board (); + game_dirty = false; + + int new_handi = 0, new_bs = 19, new_komi = 6; + + rb->set_int ("board size", " lines", UNIT_INT, + &new_bs, NULL, 1, 3, 19, NULL); + + rb->set_int ("handicap", " stones", UNIT_INT, + &new_handi, NULL, 1, 0, 9, NULL); + + if (new_handi > 0) + { + new_komi = 0; + } + else + { + new_komi = 6; + } + + /* TODO: Add formatter so komi is selected as x.5 */ + rb->set_int ("komi", "", UNIT_INT, &new_komi, NULL, 1, -100, 100, + NULL); + + gnugo_clear_board (new_bs); + + curr_game.game_record.root = sgfCreateHeaderNode (board_size, + new_komi + 0.5f, + new_handi); + + if (curr_game.game_record.root) + { + gnugo_sethand (new_handi, curr_game.game_record.root); + } + else + { + rb->splash (2 * HZ, "Failed to create new SGF tree!"); + return 0; + } + + /*sgftreeForward(&(curr_game.game_record)); */ + if ((curr_game.to_move = + gameinfo_play_sgftree (&curr_game, + &curr_game.game_record, "1")) == EMPTY) + { + if (curr_game.game_record.root) + { + sgfFreeNode (curr_game.game_record.root); + } + gameinfo_clear (&curr_game); + return 0; + } + gameinfo_print (&curr_game); + } + + + + sgf_setup_graphics (); + + sgf_draw_board (); + return 1; +} + + +static int +sgf_follow_or_play (int move) +{ + while (curr_game.game_record.lastnode && + curr_game.game_record.lastnode->child && + !get_move_prop (curr_game.game_record.lastnode->child)) + { + curr_game.game_record.lastnode = + curr_game.game_record.lastnode->child; + } + /* Now, either curr_game.game_record.lastnode == NULL (not likely, + * i don't think even possible) + * + * or curr_game.game_record.lastnode->child == NULL (most likely) + * or the next node is a move node + * + * So, if lastnode == NULL, just get the fuck out. + * if there is no child, we just create one + * + * if there is a child and it's a move, we need to check if we should + * start a new variation, or if one of the variations that exists is + * the move we're trying to add + */ + + /* TODO: Debug statement, verify this never runs */ + if (!curr_game.game_record.lastnode) + { + rb->splash (5 * HZ, "How the fuck did you get here?"); + return 0; + } + + if (!curr_game.game_record.lastnode->child) + { + if (sgfAddPlay (curr_game.game_record.lastnode, + curr_game.to_move, I (move), J (move))) + { + play_move (move, curr_game.to_move); + curr_game.game_record.lastnode = + curr_game.game_record.lastnode->child; + curr_game.to_move = OTHER_COLOR (curr_game.to_move); + game_dirty = true; + return 1; + } + else + { + rb->splash (5 * HZ, "serious error: Failed addPlay"); + return 0; + } + } + + /* otherwise, there's a child or more, see if it matches */ + SGFNode *curr_child = curr_game.game_record.lastnode->child; + + do + { + SGFProperty *temp_prop = get_move_prop (curr_child); + if (temp_prop == NULL) + { + /* no move node in this branch */ + continue; + } + else + { + /* if the move is the same, and it's for the player + * to move, then we have ourselves a mizity match */ + if (get_sgfmove (temp_prop) == move && + find_move (curr_child) == curr_game.to_move) + { + play_move (move, curr_game.to_move); + curr_game.game_record.lastnode = curr_child; + curr_game.to_move = OTHER_COLOR (curr_game.to_move); + return 1; + } + } + } + while ((curr_child = curr_child->next)); + + /* there were moves, they weren't our match, so add a new variation */ + + if (sgfAddPlay (curr_game.game_record.lastnode, + curr_game.to_move, I (move), J (move))) + { + /* yay, repeating code is FUNNNNNNNN */ + play_move (move, curr_game.to_move); + curr_game.game_record.lastnode = + curr_game.game_record.lastnode->child; + curr_game.to_move = OTHER_COLOR (curr_game.to_move); + game_dirty = true; + return 1; + } + else + { + rb->splash (2 * HZ, "Failed addPlay"); + return 0; + } + return 0; +} + +enum plugin_status +plugin_start (struct plugin_api *api, void *parameter) +{ + int btn; + size_t size; + char *buffer; + + rb = api; + + buffer = rb->plugin_get_buffer (&size); + sgf_malloc_init_heap (buffer, size); + + if (parameter) + { + save_file = sgf_malloc (rb->strlen (parameter) + 1); + rb->strcpy (save_file, parameter); + + } + else + { + save_file = sgf_malloc (rb->strlen (DEFAULT_SAVE)); + rb->strcpy (save_file, DEFAULT_SAVE); + } + + gameinfo_clear (&curr_game); + + + /* if we got a filename, try to open it and exit if it fails + * + * otherwise, see if the default exists and try it. if it + * doesn't exist or fails, then we try to start a new game + * if THAT fails, we just return failure */ + if (!parameter) + { + int result = rb->open (DEFAULT_SAVE, O_RDONLY); + + if (result > 0) + { + rb->close (result); + if (!sgf_setup_game (DEFAULT_SAVE)) + { + rb->splash (2 * HZ, "Setup failed for default save!"); + if (!sgf_setup_game (NULL)) + { + /* now something is really fucked */ + + rb->splash (4 * HZ, "Unable to start new game!"); + return PLUGIN_ERROR; + } + } + + } + else + { + if (!sgf_setup_game (NULL)) + { + /* now something is really fucked */ + + rb->splash (4 * HZ, "Unable to start new game!"); + return PLUGIN_ERROR; + } + } + + } + else + { + + if (!sgf_setup_game (parameter)) + { + rb->splash (4 * HZ, "Setup failed!"); + return PLUGIN_ERROR; + } + } + + + + int temp; + int x, y; + int selection = 0; + MENUITEM_STRINGLIST (menu, "SGFbox", NULL, + "New", "Save", "Save As", "Quit"); + + + (void) x; + (void) y; + SGFProperty *temp_prop; + for (;;) + { + btn = rb->button_get_w_tmo (HZ * 30); + /* break every 30 seconds to tell rockbox we're not idle */ + rb->reset_poweroff_timer (); + + + switch (btn) + { + case SGFBUTT_ADVANCE: + case SGFBUTT_ADVANCE | BUTTON_REPEAT: + if (!sgftreeForward (&curr_game.game_record)) + { + break; + } + + while (!(temp_prop = + get_move_prop (curr_game.game_record.lastnode))) + { + if (!sgftreeForward (&curr_game.game_record)) + { + break; + } + } + + /* now we're in a move node and temp_prop is the property */ + + temp = find_move (curr_game.game_record.lastnode); + + play_move (get_sgfmove (temp_prop), temp); + sgf_draw_board (); + curr_game.to_move = OTHER_COLOR (curr_game.to_move); + + + break; + + case SGFBUTT_RETREAT: + case SGFBUTT_RETREAT | BUTTON_REPEAT: + if (!sgftreeBack (&curr_game.game_record)) + { + break; + } + + while (!(temp_prop = + get_move_prop (curr_game.game_record.lastnode))) + { + if (!sgftreeBack (&curr_game.game_record)) + { + break; + } + } + if (undo_move (1)) + { + curr_game.to_move = OTHER_COLOR (curr_game.to_move); + } + else + { + rb->splash (2 * HZ, "Undo failed!\n"); + /* TODO: add code here for redoing the whole + * game from the SGFTree + * + * Note: I can't get undo to fail no matter what + * I try (i'm not sure it can fail), so this is + * pretty low priority + * */ + } + + sgf_draw_board (); + + break; + + case SGFBUTT_PLAY: + temp = + sgf_rotate1 (POS (cursor_x, cursor_y), REV_ORIENTATION); + if (!is_legal (temp, curr_game.to_move)) + { + rb->splash (HZ / 4, "illegal"); + break; + } + + sgf_follow_or_play (temp); + + sgf_draw_board (); + + break; + + case SGFBUTT_RIGHT: + case SGFBUTT_RIGHT | BUTTON_REPEAT: + cursor_x = cursor_x + 1; + cursor_x %= board_size; + sgf_draw_board (); + break; + + case SGFBUTT_LEFT: + case SGFBUTT_LEFT | BUTTON_REPEAT: + cursor_x = cursor_x - 1; + if (cursor_x < 0) + { + cursor_x = board_size - 1; + } + sgf_draw_board (); + break; + + case SGFBUTT_DOWN: + case SGFBUTT_DOWN | BUTTON_REPEAT: + cursor_y = cursor_y + 1; + cursor_y %= board_size; + sgf_draw_board (); + break; + + case SGFBUTT_UP: + case SGFBUTT_UP | BUTTON_REPEAT: + cursor_y = cursor_y - 1; + if (cursor_y < 0) + { + cursor_y = board_size - 1; + } + sgf_draw_board (); + break; + case SGFBUTT_VAR_TOG: + + rb->splash (HZ, "Toggled next move drawing"); + + draw_variations = !draw_variations; + sgf_draw_board (); + + + break; + case SGFBUTT_MENU: + + rb->lcd_set_foreground (LCD_WHITE); + rb->lcd_set_background (LCD_BLACK); + + bool done = false; + + while (!done) + { + selection = rb->do_menu (&menu, &selection); + + switch (selection) + { + case 0: /*"New" */ + if (!sgf_setup_game (NULL)) + { + rb->splash (2 * HZ, "New game failed!"); + } + else + { + done = true; + sgf_draw_board (); + } + break; + case 1: /*"Save" */ + case 2: /*"Save As" */ + if (!sgf_save_game (save_file == NULL || + selection == 2, false)) + /* do save_as if there is + * no save_file, or if the + * user selected it + * + * this is not a panic save */ + { + rb->splash (2 * HZ, + "Save failed or canceled!"); + DEBUGF ("Save failed!\n"); + } + else + { + done = true; + } + DEBUGF ("After save done: %d\n", done); + + break; + case MENU_ATTACHED_USB: + case 3: /*"Quit" */ + if (game_dirty) + { + sgf_save_game (false, true); + /* panic save as default */ + } + return PLUGIN_OK; + + case GO_TO_ROOT: + case GO_TO_PREVIOUS: + default: + + done = true; + break; + }; + } + + + + + + sgf_draw_board (); + break; + default: + if (rb->default_event_handler (btn) == SYS_USB_CONNECTED) + { + return PLUGIN_USB_CONNECTED; + } + break; + }; + + } + return PLUGIN_OK; +} Index: apps/plugins/goban/board.c =================================================================== --- apps/plugins/goban/board.c (revision 0) +++ apps/plugins/goban/board.c (revision 0) @@ -0,0 +1,4245 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ + * 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 and 2006 * + * 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., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02111, USA. * +\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + +/* The functions in this file implements a go board with incremental + * update of strings and liberties. + * + * See the Texinfo documentation (Utility Functions: Incremental Board) + * for an introduction. + */ + +#include "plugin.h" + +#include "board.h" +#include "hash.h" +#include "sgftree.h" + +extern struct plugin_api * rb; + +/* This can be used for internal checks w/in board.c that should + * typically not be necessary (for speed). + */ +#if 1 +#define PARANOID1(x, pos) ASSERT1(x, pos) +#else +#define PARANOID1(x, pos) +#endif + + +/* ================================================================ */ +/* data structures */ +/* ================================================================ */ + + +/* Incremental string data. */ +struct string_data { + int color; /* Color of string, BLACK or WHITE */ + int size; /* Number of stones in string. */ + int origin; /* Coordinates of "origin", i.e. */ + /* "upper left" stone. */ + int liberties; /* Number of liberties. */ + int neighbors; /* Number of neighbor strings */ + int mark; /* General purpose mark. */ +}; + +struct string_liberties_data { + int list[MAX_LIBERTIES]; /* Coordinates of liberties. */ +}; + +struct string_neighbors_data { + int list[MAXCHAIN]; /* List of neighbor string numbers. */ +}; + +/* we keep the address and the old value */ +struct change_stack_entry { + int *address; + int value; +}; + +/* we keep the address and the old value */ +struct vertex_stack_entry { + Intersection *address; + int value; +}; + + +/* Experimental results show that the average number of change stack + * entries per move usually is in the 20-30 range and very seldom + * exceeds 40. But since we have no way to recover from running out of + * stack space, we allocate with a substantial safety margin. + */ +#define STACK_SIZE 30 * MAXSTACK + + +#define CLEAR_STACKS() do { \ + change_stack_pointer = change_stack; \ + vertex_stack_pointer = vertex_stack; \ + VALGRIND_MAKE_WRITABLE(change_stack, sizeof(change_stack)); \ + VALGRIND_MAKE_WRITABLE(vertex_stack, sizeof(vertex_stack)); \ +} while (0) + +/* Begin a record : address == NULL */ +#define BEGIN_CHANGE_RECORD()\ +((change_stack_pointer++)->address = NULL,\ + (vertex_stack_pointer++)->address = NULL) + +/* Save a value : store the address and the value in the stack */ +#define PUSH_VALUE(v)\ +(change_stack_pointer->address = &(v),\ + (change_stack_pointer++)->value = (v)) + +/* Save a board value : store the address and the value in the stack */ +#define PUSH_VERTEX(v)\ +(vertex_stack_pointer->address = &(v),\ + (vertex_stack_pointer++)->value = (v)) + +#define POP_MOVE()\ + while ((--change_stack_pointer)->address)\ + *(change_stack_pointer->address) =\ + change_stack_pointer->value + + +#define POP_VERTICES()\ + while ((--vertex_stack_pointer)->address)\ + *(vertex_stack_pointer->address) =\ + vertex_stack_pointer->value + + +/* ================================================================ */ +/* static data structures */ +/* ================================================================ */ + + +/* Main array of string information. */ +static struct string_data string[MAX_STRINGS]; +static struct string_liberties_data string_libs[MAX_STRINGS]; +static struct string_neighbors_data string_neighbors[MAX_STRINGS]; + + +/* Stacks and stack pointers. */ +static struct change_stack_entry change_stack[STACK_SIZE]; +static struct change_stack_entry *change_stack_pointer; + +static struct vertex_stack_entry vertex_stack[STACK_SIZE]; +static struct vertex_stack_entry *vertex_stack_pointer; + + +/* Index into list of strings. The index is only valid if there is a + * stone at the vertex. + */ +static int string_number[BOARDMAX]; + + +/* The stones in a string are linked together in a cyclic list. + * These are the coordinates to the next stone in the string. + */ +static int next_stone[BOARDMAX]; + + +/* ---------------------------------------------------------------- */ + + +/* Macros to traverse the stones of a string. + * + * Usage: + * int s, pos; + * s = find_the_string() + * pos = FIRST_STONE(s); + * do { + * use_stone(pos); + * pos = NEXT_STONE(pos); + * } while (!BACK_TO_FIRST_STONE(s, pos)); + */ +#define FIRST_STONE(s) \ + (string[s].origin) + +#define NEXT_STONE(pos) \ + (next_stone[pos]) + +#define BACK_TO_FIRST_STONE(s, pos) \ + ((pos) == string[s].origin) + + +/* Assorted useful macros. + * + * Some of them could have been functions but are implemented as + * macros for speed. + */ + +#define LIBERTY(pos) \ + (board[pos] == EMPTY) + +#define UNMARKED_LIBERTY(pos) \ + (board[pos] == EMPTY && ml[pos] != liberty_mark) + +#define MARK_LIBERTY(pos) \ + ml[pos] = liberty_mark + +#define UNMARKED_STRING(pos) \ + (string[string_number[pos]].mark != string_mark) + +/* Note that these two macros are not complementary. Both return + * false if board[pos] != color. + */ +#define UNMARKED_COLOR_STRING(pos, color)\ + (board[pos] == color\ + && string[string_number[pos]].mark != string_mark) + +#define MARKED_COLOR_STRING(pos, color)\ + (board[pos] == color\ + && string[string_number[pos]].mark == string_mark) + +#define MARK_STRING(pos) string[string_number[pos]].mark = string_mark + +#define STRING_AT_VERTEX(pos, s, color)\ + ((board[pos] == color) && string_number[pos] == (s)) + +#define NEIGHBOR_OF_STRING(pos, s, color)\ + (STRING_AT_VERTEX(SOUTH(pos), s, color)\ + || STRING_AT_VERTEX(WEST(pos), s, color)\ + || STRING_AT_VERTEX(NORTH(pos), s, color)\ + || STRING_AT_VERTEX(EAST(pos), s, color)) + +/* These four macros have rather confusing names. It should be read as: + * "(pos) is a neighbor of string (s) of (color) in any direction except + * the specified one". + */ +#define NON_SOUTH_NEIGHBOR_OF_STRING(pos, s, color)\ + (STRING_AT_VERTEX(SOUTH(pos), s, color)\ + || STRING_AT_VERTEX(WEST(pos), s, color)\ + || STRING_AT_VERTEX(EAST(pos), s, color)) + +#define NON_WEST_NEIGHBOR_OF_STRING(pos, s, color)\ + (STRING_AT_VERTEX(WEST(pos), s, color)\ + || STRING_AT_VERTEX(NORTH(pos), s, color)\ + || STRING_AT_VERTEX(SOUTH(pos), s, color)) + +#define NON_NORTH_NEIGHBOR_OF_STRING(pos, s, color)\ + (STRING_AT_VERTEX(NORTH(pos), s, color)\ + || STRING_AT_VERTEX(EAST(pos), s, color)\ + || STRING_AT_VERTEX(WEST(pos), s, color)) + +#define NON_EAST_NEIGHBOR_OF_STRING(pos, s, color)\ + (STRING_AT_VERTEX(EAST(pos), s, color)\ + || STRING_AT_VERTEX(SOUTH(pos), s, color)\ + || STRING_AT_VERTEX(NORTH(pos), s, color)) + +#define LIBERTIES(pos)\ + string[string_number[pos]].liberties + +#define COUNTSTONES(pos) \ + string[string_number[pos]].size + +#define ADD_LIBERTY(s, pos)\ + do {\ + if (string[s].liberties < MAX_LIBERTIES)\ + string_libs[s].list[string[s].liberties] = pos;\ + string[s].liberties++;\ + } while (0) + +#define ADD_AND_MARK_LIBERTY(s, pos)\ + do {\ + if (string[s].liberties < MAX_LIBERTIES)\ + string_libs[s].list[string[s].liberties] = pos;\ + string[s].liberties++;\ + ml[pos] = liberty_mark;\ + } while (0) + +#define ADD_NEIGHBOR(s, pos)\ + string_neighbors[s].list[string[s].neighbors++] = string_number[pos] + +#define DO_ADD_STONE(pos, color)\ + do {\ + PUSH_VERTEX(board[pos]);\ + board[pos] = color;\ + hashdata_invert_stone(&board_hash, pos, color);\ + } while (0) + +#define DO_REMOVE_STONE(pos)\ + do {\ + PUSH_VERTEX(board[pos]);\ + hashdata_invert_stone(&board_hash, pos, board[pos]);\ + board[pos] = EMPTY;\ + } while (0) + + +/* ---------------------------------------------------------------- */ + + + +/* Number of the next free string. */ +static int next_string; + + +/* For marking purposes. */ +static int ml[BOARDMAX]; +static int liberty_mark; +static int string_mark; + +#if 0 +/* Forward declarations. */ +static void really_do_trymove(int pos, int color); +static int do_trymove(int pos, int color, int ignore_ko); +static void undo_trymove(void); +#endif + +static int do_approxlib(int pos, int color, int maxlib, int *libs); +static int slow_approxlib(int pos, int color, int maxlib, int *libs); +static int do_accuratelib(int pos, int color, int maxlib, int *libs); + +static int is_superko_violation(int pos, int color, enum ko_rules type); + +static void new_position(void); +static int propagate_string(int stone, int str); +static void find_liberties_and_neighbors(int s); +static int do_remove_string(int s); +static void do_commit_suicide(int pos, int color); +static void do_play_move(int pos, int color); + +static int komaster, kom_pos; + + +/* Statistics. */ +static int trymove_counter = 0; + +/* Coordinates for the eight directions, ordered + * south, west, north, east, southwest, northwest, northeast, southeast. + */ +int deltai[8] = { 1, 0, -1, 0, 1, -1, -1, 1}; +int deltaj[8] = { 0, -1, 0, 1, -1, -1, 1, 1}; +int delta[8] = { NS, -1, -NS, 1, NS-1, -NS-1, -NS+1, NS+1}; + + +/* ================================================================ */ +/* Board initialization */ +/* ================================================================ */ + +/* + * Save board state. + */ + +void +store_board(struct board_state *state) +{ + int k; + + gg_assert(stackp == 0); + + state->board_size = board_size; + + rb->memcpy(state->board, board, sizeof(board)); + rb->memcpy(state->initial_board, initial_board, sizeof(initial_board)); + + state->board_ko_pos = board_ko_pos; + state->white_captured = white_captured; + state->black_captured = black_captured; + + state->initial_board_ko_pos = initial_board_ko_pos; + state->initial_white_captured = initial_white_captured; + state->initial_black_captured = initial_black_captured; + + state->move_history_pointer = move_history_pointer; + for (k = 0; k < move_history_pointer; k++) { + state->move_history_color[k] = move_history_color[k]; + state->move_history_pos[k] = move_history_pos[k]; + state->move_history_hash[k] = move_history_hash[k]; + } + + state->komi = komi; + state->handicap = handicap; + state->move_number = movenum; +} + + +/* + * Restore a saved board state. + */ + +void +restore_board(struct board_state *state) +{ + int k; + + gg_assert(stackp == 0); + + board_size = state->board_size; + + rb->memcpy(board, state->board, sizeof(board)); + rb->memcpy(initial_board, state->initial_board, sizeof(initial_board)); + + board_ko_pos = state->board_ko_pos; + white_captured = state->white_captured; + black_captured = state->black_captured; + + initial_board_ko_pos = state->initial_board_ko_pos; + initial_white_captured = state->initial_white_captured; + initial_black_captured = state->initial_black_captured; + + move_history_pointer = state->move_history_pointer; + for (k = 0; k < move_history_pointer; k++) { + move_history_color[k] = state->move_history_color[k]; + move_history_pos[k] = state->move_history_pos[k]; + move_history_hash[k] = state->move_history_hash[k]; + } + + komi = state->komi; + handicap = state->handicap; + movenum = state->move_number; + + hashdata_recalc(&board_hash, board, board_ko_pos); + new_position(); +} + + +/* + * Clear the internal board. + */ + +void +clear_board(void) +{ + int k; + + gg_assert(board_size > 0 && board_size <= MAX_BOARD); + + rb->memset(board, EMPTY, sizeof(board)); + rb->memset(initial_board, EMPTY, sizeof(initial_board)); + for (k = 0; k < BOARDSIZE; k++) { + if (!ON_BOARD2(I(k), J(k))) { + board[k] = GRAY; + initial_board[k] = GRAY; + } + } + + board_ko_pos = NO_MOVE; + white_captured = 0; + black_captured = 0; + + komaster = EMPTY; + kom_pos = NO_MOVE; + + initial_board_ko_pos = NO_MOVE; + initial_white_captured = 0; + initial_black_captured = 0; + + move_history_pointer = 0; + movenum = 0; + + handicap = 0; + + hashdata_recalc(&board_hash, board, board_ko_pos); + new_position(); +} + +/* Test the integrity of the gray border. */ +int +test_gray_border(void) +{ + int k; + + gg_assert(board_size > 0 && board_size <= MAX_BOARD); + + for (k = 0; k < BOARDSIZE; k++) + if (!ON_BOARD2(I(k), J(k))) + if (board[k] != GRAY) + return k; + + return -1; +} + + +#if 0 + +/* ================================================================ */ +/* Temporary moves */ +/* ================================================================ */ + + +/* Stack of trial moves to get to current + * position and which color made them. Perhaps + * this should be one array of a structure + */ +static int stack[MAXSTACK]; +static int move_color[MAXSTACK]; + +static Hash_data board_hash_stack[MAXSTACK]; + +/* + * trymove pushes the position onto the stack, and makes a move + * at pos of color. Returns one if the move is legal. The + * stack pointer is only incremented if the move is legal. + * + * The way to use this is: + * + * if (trymove(...)) { + * ... + * popgo(); + * } + * + * The message can be written as a comment to an sgf file using + * sgfdump(). str can be NO_MOVE if it is not needed but otherwise + * the location of str is included in the comment. + */ + +int +trymove(int pos, int color, const char *message, int str) +{ + UNUSED(str); + /* Do the real work elsewhere. */ + if (!do_trymove(pos, color, 0)) + return 0; + + /* Store the move in an sgf tree if one is available. */ + if (sgf_dumptree) { + char buf[100]; + + if (message == NULL) + message = "UNKNOWN"; + + if (pos == NO_MOVE) { + if (komaster != EMPTY) + rb->snprintf(buf, 100, "%s (variation %d, hash %s, komaster %s:%s)", + message, count_variations, hashdata_to_string(&board_hash), + color_to_string(komaster), location_to_string(kom_pos)); + else + rb->snprintf(buf, 100, "%s (variation %d, hash %s)", message, + count_variations, hashdata_to_string(&board_hash)); + } + else { + if (komaster != EMPTY) + rb->snprintf(buf, 100, + "%s at %s (variation %d, hash %s, komaster %s:%s)", + message, location_to_string(pos), count_variations, + hashdata_to_string(&board_hash), + color_to_string(komaster), + location_to_string(kom_pos)); + else + rb->snprintf(buf, 100, "%s at %s (variation %d, hash %s)", + message, location_to_string(pos), count_variations, + hashdata_to_string(&board_hash)); + } + sgftreeAddPlayLast(sgf_dumptree, color, I(pos), J(pos)); + sgftreeAddComment(sgf_dumptree, buf); + } + + if (count_variations) + count_variations++; + stats.nodes++; + + return 1; +} + + +/* + * tryko pushes the position onto the stack, and makes a move + * at (pos) of (color). The move is allowed even if it is an + * illegal ko capture. It is to be imagined that (color) has + * made an intervening ko threat which was answered and now + * the continuation is to be explored. + * + * Return 1 if the move is legal with the above caveat. Returns + * zero if it is not legal because of suicide. + */ + +int +tryko(int pos, int color, const char *message) +{ + /* Do the real work elsewhere. */ + if (!do_trymove(pos, color, 1)) + return 0; + + if (sgf_dumptree) { + char buf[100]; + if (message == NULL) + message = "UNKNOWN"; + if (komaster != EMPTY) + rb->snprintf(buf, 100, "tryko: %s (variation %d, %s, komaster %s:%s)", + message, count_variations, hashdata_to_string(&board_hash), + color_to_string(komaster), location_to_string(kom_pos)); + else + rb->snprintf(buf, 100, "tryko: %s (variation %d, %s)", message, + count_variations, hashdata_to_string(&board_hash)); + + /* Add two pass moves to the SGF output to simulate the ko threat + * and the answer. + * + * The reason we add these is that certain SGF viewers, including + * Cgoban 1, won't properly display variations with illegal ko + * captures. SGF FF[4] compliant browsers should have no problem + * with this, though. + */ + sgftreeAddPlayLast(sgf_dumptree, color, -1, -1); + sgftreeAddComment(sgf_dumptree, "tenuki (ko threat)"); + sgftreeAddPlayLast(sgf_dumptree, OTHER_COLOR(color), -1, -1); + sgftreeAddComment(sgf_dumptree, "tenuki (answers ko threat)"); + + sgftreeAddPlayLast(sgf_dumptree, color, I(pos), J(pos)); + sgftreeAddComment(sgf_dumptree, buf); + } + + if (count_variations) + count_variations++; + stats.nodes++; + + return 1; +} + +/* Really, really make a temporary move. It is assumed that all + * necessary checks have already been made and likewise that various + * administrative bookkeeping outside of the actual board logic has + * either been done or is not needed. + */ +static void +really_do_trymove(int pos, int color) +{ + BEGIN_CHANGE_RECORD(); + PUSH_VALUE(board_ko_pos); + + /* + * FIXME: Do we really have to store board_hash in a stack? + * + * Answer: No, we don't. But for every stone that we add + * or remove, we must call hashdata_invert_stone(). This is + * not difficult per se, but the whole board.c + * will have to be checked, and there is lots of room + * for mistakes. + * + * At the same time, profiling shows that storing the + * hashdata in a stack doesn't take a lot of time, so + * this is not an urgent FIXME. + */ + rb->memcpy(&board_hash_stack[stackp], &board_hash, sizeof(board_hash)); + + if (board_ko_pos != NO_MOVE) + hashdata_invert_ko(&board_hash, board_ko_pos); + + board_ko_pos = NO_MOVE; + + stackp++; + + if (pos != PASS_MOVE) { + PUSH_VALUE(black_captured); + PUSH_VALUE(white_captured); + do_play_move(pos, color); + } +} + +/* + * Do the main work of trymove() and tryko(), i.e. the common parts. + * The ignore_ko flag tells whether an illegal ko capture may be done. + * Return 1 if the move was valid, otherwise 0. + */ + +static int +do_trymove(int pos, int color, int ignore_ko) +{ + /* 1. The color must be BLACK or WHITE. */ + gg_assert(color == BLACK || color == WHITE); + + if (pos != PASS_MOVE) { + /* 2. Unless pass, the move must be inside the board. */ + ASSERT_ON_BOARD1(pos); + + /* Update the reading tree shadow. */ + shadow[pos] = 1; + + /* 3. The location must be empty. */ + if (board[pos] != EMPTY) + return 0; + + /* 4. The location must not be the ko point, unless ignore_ko == 1. */ + if (!ignore_ko && pos == board_ko_pos) { + if (board[WEST(pos)] == OTHER_COLOR(color) + || board[EAST(pos)] == OTHER_COLOR(color)) { + return 0; + } + } + + /* 5. Test for suicide. */ + if (is_suicide(pos, color)) + return 0; + } + + /* Check for stack overflow. */ + if (stackp >= MAXSTACK-2) { + /*fprintf(stderr, + "gnugo: Truncating search. This is beyond my reading ability!\n");*/ + /* FIXME: Perhaps it's best to just assert here and be done with it? */ + if (0) { + ASSERT1(0 && "trymove stack overflow", pos); + } +#if 0 + if (verbose > 0) { + showboard(0); + dump_stack(); + } +#endif + /*fflush(stderr);*/ + return 0; + } + + + /* Only count trymove when we do create a new position. */ + trymove_counter++; + + /* So far, so good. Now push the move on the move stack. These are + * needed for dump_stack(). + */ + stack[stackp] = pos; + move_color[stackp] = color; + + really_do_trymove(pos, color); + + return 1; +} + + +/* + * popgo pops the position from the stack. + */ + +void +popgo() +{ + undo_trymove(); + + if (sgf_dumptree) { + char buf[100]; + int is_tryko = 0; + char *sgf_comment; + + /* FIXME: Change the sgfGet*Property() interface so that either + * "C" instead of "C " works or the SGFXX symbols are used. + */ + if (sgfGetCharProperty(sgf_dumptree->lastnode, "C ", &sgf_comment) + && rb->strncmp(sgf_comment, "tryko:", 6) == 0) + is_tryko = 1; + + rb->snprintf(buf, 100, "(next variation: %d)", count_variations); + sgftreeAddComment(sgf_dumptree, buf); + sgf_dumptree->lastnode = sgf_dumptree->lastnode->parent; + + /* After tryko() we need to undo two pass nodes too. */ + if (is_tryko) + sgf_dumptree->lastnode = sgf_dumptree->lastnode->parent->parent; + } +} + + +/* Restore board state to the position before the last move. This is + * accomplished by popping everything that was stored on the stacks + * since the last BEGIN_CHANGE_RECORD(). Also stackp is decreased and + * board hash is restored from stack. + * + * This undoes the effects of do_trymove() or really_do_trymove() and + * is appropriate to call instead of popgo() if you have not passed + * through trymove() or tryko(). + */ + +static void +undo_trymove() +{ + gg_assert(change_stack_pointer - change_stack <= STACK_SIZE); + + + POP_MOVE(); + POP_VERTICES(); + + stackp--; + rb->memcpy(&board_hash, &(board_hash_stack[stackp]), sizeof(board_hash)); +} + + + +/* + * dump_stack() for use under gdb prints the move stack. + */ + +void +dump_stack(void) +{ +/* + do_dump_stack(); + +#if !TRACE_READ_RESULTS + if (count_variations) + gprintf("%o (variation %d)", count_variations-1); +#else + gprintf("%o (%s)", hashdata_to_string(&board_hash)); +#endif + + gprintf("%o\n"); + fflush(stderr); +*/ +} + +/* Bare bones of dump_stack(). */ +void +do_dump_stack(void) +{ + +} + +#endif + +/* ================================================================ */ +/* Permanent moves */ +/* ================================================================ */ + + +static void +reset_move_history(void) +{ + rb->memcpy(initial_board, board, sizeof(board)); + initial_board_ko_pos = board_ko_pos; + initial_white_captured = white_captured; + initial_black_captured = black_captured; + move_history_pointer = 0; +} + +/* Place a stone on the board and update the board_hash. This operation + * destroys all move history. + */ + +void +add_stone(int pos, int color) +{ + ASSERT1(stackp == 0, pos); + ASSERT_ON_BOARD1(pos); + ASSERT1(board[pos] == EMPTY, pos); + + board[pos] = color; + hashdata_invert_stone(&board_hash, pos, color); + reset_move_history(); + new_position(); +} + + +/* Remove a stone from the board and update the board_hash. This + * operation destroys the move history. + */ + +void +remove_stone(int pos) +{ + ASSERT1(stackp == 0, pos); + ASSERT_ON_BOARD1(pos); + ASSERT1(IS_STONE(board[pos]), pos); + + hashdata_invert_stone(&board_hash, pos, board[pos]); + board[pos] = EMPTY; + reset_move_history(); + new_position(); +} + + +/* Play a move. Basically the same as play_move() below, but doesn't store + * the move in history list. + * + * Set `update_internals' to zero if you want to play several moves in a + * row to avoid overhead caused by new_position(). Don't forget to call + * it yourself after all the moves have been played. + */ +static void +play_move_no_history(int pos, int color, int update_internals) +{ +#if CHECK_HASHING + Hash_data oldkey; + + /* Check the hash table to see if it corresponds to the cumulative one. */ + hashdata_recalc(&oldkey, board, board_ko_pos); + gg_assert(hashdata_is_equal(oldkey, board_hash)); +#endif + + if (board_ko_pos != NO_MOVE) + hashdata_invert_ko(&board_hash, board_ko_pos); + board_ko_pos = NO_MOVE; + + /* If the move is a pass, we can skip some steps. */ + if (pos != PASS_MOVE) { + ASSERT_ON_BOARD1(pos); + ASSERT1(board[pos] == EMPTY, pos); + + /* Do play the move. */ + if (!is_suicide(pos, color)) + do_play_move(pos, color); + else + do_commit_suicide(pos, color); + +#if CHECK_HASHING + /* Check the hash table to see if it equals the previous one. */ + hashdata_recalc(&oldkey, board, board_ko_pos); + gg_assert(hashdata_is_equal(oldkey, board_hash)); +#endif + } + + if (update_internals || next_string == MAX_STRINGS) + new_position(); + else + CLEAR_STACKS(); +} + +/* Load the initial position and replay the first n moves. */ +static void +replay_move_history(int n) +{ + int k; + + rb->memcpy(board, initial_board, sizeof(board)); + board_ko_pos = initial_board_ko_pos; + white_captured = initial_white_captured; + black_captured = initial_black_captured; + new_position(); + + for (k = 0; k < n; k++) + play_move_no_history(move_history_pos[k], move_history_color[k], 0); + + new_position(); +} + +/* Play a move. If you want to test for legality you should first call + * is_legal(). This function strictly follows the algorithm: + * 1. Place a stone of given color on the board. + * 2. If there are any adjacent opponent strings without liberties, + * remove them and increase the prisoner count. + * 3. If the newly placed stone is part of a string without liberties, + * remove it and increase the prisoner count. + * + * In spite of the name "permanent move", this move can (usually) be + * unplayed by undo_move(), but it is significantly more costly than + * unplaying a temporary move. There are limitations on the available + * move history, so under certain circumstances the move may not be + * possible to unplay at a later time. + */ +void +play_move(int pos, int color) +{ + ASSERT1(stackp == 0, pos); + ASSERT1(color == WHITE || color == BLACK, pos); + ASSERT1(pos == PASS_MOVE || ON_BOARD1(pos), pos); + ASSERT1(pos == PASS_MOVE || board[pos] == EMPTY, pos); + ASSERT1(komaster == EMPTY && kom_pos == NO_MOVE, pos); + + if (move_history_pointer >= MAX_MOVE_HISTORY) { + /* The move history is full. We resolve this by collapsing the + * first about 10% of the moves into the initial position. + */ + int number_collapsed_moves = 1 + MAX_MOVE_HISTORY / 10; + int k; + Intersection saved_board[BOARDSIZE]; + int saved_board_ko_pos = board_ko_pos; + int saved_white_captured = white_captured; + int saved_black_captured = black_captured; + rb->memcpy(saved_board, board, sizeof(board)); + + replay_move_history(number_collapsed_moves); + + rb->memcpy(initial_board, board, sizeof(board)); + initial_board_ko_pos = board_ko_pos; + initial_white_captured = white_captured; + initial_black_captured = black_captured; + + for (k = number_collapsed_moves; k < move_history_pointer; k++) { + move_history_color[k - number_collapsed_moves] = move_history_color[k]; + move_history_pos[k - number_collapsed_moves] = move_history_pos[k]; + move_history_hash[k - number_collapsed_moves] = move_history_hash[k]; + } + move_history_pointer -= number_collapsed_moves; + + rb->memcpy(board, saved_board, sizeof(board)); + board_ko_pos = saved_board_ko_pos; + white_captured = saved_white_captured; + black_captured = saved_black_captured; + new_position(); + } + + move_history_color[move_history_pointer] = color; + move_history_pos[move_history_pointer] = pos; + move_history_hash[move_history_pointer] = board_hash; + if (board_ko_pos != NO_MOVE) + hashdata_invert_ko(&move_history_hash[move_history_pointer], board_ko_pos); + move_history_pointer++; + + play_move_no_history(pos, color, 1); + + movenum++; +} + + +/* Undo n permanent moves. Returns 1 if successful and 0 if it fails. + * If n moves cannot be undone, no move is undone. + */ +int +undo_move(int n) +{ + gg_assert(stackp == 0); + + /* Fail if and only if the move history is too short. */ + if (move_history_pointer < n) + return 0; + + replay_move_history(move_history_pointer - n); + move_history_pointer -= n; + movenum -= n; + + return 1; +} + + +/* Return the last move done by the opponent to color. Both if no move + * was found or if the last move was a pass, PASS_MOVE is returned. + */ +int +get_last_opponent_move(int color) +{ + int k; + + for (k = move_history_pointer - 1; k >= 0; k--) + if (move_history_color[k] == OTHER_COLOR(color)) + return move_history_pos[k]; + + return PASS_MOVE; +} + +/* Return the last move done by anyone. Both if no move was found or + * if the last move was a pass, PASS_MOVE is returned. + */ +int +get_last_move() +{ + if (move_history_pointer == 0) + return PASS_MOVE; + + return move_history_pos[move_history_pointer - 1]; +} + +/* Return the color of the player doing the last move. If no move was + * found, EMPTY is returned. + */ +int +get_last_player() +{ + if (move_history_pointer == 0) + return EMPTY; + + return move_history_color[move_history_pointer - 1]; +} + + +/* ================================================================ */ +/* Utility functions */ +/* ================================================================ */ + + +/* + * Test if the move is a pass or not. Return 1 if it is. + */ + +int +is_pass(int pos) +{ + return pos == 0; +} + + +/* + * is_legal(pos, color) determines whether the move (color) at pos is + * legal. This is for internal use in the engine and always assumes + * that suicide is allowed and only simple ko restrictions, no + * superko, regardless of the rules actually used in the game. + * + * Use is_allowed_move() if you want to take alternative suicide and + * ko rules into account. + */ + +int +is_legal(int pos, int color) +{ + /* 0. A pass move is always legal. */ + if (pos == 0) + return 1; + + /* 1. The move must be inside the board. */ + ASSERT_ON_BOARD1(pos); + + /* 2. The location must be empty. */ + if (board[pos] != EMPTY) + return 0; + + /* 3. The location must not be the ko point. */ + if (pos == board_ko_pos) { + /* The ko position is guaranteed to have all neighbors of the + * same color, or off board. If that color is the same as the + * move the ko is being filled, which is always allowed. This + * could be tested with has_neighbor() but here a faster test + * suffices. + */ + if (board[WEST(pos)] == OTHER_COLOR(color) + || board[EAST(pos)] == OTHER_COLOR(color)) { + return 0; + } + } + + /* Check for stack overflow. */ + if (stackp >= MAXSTACK-2) { + /*fprintf(stderr, + "gnugo: Truncating search. This is beyond my reading ability!\n");*/ + /* FIXME: Perhaps it's best to just assert here and be done with it? */ + if (0) { + ASSERT1(0 && "is_legal stack overflow", pos); + } + return 0; + } + + /* Check for suicide. */ + if (is_suicide(pos, color)) + return 0; + + return 1; +} + + +/* + * is_suicide(pos, color) determines whether the move (color) at + * (pos) would be a suicide. + * + * This is the case if + * 1. There is no neighboring empty intersection. + * 2. There is no neighboring opponent string with exactly one liberty. + * 3. There is no neighboring friendly string with more than one liberty. + */ +int +is_suicide(int pos, int color) +{ + ASSERT_ON_BOARD1(pos); + ASSERT1(board[pos] == EMPTY, pos); + + /* Check for suicide. */ + if (LIBERTY(SOUTH(pos)) + || (ON_BOARD(SOUTH(pos)) + && ((board[SOUTH(pos)] == color) ^ (LIBERTIES(SOUTH(pos)) == 1)))) + return 0; + + if (LIBERTY(WEST(pos)) + || (ON_BOARD(WEST(pos)) + && ((board[WEST(pos)] == color) ^ (LIBERTIES(WEST(pos)) == 1)))) + return 0; + + if (LIBERTY(NORTH(pos)) + || (ON_BOARD(NORTH(pos)) + && ((board[NORTH(pos)] == color) ^ (LIBERTIES(NORTH(pos)) == 1)))) + return 0; + + if (LIBERTY(EAST(pos)) + || (ON_BOARD(EAST(pos)) + && ((board[EAST(pos)] == color) ^ (LIBERTIES(EAST(pos)) == 1)))) + return 0; + + return 1; +} + + +/* + * is_illegal_ko_capture(pos, color) determines whether the move + * (color) at (pos) would be an illegal ko capture. + */ +int +is_illegal_ko_capture(int pos, int color) +{ + ASSERT_ON_BOARD1(pos); + ASSERT1(board[pos] == EMPTY, pos); + + return (pos == board_ko_pos + && ((board[WEST(pos)] == OTHER_COLOR(color)) + || (board[EAST(pos)] == OTHER_COLOR(color)))); +} + +/* + * is_allowed_move(int pos, int color) determines whether a move is + * legal with respect to the suicide and ko rules in play. + * + * This function is only valid when stackp == 0 since there is no + * tracking of superko for trymoves. + */ +int +is_allowed_move(int pos, int color) +{ + gg_assert(stackp == 0); + + /* 1. A pass move is always legal, no matter what. */ + if (pos == PASS_MOVE) + return 1; + + /* 2. The move must be inside the board. */ + ASSERT_ON_BOARD1(pos); + + /* 3. The location must be empty. */ + if (board[pos] != EMPTY) + return 0; + + /* 4. Simple ko repetition is only allowed if no ko rule is in use. + * For superko rules this check is redundant. + * + * The ko position is guaranteed to have all neighbors of the + * same color, or off board. If that color is the same as the + * move the ko is being filled, which is always allowed. This + * could be tested with has_neighbor() but here a faster test + * suffices. + */ + if (ko_rule != NONE + && pos == board_ko_pos + && (board[WEST(pos)] == OTHER_COLOR(color) + || board[EAST(pos)] == OTHER_COLOR(color))) + return 0; + + /* 5. Check for suicide. Suicide rule options: + * FORBIDDEN - No suicides allowed. + * ALLOWED - Suicide of more than one stone allowed. + * ALL_ALLOWED - All suicides allowed. + */ + if (is_suicide(pos, color)) + if (suicide_rule == FORBIDDEN + || (suicide_rule == ALLOWED + && !has_neighbor(pos, color))) + return 0; + + /* 6. Check for whole board repetitions. The superko options are + * SIMPLE, NONE - No superko restrictions. + * PSK - Repetition of a previous position forbidden. + * SSK - Repetition of a previous position with the same + * player to move forbidden. + */ + if (is_superko_violation(pos, color, ko_rule)) + return 0; + + return 1; +} + +#if 0 +/* Necessary work to set the new komaster state. */ +static void +set_new_komaster(int new_komaster) +{ + PUSH_VALUE(komaster); + hashdata_invert_komaster(&board_hash, komaster); + komaster = new_komaster; + hashdata_invert_komaster(&board_hash, komaster); +} + +/* Necessary work to set the new komaster position. */ +static void +set_new_kom_pos(int new_kom_pos) +{ + PUSH_VALUE(kom_pos); + hashdata_invert_kom_pos(&board_hash, kom_pos); + kom_pos = new_kom_pos; + hashdata_invert_kom_pos(&board_hash, kom_pos); +} +#endif +/* Variation of trymove()/tryko() where ko captures (both conditional + * and unconditional) must follow a komaster scheme. + * + * Historical note: Up to GNU Go 3.4 five different komaster schemes + * were implemented and could easily be switched between. In GNU Go + * 3.5.1 four of them were removed to simplify the code and because it + * no longer seemed interesting to be able to switch. The remaining + * komaster scheme was previously known as komaster scheme 5 (or V). + * + * FIXME: This function could be optimized by integrating the + * trymove()/tryko() code. + */ + +/* V. Complex scheme, O to move. + * + * 1. Komaster is EMPTY. + * 1a) Unconditional ko capture is allowed. + * Komaster remains EMPTY if previous move was not a ko capture. + * Komaster is set to WEAK_KO if previous move was a ko capture + * and kom_pos is set to the old value of board_ko_pos. + * 1b) Conditional ko capture is allowed. Komaster is set to O and + * kom_pos to the location of the ko, where a stone was + * just removed. + * + * 2. Komaster is O: + * 2a) Only nested ko captures are allowed. Kom_pos is moved to the + * new removed stone. + * 2b) If komaster fills the ko at kom_pos then komaster reverts to + * EMPTY. + * + * 3. Komaster is X: + * Play at kom_pos is not allowed. Any other ko capture + * is allowed. If O takes another ko, komaster becomes GRAY_X. + * + * 4. Komaster is GRAY_O or GRAY_X: + * Ko captures are not allowed. If the ko at kom_pos is + * filled then the komaster reverts to EMPTY. + * + * 5. Komaster is WEAK_KO: + * 5a) After a non-ko move komaster reverts to EMPTY. + * 5b) Unconditional ko capture is only allowed if it is nested ko capture. + * Komaster is changed to WEAK_X and kom_pos to the old value of + * board_ko_pos. + * 5c) Conditional ko capture is allowed according to the rules of 1b. + */ +#if 0 +int +komaster_trymove(int pos, int color, const char *message, int str, + int *is_conditional_ko, int consider_conditional_ko) +{ + int other = OTHER_COLOR(color); + int ko_move; + int kpos; + int previous_board_ko_pos = board_ko_pos; + + *is_conditional_ko = 0; + ko_move = is_ko(pos, color, &kpos); + + if (ko_move) { + /* If opponent is komaster we may not capture his ko. */ + if (komaster == other && pos == kom_pos) + return 0; + + /* If komaster is gray we may not capture ko at all. */ + if (komaster == GRAY_WHITE || komaster == GRAY_BLACK) + return 0; + + /* If we are komaster, we may only do nested captures. */ + if (komaster == color && !DIAGONAL_NEIGHBORS(kpos, kom_pos)) + return 0; + + /* If komaster is WEAK_KO, we may only do nested ko capture or + * conditional ko capture. + */ + if (komaster == WEAK_KO) { + if (pos != board_ko_pos && !DIAGONAL_NEIGHBORS(kpos, kom_pos)) + return 0; + } + } + + if (!trymove(pos, color, message, str)) { + if (!consider_conditional_ko) + return 0; + + if (!tryko(pos, color, message)) + return 0; /* Suicide. */ + + *is_conditional_ko = 1; + + /* Conditional ko capture, set komaster parameters. */ + if (komaster == EMPTY || komaster == WEAK_KO) { + set_new_komaster(color); + set_new_kom_pos(kpos); + return 1; + } + } + + if (!ko_move) { + /* If we are komaster, check whether the ko was resolved by the + * current move. If that is the case, revert komaster to EMPTY. + * + * The ko has been resolved in favor of the komaster if it has + * been filled, or if it is no longer a ko and an opponent move + * there is suicide. + */ + if (((komaster == color + || (komaster == GRAY_WHITE && color == WHITE) + || (komaster == GRAY_BLACK && color == BLACK)) + && (IS_STONE(board[kom_pos]) + || (!is_ko(kom_pos, other, NULL) + && is_suicide(kom_pos, other))))) { + set_new_komaster(EMPTY); + set_new_kom_pos(NO_MOVE); + } + + if (komaster == WEAK_KO) { + set_new_komaster(EMPTY); + set_new_kom_pos(NO_MOVE); + } + + return 1; + } + + if (komaster == other) { + if (color == WHITE) + set_new_komaster(GRAY_BLACK); + else + set_new_komaster(GRAY_WHITE); + } + else if (komaster == color) { + /* This is where we update kom_pos after a nested capture. */ + set_new_kom_pos(kpos); + } + else { + /* We can reach here when komaster is EMPTY or WEAK_KO. If previous + * move was also a ko capture, we now set komaster to WEAK_KO. + */ + if (previous_board_ko_pos != NO_MOVE) { + set_new_komaster(WEAK_KO); + set_new_kom_pos(previous_board_ko_pos); + } + } + + return 1; +} +#endif +int +get_komaster() +{ + return komaster; +} + +int +get_kom_pos() +{ + return kom_pos; +} + + +/* Determine whether vertex is on the edge. */ +int +is_edge_vertex(int pos) +{ + ASSERT_ON_BOARD1(pos); + if (!ON_BOARD(SW(pos)) + || !ON_BOARD(NE(pos))) + return 1; + + return 0; +} + +/* Distance to the edge. */ +int +edge_distance(int pos) +{ + int i = I(pos); + int j = J(pos); + ASSERT_ON_BOARD1(pos); + return gg_min(gg_min(i, board_size-1 - i), gg_min(j, board_size-1 - j)); +} + + +/* Determine whether vertex is a corner. */ +int +is_corner_vertex(int pos) +{ + ASSERT_ON_BOARD1(pos); + if ((!ON_BOARD(WEST(pos)) || !ON_BOARD(EAST(pos))) + && (!ON_BOARD(SOUTH(pos)) || !ON_BOARD(NORTH(pos)))) + return 1; + + return 0; +} + + +/* Returns true if the empty vertex respectively the string at pos1 is + * adjacent to the empty vertex respectively the string at pos2. + */ +int +are_neighbors(int pos1, int pos2) +{ + if (board[pos1] == EMPTY) { + if (board[pos2] == EMPTY) + return (gg_abs(pos1 - pos2) == NS || gg_abs(pos1 - pos2) == WE); + else + return neighbor_of_string(pos1, pos2); + } + else { + if (board[pos2] == EMPTY) + return neighbor_of_string(pos2, pos1); + else + return adjacent_strings(pos1, pos2); + } +} + + +/* Count the number of liberties of the string at pos. pos must not be + * empty. + */ +int +countlib(int str) +{ + ASSERT1(IS_STONE(board[str]), str); + + /* We already know the number of liberties. Just look it up. */ + return string[string_number[str]].liberties; +} + + +/* Find the liberties of the string at str. str must not be + * empty. The locations of up to maxlib liberties are written into + * libs[]. The full number of liberties is returned. + * + * If you want the locations of all liberties, whatever their number, + * you should pass MAXLIBS as the value for maxlib and allocate space + * for libs[] accordingly. + */ + +int +findlib(int str, int maxlib, int *libs) +{ + int k; + int liberties; + int s; + + ASSERT1(IS_STONE(board[str]), str); + ASSERT1(libs != NULL, str); + + /* We already have the list of liberties and only need to copy it to + * libs[]. + * + * However, if the string has more than MAX_LIBERTIES liberties the + * list is truncated and if maxlib is also larger than MAX_LIBERTIES + * we have to traverse the stones in the string in order to find + * where the liberties are. + */ + s = string_number[str]; + liberties = string[s].liberties; + + if (liberties <= MAX_LIBERTIES || maxlib <= MAX_LIBERTIES) { + /* The easy case, it suffices to copy liberty locations from the + * incrementally updated list. + */ + for (k = 0; k < maxlib && k < liberties; k++) + libs[k] = string_libs[s].list[k]; + } + else { + /* The harder case, where we have to traverse the stones in the + * string. We don't have to check explicitly if we are back to + * the start of the chain since we will run out of liberties + * before that happens. + */ + int pos; + liberty_mark++; + for (k = 0, pos = FIRST_STONE(s); + k < maxlib && k < liberties; + pos = NEXT_STONE(pos)) { + if (UNMARKED_LIBERTY(SOUTH(pos))) { + libs[k++] = SOUTH(pos); + MARK_LIBERTY(SOUTH(pos)); + if (k >= maxlib) + break; + } + + if (UNMARKED_LIBERTY(WEST(pos))) { + libs[k++] = WEST(pos); + MARK_LIBERTY(WEST(pos)); + if (k >= maxlib) + break; + } + + if (UNMARKED_LIBERTY(NORTH(pos))) { + libs[k++] = NORTH(pos); + MARK_LIBERTY(NORTH(pos)); + if (k >= maxlib) + break; + } + + if (UNMARKED_LIBERTY(EAST(pos))) { + libs[k++] = EAST(pos); + MARK_LIBERTY(EAST(pos)); + if (k >= maxlib) + break; + } + } + } + + return liberties; +} + +/* Count the liberties a stone of the given color would get if played + * at (pos). The location (pos) must be empty. + * + * The intent of this function is to be as fast as possible, not + * necessarily complete. But if it returns a positive value (meaning + * it has succeeded), the value is guaranteed to be correct. + * + * Captures are ignored based on the ignore_capture flag. The function + * fails if there are more than two neighbor strings of the same + * color. In this case, the return value is -1. Captures are handled + * in a very limited way, so if ignore_capture is 0, and a capture is + * required, it will often return -1. + * + * Note well, that it relies on incremental data. + */ + +int +fastlib(int pos, int color, int ignore_captures) +{ + int ally1 = -1; + int ally2 = -1; + int fast_liberties = 0; + + ASSERT1(board[pos] == EMPTY, pos); + ASSERT1(IS_STONE(color), pos); + + /* Find neighboring strings of the same color. If there are more than two of + * them, we give up (it's too difficult to count their common liberties). + */ + if (board[SOUTH(pos)] == color) { + ally1 = string_number[SOUTH(pos)]; + + if (board[WEST(pos)] == color + && string_number[WEST(pos)] != ally1) { + ally2 = string_number[WEST(pos)]; + + if (board[NORTH(pos)] == color + && string_number[NORTH(pos)] != ally1 + && string_number[NORTH(pos)] != ally2) + return -1; + } + else if (board[NORTH(pos)] == color + && string_number[NORTH(pos)] != ally1) + ally2 = string_number[NORTH(pos)]; + + if (board[EAST(pos)] == color + && string_number[EAST(pos)] != ally1) { + if (ally2 < 0) + ally2 = string_number[EAST(pos)]; + else if (string_number[EAST(pos)] != ally2) + return -1; + } + } + else if (board[WEST(pos)] == color) { + ally1 = string_number[WEST(pos)]; + + if (board[NORTH(pos)] == color + && string_number[NORTH(pos)] != ally1) { + ally2 = string_number[NORTH(pos)]; + + if (board[EAST(pos)] == color + && string_number[EAST(pos)] != ally1 + && string_number[EAST(pos)] != ally2) + return -1; + } + else if (board[EAST(pos)] == color + && string_number[EAST(pos)] != ally1) + ally2 = string_number[EAST(pos)]; + } + else if (board[NORTH(pos)] == color) { + ally1 = string_number[NORTH(pos)]; + + if (board[EAST(pos)] == color + && string_number[EAST(pos)] != ally1) + ally2 = string_number[EAST(pos)]; + } + else if (board[EAST(pos)] == color) + ally1 = string_number[EAST(pos)]; + + /* If we are to ignore captures, the things are very easy. */ + if (ignore_captures) { + if (ally1 < 0) { /* No allies */ + if (LIBERTY(SOUTH(pos))) + fast_liberties++; + if (LIBERTY(WEST(pos))) + fast_liberties++; + if (LIBERTY(NORTH(pos))) + fast_liberties++; + if (LIBERTY(EAST(pos))) + fast_liberties++; + } + else if (ally2 < 0) { /* One ally */ + if (LIBERTY(SOUTH(pos)) + && !NON_SOUTH_NEIGHBOR_OF_STRING(SOUTH(pos), ally1, color)) + fast_liberties++; + if (LIBERTY(WEST(pos)) + && !NON_WEST_NEIGHBOR_OF_STRING(WEST(pos), ally1, color)) + fast_liberties++; + if (LIBERTY(NORTH(pos)) + && !NON_NORTH_NEIGHBOR_OF_STRING(NORTH(pos), ally1, color)) + fast_liberties++; + if (LIBERTY(EAST(pos)) + && !NON_EAST_NEIGHBOR_OF_STRING(EAST(pos), ally1, color)) + fast_liberties++; + + fast_liberties += string[ally1].liberties - 1; + } + else { /* Two allies */ + if (LIBERTY(SOUTH(pos)) + && !NON_SOUTH_NEIGHBOR_OF_STRING(SOUTH(pos), ally1, color) + && !NON_SOUTH_NEIGHBOR_OF_STRING(SOUTH(pos), ally2, color)) + fast_liberties++; + if (LIBERTY(WEST(pos)) + && !NON_WEST_NEIGHBOR_OF_STRING(WEST(pos), ally1, color) + && !NON_WEST_NEIGHBOR_OF_STRING(WEST(pos), ally2, color)) + fast_liberties++; + if (LIBERTY(NORTH(pos)) + && !NON_NORTH_NEIGHBOR_OF_STRING(NORTH(pos), ally1, color) + && !NON_NORTH_NEIGHBOR_OF_STRING(NORTH(pos), ally2, color)) + fast_liberties++; + if (LIBERTY(EAST(pos)) + && !NON_EAST_NEIGHBOR_OF_STRING(EAST(pos), ally1, color) + && !NON_EAST_NEIGHBOR_OF_STRING(EAST(pos), ally2, color)) + fast_liberties++; + + fast_liberties += string[ally1].liberties + string[ally2].liberties + - count_common_libs(string[ally1].origin, string[ally2].origin) - 1; + } + } + /* We are to take captures into account. This case is much more rare, so + * it is not optimized much. + */ + else { + int k; + + for (k = 0; k < 4; k++) { + int neighbor = pos + delta[k]; + + if (LIBERTY(neighbor) + && (ally1 < 0 || !NEIGHBOR_OF_STRING(neighbor, ally1, color)) + && (ally2 < 0 || !NEIGHBOR_OF_STRING(neighbor, ally2, color))) + fast_liberties++; + else if (board[neighbor] == OTHER_COLOR(color) /* A capture */ + && LIBERTIES(neighbor) == 1) { + int neighbor_size = COUNTSTONES(neighbor); + + if (neighbor_size == 1 || (neighbor_size == 2 && ally1 < 0)) + fast_liberties++; + else + return -1; + } + } + + if (ally1 >= 0) { + fast_liberties += string[ally1].liberties - 1; + if (ally2 >= 0) + fast_liberties += string[ally2].liberties + - count_common_libs(string[ally1].origin, string[ally2].origin); + } + } + + return fast_liberties; +} + + +/* Effectively true unless we store full position in hash. */ +#define USE_BOARD_CACHES (NUM_HASHVALUES <= 4) + +struct board_cache_entry { + int threshold; + int liberties; + Hash_data position_hash; +}; + + +/* approxlib() cache. */ +static struct board_cache_entry approxlib_cache[BOARDMAX][2]; + + +/* Clears approxlib() cache. This function should be called only once + * during engine initialization. Sets thresholds to zero. + */ +void +clear_approxlib_cache(void) +{ + int pos; + + for (pos = BOARDMIN; pos < BOARDMAX; pos++) { + approxlib_cache[pos][0].threshold = 0; + approxlib_cache[pos][1].threshold = 0; + } +} + + +/* Find the liberties a stone of the given color would get if played + * at (pos), ignoring possible captures of opponent stones. (pos) + * must be empty. If libs != NULL, the locations of up to maxlib + * liberties are written into libs[]. The counting of liberties may + * or may not be halted when maxlib is reached. The number of liberties + * found is returned. + * + * If you want the number or the locations of all liberties, however + * many they are, you should pass MAXLIBS as the value for maxlib and + * allocate space for libs[] accordingly. + */ +int +approxlib(int pos, int color, int maxlib, int *libs) +{ + int liberties; + +#ifdef USE_BOARD_CACHES + + struct board_cache_entry *entry = &approxlib_cache[pos][color - 1]; + + ASSERT1(board[pos] == EMPTY, pos); + ASSERT1(IS_STONE(color), pos); + + if (!libs) { + /* First see if this result is cached. */ + if (hashdata_is_equal(board_hash, entry->position_hash) + && maxlib <= entry->threshold) { + return entry->liberties; + } + + liberties = fastlib(pos, color, 1); + if (liberties >= 0) { + /* Since fastlib() always returns precise result and doesn't take + * `maxlib' into account, we set threshold to MAXLIBS so that this + * result is used regardless of any `maxlib' passed. + */ + entry->threshold = MAXLIBS; + entry->liberties = liberties; + entry->position_hash = board_hash; + + return liberties; + } + } + + /* We initialize the cache entry threshold to `maxlib'. If do_approxlib() + * or slow_approxlib() finds all the liberties (that is, they don't use + * `maxlib' value for an early return), they will set threshold to + * MAXLIBS themselves. + */ + entry->threshold = maxlib; + + if (maxlib <= MAX_LIBERTIES) + liberties = do_approxlib(pos, color, maxlib, libs); + else + liberties = slow_approxlib(pos, color, maxlib, libs); + + entry->liberties = liberties; + entry->position_hash = board_hash; + +#else /* not USE_BOARD_CACHES */ + + ASSERT1(board[pos] == EMPTY, pos); + ASSERT1(IS_STONE(color), pos); + + if (!libs) { + liberties = fastlib(pos, color, 1); + if (liberties >= 0) + return liberties; + } + + if (maxlib <= MAX_LIBERTIES) + liberties = do_approxlib(pos, color, maxlib, libs); + else + liberties = slow_approxlib(pos, color, maxlib, libs); + +#endif /* not USE_BOARD_CACHES */ + + return liberties; +} + + +/* Does the real work of approxlib(). */ +static int +do_approxlib(int pos, int color, int maxlib, int *libs) +{ + int k; + int liberties = 0; + + /* Look for empty neighbors and the liberties of the adjacent + * strings of the given color. The algorithm below won't work + * correctly if any of the adjacent strings have more than + * MAX_LIBERTIES liberties AND maxlib is larger than MAX_LIBERTIES. + * therefore approxlib() calls more robust slow_approxlib() if + * this might be the case. + */ + + /* Start by marking pos itself so it isn't counted among its own + * liberties. + */ + liberty_mark++; + MARK_LIBERTY(pos); + + if (UNMARKED_LIBERTY(SOUTH(pos))) { + if (libs != NULL) + libs[liberties] = SOUTH(pos); + liberties++; + /* Stop counting if we reach maxlib. */ + if (liberties >= maxlib) + return liberties; + MARK_LIBERTY(SOUTH(pos)); + } + else if (board[SOUTH(pos)] == color) { + int s = string_number[SOUTH(pos)]; + for (k = 0; k < string[s].liberties; k++) { + int lib = string_libs[s].list[k]; + if (UNMARKED_LIBERTY(lib)) { + if (libs != NULL) + libs[liberties] = lib; + liberties++; + if (liberties >= maxlib) + return liberties; + MARK_LIBERTY(lib); + } + } + } + + if (UNMARKED_LIBERTY(WEST(pos))) { + if (libs != NULL) + libs[liberties] = WEST(pos); + liberties++; + /* Stop counting if we reach maxlib. */ + if (liberties >= maxlib) + return liberties; + MARK_LIBERTY(WEST(pos)); + } + else if (board[WEST(pos)] == color) { + int s = string_number[WEST(pos)]; + for (k = 0; k < string[s].liberties; k++) { + int lib = string_libs[s].list[k]; + if (UNMARKED_LIBERTY(lib)) { + if (libs != NULL) + libs[liberties] = lib; + liberties++; + if (liberties >= maxlib) + return liberties; + MARK_LIBERTY(lib); + } + } + } + + if (UNMARKED_LIBERTY(NORTH(pos))) { + if (libs != NULL) + libs[liberties] = NORTH(pos); + liberties++; + /* Stop counting if we reach maxlib. */ + if (liberties >= maxlib) + return liberties; + MARK_LIBERTY(NORTH(pos)); + } + else if (board[NORTH(pos)] == color) { + int s = string_number[NORTH(pos)]; + for (k = 0; k < string[s].liberties; k++) { + int lib = string_libs[s].list[k]; + if (UNMARKED_LIBERTY(lib)) { + if (libs != NULL) + libs[liberties] = lib; + liberties++; + if (liberties >= maxlib) + return liberties; + MARK_LIBERTY(lib); + } + } + } + + if (UNMARKED_LIBERTY(EAST(pos))) { + if (libs != NULL) + libs[liberties] = EAST(pos); + liberties++; + /* Unneeded since we're about to leave. */ +#if 0 + if (liberties >= maxlib) + return liberties; + MARK_LIBERTY(EAST(pos)); +#endif + } + else if (board[EAST(pos)] == color) { + int s = string_number[EAST(pos)]; + for (k = 0; k < string[s].liberties; k++) { + int lib = string_libs[s].list[k]; + if (UNMARKED_LIBERTY(lib)) { + if (libs != NULL) + libs[liberties] = lib; + liberties++; + if (liberties >= maxlib) + return liberties; + MARK_LIBERTY(lib); + } + } + } + +#if USE_BOARD_CACHES + /* If we reach here, then we have counted _all_ the liberties, so + * we set threshold to MAXLIBS (the result is the same regardless + * of `maxlib' value). + */ + if (!libs) + approxlib_cache[pos][color - 1].threshold = MAXLIBS; +#endif + return liberties; +} + + +/* Find the liberties a move of the given color at pos would have, + * excluding possible captures, by traversing all adjacent friendly + * strings. This is a fallback used by approxlib() when a faster + * algorithm can't be used. + */ +static int +slow_approxlib(int pos, int color, int maxlib, int *libs) +{ + int k; + int liberties = 0; + + liberty_mark++; + MARK_LIBERTY(pos); + string_mark++; + for (k = 0; k < 4; k++) { + int d = delta[k]; + if (UNMARKED_LIBERTY(pos + d)) { + if (libs) + libs[liberties] = pos + d; + liberties++; + if (liberties == maxlib) + return liberties; + MARK_LIBERTY(pos + d); + } + else if (board[pos + d] == color + && UNMARKED_STRING(pos + d)) { + int s = string_number[pos + d]; + int pos2; + pos2 = FIRST_STONE(s); + do { + int l; + for (l = 0; l < 4; l++) { + int d2 = delta[l]; + if (UNMARKED_LIBERTY(pos2 + d2)) { + if (libs) + libs[liberties] = pos2 + d2; + liberties++; + if (liberties == maxlib) + return liberties; + MARK_LIBERTY(pos2 + d2); + } + } + + pos2 = NEXT_STONE(pos2); + } while (!BACK_TO_FIRST_STONE(s, pos2)); + MARK_STRING(pos + d); + } + } + +#if USE_BOARD_CACHES + /* If we reach here, then we have counted _all_ the liberties, so + * we set threshold to MAXLIBS (the result is the same regardless + * of `maxlib' value). + */ + if (!libs) + approxlib_cache[pos][color - 1].threshold = MAXLIBS; +#endif + return liberties; +} + + +/* accuratelib() cache. */ +static struct board_cache_entry accuratelib_cache[BOARDMAX][2]; + + +/* Clears accuratelib() cache. This function should be called only once + * during engine initialization. Sets thresholds to zero. + */ +void +clear_accuratelib_cache(void) +{ + int pos; + + for (pos = BOARDMIN; pos < BOARDMAX; pos++) { + accuratelib_cache[pos][0].threshold = 0; + accuratelib_cache[pos][1].threshold = 0; + } +} + + +/* Find the liberties a stone of the given color would get if played + * at (pos). This function takes into consideration all captures. Its + * return value is exact in that sense it counts all the liberties, + * unless (maxlib) allows it to stop earlier. (pos) must be empty. If + * libs != NULL, the locations of up to maxlib liberties are written + * into libs[]. The counting of liberties may or may not be halted + * when maxlib is reached. The number of found liberties is returned. + * + * This function guarantees that liberties which are not results of + * captures come first in libs[] array. To find whether all the + * liberties starting from a given one are results of captures, one + * may use if (board[libs[k]] != EMPTY) construction. + * + * If you want the number or the locations of all liberties, however + * many they are, you should pass MAXLIBS as the value for maxlib and + * allocate space for libs[] accordingly. + */ +int +accuratelib(int pos, int color, int maxlib, int *libs) +{ + int liberties; + +#ifdef USE_BOARD_CACHES + + struct board_cache_entry *entry = &accuratelib_cache[pos][color - 1]; + + ASSERT1(board[pos] == EMPTY, pos); + ASSERT1(IS_STONE(color), pos); + + if (!libs) { + /* First see if this result is cached. */ + if (hashdata_is_equal(board_hash, entry->position_hash) + && maxlib <= entry->threshold) { + return entry->liberties; + } + + liberties = fastlib(pos, color, 0); + if (liberties >= 0) { + /* Since fastlib() always returns precise result and doesn't take + * `maxlib' into account, we set threshold to MAXLIBS so that this + * result is used regardless of any `maxlib' passed. + */ + entry->threshold = MAXLIBS; + entry->liberties = liberties; + entry->position_hash = board_hash; + + return liberties; + } + } + + liberties = do_accuratelib(pos, color, maxlib, libs); + + /* If accuratelib() found less than `maxlib' liberties, then its + * result is certainly independent of `maxlib' and we set threshold + * to MAXLIBS. + */ + entry->threshold = liberties < maxlib ? MAXLIBS : maxlib; + entry->liberties = liberties; + entry->position_hash = board_hash; + +#else /* not USE_BOARD_CACHES */ + + ASSERT1(board[pos] == EMPTY, pos); + ASSERT1(IS_STONE(color), pos); + + if (!libs) { + liberties = fastlib(pos, color, 0); + if (liberties >= 0) + return liberties; + } + + liberties = do_accuratelib(pos, color, maxlib, libs); + +#endif /* not USE_BOARD_CACHES */ + + return liberties; +} + + +/* Does the real work of accuratelib(). */ +static int +do_accuratelib(int pos, int color, int maxlib, int *libs) +{ + int k, l; + int liberties = 0; + int lib; + int captured[4]; + int captures = 0; + + string_mark++; + liberty_mark++; + MARK_LIBERTY(pos); + + for (k = 0; k < 4; k++) { + int pos2 = pos + delta[k]; + if (UNMARKED_LIBERTY(pos2)) { + /* A trivial liberty */ + if (libs) + libs[liberties] = pos2; + liberties++; + if (liberties >= maxlib) + return liberties; + + MARK_LIBERTY(pos2); + } + else if (UNMARKED_COLOR_STRING(pos2, color)) { + /* An own neighbor string */ + struct string_data *s = &string[string_number[pos2]]; + struct string_liberties_data *sl = &string_libs[string_number[pos2]]; + + if (s->liberties <= MAX_LIBERTIES || maxlib <= MAX_LIBERTIES - 1) { + /* The easy case - we already have all (necessary) liberties of + * the string listed + */ + for (l = 0; l < s->liberties; l++) { + lib = sl->list[l]; + if (UNMARKED_LIBERTY(lib)) { + if (libs) + libs[liberties] = lib; + liberties++; + if (liberties >= maxlib) + return liberties; + + MARK_LIBERTY(lib); + } + } + } + else { + /* The harder case - we need to find all the liberties of the + * string by traversing its stones. We stop as soon as we have + * traversed all the stones or have reached maxlib. Unfortunately, + * we cannot use the trick from findlib() since some of the + * liberties may already have been marked. + */ + int stone = pos2; + do { + if (UNMARKED_LIBERTY(SOUTH(stone))) { + if (libs) + libs[liberties] = SOUTH(stone); + liberties++; + if (liberties >= maxlib) + return liberties; + + MARK_LIBERTY(SOUTH(stone)); + } + + if (UNMARKED_LIBERTY(WEST(stone))) { + if (libs) + libs[liberties] = WEST(stone); + liberties++; + if (liberties >= maxlib) + return liberties; + + MARK_LIBERTY(WEST(stone)); + } + + if (UNMARKED_LIBERTY(NORTH(stone))) { + if (libs) + libs[liberties] = NORTH(stone); + liberties++; + if (liberties >= maxlib) + return liberties; + + MARK_LIBERTY(NORTH(stone)); + } + + if (UNMARKED_LIBERTY(EAST(stone))) { + if (libs) + libs[liberties] = EAST(stone); + liberties++; + if (liberties >= maxlib) + return liberties; + + MARK_LIBERTY(EAST(stone)); + } + + stone = NEXT_STONE(stone); + } while (stone != pos2); + } + + MARK_STRING(pos2); + } + else if (board[pos2] == OTHER_COLOR(color) + && string[string_number[pos2]].liberties == 1) { + /* A capture. */ + captured[captures++] = pos2; + } + } + + /* Now we look at all the captures found in the previous step */ + for (k = 0; k < captures; k++) { + lib = captured[k]; + + /* Add the stone adjacent to (pos) to the list of liberties if + * it is not also adjacent to an own marked string (otherwise, + * it will be added later). + */ + if (!MARKED_COLOR_STRING(SOUTH(lib), color) + && !MARKED_COLOR_STRING(WEST(lib), color) + && !MARKED_COLOR_STRING(NORTH(lib), color) + && !MARKED_COLOR_STRING(EAST(lib), color)) { + if (libs) + libs[liberties] = lib; + liberties++; + if (liberties >= maxlib) + return liberties; + } + + /* Check if we already know of this capture. */ + for (l = 0; l < k; l++) + if (string_number[captured[l]] == string_number[lib]) + break; + + if (l == k) { + /* Traverse all the stones of the capture and add to the list + * of liberties those, which are adjacent to at least one own + * marked string. + */ + do { + if (MARKED_COLOR_STRING(SOUTH(lib), color) + || MARKED_COLOR_STRING(WEST(lib), color) + || MARKED_COLOR_STRING(NORTH(lib), color) + || MARKED_COLOR_STRING(EAST(lib), color)) { + if (libs) + libs[liberties] = lib; + liberties++; + if (liberties >= maxlib) + return liberties; + } + + lib = NEXT_STONE(lib); + } while (lib != captured[k]); + } + } + + return liberties; +} + + +/* Find the number of common liberties of the two strings at str1 and str2. + */ + +int +count_common_libs(int str1, int str2) +{ + int all_libs1[MAXLIBS], *libs1; + int liberties1, liberties2; + int commonlibs = 0; + int k, n, tmp; + + ASSERT_ON_BOARD1(str1); + ASSERT_ON_BOARD1(str2); + ASSERT1(IS_STONE(board[str1]), str1); + ASSERT1(IS_STONE(board[str2]), str2); + + n = string_number[str1]; + liberties1 = string[n].liberties; + + if (liberties1 > string[string_number[str2]].liberties) { + n = string_number[str2]; + liberties1 = string[n].liberties; + tmp = str1; + str1 = str2; + str2 = tmp; + } + + if (liberties1 <= MAX_LIBERTIES) { + /* Speed optimization: don't copy liberties with findlib */ + libs1 = string_libs[n].list; + n = string_number[str2]; + liberties2 = string[n].liberties; + + if (liberties2 <= MAX_LIBERTIES) { + /* Speed optimization: NEIGHBOR_OF_STRING is quite expensive */ + liberty_mark++; + + for (k = 0; k < liberties1; k++) + MARK_LIBERTY(libs1[k]); + + libs1 = string_libs[n].list; + for (k = 0; k < liberties2; k++) + if (!UNMARKED_LIBERTY(libs1[k])) + commonlibs++; + + return commonlibs; + } + } + else { + findlib(str1, MAXLIBS, all_libs1); + libs1 = all_libs1; + } + + for (k = 0; k < liberties1; k++) + if (NEIGHBOR_OF_STRING(libs1[k], string_number[str2], board[str2])) + commonlibs++; + + return commonlibs; +} + + +/* Find the common liberties of the two strings at str1 and str2. The + * locations of up to maxlib common liberties are written into libs[]. + * The full number of common liberties is returned. + * + * If you want the locations of all common liberties, whatever their + * number, you should pass MAXLIBS as the value for maxlib and + * allocate space for libs[] accordingly. + */ + +int +find_common_libs(int str1, int str2, int maxlib, int *libs) +{ + int all_libs1[MAXLIBS], *libs1; + int liberties1, liberties2; + int commonlibs = 0; + int k, n, tmp; + + ASSERT_ON_BOARD1(str1); + ASSERT_ON_BOARD1(str2); + ASSERT1(IS_STONE(board[str1]), str1); + ASSERT1(IS_STONE(board[str2]), str2); + ASSERT1(libs != NULL, str1); + + n = string_number[str1]; + liberties1 = string[n].liberties; + + if (liberties1 > string[string_number[str2]].liberties) { + n = string_number[str2]; + liberties1 = string[n].liberties; + tmp = str1; + str1 = str2; + str2 = tmp; + } + + if (liberties1 <= MAX_LIBERTIES) { + /* Speed optimization: don't copy liberties with findlib */ + libs1 = string_libs[n].list; + n = string_number[str2]; + liberties2 = string[n].liberties; + + if (liberties2 <= MAX_LIBERTIES) { + /* Speed optimization: NEIGHBOR_OF_STRING is quite expensive */ + liberty_mark++; + + for (k = 0; k < liberties1; k++) + MARK_LIBERTY(libs1[k]); + + libs1 = string_libs[n].list; + for (k = 0; k < liberties2; k++) + if (!UNMARKED_LIBERTY(libs1[k])) { + if (commonlibs < maxlib) + libs[commonlibs] = libs1[k]; + commonlibs++; + } + + return commonlibs; + } + } + else { + findlib(str1, MAXLIBS, all_libs1); + libs1 = all_libs1; + } + + for (k = 0; k < liberties1; k++) + if (NEIGHBOR_OF_STRING(libs1[k], string_number[str2], board[str2])) { + if (commonlibs < maxlib) + libs[commonlibs] = libs1[k]; + commonlibs++; + } + + return commonlibs; +} + + +/* Determine whether two strings have at least one common liberty. + * If they do and lib != NULL, one common liberty is returned in *lib. + */ +int +have_common_lib(int str1, int str2, int *lib) +{ + int all_libs1[MAXLIBS], *libs1; + int liberties1; + int k, n, tmp; + + ASSERT_ON_BOARD1(str1); + ASSERT_ON_BOARD1(str2); + ASSERT1(IS_STONE(board[str1]), str1); + ASSERT1(IS_STONE(board[str2]), str2); + + n = string_number[str1]; + liberties1 = string[n].liberties; + + if (liberties1 > string[string_number[str2]].liberties) { + n = string_number[str2]; + liberties1 = string[n].liberties; + tmp = str1; + str1 = str2; + str2 = tmp; + } + + if (liberties1 <= MAX_LIBERTIES) + /* Speed optimization: don't copy liberties with findlib */ + libs1 = string_libs[n].list; + else { + findlib(str1, MAXLIBS, all_libs1); + libs1 = all_libs1; + } + + for (k = 0; k < liberties1; k++) { + if (NEIGHBOR_OF_STRING(libs1[k], string_number[str2], board[str2])) { + if (lib) + *lib = libs1[k]; + return 1; + } + } + + return 0; +} + + + +/* + * Report the number of stones in a string. + */ + +int +countstones(int str) +{ + ASSERT_ON_BOARD1(str); + ASSERT1(IS_STONE(board[str]), str); + + return COUNTSTONES(str); +} + + +/* Find the stones of the string at str. str must not be + * empty. The locations of up to maxstones stones are written into + * stones[]. The full number of stones is returned. + */ + +int +findstones(int str, int maxstones, int *stones) +{ + int s; + int size; + int pos; + int k; + + ASSERT_ON_BOARD1(str); + ASSERT1(IS_STONE(board[str]), str); + + s = string_number[str]; + size = string[s].size; + + /* Traverse the stones of the string, by following the cyclic chain. */ + pos = FIRST_STONE(s); + for (k = 0; k < maxstones && k < size; k++) { + stones[k] = pos; + pos = NEXT_STONE(pos); + } + + return size; +} + + +/* Counts how many stones in str1 are directly adjacent to str2. + * A limit can be given in the maxstones parameter so that the + * function returns immediately. See fast_defense() in reading.c + */ + +int +count_adjacent_stones(int str1, int str2, int maxstones) +{ + int s1, s2; + int size; + int pos; + int k; + int count = 0; + + ASSERT_ON_BOARD1(str1); + ASSERT1(IS_STONE(board[str1]), str1); + ASSERT_ON_BOARD1(str2); + ASSERT1(IS_STONE(board[str2]), str2); + + s1 = string_number[str1]; + s2 = string_number[str2]; + size = string[s1].size; + + /* Traverse the stones of the string, by following the cyclic chain. */ + pos = FIRST_STONE(s1); + for (k = 0; k < size && count < maxstones; k++) { + if (NEIGHBOR_OF_STRING(pos, s2, board[str2])) + count++; + pos = NEXT_STONE(pos); + } + + return count; +} + + +/* chainlinks returns (in the (adj) array) the chains surrounding + * the string at (str). The number of chains is returned. + */ + +int +chainlinks(int str, int adj[MAXCHAIN]) +{ + struct string_data *s; + struct string_neighbors_data *sn; + int k; + + ASSERT1(IS_STONE(board[str]), str); + + /* We already have the list ready, just copy it and fill in the + * desired information. + */ + s = &string[string_number[str]]; + sn = &string_neighbors[string_number[str]]; + for (k = 0; k < s->neighbors; k++) + adj[k] = string[sn->list[k]].origin; + + return s->neighbors; +} + + +/* chainlinks2 returns (in adj array) those chains surrounding + * the string at str which have exactly lib liberties. The number + * of such chains is returned. + */ + +int +chainlinks2(int str, int adj[MAXCHAIN], int lib) +{ + struct string_data *s, *t; + struct string_neighbors_data *sn; + int k; + int neighbors; + + ASSERT1(IS_STONE(board[str]), str); + + /* We already have the list ready, just copy the strings with the + * right number of liberties. + */ + neighbors = 0; + s = &string[string_number[str]]; + sn = &string_neighbors[string_number[str]]; + for (k = 0; k < s->neighbors; k++) { + t = &string[sn->list[k]]; + if (t->liberties == lib) + adj[neighbors++] = t->origin; + } + return neighbors; +} + + +/* chainlinks3 returns (in adj array) those chains surrounding + * the string at str, which have less or equal lib liberties. + * The number of such chains is returned. + */ + +int +chainlinks3(int str, int adj[MAXCHAIN], int lib) +{ + struct string_data *s, *t; + struct string_neighbors_data *sn; + int k; + int neighbors; + + ASSERT1(IS_STONE(board[str]), str); + + /* We already have the list ready, just copy the strings with the + * right number of liberties. + */ + neighbors = 0; + s = &string[string_number[str]]; + sn = &string_neighbors[string_number[str]]; + for (k = 0; k < s->neighbors; k++) { + t = &string[sn->list[k]]; + if (t->liberties <= lib) + adj[neighbors++] = t->origin; + } + return neighbors; +} + + +/* extended_chainlinks() returns (in the (adj) array) the opponent + * strings being directly adjacent to (str) or having a common liberty + * with (str). The number of such strings is returned. + * + * If the both_colors parameter is true, also own strings sharing a + * liberty are returned. + */ + +int +extended_chainlinks(int str, int adj[MAXCHAIN], int both_colors) +{ + struct string_data *s; + struct string_neighbors_data *sn; + int n; + int k; + int r; + int libs[MAXLIBS]; + int liberties; + + ASSERT1(IS_STONE(board[str]), str); + + /* We already have the list of directly adjacent strings ready, just + * copy it and mark the strings. + */ + s = &string[string_number[str]]; + sn = &string_neighbors[string_number[str]]; + string_mark++; + for (n = 0; n < s->neighbors; n++) { + adj[n] = string[sn->list[n]].origin; + MARK_STRING(adj[n]); + } + + /* Get the liberties. */ + liberties = findlib(str, MAXLIBS, libs); + + /* Look for unmarked opponent strings next to a liberty and add the + * ones which are found to the output. + */ + for (r = 0; r < liberties; r++) { + for (k = 0; k < 4; k++) { + if ((board[libs[r] + delta[k]] == OTHER_COLOR(board[str]) + || (both_colors && board[libs[r] + delta[k]] == board[str])) + && UNMARKED_STRING(libs[r] + delta[k])) { + adj[n] = string[string_number[libs[r] + delta[k]]].origin; + MARK_STRING(adj[n]); + n++; + } + } + } + + return n; +} + + +/* + * Find the origin of a worm, i.e. the point with the + * smallest 1D board coordinate. The idea is to have a canonical + * reference point for a string. + */ + +int +find_origin(int str) +{ + ASSERT1(IS_STONE(board[str]), str); + + return string[string_number[str]].origin; +} + + +/* Determine whether a move by color at (pos) would be a self atari, + * i.e. whether it would get more than one liberty. This function + * returns true also for the case of a suicide move. + */ + +int +is_self_atari(int pos, int color) +{ + int other = OTHER_COLOR(color); + /* number of empty neighbors */ + int trivial_liberties = 0; + /* number of captured opponent strings */ + int captures = 0; + /* Whether there is a friendly neighbor with a spare liberty. If it + * has more than one spare liberty we immediately return 0. + */ + int far_liberties = 0; + + ASSERT_ON_BOARD1(pos); + ASSERT1(board[pos] == EMPTY, pos); + ASSERT1(IS_STONE(color), pos); + + /* 1. Try first to solve the problem without much work. */ + string_mark++; + + if (LIBERTY(SOUTH(pos))) + trivial_liberties++; + else if (board[SOUTH(pos)] == color) { + if (LIBERTIES(SOUTH(pos)) > 2) + return 0; + if (LIBERTIES(SOUTH(pos)) == 2) + far_liberties++; + } + else if (board[SOUTH(pos)] == other + && LIBERTIES(SOUTH(pos)) == 1 && UNMARKED_STRING(SOUTH(pos))) { + captures++; + MARK_STRING(SOUTH(pos)); + } + + if (LIBERTY(WEST(pos))) + trivial_liberties++; + else if (board[WEST(pos)] == color) { + if (LIBERTIES(WEST(pos)) > 2) + return 0; + if (LIBERTIES(WEST(pos)) == 2) + far_liberties++; + } + else if (board[WEST(pos)] == other + && LIBERTIES(WEST(pos)) == 1 && UNMARKED_STRING(WEST(pos))) { + captures++; + MARK_STRING(WEST(pos)); + } + + if (LIBERTY(NORTH(pos))) + trivial_liberties++; + else if (board[NORTH(pos)] == color) { + if (LIBERTIES(NORTH(pos)) > 2) + return 0; + if (LIBERTIES(NORTH(pos)) == 2) + far_liberties++; + } + else if (board[NORTH(pos)] == other + && LIBERTIES(NORTH(pos)) == 1 && UNMARKED_STRING(NORTH(pos))) { + captures++; + MARK_STRING(NORTH(pos)); + } + + if (LIBERTY(EAST(pos))) + trivial_liberties++; + else if (board[EAST(pos)] == color) { + if (LIBERTIES(EAST(pos)) > 2) + return 0; + if (LIBERTIES(EAST(pos)) == 2) + far_liberties++; + } + else if (board[EAST(pos)] == other + && LIBERTIES(EAST(pos)) == 1 && UNMARKED_STRING(EAST(pos))) { + captures++; +#if 0 + MARK_STRING(EAST(pos)); +#endif + } + + /* Each captured string is guaranteed to produce at least one + * liberty. These are disjoint from both trivial liberties and far + * liberties. The two latter may however coincide. + */ + if (trivial_liberties + captures >= 2) + return 0; + + if ((far_liberties > 0) + captures >= 2) + return 0; + + if (captures == 0 && far_liberties + trivial_liberties <= 1) + return 1; + + /* 2. It was not so easy. We use accuratelib() in this case. */ + return accuratelib(pos, color, 2, NULL) <= 1; +} + + +/* + * Returns true if pos is a liberty of the string at str. + */ + +int +liberty_of_string(int pos, int str) +{ + ASSERT_ON_BOARD1(pos); + ASSERT_ON_BOARD1(str); + if (IS_STONE(board[pos])) + return 0; + + return NEIGHBOR_OF_STRING(pos, string_number[str], board[str]); +} + + +/* + * Returns true if pos is a second order liberty of the string at str. + */ +int +second_order_liberty_of_string(int pos, int str) +{ + int k; + ASSERT_ON_BOARD1(pos); + ASSERT_ON_BOARD1(str); + + for (k = 0; k < 4; k++) + if (board[pos + delta[k]] == EMPTY + && NEIGHBOR_OF_STRING(pos + delta[k], string_number[str], board[str])) + return 1; + + return 0; +} + + +/* + * Returns true if pos is adjacent to the string at str. + */ + +int +neighbor_of_string(int pos, int str) +{ + int color = board[str]; + + ASSERT1(IS_STONE(color), str); + ASSERT_ON_BOARD1(pos); + + return NEIGHBOR_OF_STRING(pos, string_number[str], color); +} + +/* + * Returns true if (pos) has a neighbor of color (color). + */ + +int +has_neighbor(int pos, int color) +{ + ASSERT_ON_BOARD1(pos); + ASSERT1(IS_STONE(color), pos); + + return (board[SOUTH(pos)] == color + || board[WEST(pos)] == color + || board[NORTH(pos)] == color + || board[EAST(pos)] == color); +} + +/* + * Returns true if str1 and str2 belong to the same string. + */ + +int +same_string(int str1, int str2) +{ + ASSERT_ON_BOARD1(str1); + ASSERT_ON_BOARD1(str2); + ASSERT1(IS_STONE(board[str1]), str1); + ASSERT1(IS_STONE(board[str2]), str2); + + return string_number[str1] == string_number[str2]; +} + + +/* + * Returns true if the strings at str1 and str2 are adjacent. + */ + +int +adjacent_strings(int str1, int str2) +{ + int s1, s2; + int k; + + ASSERT_ON_BOARD1(str1); + ASSERT_ON_BOARD1(str2); + ASSERT1(IS_STONE(board[str1]), str1); + ASSERT1(IS_STONE(board[str2]), str2); + + s1 = string_number[str1]; + s2 = string_number[str2]; + + for (k = 0; k < string[s1].neighbors; k++) + if (string_neighbors[s1].list[k] == s2) + return 1; + + return 0; +} + + +/* + * Return true if the move (pos) by (color) is a ko capture + * (whether capture is legal on this move or not). If so, + * and if ko_pos is not a NULL pointer, then + * *ko_pos returns the location of the captured ko stone. + * If the move is not a ko capture, *ko_pos is set to 0. + * + * A move is a ko capture if and only if + * 1. All neighbors are opponent stones. + * 2. The number of captured stones is exactly one. + */ + +int +is_ko(int pos, int color, int *ko_pos) +{ + int other = OTHER_COLOR(color); + int captures = 0; + int kpos = 0; + + ASSERT_ON_BOARD1(pos); + ASSERT1(color == WHITE || color == BLACK, pos); + + if (ON_BOARD(SOUTH(pos))) { + if (board[SOUTH(pos)] != other) + return 0; + else if (LIBERTIES(SOUTH(pos)) == 1) { + kpos = SOUTH(pos); + captures += string[string_number[SOUTH(pos)]].size; + if (captures > 1) + return 0; + } + } + + if (ON_BOARD(WEST(pos))) { + if (board[WEST(pos)] != other) + return 0; + else if (LIBERTIES(WEST(pos)) == 1) { + kpos = WEST(pos); + captures += string[string_number[WEST(pos)]].size; + if (captures > 1) + return 0; + } + } + + if (ON_BOARD(NORTH(pos))) { + if (board[NORTH(pos)] != other) + return 0; + else if (LIBERTIES(NORTH(pos)) == 1) { + kpos = NORTH(pos); + captures += string[string_number[NORTH(pos)]].size; + if (captures > 1) + return 0; + } + } + + if (ON_BOARD(EAST(pos))) { + if (board[EAST(pos)] != other) + return 0; + else if (LIBERTIES(EAST(pos)) == 1) { + kpos = EAST(pos); + captures += string[string_number[EAST(pos)]].size; + if (captures > 1) + return 0; + } + } + + if (captures == 1) { + if (ko_pos) + *ko_pos = kpos; + return 1; + } + return 0; +} + + +/* Return true if pos is either a stone, which if captured would give + * ko, or if pos is an empty intersection adjacent to a ko stone. + */ +int +is_ko_point(int pos) +{ + ASSERT_ON_BOARD1(pos); + + if (board[pos] == EMPTY) { + int color; + if (ON_BOARD(SOUTH(pos))) + color = board[SOUTH(pos)]; + else + color = board[NORTH(pos)]; + if (IS_STONE(color) && is_ko(pos, OTHER_COLOR(color), NULL)) + return 1; + } + else { + struct string_data *s = &string[string_number[pos]]; + struct string_liberties_data *sl = &string_libs[string_number[pos]]; + if (s->liberties == 1 && s->size == 1 + && is_ko(sl->list[0], OTHER_COLOR(s->color), NULL)) + return 1; + } + + return 0; +} + + +/* Return true if a move by color at pos is a superko violation + * according to the specified type of ko rules. This function does not + * detect simple ko unless it's also a superko violation. + * + * The superko detection is done by comparing board hashes from + * previous positions. For this to work correctly it's necessary to + * remove the contribution to the hash from the simple ko position. + * The move_history_hash array contains board hashes for previous + * positions, also without simple ko position contributions. + */ +static int +is_superko_violation(int pos, int color, enum ko_rules type) +{ +(void)pos; +(void)color; +(void)type; +#if 0 + Hash_data this_board_hash = board_hash; + Hash_data new_board_hash; + int k; + + /* No superko violations if the ko rule is not a superko rule. */ + if (type == NONE || type == SIMPLE) + return 0; + + if (board_ko_pos != NO_MOVE) + hashdata_invert_ko(&this_board_hash, board_ko_pos); + + really_do_trymove(pos, color); + new_board_hash = board_hash; + if (board_ko_pos != NO_MOVE) + hashdata_invert_ko(&new_board_hash, board_ko_pos); + undo_trymove(); + + /* The current position is only a problem with positional superko + * and a single stone suicide. + */ + if (type == PSK && hashdata_is_equal(this_board_hash, new_board_hash)) + return 1; + + for (k = move_history_pointer - 1; k >= 0; k--) + if (hashdata_is_equal(move_history_hash[k], new_board_hash) + && (type == PSK + || move_history_color[k] == OTHER_COLOR(color))) + return 1; + + return 0; +#else + +return 0; +#endif +} + +/* Returns 1 if at least one string is captured when color plays at pos. + */ +int +does_capture_something(int pos, int color) +{ + int other = OTHER_COLOR(color); + + ASSERT1(board[pos] == EMPTY, pos); + + if (board[SOUTH(pos)] == other && LIBERTIES(SOUTH(pos)) == 1) + return 1; + + if (board[WEST(pos)] == other && LIBERTIES(WEST(pos)) == 1) + return 1; + + if (board[NORTH(pos)] == other && LIBERTIES(NORTH(pos)) == 1) + return 1; + + if (board[EAST(pos)] == other && LIBERTIES(EAST(pos)) == 1) + return 1; + + return 0; +} + + +/* For each stone in the string at pos, set mx to value mark. */ +void +mark_string(int str, signed char mx[BOARDMAX], signed char mark) +{ + int pos = str; + + ASSERT1(IS_STONE(board[str]), str); + + do { + mx[pos] = mark; + pos = NEXT_STONE(pos); + } while (pos != str); +} + +#if 0 + +/* Returns true if at least one move has been played at pos + * at deeper than level 'cutoff' in the reading tree. + */ +int +move_in_stack(int pos, int cutoff) +{ + int k; + for (k = cutoff; k < stackp; k++) + if (stack[k] == pos) + return 1; + + return 0; +} + + +/* Retrieve a move from the move stack. */ +void +get_move_from_stack(int k, int *move, int *color) +{ + gg_assert(k < stackp); + *move = stack[k]; + *color = move_color[k]; +} + +#endif +/* Return the number of stones of the indicated color(s) on the board. + * This only counts stones in the permanent position, not stones placed + * by trymove() or tryko(). Use stones_on_board(BLACK | WHITE) to get + * the total number of stones on the board. + * + * FIXME: This seems wrong, it uses the modified board, not the permanent + * one. /ab + */ +int +stones_on_board(int color) +{ + static int stone_count_for_position = -1; + static int white_stones = 0; + static int black_stones = 0; + + gg_assert(stackp == 0); + + if (stone_count_for_position != position_number) { + int pos; + white_stones = 0; + black_stones = 0; + for (pos = BOARDMIN; pos < BOARDMAX; pos++) { + if (board[pos] == WHITE) + white_stones++; + else if (board[pos] == BLACK) + black_stones++; + } + + stone_count_for_position = position_number; + } + + return ((color & BLACK ? black_stones : 0) + + (color & WHITE ? white_stones : 0)); +} + + +/* ===================== Statistics ============================= */ + + +/* Clear statistics. */ +void +reset_trymove_counter() +{ + trymove_counter = 0; +} + + +/* Retrieve statistics. */ +int +get_trymove_counter() +{ + return trymove_counter; +} + + +/* ================================================================ */ +/* Lower level functions */ +/* ================================================================ */ + + +/* This function should be called if the board is modified by other + * means than do_play_move() or undo_trymove(). + * + * We have reached a new position. Increase the position counter and + * re-initialize the incremental strings. + * + * Set up incremental board structures and populate them with the + * strings available in the position given by board[]. Clear the stacks + * and start the mark numbers from zero. All undo information is lost + * by calling this function. + */ + +static void +new_position(void) +{ + int pos; + int s; + + position_number++; + next_string = 0; + liberty_mark = 0; + string_mark = 0; + CLEAR_STACKS(); + + rb->memset(string, 0, sizeof(string)); + rb->memset(string_libs, 0, sizeof(string_libs)); + rb->memset(string_neighbors, 0, sizeof(string_neighbors)); + rb->memset(ml, 0, sizeof(ml)); + VALGRIND_MAKE_WRITABLE(next_stone, sizeof(next_stone)); + + /* propagate_string relies on non-assigned stones to have + * string_number -1. + */ + for (pos = BOARDMIN; pos < BOARDMAX; pos++) + if (ON_BOARD(pos)) + string_number[pos] = -1; + + /* Find the existing strings. */ + for (pos = BOARDMIN; pos < BOARDMAX; pos++) { + if (!ON_BOARD(pos)) + continue; + if (IS_STONE(board[pos]) && string_number[pos] == -1) { + string_number[pos] = next_string; + string[next_string].size = propagate_string(pos, pos); + string[next_string].color = board[pos]; + string[next_string].origin = pos; + string[next_string].mark = 0; + next_string++; + PARANOID1(next_string < MAX_STRINGS, pos); + } + } + + /* Fill in liberty and neighbor info. */ + for (s = 0; s < next_string; s++) { + find_liberties_and_neighbors(s); + } +} + + +#if 0 + +/* + * Debug function. Dump all string information. + */ + +static void +dump_incremental_board(void) +{ + int pos; + int s; + int i; + + for (pos = BOARDMIN; pos < BOARDMAX; pos++) { + if (!ON_BOARD(pos)) + continue; + if (board[pos] == EMPTY) + fprintf(stderr, " . "); + else + fprintf(stderr, "%2d ", string_number[pos]); + fprintf(stderr, "\n"); + } + + for (s = 0; s < next_string; s++) { + if (board[string[s].origin] == EMPTY) + continue; + + gprintf("%o%d %s %1m size %d, %d liberties, %d neighbors\n", s, + color_to_string(string[s].color), + string[s].origin, string[s].size, + string[s].liberties, string[s].neighbors); + gprintf("%ostones:"); + + pos = FIRST_STONE(s); + do { + gprintf("%o %1m", pos); + pos = NEXT_STONE(pos); + } while (!BACK_TO_FIRST_STONE(s, pos)); + + gprintf("%o\nliberties:"); + for (i = 0; i < string[s].liberties; i++) + gprintf("%o %1m", string[s].libs[i]); + + gprintf("%o\nneighbors:"); + for (i = 0; i < string[s].neighbors; i++) + gprintf("%o %d(%1m)", string[s].neighborlist[i], + string[string[s].neighborlist[i]].origin); + gprintf("%o\n\n"); + } +} +#endif + + +/* Build a string and its cyclic list representation from scratch. + * propagate_string(stone, str) adds the stone (stone) to the string + * (str) and recursively continues with not already included friendly + * neighbors. To start a new string at (stone), use + * propagate_string(stone, stone). The size of the string is returned. + */ + +static int +propagate_string(int stone, int str) +{ + int size = 1; + int k; + + if (stone == str) { + /* Start a new string. */ + next_stone[stone] = stone; + } + else { + /* Link the stone at (stone) to the string including (str) */ + string_number[stone] = string_number[str]; + next_stone[stone] = next_stone[str]; + next_stone[str] = stone; + } + + /* Look in all four directions for more stones to add. */ + for (k = 0; k < 4; k++) { + int d = delta[k]; + if (ON_BOARD(stone + d) + && board[stone + d] == board[stone] + && string_number[stone + d] == -1) + size += propagate_string(stone + d, str); + } + + return size; +} + + +/* Build the lists of liberties and neighbors of a string from + * scratch. No information is pushed onto the stack by this function. + */ + +static void +find_liberties_and_neighbors(int s) +{ + int pos; + int other = OTHER_COLOR(string[s].color); + + /* Clear the marks. */ + liberty_mark++; + string_mark++; + + /* Traverse the stones of the string, by following the cyclic chain. */ + pos = FIRST_STONE(s); + do { + /* Look in each direction for new liberties or new neighbors. Mark + * already visited liberties and neighbors. + */ + if (UNMARKED_LIBERTY(SOUTH(pos))) { + ADD_AND_MARK_LIBERTY(s, SOUTH(pos)); + } + else if (UNMARKED_COLOR_STRING(SOUTH(pos), other)) { + ADD_NEIGHBOR(s, SOUTH(pos)); + MARK_STRING(SOUTH(pos)); + } + + if (UNMARKED_LIBERTY(WEST(pos))) { + ADD_AND_MARK_LIBERTY(s, WEST(pos)); + } + else if (UNMARKED_COLOR_STRING(WEST(pos), other)) { + ADD_NEIGHBOR(s, WEST(pos)); + MARK_STRING(WEST(pos)); + } + + if (UNMARKED_LIBERTY(NORTH(pos))) { + ADD_AND_MARK_LIBERTY(s, NORTH(pos)); + } + else if (UNMARKED_COLOR_STRING(NORTH(pos), other)) { + ADD_NEIGHBOR(s, NORTH(pos)); + MARK_STRING(NORTH(pos)); + } + + if (UNMARKED_LIBERTY(EAST(pos))) { + ADD_AND_MARK_LIBERTY(s, EAST(pos)); + } + else if (UNMARKED_COLOR_STRING(EAST(pos), other)) { + ADD_NEIGHBOR(s, EAST(pos)); + MARK_STRING(EAST(pos)); + } + + pos = NEXT_STONE(pos); + } while (!BACK_TO_FIRST_STONE(s, pos)); +} + + +/* Update the liberties of a string from scratch, first pushing the + * old information. + */ + +static void +update_liberties(int s) +{ + int pos; + int k; + + /* Push the old information. */ + PUSH_VALUE(string[s].liberties); + for (k = 0; k < string[s].liberties && k < MAX_LIBERTIES; k++) { + PUSH_VALUE(string_libs[s].list[k]); + } + string[s].liberties = 0; + + /* Clear the liberty mark. */ + liberty_mark++; + + /* Traverse the stones of the string, by following the cyclic chain. */ + pos = FIRST_STONE(s); + do { + /* Look in each direction for new liberties. Mark already visited + * liberties. + */ + if (UNMARKED_LIBERTY(SOUTH(pos))) { + ADD_AND_MARK_LIBERTY(s, SOUTH(pos)); + } + + if (UNMARKED_LIBERTY(WEST(pos))) { + ADD_AND_MARK_LIBERTY(s, WEST(pos)); + } + + if (UNMARKED_LIBERTY(NORTH(pos))) { + ADD_AND_MARK_LIBERTY(s, NORTH(pos)); + } + + if (UNMARKED_LIBERTY(EAST(pos))) { + ADD_AND_MARK_LIBERTY(s, EAST(pos)); + } + + pos = NEXT_STONE(pos); + } while (!BACK_TO_FIRST_STONE(s, pos)); +} + + +/* Remove a string from the list of neighbors and push the changed + * information. + */ + +static void +remove_neighbor(int str_number, int n) +{ + int k; + int done = 0; + struct string_data *s = &string[str_number]; + struct string_neighbors_data *sn = &string_neighbors[str_number]; + for (k = 0; k < s->neighbors; k++) + if (sn->list[k] == n) { + /* We need to push the last entry too because it may become + * destroyed later. + */ + PUSH_VALUE(sn->list[s->neighbors - 1]); + PUSH_VALUE(sn->list[k]); + PUSH_VALUE(s->neighbors); + sn->list[k] = sn->list[s->neighbors - 1]; + s->neighbors--; + done = 1; + break; + } + gg_assert(done); +} + + +/* Remove one liberty from the list of liberties, pushing changed + * information. If the string had more liberties than the size of the + * list, rebuild the list from scratch. + */ + +static void +remove_liberty(int str_number, int pos) +{ + int k; + struct string_data *s = &string[str_number]; + struct string_liberties_data *sl = &string_libs[str_number]; + + if (s->liberties > MAX_LIBERTIES) + update_liberties(str_number); + else { + for (k = 0; k < s->liberties; k++) + if (sl->list[k] == pos) { + /* We need to push the last entry too because it may become + * destroyed later. + */ + PUSH_VALUE(sl->list[s->liberties - 1]); + PUSH_VALUE(sl->list[k]); + PUSH_VALUE(s->liberties); + sl->list[k] = sl->list[s->liberties - 1]; + s->liberties--; + break; + } + } +} + + +/* Remove a string from the board, pushing necessary information to + * restore it. Return the number of removed stones. + */ + +static int +do_remove_string(int s) +{ + int pos; + int k; + int size = string[s].size; + + /* Traverse the stones of the string, by following the cyclic chain. */ + pos = FIRST_STONE(s); + do { + /* Push color, string number and cyclic chain pointers. */ + PUSH_VALUE(string_number[pos]); + PUSH_VALUE(next_stone[pos]); + DO_REMOVE_STONE(pos); + pos = NEXT_STONE(pos); + } while (!BACK_TO_FIRST_STONE(s, pos)); + + /* The neighboring strings have obtained some new liberties and lost + * a neighbor. For speed reasons we handle two most common cases + * when string size is 1 or 2 stones here instead of calling + * update_liberties(). + */ + if (size == 1) { + for (k = 0; k < string[s].neighbors; k++) { + int neighbor = string_neighbors[s].list[k]; + + remove_neighbor(neighbor, s); + PUSH_VALUE(string[neighbor].liberties); + + if (string[neighbor].liberties < MAX_LIBERTIES) + string_libs[neighbor].list[string[neighbor].liberties] = pos; + string[neighbor].liberties++; + } + } + else if (size == 2) { + int other = OTHER_COLOR(string[s].color); + int pos2 = NEXT_STONE(pos); + + for (k = 0; k < string[s].neighbors; k++) { + int neighbor = string_neighbors[s].list[k]; + + remove_neighbor(neighbor, s); + PUSH_VALUE(string[neighbor].liberties); + + if (NEIGHBOR_OF_STRING(pos, neighbor, other)) { + if (string[neighbor].liberties < MAX_LIBERTIES) + string_libs[neighbor].list[string[neighbor].liberties] = pos; + string[neighbor].liberties++; + } + + if (NEIGHBOR_OF_STRING(pos2, neighbor, other)) { + if (string[neighbor].liberties < MAX_LIBERTIES) + string_libs[neighbor].list[string[neighbor].liberties] = pos2; + string[neighbor].liberties++; + } + } + } + else { + for (k = 0; k < string[s].neighbors; k++) { + remove_neighbor(string_neighbors[s].list[k], s); + update_liberties(string_neighbors[s].list[k]); + } + } + + /* Update the number of captured stones. These are assumed to + * already have been pushed. + */ + if (string[s].color == WHITE) + white_captured += size; + else + black_captured += size; + + return size; +} + + +/* We have played an isolated new stone and need to create a new + * string for it. + */ +static void +create_new_string(int pos) +{ + int s; + int color = board[pos]; + int other = OTHER_COLOR(color); + + /* Get the next free string number. */ + PUSH_VALUE(next_string); + s = next_string++; + PARANOID1(s < MAX_STRINGS, pos); + string_number[pos] = s; + /* Set up a size one cycle for the string. */ + next_stone[pos] = pos; + + /* Set trivially known values and initialize the rest to zero. */ + string[s].color = color; + string[s].size = 1; + string[s].origin = pos; + string[s].liberties = 0; + string[s].neighbors = 0; + string[s].mark = 0; + + /* Clear the string mark. */ + string_mark++; + + /* In each direction, look for a liberty or a nonmarked opponent + * neighbor. Mark visited neighbors. There is no need to mark the + * liberties since we can't find them twice. */ + if (LIBERTY(SOUTH(pos))) { + ADD_LIBERTY(s, SOUTH(pos)); + } + else if (UNMARKED_COLOR_STRING(SOUTH(pos), other)) { + int s2 = string_number[SOUTH(pos)]; + /* Add the neighbor to our list. */ + ADD_NEIGHBOR(s, SOUTH(pos)); + /* Add us to our neighbor's list. */ + PUSH_VALUE(string[s2].neighbors); + ADD_NEIGHBOR(s2, pos); + MARK_STRING(SOUTH(pos)); + } + + if (LIBERTY(WEST(pos))) { + ADD_LIBERTY(s, WEST(pos)); + } + else if (UNMARKED_COLOR_STRING(WEST(pos), other)) { + int s2 = string_number[WEST(pos)]; + /* Add the neighbor to our list. */ + ADD_NEIGHBOR(s, WEST(pos)); + /* Add us to our neighbor's list. */ + PUSH_VALUE(string[s2].neighbors); + ADD_NEIGHBOR(s2, pos); + MARK_STRING(WEST(pos)); + } + + if (LIBERTY(NORTH(pos))) { + ADD_LIBERTY(s, NORTH(pos)); + } + else if (UNMARKED_COLOR_STRING(NORTH(pos), other)) { + int s2 = string_number[NORTH(pos)]; + /* Add the neighbor to our list. */ + ADD_NEIGHBOR(s, NORTH(pos)); + /* Add us to our neighbor's list. */ + PUSH_VALUE(string[s2].neighbors); + ADD_NEIGHBOR(s2, pos); + MARK_STRING(NORTH(pos)); + } + + if (LIBERTY(EAST(pos))) { + ADD_LIBERTY(s, EAST(pos)); + } + else if (UNMARKED_COLOR_STRING(EAST(pos), other)) { + int s2 = string_number[EAST(pos)]; + /* Add the neighbor to our list. */ + ADD_NEIGHBOR(s, EAST(pos)); + /* Add us to our neighbor's list. */ + PUSH_VALUE(string[s2].neighbors); + ADD_NEIGHBOR(s2, pos); + /* No need to mark since no visits left. */ +#if 0 + MARK_STRING(EAST(pos)); +#endif + } +} + + +/* We have played a stone with exactly one friendly neighbor. Add the + * new stone to that string. + */ +static void +extend_neighbor_string(int pos, int s) +{ + int k; + int liberties_updated = 0; + int color = board[pos]; + int other = OTHER_COLOR(color); + + /* Link in the stone in the cyclic list. */ + int pos2 = string[s].origin; + next_stone[pos] = next_stone[pos2]; + PUSH_VALUE(next_stone[pos2]); + next_stone[pos2] = pos; + + /* Do we need to update the origin? */ + if (pos < pos2) { + PUSH_VALUE(string[s].origin); + string[s].origin = pos; + } + + string_number[pos] = s; + + /* The size of the string has increased by one. */ + PUSH_VALUE(string[s].size); + string[s].size++; + + /* If s has too many liberties, we don't know where they all are and + * can't update the liberties with the algorithm we otherwise + * use. In that case we can only recompute the liberties from + * scratch. + */ + if (string[s].liberties > MAX_LIBERTIES) { + update_liberties(s); + liberties_updated = 1; + } + else { + /* The place of the new stone is no longer a liberty. */ + remove_liberty(s, pos); + } + + /* Mark old neighbors of the string. */ + string_mark++; + for (k = 0; k < string[s].neighbors; k++) + string[string_neighbors[s].list[k]].mark = string_mark; + + /* Look at the neighbor locations of pos for new liberties and/or + * neighbor strings. + */ + + /* If we find a liberty, look two steps away to determine whether + * this already is a liberty of s. + */ + if (LIBERTY(SOUTH(pos))) { + if (!liberties_updated + && !NON_SOUTH_NEIGHBOR_OF_STRING(SOUTH(pos), s, color)) + ADD_LIBERTY(s, SOUTH(pos)); + } + else if (UNMARKED_COLOR_STRING(SOUTH(pos), other)) { + int s2 = string_number[SOUTH(pos)]; + PUSH_VALUE(string[s].neighbors); + ADD_NEIGHBOR(s, SOUTH(pos)); + PUSH_VALUE(string[s2].neighbors); + ADD_NEIGHBOR(s2, pos); + MARK_STRING(SOUTH(pos)); + } + + if (LIBERTY(WEST(pos))) { + if (!liberties_updated + && !NON_WEST_NEIGHBOR_OF_STRING(WEST(pos), s, color)) + ADD_LIBERTY(s, WEST(pos)); + } + else if (UNMARKED_COLOR_STRING(WEST(pos), other)) { + int s2 = string_number[WEST(pos)]; + PUSH_VALUE(string[s].neighbors); + ADD_NEIGHBOR(s, WEST(pos)); + PUSH_VALUE(string[s2].neighbors); + ADD_NEIGHBOR(s2, pos); + MARK_STRING(WEST(pos)); + } + + if (LIBERTY(NORTH(pos))) { + if (!liberties_updated + && !NON_NORTH_NEIGHBOR_OF_STRING(NORTH(pos), s, color)) + ADD_LIBERTY(s, NORTH(pos)); + } + else if (UNMARKED_COLOR_STRING(NORTH(pos), other)) { + int s2 = string_number[NORTH(pos)]; + PUSH_VALUE(string[s].neighbors); + ADD_NEIGHBOR(s, NORTH(pos)); + PUSH_VALUE(string[s2].neighbors); + ADD_NEIGHBOR(s2, pos); + MARK_STRING(NORTH(pos)); + } + + if (LIBERTY(EAST(pos))) { + if (!liberties_updated + && !NON_EAST_NEIGHBOR_OF_STRING(EAST(pos), s, color)) + ADD_LIBERTY(s, EAST(pos)); + } + else if (UNMARKED_COLOR_STRING(EAST(pos), other)) { + int s2 = string_number[EAST(pos)]; + PUSH_VALUE(string[s].neighbors); + ADD_NEIGHBOR(s, EAST(pos)); + PUSH_VALUE(string[s2].neighbors); + ADD_NEIGHBOR(s2, pos); +#if 0 + MARK_STRING(EAST(pos)); +#endif + } + +} + + +/* Incorporate the string at pos with the string s. + */ + +static void +assimilate_string(int s, int pos) +{ + int k; + int last; + int s2 = string_number[pos]; + string[s].size += string[s2].size; + + /* Walk through the s2 stones and change string number. Also pick up + * the last stone in the cycle for later use. + */ + pos = FIRST_STONE(s2); + do { + PUSH_VALUE(string_number[pos]); + string_number[pos] = s; + last = pos; + pos = NEXT_STONE(pos); + } while (!BACK_TO_FIRST_STONE(s2, pos)); + + /* Link the two cycles together. */ + { + int pos2 = string[s].origin; + PUSH_VALUE(next_stone[last]); + PUSH_VALUE(next_stone[pos2]); + next_stone[last] = next_stone[pos2]; + next_stone[pos2] = string[s2].origin; + + /* Do we need to update the origin? */ + if (string[s2].origin < pos2) + string[s].origin = string[s2].origin; + } + + /* Pick up the liberties of s2 that we don't already have. + * It is assumed that the liberties of s have been marked before + * this function is called. + */ + if (string[s2].liberties <= MAX_LIBERTIES) { + for (k = 0; k < string[s2].liberties; k++) { + int pos2 = string_libs[s2].list[k]; + if (UNMARKED_LIBERTY(pos2)) { + ADD_AND_MARK_LIBERTY(s, pos2); + } + } + } + else { + /* If s2 had too many liberties the above strategy wouldn't be + * effective, since not all liberties are listed in + * libs[] the chain of stones for s2 is no + * longer available (it has already been merged with s) so we + * can't reconstruct the s2 liberties. Instead we capitulate and + * rebuild the list of liberties for s (including the neighbor + * strings assimilated so far) from scratch. + */ + liberty_mark++; /* Reset the mark. */ + string[s].liberties = 0; /* To avoid pushing the current list. */ + update_liberties(s); + } + + /* Remove s2 as neighbor to the neighbors of s2 and instead add s if + * they don't already have added it. Also add the neighbors of s2 as + * neighbors of s, unless they already have been added. The already + * known neighbors of s are assumed to have been marked before this + * function is called. + */ + for (k = 0; k < string[s2].neighbors; k++) { + int t = string_neighbors[s2].list[k]; + remove_neighbor(t, s2); + if (string[t].mark != string_mark) { + PUSH_VALUE(string[t].neighbors); + string_neighbors[t].list[string[t].neighbors++] = s; + string_neighbors[s].list[string[s].neighbors++] = t; + string[t].mark = string_mark; + } + } +} + + +/* Create a new string for the stone at pos and assimilate all + * friendly neighbor strings. + */ + +static void +assimilate_neighbor_strings(int pos) +{ + int s; + int color = board[pos]; + int other = OTHER_COLOR(color); + + /* Get the next free string number. */ + PUSH_VALUE(next_string); + s = next_string++; + PARANOID1(s < MAX_STRINGS, pos); + string_number[pos] = s; + /* Set up a size one cycle for the string. */ + next_stone[pos] = pos; + + /* Set trivially known values and initialize the rest to zero. */ + string[s].color = color; + string[s].size = 1; + string[s].origin = pos; + string[s].liberties = 0; + string[s].neighbors = 0; + + /* Clear the marks. */ + liberty_mark++; + string_mark++; + + /* Mark ourselves. */ + string[s].mark = string_mark; + + /* Look in each direction for + * + * 1. liberty: Add if not already visited. + * 2. opponent string: Add it among our neighbors and us among its + * neighbors, unless already visited. + * 3. friendly string: Assimilate. + */ + if (UNMARKED_LIBERTY(SOUTH(pos))) { + ADD_AND_MARK_LIBERTY(s, SOUTH(pos)); + } + else if (UNMARKED_COLOR_STRING(SOUTH(pos), other)) { + ADD_NEIGHBOR(s, SOUTH(pos)); + PUSH_VALUE(string[string_number[SOUTH(pos)]].neighbors); + ADD_NEIGHBOR(string_number[SOUTH(pos)], pos); + MARK_STRING(SOUTH(pos)); + } + else if (UNMARKED_COLOR_STRING(SOUTH(pos), color)) { + assimilate_string(s, SOUTH(pos)); + } + + if (UNMARKED_LIBERTY(WEST(pos))) { + ADD_AND_MARK_LIBERTY(s, WEST(pos)); + } + else if (UNMARKED_COLOR_STRING(WEST(pos), other)) { + ADD_NEIGHBOR(s, WEST(pos)); + PUSH_VALUE(string[string_number[WEST(pos)]].neighbors); + ADD_NEIGHBOR(string_number[WEST(pos)], pos); + MARK_STRING(WEST(pos)); + } + else if (UNMARKED_COLOR_STRING(WEST(pos), color)) { + assimilate_string(s, WEST(pos)); + } + + if (UNMARKED_LIBERTY(NORTH(pos))) { + ADD_AND_MARK_LIBERTY(s, NORTH(pos)); + } + else if (UNMARKED_COLOR_STRING(NORTH(pos), other)) { + ADD_NEIGHBOR(s, NORTH(pos)); + PUSH_VALUE(string[string_number[NORTH(pos)]].neighbors); + ADD_NEIGHBOR(string_number[NORTH(pos)], pos); + MARK_STRING(NORTH(pos)); + } + else if (UNMARKED_COLOR_STRING(NORTH(pos), color)) { + assimilate_string(s, NORTH(pos)); + } + + if (UNMARKED_LIBERTY(EAST(pos))) { +#if 0 + ADD_AND_MARK_LIBERTY(s, EAST(pos)); +#else + ADD_LIBERTY(s, EAST(pos)); +#endif + } + else if (UNMARKED_COLOR_STRING(EAST(pos), other)) { + ADD_NEIGHBOR(s, EAST(pos)); + PUSH_VALUE(string[string_number[EAST(pos)]].neighbors); + ADD_NEIGHBOR(string_number[EAST(pos)], pos); +#if 0 + MARK_STRING(EAST(pos)); +#endif + } + else if (UNMARKED_COLOR_STRING(EAST(pos), color)) { + assimilate_string(s, EAST(pos)); + } +} + + +/* Suicide at `pos' (the function assumes that the move is indeed suicidal). + * Remove the neighboring friendly strings. + */ + +static void +do_commit_suicide(int pos, int color) +{ + if (board[SOUTH(pos)] == color) + do_remove_string(string_number[SOUTH(pos)]); + + if (board[WEST(pos)] == color) + do_remove_string(string_number[WEST(pos)]); + + if (board[NORTH(pos)] == color) + do_remove_string(string_number[NORTH(pos)]); + + if (board[EAST(pos)] == color) + do_remove_string(string_number[EAST(pos)]); + + /* Count the stone we "played" as captured. */ + if (color == WHITE) + white_captured++; + else + black_captured++; +} + + +/* Play a move without legality checking. This is a low-level function, + * it assumes that the move is not a suicide. Such cases must be handled + * where the function is called. + */ + +static void +do_play_move(int pos, int color) +{ + int other = OTHER_COLOR(color); + int captured_stones = 0; + int neighbor_allies = 0; + int s = -1; + + /* Clear string mark. */ + string_mark++; + + /* Put down the stone. We also set its string number to -1 for a while + * so that NEIGHBOR_OF_STRING() and friends don't get confused with the + * stone. + */ + DO_ADD_STONE(pos, color); + string_number[pos] = -1; + + /* Look in all directions. Count the number of neighbor strings of the same + * color, remove captured strings and remove `pos' as liberty for opponent + * strings that are not captured. + */ + if (board[SOUTH(pos)] == color) { + neighbor_allies++; + s = string_number[SOUTH(pos)]; + MARK_STRING(SOUTH(pos)); + } + else if (board[SOUTH(pos)] == other) { + if (LIBERTIES(SOUTH(pos)) > 1) { + remove_liberty(string_number[SOUTH(pos)], pos); + MARK_STRING(SOUTH(pos)); + } + else + captured_stones += do_remove_string(string_number[SOUTH(pos)]); + } + + if (UNMARKED_COLOR_STRING(WEST(pos), color)) { + neighbor_allies++; + s = string_number[WEST(pos)]; + MARK_STRING(WEST(pos)); + } + else if (UNMARKED_COLOR_STRING(WEST(pos), other)) { + if (LIBERTIES(WEST(pos)) > 1) { + remove_liberty(string_number[WEST(pos)], pos); + MARK_STRING(WEST(pos)); + } + else + captured_stones += do_remove_string(string_number[WEST(pos)]); + } + + if (UNMARKED_COLOR_STRING(NORTH(pos), color)) { + neighbor_allies++; + s = string_number[NORTH(pos)]; + MARK_STRING(NORTH(pos)); + } + else if (UNMARKED_COLOR_STRING(NORTH(pos), other)) { + if (LIBERTIES(NORTH(pos)) > 1) { + remove_liberty(string_number[NORTH(pos)], pos); + MARK_STRING(NORTH(pos)); + } + else + captured_stones += do_remove_string(string_number[NORTH(pos)]); + } + + if (UNMARKED_COLOR_STRING(EAST(pos), color)) { + neighbor_allies++; + s = string_number[EAST(pos)]; +#if 0 + MARK_STRING(EAST(pos)); +#endif + } + else if (UNMARKED_COLOR_STRING(EAST(pos), other)) { + if (LIBERTIES(EAST(pos)) > 1) { + remove_liberty(string_number[EAST(pos)], pos); +#if 0 + MARK_STRING(EAST(pos)); +#endif + } + else + captured_stones += do_remove_string(string_number[EAST(pos)]); + } + + /* Choose strategy depending on the number of friendly neighbors. */ + if (neighbor_allies == 0) + create_new_string(pos); + else if (neighbor_allies == 1) { + gg_assert(s >= 0); + extend_neighbor_string(pos, s); + return; /* can't be a ko, we're done */ + } + else { + assimilate_neighbor_strings(pos); + return; /* can't be a ko, we're done */ + } + + /* Check whether this move was a ko capture and if so set + * board_ko_pos. + * + * No need to push board_ko_pos on the stack, + * because this has been done earlier. + */ + s = string_number[pos]; + if (string[s].liberties == 1 + && string[s].size == 1 + && captured_stones == 1) { + /* In case of a double ko: clear old ko position first. */ + if (board_ko_pos != NO_MOVE) + hashdata_invert_ko(&board_hash, board_ko_pos); + board_ko_pos = string_libs[s].list[0]; + hashdata_invert_ko(&board_hash, board_ko_pos); + } +} + + + +/* ================================================================ * + * The following functions don't actually belong here. They are + * only here because they are faster here where they have access to + * the incremental data structures. + * ================================================================ */ + + +/* Help collect the data needed by order_moves() in reading.c. + * It's the caller's responsibility to initialize the result parameters. + */ +#define NO_UNROLL 0 +void +incremental_order_moves(int move, int color, int str, + int *number_edges, int *number_same_string, + int *number_own, int *number_opponent, + int *captured_stones, int *threatened_stones, + int *saved_stones, int *number_open) +{ +#if NO_UNROLL == 1 + int pos; + int k; + + /* Clear the string mark. */ + string_mark++; + + for (k = 0; k < 4; k++) { + pos = move + delta[k]; + if (!ON_BOARD(pos)) + (*number_edges)++; + else if (board[pos] == EMPTY) + (*number_open)++; + else { + int s = string_number[pos]; + if (string_number[str] == s) + (*number_same_string)++; + + if (board[pos] == color) { + (*number_own)++; + if (string[s].liberties == 1) + (*saved_stones) += string[s].size; + } + else { + (*number_opponent)++; + if (string[s].liberties == 1) { + int r; + struct string_data *t; + (*captured_stones) += string[s].size; + for (r = 0; r < string[s].neighbors; r++) { + t = &string[string[s].neighborlist[r]]; + if (t->liberties == 1) + (*saved_stones) += t->size; + } + } + else if (string[s].liberties == 2 && UNMARKED_STRING(pos)) { + (*threatened_stones) += string[s].size; + MARK_STRING(pos); + } + } + } + } + +#else +#define code1(arg) \ + if (!ON_BOARD(arg)) \ + (*number_edges)++; \ + else if (board[arg] == EMPTY) \ + (*number_open)++; \ + else { \ + int s = string_number[arg]; \ + if (string_number[str] == s) \ + (*number_same_string)++; \ + if (board[arg] == color) { \ + (*number_own)++; \ + if (string[s].liberties == 1) \ + (*saved_stones) += string[s].size; \ + } \ + else { \ + (*number_opponent)++; \ + if (string[s].liberties == 1) { \ + int r; \ + struct string_data *t; \ + (*captured_stones) += string[s].size; \ + for (r = 0; r < string[s].neighbors; r++) { \ + t = &string[string_neighbors[s].list[r]]; \ + if (t->liberties == 1) \ + (*saved_stones) += t->size; \ + } \ + } \ + else if (string[s].liberties == 2 && UNMARKED_STRING(arg)) { \ + (*threatened_stones) += string[s].size; \ + MARK_STRING(arg); \ + } \ + } \ + } + + /* Clear the string mark. */ + string_mark++; + + code1(SOUTH(move)); + code1(WEST(move)); + code1(NORTH(move)); + code1(EAST(move)); +#endif +} + + +int +square_dist(int pos1, int pos2) +{ + int idist = I(pos1) - I(pos2); + int jdist = J(pos1) - J(pos2); + return idist*idist + jdist*jdist; +} + + +/* + * Local Variables: + * tab-width: 8 + * c-basic-offset: 2 + * End: + */ Index: apps/plugins/goban/hash.h =================================================================== --- apps/plugins/goban/hash.h (revision 0) +++ apps/plugins/goban/hash.h (revision 0) @@ -0,0 +1,157 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ + * 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 and 2006 * + * 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., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02111, USA. * +\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _HASH_H_ +#define _HASH_H_ + +#include + + +/* + * This file, together with engine/hash.c implements hashing of go positions + * using a method known as Zobrist hashing. See the Texinfo documentation + * (Reading/Hashing) for more information. + */ + +/* Hash values and the compact board representation should use the + * longest integer type that the platform can handle efficiently. + * Typically this would be a 32 bit integer on a 32 bit platform and a + * 64 bit integer on a 64 bit platform. + * + * Our current assumption is that unsigned long has this + * characteristic. Should it turn out to be false for some platform + * we'll add conditional code to choose some other type. + * + * At the few places in the code where the actual size of these types + * matter, the code should use sizeof(type) to test for this. Notice + * that ISO C guarantees a long to be at least 32 bits. + * + * On (future) platforms with word length 128 bits or more, it might + * be a waste to use more than 64 bit hashvalues, since the decreased + * risk for hash collisions probably isn't worth the increased storage + * cost. + */ +typedef unsigned long Hashvalue; +#define SIZEOF_HASHVALUE 4 +#define HASHVALUE_PRINT_FORMAT "%0*lx" + +/* for testing: Enables a lot of checks. */ +#define CHECK_HASHING 0 + +/* Dump (almost) all read results. */ +#define TRACE_READ_RESULTS 0 + +/* How many bits should be used at least for hashing? Set this to 32 for + * some memory save and speedup, at the cost of occasional irreproducable + * mistakes (and possibly assertion failures). + * With 64 bits, there should be less than one such mistake in 10^9 games. + * Set this to 96 if this is not safe enough for you. + */ +#define MIN_HASHBITS 32 + + +#define NUM_HASHVALUES (1 + (MIN_HASHBITS - 1) / (CHAR_BIT * SIZEOF_HASHVALUE)) + +/* This struct is maintained by the machinery that updates the board + * to provide incremental hashing. Examples: trymove(), play_move(), ... + */ + +typedef struct { + Hashvalue hashval[NUM_HASHVALUES]; +} Hash_data; + +extern Hash_data board_hash; + +Hash_data goal_to_hashvalue(const signed char *goal); + +void hash_init_zobrist_array(Hash_data *array, int size); +void hash_init(void); +#define INIT_ZOBRIST_ARRAY(a) \ + hash_init_zobrist_array(a, (int) (sizeof(a) / sizeof(a[0]))) + +void hashdata_clear(Hash_data *hd); +void hashdata_recalc(Hash_data *hd, Intersection *board, int ko_pos); +void hashdata_invert_ko(Hash_data *hd, int pos); +void hashdata_invert_stone(Hash_data *hd, int pos, int color); +void hashdata_invert_komaster(Hash_data *hd, int komaster); +void hashdata_invert_kom_pos(Hash_data *hd, int kom_pos); + +char *hashdata_to_string(Hash_data *hashdata); + + + +/* ---------------------------------------------------------------- */ + +/* There is no need to involve all bits in the remainder computation + * as long as we only use it to compute a key into a hash table. 32 + * random bits are sufficient to get an even distribution within any + * hashtable of reasonable size. By never using more than 32 bits we + * also reduce the platform dependency of the GNU Go engine. +*/ +#define hashdata_remainder(hd, num) \ + (((hd).hashval[0] & 0xffffffffU) % (num)) + +#if NUM_HASHVALUES == 1 + +#define hashdata_is_equal(hd1, hd2) \ + ((hd1).hashval[0] == (hd2).hashval[0]) + +#define hashdata_xor(hd1, hd2) \ + (hd1).hashval[0] ^= (hd2).hashval[0] + +#elif NUM_HASHVALUES == 2 + +#define hashdata_is_equal(hd1, hd2) \ + ((hd1).hashval[0] == (hd2).hashval[0] \ + && (hd1).hashval[1] == (hd2).hashval[1]) + +#define hashdata_xor(hd1, hd2) \ + do { \ + (hd1).hashval[0] ^= (hd2).hashval[0]; \ + (hd1).hashval[1] ^= (hd2).hashval[1]; \ + } while (0) + +#else + +int hashdata_is_equal_func(Hash_data *hd1, Hash_data *hd2); + +#define hashdata_is_equal(hd1, hd2) \ + hashdata_is_equal_func(&(hd1), &(hd2)) + +#define hashdata_xor(hd1, hd2) \ + do { \ + int i; \ + for (i = 0; i < NUM_HASHVALUES; i++) \ + (hd1).hashval[i] ^= (hd2).hashval[i]; \ + } while (0) + +#endif + +#endif + + +/* + * Local Variables: + * tab-width: 8 + * c-basic-offset: 2 + * End: + */ Index: apps/plugins/goban/goban.h =================================================================== --- apps/plugins/goban/goban.h (revision 0) +++ apps/plugins/goban/goban.h (revision 0) @@ -0,0 +1,149 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007 Joshua Simmons * + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + + +#ifdef HAVE_LCD_COLOR +#define BOARD_COLOR LCD_RGBPACK(184,136,72) +#define WHITE_COLOR LCD_RGBPACK(255,255,255) +#define BLACK_COLOR LCD_RGBPACK(0,0,0) +#define LINE_COLOR LCD_RGBPACK(0,0,0) +#define BACKGROUND_COLOR LCD_RGBPACK(41,104,74) +#define CURSOR_COLOR LCD_RGBPACK(255,0,0) +#elif LCD_DEPTH > 1 /* grayscale */ +#define BOARD_COLOR LCD_LIGHTGRAY +#define WHITE_COLOR LCD_WHITE +#define BLACK_COLOR LCD_BLACK +#define LINE_COLOR LCD_BLACK +#define BACKGROUND_COLOR LCD_DARKGRAY +#define CURSOR_COLOR LCD_DARKGRAY +#endif + +#if (CONFIG_KEYPAD == SANSA_E200_PAD) +#define SGFBUTT_UP BUTTON_UP +#define SGFBUTT_DOWN BUTTON_DOWN +#define SGFBUTT_RIGHT BUTTON_RIGHT +#define SGFBUTT_LEFT BUTTON_LEFT +#define SGFBUTT_RETREAT BUTTON_SCROLL_UP +#define SGFBUTT_ADVANCE BUTTON_SCROLL_DOWN +#define SGFBUTT_PLAY BUTTON_SELECT +#define SGFBUTT_VAR_TOG BUTTON_REC +#define SGFBUTT_MENU BUTTON_POWER +#elif (CONFIG_KEYPAD == GIGABEAT_PAD) +#define SGFBUTT_UP BUTTON_UP +#define SGFBUTT_DOWN BUTTON_DOWN +#define SGFBUTT_RIGHT BUTTON_RIGHT +#define SGFBUTT_LEFT BUTTON_LEFT +#define SGFBUTT_RETREAT BUTTON_VOL_DOWN +#define SGFBUTT_ADVANCE BUTTON_VOL_UP +#define SGFBUTT_PLAY BUTTON_SELECT +#define SGFBUTT_VAR_TOG BUTTON_A +#define SGFBUTT_MENU BUTTON_MENU +#elif (CONFIG_KEYPAD == IRIVER_H10_PAD) +#define SGFBUTT_UP BUTTON_SCROLL_UP +#define SGFBUTT_DOWN BUTTON_SCROLL_DOWN +#define SGFBUTT_RIGHT BUTTON_RIGHT +#define SGFBUTT_LEFT BUTTON_LEFT +#define SGFBUTT_RETREAT BUTTON_FF +#define SGFBUTT_ADVANCE BUTTON_REW +#define SGFBUTT_PLAY BUTTON_PLAY | BUTTON_REL +#define SGFBUTT_VAR_TOG BUTTON_POWER +#define SGFBUTT_MENU BUTTON_PLAY | BUTTON_REPEAT +#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \ + (CONFIG_KEYPAD == IPOD_3G_PAD) +#define SGFBUTT_UP BUTTON_MENU +#define SGFBUTT_DOWN BUTTON_PLAY +#define SGFBUTT_RIGHT BUTTON_RIGHT +#define SGFBUTT_LEFT BUTTON_LEFT +#define SGFBUTT_RETREAT BUTTON_SCROLL_FWD +#define SGFBUTT_ADVANCE BUTTON_SCROLL_BACK +#define SGFBUTT_PLAY BUTTON_SELECT | BUTTON_REL +#define SGFBUTT_VAR_TOG BUTTON_NONE /* what can i do for this? */ +#define SGFBUTT_MENU BUTTON_SELECT | BUTTON_REPEAT +#elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \ + (CONFIG_KEYPAD == IRIVER_H300_PAD) +#define SGFBUTT_UP BUTTON_UP +#define SGFBUTT_DOWN BUTTON_DOWN +#define SGFBUTT_RIGHT BUTTON_RIGHT +#define SGFBUTT_LEFT BUTTON_LEFT +#define SGFBUTT_RETREAT BUTTON_OFF +#define SGFBUTT_ADVANCE BUTTON_ON +#define SGFBUTT_PLAY BUTTON_SELECT +#define SGFBUTT_VAR_TOG BUTTON_REC +#define SGFBUTT_MENU BUTTON_MODE +#elif (CONFIG_KEYPAD == IAUDIO_X5M5_PAD) +#define SGFBUTT_UP BUTTON_UP +#define SGFBUTT_DOWN BUTTON_DOWN +#define SGFBUTT_RIGHT BUTTON_RIGHT +#define SGFBUTT_LEFT BUTTON_LEFT +#define SGFBUTT_RETREAT BUTTON_PLAY +#define SGFBUTT_ADVANCE BUTTON_REC +#define SGFBUTT_PLAY BUTTON_SELECT | BUTTON_REL +#define SGFBUTT_VAR_TOG BUTTON_POWER +#define SGFBUTT_MENU BUTTON_SELECT | BUTTON_REPEAT + +#else +#error keypad isn't set up on your player +#endif + + + +#define MARK_VARIATION 0 +#define MARK_SQUARE 1 +#define MARK_CIRCLE 2 +#define MARK_TRIANGLE 3 +#define MARK_LAST_MOVE 4 + + + + + + +/* 0-7 */ +/* 0 = normal + * 1 = 90 right rot + * 2 = 180 + * 3 = 270 + * 4 - 7 are various flips */ +#define BOARD_ORIENTATION 4 + +#if (BOARD_ORIENTATION == 0) +#define REV_ORIENTATION BOARD_ORIENTATION +#elif (BOARD_ORIENTATION < 4) +#define REV_ORIENTATION (4 - BOARD_ORIENTATION) +#elif (BOARD_ORIENTATION >= 4 && BOARD_ORIENTATION <= 7) +#define REV_ORIENTATION BOARD_ORIENTATION +#else +#error invalid board orientation selected +#endif + +#define LCD_MIN_DIMENSION (LCD_HEIGHT > LCD_WIDTH ? LCD_WIDTH : LCD_HEIGHT) + +#define MAX_BOARD_SIZE 37 + +#define MAX_CIRCLE_SIZE 512 + +#define DEFAULT_SAVE "/default.sgf" + +#if (LCD_DEPTH == 1) +#define OUTLINE_STONES 1 +#else +#define OUTLINE_STONES 0 +#endif + +#define min(x, y) (x < y ? x : y); Index: apps/plugins/goban/Makefile =================================================================== --- apps/plugins/goban/Makefile (revision 0) +++ apps/plugins/goban/Makefile (revision 0) @@ -0,0 +1,111 @@ +# __________ __ ___. +# Open \______ \ ____ ____ | | _\_ |__ _______ ___ +# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / +# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < +# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ +# \/ \/ \/ \/ \/ +# $Id: Makefile 12318 2007-02-15 20:28:08Z amiconn $ +# + +INCLUDES = -I$(APPSDIR) -I.. -I. $(TARGET_INC) -I$(FIRMDIR)/include -I$(FIRMDIR)/export \ + -I$(FIRMDIR)/common -I$(FIRMDIR)/drivers -I$(OUTDIR) -I$(BUILDDIR) \ + -I$(BUILDDIR)/pluginbitmaps +CFLAGS = $(INCLUDES) $(GCCOPTS) $(TARGET) $(EXTRA_DEFINES) \ + -DTARGET_ID=$(TARGET_ID) -DMEM=${MEMORYSIZE} -DPLUGIN + +ifdef APPEXTRA + INCLUDES += $(patsubst %,-I$(APPSDIR)/%,$(subst :, ,$(APPEXTRA))) +endif + +LINKFILE := $(OBJDIR)/link.lds +DEPFILE = $(OBJDIR)/dep-sgf + +# This sets up 'SRC' based on the files mentioned in SOURCES +include $(TOOLSDIR)/makesrc.inc + +SOURCES = $(SRC) +OBJS := $(SRC:%.c=$(OBJDIR)/%.o) +DIRS = . + +ifndef SIMVER + LDS := ../plugin.lds + OUTPUT = $(OUTDIR)/goban.rock +else ## simulators + OUTPUT = $(OUTDIR)/goban.rock +endif + +all: $(OUTPUT) + +ifndef SIMVER +$(OBJDIR)/goban.elf: $(OBJS) $(LINKFILE) $(BITMAPLIBS) + $(call PRINTS,LD $(@F))$(CC) $(CFLAGS) -o $@ $(OBJS) -L$(BUILDDIR) -lplugin -lgcc \ + $(LINKBITMAPS) -T$(LINKFILE) -Wl,-Map,$(OBJDIR)/goban.map + +$(OUTPUT): $(OBJDIR)/goban.elf + $(call PRINTS,OBJCOPY $(@F))$(OC) -O binary $< $@ +else + +ifeq ($(SIMVER), x11) +################################################### +# This is the X11 simulator version + +$(OUTPUT): $(OBJS) + $(call PRINTS,LD $(@F))$(CC) $(CFLAGS) $(SHARED_FLAG) $(OBJS) -L$(BUILDDIR) -lplugin $(LINKBITMAPS) -o $@ +ifeq ($(findstring CYGWIN,$(UNAME)),CYGWIN) +# 'x' must be kept or you'll have "Win32 error 5" +# $ fgrep 5 /usr/include/w32api/winerror.h | head -1 +# #define ERROR_ACCESS_DENIED 5L +else + @chmod -x $@ +endif + +else # end of x11-simulator +ifeq ($(SIMVER), sdl) +################################################### +# This is the SDL simulator version + +$(OUTPUT): $(OBJS) + $(call PRINTS,LD $(@F))$(CC) $(CFLAGS) $(SHARED_FLAG) $(OBJS) -L$(BUILDDIR) -lplugin $(LINKBITMAPS) -o $@ +ifeq ($(findstring CYGWIN,$(UNAME)),CYGWIN) +# 'x' must be kept or you'll have "Win32 error 5" +# $ fgrep 5 /usr/include/w32api/winerror.h | head -1 +# #define ERROR_ACCESS_DENIED 5L +else + @chmod -x $@ +endif + +else # end of sdl-simulator +################################################### +# This is the win32 simulator version +DLLTOOLFLAGS = --export-all +DLLWRAPFLAGS = -s --entry _DllMain@12 --target=i386-mingw32 -mno-cygwin + +$(OUTPUT): $(OBJS) + $(call PRINTS,DLL $(@F))$(DLLTOOL) $(DLLTOOLFLAGS) -z $(OBJDIR)/$*.def $(OBJS) + $(SILENT)$(DLLWRAP) $(DLLWRAPFLAGS) --def $(OBJDIR)/$*.def $(OBJS) \ + $(BUILDDIR)/libplugin.a $(BITMAPLIBS) -o $@ +ifeq ($(findstring CYGWIN,$(UNAME)),CYGWIN) +# 'x' must be kept or you'll have "Win32 error 5" +# $ fgrep 5 /usr/include/w32api/winerror.h | head -1 +# #define ERROR_ACCESS_DENIED 5L +else + @chmod -x $@ +endif +endif # end of win32-simulator +endif +endif # end of simulator section + + +include $(TOOLSDIR)/make.inc + +# MEMORYSIZE should be passed on to this makefile with the chosen memory size +# given in number of MB +$(LINKFILE): $(LDS) + $(call PRINTS,build $(@F))cat $< | $(CC) -DMEMORYSIZE=$(MEMORYSIZE) $(INCLUDES) $(TARGET) \ + $(DEFINES) -E -P - >$@ + +clean: + $(call PRINTS,cleaning sgf)rm -rf $(OBJDIR)/goban + $(SILENT)rm -f $(OBJDIR)/goban.* $(DEPFILE) + +-include $(DEPFILE) Index: apps/plugins/goban/board.h =================================================================== --- apps/plugins/goban/board.h (revision 0) +++ apps/plugins/goban/board.h (revision 0) @@ -0,0 +1,476 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ + * 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 and 2006 * + * 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., 51 Franklin Street, Fifth Floor, * + * Boston, MA 02111, USA. * +\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef _BOARD_H_ +#define _BOARD_H_ + +#include +#include "sgftree.h" + +/* This type is used to store each intersection on the board. + * + * On a 486, char is best, since the time taken to push and pop + * becomes significant otherwise. On other platforms, an int may + * be better, e.g. if memcpy() is particularly fast, or if + * character access is very slow. + */ + +typedef unsigned char Intersection; + +/* FIXME: This is very ugly but we can't include hash.h until we have + * defined Intersection. And we do need to include it before using + * Hash_data. + */ +#include "hash.h" + +/* local versions of absolute value, min and max */ + +#define gg_abs(x) ((x) < 0 ? -(x) : (x)) +#define gg_min(a, b) ((a)<(b) ? (a) : (b)) +#define gg_max(a, b) ((a)<(b) ? (b) : (a)) + +/* Avoid compiler warnings with unused parameters */ +#define UNUSED(x) (void)x + + +/* A string with n stones can have at most 2(n+1) liberties. From this + * follows that an upper bound on the number of liberties of a string + * on a board of size N^2 is 2/3 (N^2+1). + */ +#define MAXLIBS (2*(MAX_BOARD*MAX_BOARD + 1)/3) +/* This is a smaller, practical number of liberties that we care to keep track of. */ +#define MAX_LIBERTIES 8 + + +/* This is an upper bound on the number of strings that can exist on + * the board simultaneously. Since each string must have at least one + * liberty and each empty point can provide a liberty to at most four + * strings, at least one out of five board points must be empty. + * + * FIXME: This is not sufficiently large. Above stackp==0, the + * incremental board code doesn't re-use the entries for + * removed or merged strings, while new strings require new + * entries. This is a problem only in very pathological cases, + * and is extremely unlikely to occur in practice. + * + * Actually, in the not all that pathological case of a + * repeated triple ko cycle, each move creates a new string and + * thus makes use of one more string, which relatively quickly + * will exhaust the available strings. For a safe upper bound + * MAX_STRINGS should be set to + * MAX_STACK + 4 * MAX_BOARD * MAX_BOARD / 5. + * It's not clear that it's worth the extra memory, however. + */ +#define MAX_STRINGS (4 * MAX_BOARD * MAX_BOARD / 5) + +/* Per gf: Unconditional_life() can get very close to filling the + * entire board under certain circumstances. This was discussed in + * the list around August 21, 2001, in a thread with the subject + * "gnugo bug logs". + */ +#define MAXSTACK MAX_BOARD * MAX_BOARD +#define MAXCHAIN 160 + +#define HASH_RANDOM_SEED 12345 + +/* ================================================================ * + * One-dimensional board * + * ================================================================ */ + +/* Board sizes */ + +#define DEFAULT_BOARD_SIZE 19 + +#define MIN_BOARD 1 /* Minimum supported board size. */ +#define MAX_BOARD 19 /* Maximum supported board size. */ +#define MAX_HANDICAP 9 /* Maximum supported handicap. */ +#define MAX_MOVE_HISTORY 500 /* Max number of moves remembered. */ + +/* Colors and komaster states. */ +enum colors { + EMPTY, + WHITE, + BLACK, + GRAY, + GRAY_WHITE, + GRAY_BLACK, + WEAK_KO, + NUM_KOMASTER_STATES +}; + +#define COLOR_NAMES \ + "empty", \ + "white", \ + "black", \ + "gray", \ + "gray_white", \ + "gray_black", \ + "weak_ko" + +const char *color_to_string(int color); + +#define OTHER_COLOR(color) (WHITE+BLACK-(color)) +#define IS_STONE(arg) ((arg) == WHITE || (arg) == BLACK) + +/* Note that POS(-1, -1) == 0 + * DELTA() is defined so that POS(i+di, j+dj) = POS(i, j) + DELTA(di, dj). + */ +#define BOARDSIZE ((MAX_BOARD + 2) * (MAX_BOARD + 1) + 1) +#define BOARDMIN (MAX_BOARD + 2) +#define BOARDMAX (MAX_BOARD + 1) * (MAX_BOARD + 1) +#define POS(i, j) ((MAX_BOARD + 2) + (i) * (MAX_BOARD + 1) + (j)) +#define DELTA(di, dj) ((di) * (MAX_BOARD + 1) + (dj)) +#define I(pos) ((pos) / (MAX_BOARD + 1) - 1) +#define J(pos) ((pos) % (MAX_BOARD + 1) - 1) +#define PASS_MOVE 0 +#define NO_MOVE PASS_MOVE +#define NS (MAX_BOARD + 1) +#define WE 1 +#define SOUTH(pos) ((pos) + NS) +#define WEST(pos) ((pos) - 1) +#define NORTH(pos) ((pos) - NS) +#define EAST(pos) ((pos) + 1) +#define SW(pos) ((pos) + NS - 1) +#define NW(pos) ((pos) - NS - 1) +#define NE(pos) ((pos) - NS + 1) +#define SE(pos) ((pos) + NS + 1) +#define SS(pos) ((pos) + 2 * NS) +#define WW(pos) ((pos) - 2) +#define NN(pos) ((pos) - 2 * NS) +#define EE(pos) ((pos) + 2) + +#define DIRECT_NEIGHBORS(pos1, pos2) \ + ((pos1) == SOUTH(pos2) \ + || (pos1) == WEST(pos2) \ + || (pos1) == NORTH(pos2) \ + || (pos1) == EAST(pos2)) + +#define DIAGONAL_NEIGHBORS(pos1, pos2) \ + ((pos1) == SW(pos2) \ + || (pos1) == NW(pos2) \ + || (pos1) == NE(pos2) \ + || (pos1) == SE(pos2)) + +#define BOARD(i, j) board[POS(i, j)] + + +#define MIRROR_MOVE(pos) POS(board_size - 1 - I(pos), board_size - 1 - J(pos)) + +/* ================================================================ */ +/* global variables */ +/* ================================================================ */ + +/* The board and the other parameters deciding the current position. */ +extern int board_size; /* board size (usually 19) */ +extern Intersection board[BOARDSIZE]; /* go board */ +extern int board_ko_pos; +extern int black_captured; /* num. of black stones captured */ +extern int white_captured; + +extern Intersection initial_board[BOARDSIZE]; +extern int initial_board_ko_pos; +extern int initial_white_captured; +extern int initial_black_captured; +extern int move_history_color[MAX_MOVE_HISTORY]; +extern int move_history_pos[MAX_MOVE_HISTORY]; +extern Hash_data move_history_hash[MAX_MOVE_HISTORY]; +extern int move_history_pointer; + +extern float komi; +extern int handicap; /* used internally in chinese scoring */ +extern int movenum; /* movenumber - used for debug output */ + +extern signed char shadow[BOARDMAX]; /* reading tree shadow */ + +enum suicide_rules { + FORBIDDEN, + ALLOWED, + ALL_ALLOWED +}; +extern enum suicide_rules suicide_rule; + +enum ko_rules { + SIMPLE, + NONE, + PSK, + SSK +}; +extern enum ko_rules ko_rule; + + +extern int stackp; /* stack pointer */ +extern int count_variations; /* count (decidestring) */ +extern SGFTree *sgf_dumptree; + + +/* This struct holds the internal board state. */ +struct board_state { + int board_size; + + Intersection board[BOARDSIZE]; + int board_ko_pos; + int black_captured; + int white_captured; + + Intersection initial_board[BOARDSIZE]; + int initial_board_ko_pos; + int initial_white_captured; + int initial_black_captured; + int move_history_color[MAX_MOVE_HISTORY]; + int move_history_pos[MAX_MOVE_HISTORY]; + Hash_data move_history_hash[MAX_MOVE_HISTORY]; + int move_history_pointer; + + float komi; + int handicap; + int move_number; +}; + +/* This is increased by one anytime a move is (permanently) played or + * the board is cleared. + */ +extern int position_number; + +/* ================================================================ */ +/* board.c functions */ +/* ================================================================ */ + + +/* Functions handling the permanent board state. */ +void clear_board(void); +int test_gray_border(void); +void setup_board(Intersection new_board[MAX_BOARD][MAX_BOARD], int ko_pos, + int *last, float new_komi, int w_captured, int b_captured); +void add_stone(int pos, int color); +void remove_stone(int pos); +void play_move(int pos, int color); +int undo_move(int n); + +void store_board(struct board_state *state); +void restore_board(struct board_state *state); + +/* Information about the permanent board. */ +int get_last_move(void); +int get_last_player(void); +int get_last_opponent_move(int color); +int stones_on_board(int color); + +/* Functions handling the variable board state. */ +int trymove(int pos, int color, const char *message, int str); +int tryko(int pos, int color, const char *message); +void popgo(void); +int komaster_trymove(int pos, int color, + const char *message, int str, + int *is_conditional_ko, int consider_conditional_ko); +int get_komaster(void); +int get_kom_pos(void); + +int move_in_stack(int pos, int cutoff); +void get_move_from_stack(int k, int *move, int *color); +void dump_stack(void); +void do_dump_stack(void); + +void reset_trymove_counter(void); +int get_trymove_counter(void); + +/* move properties */ +int is_pass(int pos); +int is_legal(int pos, int color); +int is_suicide(int pos, int color); +int is_illegal_ko_capture(int pos, int color); +int is_allowed_move(int pos, int color); +int is_ko(int pos, int color, int *ko_pos); +int is_ko_point(int pos); +int does_capture_something(int pos, int color); +int is_self_atari(int pos, int color); + +/* Purely gemoetric functions */ +int is_edge_vertex(int pos); +int is_corner_vertex(int pos); +int edge_distance(int pos); +int square_dist(int pos1, int pos2); + +/* Basic string information. */ +int find_origin(int str); +int chainlinks(int str, int adj[MAXCHAIN]); +int chainlinks2(int str, int adj[MAXCHAIN], int lib); +int chainlinks3(int str, int adj[MAXCHAIN], int lib); +int extended_chainlinks(int str, int adj[MAXCHAIN], int both_colors); + +int liberty_of_string(int pos, int str); +int second_order_liberty_of_string(int pos, int str); +int neighbor_of_string(int pos, int str); +int has_neighbor(int pos, int color); +int same_string(int str1, int str2); +int adjacent_strings(int str1, int str2); +void mark_string(int str, signed char mx[BOARDMAX], signed char mark); +int are_neighbors(int pos1, int pos2); + +/* Count and/or find liberties at (pos). */ +int countlib(int str); +int findlib(int str, int maxlib, int *libs); +int fastlib(int pos, int color, int ignore_captures); +int approxlib(int pos, int color, int maxlib, int *libs); +int accuratelib(int pos, int color, int maxlib, int *libs); +int count_common_libs(int str1, int str2); +int find_common_libs(int str1, int str2, int maxlib, int *libs); +int have_common_lib(int str1, int str2, int *lib); + +/* Count the number of stones in a string. */ +int countstones(int str); +int findstones(int str, int maxstones, int *stones); +int count_adjacent_stones(int str1, int str2, int maxstones); + +/* Special function for reading.c */ +void incremental_order_moves(int move, int color, int string, + int *number_edges, int *number_same_string, + int *number_own, int *number_opponent, + int *captured_stones, int *threatened_stones, + int *saved_stones, int *number_open); + +/* Board caches initialization functions. */ +void clear_approxlib_cache(void); +void clear_accuratelib_cache(void); + + +/* Is this point inside the board? */ +#if 0 +#define ON_BOARD2(i, j) ((i)>=0 && (j)>=0 && (i) something) + * is equivalent to + * if ((unsigned) expr > something) + * + * (I think gcc knows this trick, but it does no harm to + * encode it explicitly since it saves typing !) + */ +#define ON_BOARD2(i, j) ((unsigned) (i) < (unsigned) board_size &&\ + (unsigned) (j) < (unsigned) board_size) +#endif + +#define ASSERT_ON_BOARD2(i, j) ASSERT2(ON_BOARD2((i), (j)), (i), (j)) + +#define ON_BOARD1(pos) (((unsigned) (pos) < BOARDSIZE) && board[pos] != GRAY) +#define ON_BOARD(pos) (board[pos] != GRAY) +#define ASSERT_ON_BOARD1(pos) ASSERT1(ON_BOARD1(pos), (pos)) + +/* Coordinates for the eight directions, ordered + * south, west, north, east, southwest, northwest, northeast, southeast. + * Defined in board.c. + */ +extern int deltai[8]; /* = { 1, 0, -1, 0, 1, -1, -1, 1}; */ +extern int deltaj[8]; /* = { 0, -1, 0, 1, -1, -1, 1, 1}; */ +extern int delta[8]; /* = { NS, -1, -NS, 1, NS-1, -NS-1, -NS+1, NS+1}; */ + + + +/* ================================================================ */ +/* Other functions */ +/* ================================================================ */ + + +/* SGF routines for debugging purposes in sgffile.c */ +void sgffile_begindump(struct SGFTree_t *tree); +void sgffile_enddump(const char *filename); + + +/* Hashing and Caching statistics. */ +struct stats_data { + int nodes; /* Number of visited nodes while reading */ + int read_result_entered; /* Number of read results entered. */ + int read_result_hits; /* Number of hits of read results. */ + int trusted_read_result_hits; /* Number of hits of read results */ + /* with sufficient remaining depth. */ +}; + +extern struct stats_data stats; + + +/* printutils.c */ +/*int gprintf(const char *fmt, ...); +void vgprintf(FILE *outputfile, const char *fmt, va_list ap); +void mprintf(const char *fmt, ...); +void gfprintf(FILE *outfile, const char *fmt, ...); +*/ +const char *color_to_string(int color); +const char *location_to_string(int pos); +void location_to_buffer(int pos, char *buf); + +int string_to_location(int boardsize, const char *str); + +int is_hoshi_point(int m, int n); +/* +void draw_letter_coordinates(FILE *outfile); +void simple_showboard(FILE *outfile); +*/ +void mark_goal_in_sgf(signed char goal[BOARDMAX]); + +/* ================================================================ */ +/* assertions */ +/* ================================================================ */ + +/* Our own abort() which prints board state on the way out. + * (pos) is a "relevant" board position for info. + */ +#define abortgo(x,y,z,a,b) (void)x +/* +void abortgo(const char *file, int line, const char *msg, int pos) +#ifdef __GNUC__ + __attribute__ ((noreturn)) +#endif + ; +*/ + +#define GG_TURN_OFF_ASSERTS 1 + +#ifdef GG_TURN_OFF_ASSERTS +#define ASSERT2(x, i, j) +#define ASSERT1(x, pos) +#else +/* avoid dangling else */ +/* FIXME: Should probably re-write these using do {...} while (0) idiom. */ +#define ASSERT2(x, i, j) if (x) ; else abortgo(__FILE__, __LINE__, #x, POS(i, j)) +#define ASSERT1(x, pos) if (x) ; else abortgo(__FILE__, __LINE__, #x, pos) +#endif + +#define gg_assert(x) ASSERT1(x, NO_MOVE) + + +#define USE_VALGRIND 0 +/* Are we using valgrind memory checking? */ +#if USE_VALGRIND +#include +#else +#define VALGRIND_MAKE_WRITABLE(a, b) +#endif + +#endif /* _BOARD_H_ */ + + +/* + * Local Variables: + * tab-width: 8 + * c-basic-offset: 2 + * End: + */ Index: apps/plugins/SUBDIRS =================================================================== --- apps/plugins/SUBDIRS (revision 15606) +++ apps/plugins/SUBDIRS (working copy) @@ -24,6 +24,7 @@ /* For all 2bpp and colour targets */ #if (LCD_DEPTH >= 2) && !defined(OLYMPUS_MROBE_500) zxbox +goban #endif /* For all the colour targets, iriver H1x0 and iAudio M5 */