/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ * This is GNU Go, a Go program. Contact gnugo@gnu.org, or see * * http://www.gnu.org/software/gnugo/ for more information. * * * * Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, * * 2008 and 2009 by the Free Software Foundation. * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation - version 3 or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License in file COPYING for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02111, USA. * \* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* A "dragon" is a union of strings of the same color which will be * treated as a unit. The dragons are generated anew at each * move. If two strings are in the dragon, it is GNU Go's working * hypothesis that they will live or die together and are * effectively connected. * * _____/| (! !) * / ____/| /@ @) * / / __ // +--oo * | / | >> /< _-v--} * | | UUU\\\ / / \\ * | | __ _\\\ \ \ U * | | / V \\--> \ \ * | <_/ \_/ } * | __ ____ / * \ / \___/ / /\ * < \< < <\ \ * ( ))) ( ))))) */ #include "gnugo.h" #include #include #include #include #include "liberty.h" #include "gg_utils.h" static void initialize_supplementary_dragon_data(void); static void find_lunches(void); static void eye_computations(void); static void revise_inessentiality(void); static void find_neighbor_dragons(void); static void add_adjacent_dragons(int a, int b); static void add_adjacent_dragon(int a, int b); static int dragon_invincible(int pos); static int dragon_looks_inessential(int origin); static void identify_thrashing_dragons(void); static void analyze_false_eye_territory(void); static int connected_to_eye(int pos, int str, int color, int eye_color, struct eye_data *eye); static void connected_to_eye_recurse(int pos, int str, int color, int eye_color, struct eye_data *eye, signed char *mx, signed char *me, int *halfeyes); static enum dragon_status compute_crude_status(int pos); static int compute_escape(int pos, int dragon_status_known); static void compute_surrounding_moyo_sizes(const struct influence_data *q); static void clear_cut_list(void); static int dragon2_initialized; static int lively_white_dragons; static int lively_black_dragons; /* This is a private array to obtain a list of worms belonging to each * dragon. Public access is via first_worm_in_dragon() and * next_worm_in_dragon(). */ static int next_worm_list[BOARDMAX]; /* Alternative for DRAGON2 macro with asserts. */ struct dragon_data2 * dragon2_func(int pos) { ASSERT1(ON_BOARD1(pos) && dragon[pos].id >= 0 && dragon[pos].id < number_of_dragons, pos); return &dragon2[dragon[pos].id]; } /* This basic function finds all dragons and collects some basic information * about them in the dragon array. * * color is the player in turn to move. This does in no way affect the * information collected about the dragons, but it does affect what * information is passed on to the move generation code. If * color == EMPTY no information at all is passed on to the move generation. */ void make_dragons(int stop_before_owl) { int str; int d; dragon2_initialized = 0; initialize_dragon_data(); /* Find explicit connections patterns in database and amalgamate * involved dragons. */ memset(cutting_points, 0, sizeof(cutting_points)); find_cuts(); find_connections(); /* At this time, all dragons have been finalized and we can * initialize the dragon2[] array. After that we can no longer allow * amalgamation of dragons. */ initialize_supplementary_dragon_data(); make_domains(black_eye, white_eye, 0); /* Find adjacent worms which can be easily captured: */ find_lunches(); /* Find topological half eyes and false eyes. */ find_half_and_false_eyes(BLACK, black_eye, half_eye, NULL); find_half_and_false_eyes(WHITE, white_eye, half_eye, NULL); /* Compute the number of eyes, half eyes, determine attack/defense points * etc. for all eye spaces. */ eye_computations(); /* Try to determine whether topologically false and half eye points * contribute to territory even if the eye doesn't solidify. */ analyze_false_eye_territory(); /* Now we compute the genus. */ for (d = 0; d < number_of_dragons; d++) compute_dragon_genus(dragon2[d].origin, &dragon2[d].genus, NO_MOVE); /* Compute the escape route measure. */ for (str = BOARDMIN; str < BOARDMAX; str++) if (IS_STONE(board[str]) && dragon[str].origin == str) DRAGON2(str).escape_route = compute_escape(str, 0); /* Set dragon weaknesses according to initial_influence. */ compute_refined_dragon_weaknesses(); for (d = 0; d < number_of_dragons; d++) dragon2[d].weakness_pre_owl = dragon2[d].weakness; /* Determine status: ALIVE, DEAD, CRITICAL or UNKNOWN */ for (str = BOARDMIN; str < BOARDMAX; str++) if (ON_BOARD(str)) if (dragon[str].origin == str && board[str]) dragon[str].crude_status = compute_crude_status(str); /* We must update the dragon status at every intersection before we * call the owl code. This updates all fields. */ for (str = BOARDMIN; str < BOARDMAX; str++) if (ON_BOARD(str) && board[str] != EMPTY) dragon[str] = dragon[dragon[str].origin]; find_neighbor_dragons(); for (d = 0; d < number_of_dragons; d++) { dragon2[d].surround_status = compute_surroundings(dragon2[d].origin, NO_MOVE, 0, &(dragon2[d].surround_size)); if (dragon2[d].surround_status == SURROUNDED) { dragon2[d].escape_route = 0; if (debug & DEBUG_DRAGONS) gprintf("surrounded dragon found at %1m\n", dragon2[d].origin); } else if (dragon2[d].surround_status == WEAKLY_SURROUNDED) { dragon2[d].escape_route /= 2; if (debug & DEBUG_DRAGONS) gprintf("weakly surrounded dragon found at %1m\n", dragon2[d].origin); } } if (stop_before_owl) return; /* Determine life and death status of each dragon using the owl code * if necessary. */ start_timer(2); for (str = BOARDMIN; str < BOARDMAX; str++) if (ON_BOARD(str)) { int attack_point = NO_MOVE; int defense_point = NO_MOVE; struct eyevalue no_eyes; set_eyevalue(&no_eyes, 0, 0, 0, 0); if (board[str] == EMPTY || dragon[str].origin != str) continue; /* Some dragons can be ignored but be extra careful with big dragons. */ if (crude_dragon_weakness(ALIVE, &no_eyes, 0, DRAGON2(str).moyo_territorial_value, DRAGON2(str).escape_route - 10) < 0.00001 + gg_max(0.12, 0.32 - 0.01*dragon[str].effective_size)) { DRAGON2(str).owl_status = UNCHECKED; DRAGON2(str).owl_attack_point = NO_MOVE; DRAGON2(str).owl_defense_point = NO_MOVE; } else { int acode = 0; int dcode = 0; int kworm = NO_MOVE; int owl_nodes_before = get_owl_node_counter(); start_timer(3); acode = owl_attack(str, &attack_point, &DRAGON2(str).owl_attack_certain, &kworm); DRAGON2(str).owl_attack_node_count = get_owl_node_counter() - owl_nodes_before; if (acode != 0) { DRAGON2(str).owl_attack_point = attack_point; DRAGON2(str).owl_attack_code = acode; DRAGON2(str).owl_attack_kworm = kworm; if (attack_point != NO_MOVE) { kworm = NO_MOVE; dcode = owl_defend(str, &defense_point, &DRAGON2(str).owl_defense_certain, &kworm); if (dcode != 0) { if (defense_point != NO_MOVE) { DRAGON2(str).owl_status = (acode == GAIN ? ALIVE : CRITICAL); DRAGON2(str).owl_defense_point = defense_point; DRAGON2(str).owl_defense_code = dcode; DRAGON2(str).owl_defense_kworm = kworm; } else { /* Due to irregularities in the owl code, it may * occasionally happen that a dragon is found to be * attackable but also alive as it stands. In this case * we still choose to say that the owl_status is * CRITICAL, although we don't have any defense move to * propose. Having the status right is important e.g. * for connection moves to be properly valued. */ DRAGON2(str).owl_status = (acode == GAIN ? ALIVE : CRITICAL); DEBUG(DEBUG_OWL_PERFORMANCE, "Inconsistent owl attack and defense results for %1m.\n", str); /* Let's see whether the attacking move might be the right * defense: */ dcode = owl_does_defend(DRAGON2(str).owl_attack_point, str, NULL); if (dcode != 0) { DRAGON2(str).owl_defense_point = DRAGON2(str).owl_attack_point; DRAGON2(str).owl_defense_code = dcode; } } } } if (dcode == 0) { DRAGON2(str).owl_status = DEAD; DRAGON2(str).owl_defense_point = NO_MOVE; DRAGON2(str).owl_defense_code = 0; } } else { if (!DRAGON2(str).owl_attack_certain) { kworm = NO_MOVE; dcode = owl_defend(str, &defense_point, &DRAGON2(str).owl_defense_certain, &kworm); if (dcode != 0) { /* If the result of owl_attack was not certain, we may * still want the result of owl_defend */ DRAGON2(str).owl_defense_point = defense_point; DRAGON2(str).owl_defense_code = dcode; DRAGON2(str).owl_defense_kworm = kworm; } } DRAGON2(str).owl_status = ALIVE; DRAGON2(str).owl_attack_point = NO_MOVE; DRAGON2(str).owl_attack_code = 0; } } } time_report(2, " owl reading", NO_MOVE, 1.0); /* Compute the status to be used by the matcher. We most trust the * owl status, if it is available. If it's not we assume that we are * already confident that the dragon is alive, regardless of * crude_status. */ for (str = BOARDMIN; str < BOARDMAX; str++) if (IS_STONE(board[str])) { if (DRAGON2(str).owl_status != UNCHECKED) dragon[str].status = DRAGON2(str).owl_status; else dragon[str].status = ALIVE; } /* The dragon data is now correct at the origin of each dragon but * we need to copy it to every vertex. */ for (str = BOARDMIN; str < BOARDMAX; str++) if (ON_BOARD(str) && board[str] != EMPTY) dragon[str] = dragon[dragon[str].origin]; identify_thrashing_dragons(); /* Owl threats. */ for (str = BOARDMIN; str < BOARDMAX; str++) if (ON_BOARD(str) && board[str] != EMPTY && dragon[str].origin == str) { struct eyevalue no_eyes; set_eyevalue(&no_eyes, 0, 0, 0, 0); if (crude_dragon_weakness(ALIVE, &no_eyes, 0, DRAGON2(str).moyo_territorial_value, DRAGON2(str).escape_route - 10) < 0.00001 + gg_max(0.12, 0.32 - 0.01*dragon[str].effective_size)) { DRAGON2(str).owl_threat_status = UNCHECKED; DRAGON2(str).owl_second_attack_point = NO_MOVE; DRAGON2(str).owl_second_defense_point = NO_MOVE; } else { int acode = DRAGON2(str).owl_attack_code; int dcode = DRAGON2(str).owl_defense_code; int defense_point, second_defense_point; if (get_level() >= 8 && !disable_threat_computation && (owl_threats || thrashing_stone[str])) { if (acode && !dcode && DRAGON2(str).owl_attack_point != NO_MOVE) { if (owl_threaten_defense(str, &defense_point, &second_defense_point)) { DRAGON2(str).owl_threat_status = CAN_THREATEN_DEFENSE; DRAGON2(str).owl_defense_point = defense_point; DRAGON2(str).owl_second_defense_point = second_defense_point; } else DRAGON2(str).owl_threat_status = DEAD; } else if (!acode) { int attack_point, second_attack_point; if (owl_threaten_attack(str, &attack_point, &second_attack_point)) { DRAGON2(str).owl_threat_status = CAN_THREATEN_ATTACK; DRAGON2(str).owl_attack_point = attack_point; DRAGON2(str).owl_second_attack_point = second_attack_point; } else DRAGON2(str).owl_threat_status = ALIVE; } } } } /* Once again, the dragon data is now correct at the origin of each dragon * but we need to copy it to every vertex. */ for (str = BOARDMIN; str < BOARDMAX; str++) if (ON_BOARD(str) && board[str] != EMPTY) dragon[str] = dragon[dragon[str].origin]; time_report(2, " owl threats ", NO_MOVE, 1.0); /* Compute the safety value. */ for (d = 0; d < number_of_dragons; d++) { int true_genus; int origin = dragon2[d].origin; struct eyevalue *genus = &dragon2[d].genus; /* FIXME: We lose information when constructing true_genus. This * code can be improved. */ true_genus = max_eyes(genus) + min_eyes(genus); if (dragon_looks_inessential(origin)) dragon2[d].safety = INESSENTIAL; else if (dragon[origin].size == worm[origin].size && worm[origin].attack_codes[0] != 0 && worm[origin].defense_codes[0] == 0) dragon2[d].safety = TACTICALLY_DEAD; else if (0) /* Seki is detected by the call to semeai() below. */ dragon2[d].safety = ALIVE_IN_SEKI; else if (dragon_invincible(origin)) { dragon2[d].safety = INVINCIBLE; /* Sometimes the owl analysis may have misevaluated invincible * dragons, typically if they live by topologically false eyes. * Therefore we also set the status here. */ DRAGON(d).status = ALIVE; } else if (dragon2[d].owl_status == DEAD) dragon2[d].safety = DEAD; else if (dragon2[d].owl_status == CRITICAL) dragon2[d].safety = CRITICAL; else if (true_genus >= 6 || dragon2[d].moyo_size > 20) dragon2[d].safety = STRONGLY_ALIVE; else dragon2[d].safety = ALIVE; } /* The status is now correct at the origin of each dragon * but we need to copy it to every vertex. */ for (str = BOARDMIN; str < BOARDMAX; str++) if (ON_BOARD(str)) dragon[str].status = dragon[dragon[str].origin].status; /* Revise inessentiality of critical worms and dragons. */ revise_inessentiality(); semeai(); time_report(2, " semeai module", NO_MOVE, 1.0); /* Count the non-dead dragons. */ lively_white_dragons = 0; lively_black_dragons = 0; for (d = 0; d < number_of_dragons; d++) if (DRAGON(d).status != DEAD) { if (DRAGON(d).color == WHITE) lively_white_dragons++; else lively_black_dragons++; } } /* Find capturable worms adjacent to each dragon. */ static void find_lunches() { int str; for (str = BOARDMIN; str < BOARDMAX; str++) if (ON_BOARD(str)) { int food; if (worm[str].origin != str || board[str] == EMPTY || worm[str].lunch == NO_MOVE) continue; food = worm[str].lunch; /* In contrast to worm lunches, a dragon lunch must also be * able to defend itself. */ if (worm[food].defense_codes[0] == 0) continue; /* Tell the move generation code about the lunch. */ add_lunch(str, food); /* If several lunches are found, we pick the juiciest. * First maximize cutstone, then minimize liberties. */ { int origin = dragon[str].origin; int lunch = DRAGON2(origin).lunch; if (lunch == NO_MOVE || worm[food].cutstone > worm[lunch].cutstone || (worm[food].cutstone == worm[lunch].cutstone && (worm[food].liberties < worm[lunch].liberties))) { DRAGON2(origin).lunch = worm[food].origin; TRACE("at %1m setting %1m.lunch to %1m (cutstone=%d)\n", str, origin, worm[food].origin, worm[food].cutstone); } } } } /* Compute the value of each eye space. Store its attack and defense point. * A more comlete list of attack and defense points is stored in the lists * black_vital_points and white_vital_points. */ static void eye_computations() { int str; for (str = BOARDMIN; str < BOARDMAX; str++) { if (!ON_BOARD(str)) continue; if (black_eye[str].color == BLACK && black_eye[str].origin == str) { struct eyevalue value; int attack_point, defense_point; compute_eyes(str, &value, &attack_point, &defense_point, black_eye, half_eye, 1); DEBUG(DEBUG_EYES, "Black eyespace at %1m: %s\n", str, eyevalue_to_string(&value)); black_eye[str].value = value; propagate_eye(str, black_eye); } if (white_eye[str].color == WHITE && white_eye[str].origin == str) { struct eyevalue value; int attack_point, defense_point; compute_eyes(str, &value, &attack_point, &defense_point, white_eye, half_eye, 1); DEBUG(DEBUG_EYES, "White eyespace at %1m: %s\n", str, eyevalue_to_string(&value)); white_eye[str].value = value; propagate_eye(str, white_eye); } } } /* This function revises the inessentiality of critical worms and dragons * according to the criteria explained in the comments below. */ static void revise_inessentiality() { int str; /* Revise essentiality of critical worms. Specifically, a critical * worm which is adjacent to no enemy dragon with status * better than DEAD, is considered INESSENTIAL. * * A typical case of this is * * |.XXXX * |.OO.X * |X.O.X * |.OO.X * +----- * * However, to be able to deal with the position * * |.XXXX * |.OOOO * |..O.O * |X.OOO * |..O.O * +----- * * we need to extend "adjacent" to "adjacent or shares a liberty", * which is why we use extended_chainlinks() rather than * chainlinks(). * * Finally, if the position above is slightly modified to * * |.XXXXX * |.OOOOO * |...O.O * |X..OOO * |...O.O * +------ * * we have a position where the critical X stone doesn't share a * liberty with any string at all. Thus the revised rule is: * * A critical worm which is adjacent to or share a liberty with at * least one dead opponent dragon and no opponent dragon which is * not dead, is considered inessential. */ for (str = BOARDMIN; str < BOARDMAX; str++) if (ON_BOARD(str)) { if (is_worm_origin(str, str) && worm[str].attack_codes[0] != 0 && worm[str].defense_codes[0] != 0 && !worm[str].inessential) { int adjs[MAXCHAIN]; int neighbors; int r; int essential = 0; neighbors = extended_chainlinks(str, adjs, 0); for (r = 0; r < neighbors; r++) if (dragon[adjs[r]].status != DEAD) { essential = 1; break; } if (!essential && neighbors > 0) { DEBUG(DEBUG_WORMS, "Worm %1m revised to be inessential.\n", str); worm[str].inessential = 1; propagate_worm(str); } } } /* Revise essentiality of critical dragons. Specifically, a critical * dragon consisting entirely of inessential worms is considered * INESSENTIAL. */ for (str = BOARDMIN; str < BOARDMAX; str++) { if (ON_BOARD(str) && board[str] != EMPTY && dragon[str].origin == str && DRAGON2(str).safety == CRITICAL) { int w; for (w = first_worm_in_dragon(str); w != NO_MOVE; w = next_worm_in_dragon(w)) { if (!worm[w].inessential) break; } if (w == NO_MOVE) { DEBUG(DEBUG_DRAGONS, "Dragon %1m revised to be inessential.\n", str); DRAGON2(str).safety = INESSENTIAL; } } } } /* Initialize the dragon[] array. */ void initialize_dragon_data(void) { int str; /* VALGRIND_MAKE_WRITABLE(dragon, BOARDMAX * sizeof(struct dragon_data)); */ for (str = BOARDMIN; str < BOARDMAX; str++) if (ON_BOARD(str)) { dragon[str].id = -1; dragon[str].size = worm[str].size; dragon[str].effective_size = worm[str].effective_size; dragon[str].color = worm[str].color; dragon[str].origin = worm[str].origin; dragon[str].crude_status = UNKNOWN; dragon[str].status = UNKNOWN; half_eye[str].type = 0; half_eye[str].value = 10.0; /* Something big. */ if (IS_STONE(board[str]) && worm[str].origin == str) DEBUG(DEBUG_DRAGONS, "Initializing dragon from worm at %1m, size %d\n", str, worm[str].size); } memset(next_worm_list, 0, sizeof(next_worm_list)); /* We need to reset this to avoid trouble on an empty board when * moves have previously been generated for a non-empty board. * * Comment: The cause of this is that make_dragons() is not called * for an empty board, only initialize_dragon_data(), so we never * reach initialize_supplementary_dragon_data(). */ number_of_dragons = 0; clear_cut_list(); memset(black_vital_points, 0, BOARDMAX * sizeof(struct vital_eye_points)); memset(white_vital_points, 0, BOARDMAX * sizeof(struct vital_eye_points)); } /* Initialize the dragon2[] array. */ static void initialize_supplementary_dragon_data(void) { int str; int d; int origin; /* Give each dragon (caves excluded) an id number for indexing into * the dragon2 array. After this the DRAGON2 macro can be used. */ number_of_dragons = 0; for (str = BOARDMIN; str < BOARDMAX; str++) { if (!ON_BOARD(str)) continue; origin = dragon[str].origin; if (board[str] == EMPTY) continue; if (dragon[origin].id == -1) dragon[origin].id = number_of_dragons++; dragon[str].id = dragon[origin].id; } /* Now number_of_dragons contains the number of dragons and we can * allocate a dragon2 array of the appropriate size. First throw * away the old array. * * FIXME: As a future optimization we should only allocate a new * array if the old one is too small. */ if (dragon2 != NULL) free(dragon2); dragon2 = malloc(number_of_dragons * sizeof(*dragon2)); gg_assert(dragon2 != NULL); /* Find the origins of the dragons to establish the mapping back to * the board. After this the DRAGON macro can be used. */ for (str = BOARDMIN; str < BOARDMAX; str++) { if (!ON_BOARD(str)) continue; if (IS_STONE(board[str]) && dragon[str].origin == str) { DRAGON2(str).origin = str; } } /* Initialize the rest of the dragon2 data. */ for (d = 0; d < number_of_dragons; d++) { dragon2[d].neighbors = 0; dragon2[d].hostile_neighbors = 0; dragon2[d].moyo_size = -1; dragon2[d].moyo_territorial_value = 0.0; dragon2[d].safety = -1; dragon2[d].escape_route = 0; dragon2[d].heye = NO_MOVE; dragon2[d].lunch = NO_MOVE; dragon2[d].surround_status = 0; set_eyevalue(&dragon2[d].genus, 0, 0, 0, 0); dragon2[d].semeais = 0; dragon2[d].semeai_defense_code = 0; dragon2[d].semeai_defense_point = NO_MOVE; dragon2[d].semeai_attack_code = 0; dragon2[d].semeai_attack_point = NO_MOVE; dragon2[d].owl_attack_point = NO_MOVE; dragon2[d].owl_attack_code = 0; dragon2[d].owl_attack_certain = 1; dragon2[d].owl_defense_point = NO_MOVE; dragon2[d].owl_defense_code = 0; dragon2[d].owl_defense_certain = 1; dragon2[d].owl_status = UNCHECKED; dragon2[d].owl_threat_status = UNCHECKED; dragon2[d].owl_second_attack_point = NO_MOVE; dragon2[d].owl_second_defense_point = NO_MOVE; } dragon2_initialized = 1; } /* Examine which dragons are adjacent to each other. This is * complicated by the fact that adjacency may involve a certain * amount of empty space. * * The approach we use is to extend the dragons into their * surrounding influence areas until they collide. We also accept * one step extensions into neutral regions. After having done this * we can look for immediate adjacencies. */ static void find_neighbor_dragons() { int m, n; int pos; int pos2; int i, j; int d; int dragons[BOARDMAX]; int distances[BOARDMAX]; int dist; int k; int color; gg_assert(dragon2_initialized); /* Initialize the arrays. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (IS_STONE(board[pos])) { dragons[pos] = dragon[pos].id; distances[pos] = 0; } else if (ON_BOARD(pos)) { dragons[pos] = -1; distances[pos] = -1; } } /* Expand from dist-1 to dist. Break out of the loop at the end if * we couldn't expand anything. Never expand more than five steps. */ for (dist = 1; dist <= 5; dist++) { int found_one = 0; for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (!ON_BOARD(pos)) continue; if (distances[pos] != dist-1 || dragons[pos] < 0) continue; color = DRAGON(dragons[pos]).color; for (k = 0; k < 4; k++) { pos2 = pos + delta[k]; if (!ON_BOARD1(pos2)) continue; /* Consider expansion from (pos) to adjacent intersection * (pos2). */ if (distances[pos2] >= 0 && distances[pos2] < dist) continue; /* (pos2) already occupied. */ /* We can always expand the first step, regardless of influence. */ if (dist == 1 || (whose_area(INITIAL_INFLUENCE(color), pos) == color && whose_area(INITIAL_INFLUENCE(color), pos2) != OTHER_COLOR(color))) { /* Expansion ok. Now see if someone else has tried to * expand here. In that case we indicate a collision by * setting the dragon number to -2. */ if (distances[pos2] == dist) { if (dragons[pos2] != dragons[pos]) dragons[pos2] = -2; } else { dragons[pos2] = dragons[pos]; distances[pos2] = dist; found_one = 1; } } } } if (!found_one) break; } if (0) { for (m = 0; m < board_size; m++) { for (n = 0; n < board_size; n++) fprintf(stderr, "%3d", dragons[POS(m, n)]); fprintf(stderr, "\n"); } fprintf(stderr, "\n"); for (m = 0; m < board_size; m++) { for (n = 0; n < board_size; n++) fprintf(stderr, "%3d", distances[POS(m, n)]); fprintf(stderr, "\n"); } fprintf(stderr, "\n"); } /* Now go through dragons to find neighbors. It suffices to look * south and east for neighbors. In the case of a collision zone * where dragons==-2 we set all the neighbors of this intersection * as adjacent to each other. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (!ON_BOARD(pos)) continue; if (dragons[pos] == -2) { int neighbors = 0; int adjacent[4]; for (k = 0; k < 4; k++) { pos2 = pos + delta[k]; if (ON_BOARD1(pos2) && dragons[pos2] >= 0) adjacent[neighbors++] = dragons[pos2]; } for (i = 0; i < neighbors; i++) for (j = i+1; j < neighbors; j++) add_adjacent_dragons(adjacent[i], adjacent[j]); } else if (dragons[pos] >= 0) { if (ON_BOARD(NORTH(pos))) { if (dragons[NORTH(pos)] >= 0 && dragons[NORTH(pos)] != dragons[pos]) add_adjacent_dragons(dragons[pos], dragons[NORTH(pos)]); } if (ON_BOARD(EAST(pos))) { if (dragons[EAST(pos)] >= 0 && dragons[EAST(pos)] != dragons[pos]) add_adjacent_dragons(dragons[pos], dragons[EAST(pos)]); } } } if (0) { for (d = 0; d < number_of_dragons; d++) { gprintf("dragon %d at %1m:", d, dragon2[d].origin); for (i = 0; i < dragon2[d].neighbors; i++) gprintf(" %1m(%d)", dragon2[dragon2[d].adjacent[i]].origin, dragon2[d].adjacent[i]); gprintf("\n"); } } } /* Add the dragons with id a and b as adjacent to each other. */ static void add_adjacent_dragons(int a, int b) { gg_assert(a >= 0 && a < number_of_dragons && b >= 0 && b < number_of_dragons); if (a == b) return; add_adjacent_dragon(a, b); add_adjacent_dragon(b, a); } /* Add the dragon with id b as adjacent to a. */ static void add_adjacent_dragon(int a, int b) { int i; gg_assert(a >= 0 && a < number_of_dragons && b >= 0 && b < number_of_dragons); /* If the array of adjacent dragons already is full, ignore * additional neighbors. */ if (dragon2[a].neighbors == MAX_NEIGHBOR_DRAGONS) return; for (i = 0; i < dragon2[a].neighbors; i++) if (dragon2[a].adjacent[i] == b) return; dragon2[a].adjacent[dragon2[a].neighbors++] = b; if (DRAGON(a).color == OTHER_COLOR(DRAGON(b).color)) dragon2[a].hostile_neighbors++; } /* A dragon is considered invincible if it satisfies either of the two * following conditions: * a) At least two distinct eyespaces without topological halfeyes, * marginal vertices, or tactically critical or alive opponent strings. * Furthermore there may not be an owl attack of the dragon. * b) At least one string which is unconditionally alive according to the * unconditional_life() function in utils.c. * * For the requirement on opponent strings in a), see e.g. * seki:404,408,409,413,504,604,908. */ static int dragon_invincible(int dr) { struct eye_data *eye; int eye_color; int k; int pos; int strong_eyes = 0; int mx[BOARDMAX]; ASSERT1(IS_STONE(board[dr]), dr); /* First look for invincible strings in the dragon. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (ON_BOARD(pos) && is_same_dragon(pos, dr) && worm[pos].invincible) return 1; } /* Can the dragon be owl attacked? */ if (DRAGON2(dr).owl_status != UNCHECKED && DRAGON2(dr).owl_status != ALIVE) return 0; /* Examine the eye spaces. */ if (board[dr] == BLACK) { eye = black_eye; eye_color = BLACK; } else { eye = white_eye; eye_color = WHITE; } memset(mx, 0, sizeof(mx)); for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] == board[dr] && is_same_dragon(pos, dr)) { for (k = 0; k < 4; k++) { int pos2 = pos + delta[k]; if (ON_BOARD(pos2) && eye[pos2].color == eye_color && eye[pos2].origin != NO_MOVE) { if (eye[pos2].marginal || is_halfeye(half_eye, pos2)) mx[eye[pos2].origin] = 2; /* bad eye */ else if (mx[eye[pos2].origin] == 0) mx[eye[pos2].origin] = 1; /* good eye */ if (board[pos2] == OTHER_COLOR(board[dr]) && (!attack(pos2, NULL) || find_defense(pos2, NULL))) mx[eye[pos2].origin] = 2; /* bad eye */ } } } } for (pos = BOARDMIN; pos < BOARDMAX; pos++) { /* Necessary to check eye margins here since the loop above only * considers margins which are directly adjacent to some stone of * the dragon. */ if (mx[pos] == 1 && eye[pos].msize == 0) strong_eyes++; } if (strong_eyes >= 2) return 1; return 0; } /* A dragon looks inessential if it satisfies all of * 1. Is a single string. * 2. Is not owl substantial. * * FIXME: Probably need a better definition of INESSENTIAL dragons. * There are cases where a string is owl insubstantial * yet allowing it to be captured greatly weakens our * position. */ static int dragon_looks_inessential(int origin) { #if 0 int d; int k; #endif if (dragon[origin].size != worm[origin].size) return 0; if (owl_substantial(origin)) return 0; #if 0 /* This is a proposed modification which solves 13x13:72 but * breaks buzco:5. It adds the two requirements: * * 3. Has no opponent neighbor with status better than DEAD. * 4. Has no opponent neighbor with escape value bigger than 0. * * This probably needs to be revised before it's enabled. */ for (k = 0; k < DRAGON2(origin).neighbors; k++) { d = DRAGON2(origin).adjacent[k]; if (DRAGON(d).color != board[origin] && (DRAGON(d).status != DEAD || dragon2[d].escape_route > 0)) return 0; } #endif return 1; } /* Report which stones are alive if it's (color)'s turn to move. I.e. * critical stones belonging to (color) are considered alive. * A stone is dead resp. critical if the tactical reading code _or_ the * owl code thinks so. */ static void get_alive_stones(int color, signed char safe_stones[BOARDMAX]) { int d; get_lively_stones(color, safe_stones); for (d = 0; d < number_of_dragons; d++) { if (dragon2[d].safety == DEAD || (dragon2[d].safety == CRITICAL && board[dragon2[d].origin] == OTHER_COLOR(color))) { mark_dragon(dragon2[d].origin, safe_stones, 0); } } } /* If the opponent's last move is a dead dragon, this is called a * *thrashing dragon*. We must be careful because the opponent may be * trying to trick us, so even though GNU Go thinks the stone is dead, * we should consider attacking it, particularly if we are ahead. * * This function determines whether the last played move is part of a * dead dragon. It also looks for dead friendly neighbors of the * thrashing dragon, which are also considered as thrashing. The * stones of the primary thrashing dragon are marked by 1 in the * thrashing_stone[] array and its neighbors are marked by 2. * Neighbors of neighbors are marked 3, and so on, up to at most * distance 5. */ static void identify_thrashing_dragons() { int k; int dist; int last_move; int color; thrashing_dragon = 0; memset(thrashing_stone, 0, sizeof(thrashing_stone)); last_move = get_last_move(); if (last_move == NO_MOVE || dragon[last_move].status != DEAD) return; thrashing_dragon = dragon[last_move].origin; DEBUG(DEBUG_DRAGONS, "thrashing dragon found at %1m\n", thrashing_dragon); mark_dragon(thrashing_dragon, thrashing_stone, 1); color = board[thrashing_dragon]; for (dist = 1; dist < 5; dist++) { int pos; for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (board[pos] != color || dragon[pos].origin != pos || thrashing_stone[pos] != dist) continue; for (k = 0; k < DRAGON2(pos).neighbors; k++) { int d = DRAGON2(pos).adjacent[k]; if (DRAGON(d).color == color && DRAGON(d).status == DEAD && thrashing_stone[dragon2[d].origin] == 0) { DEBUG(DEBUG_DRAGONS, "neighbor at distance %d of thrashing dragon found at %1m\n", dist + 1, DRAGON(d).origin); mark_dragon(DRAGON(d).origin, thrashing_stone, (signed char)(dist + 1)); } } } } } static void set_dragon_strengths(const signed char safe_stones[BOARDMAX], float strength[BOARDMAX]) { int ii; for (ii = BOARDMIN; ii < BOARDMAX; ii++) if (ON_BOARD(ii)) { if (safe_stones[ii]) { ASSERT1(IS_STONE(board[ii]), ii); strength[ii] = DEFAULT_STRENGTH * (1.0 - 0.3 * DRAGON2(ii).weakness_pre_owl); } else strength[ii] = 0.0; } } /* Marks all inessential stones with INFLUENCE_SAFE_STONE, leaves * everything else unchanged. */ void mark_inessential_stones(int color, signed char safe_stones[BOARDMAX]) { int ii; for (ii = BOARDMIN; ii < BOARDMAX; ii++) if (IS_STONE(board[ii]) && (DRAGON2(ii).safety == INESSENTIAL || (worm[ii].inessential /* FIXME: Why is the check below needed? * Why does it use .safety, not .status? /ab */ && ((DRAGON2(ii).safety != DEAD && DRAGON2(ii).safety != TACTICALLY_DEAD && DRAGON2(ii).safety != CRITICAL) || (DRAGON2(ii).safety == CRITICAL && board[ii] == color))))) safe_stones[ii] = INFLUENCE_SAFE_STONE; } void set_strength_data(int color, signed char safe_stones[BOARDMAX], float strength[BOARDMAX]) { gg_assert(IS_STONE(color) || color == EMPTY); get_alive_stones(color, safe_stones); set_dragon_strengths(safe_stones, strength); mark_inessential_stones(color, safe_stones); } void compute_dragon_influence() { signed char safe_stones[BOARDMAX]; float strength[BOARDMAX]; set_strength_data(BLACK, safe_stones, strength); compute_influence(BLACK, safe_stones, strength, &initial_black_influence, NO_MOVE, "initial black influence, dragons known"); break_territories(BLACK, &initial_black_influence, 1, NO_MOVE); set_strength_data(WHITE, safe_stones, strength); compute_influence(WHITE, safe_stones, strength, &initial_white_influence, NO_MOVE, "initial white influence, dragons known"); break_territories(WHITE, &initial_white_influence, 1, NO_MOVE); } /* Compute dragon's genus, possibly excluding one given eye. To * compute full genus, just set `eye_to_exclude' to NO_MOVE. */ void compute_dragon_genus(int d, struct eyevalue *genus, int eye_to_exclude) { int pos; int dr; ASSERT1(IS_STONE(board[d]), d); gg_assert(eye_to_exclude == NO_MOVE || ON_BOARD1(eye_to_exclude)); set_eyevalue(genus, 0, 0, 0, 0); if (board[d] == BLACK) { for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (!ON_BOARD(pos)) continue; if (black_eye[pos].color == BLACK && black_eye[pos].origin == pos && (eye_to_exclude == NO_MOVE || black_eye[eye_to_exclude].origin != pos) && find_eye_dragons(pos, black_eye, BLACK, &dr, 1) == 1 && is_same_dragon(dr, d)) { TRACE("eye at %1m (%s) found for dragon at %1m--augmenting genus\n", pos, eyevalue_to_string(&black_eye[pos].value), dr); if (eye_to_exclude == NO_MOVE && (eye_move_urgency(&black_eye[pos].value) > eye_move_urgency(genus))) DRAGON2(d).heye = black_vital_points[pos].defense_points[0]; add_eyevalues(genus, &black_eye[pos].value, genus); } } } else { for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (!ON_BOARD(pos)) continue; if (white_eye[pos].color == WHITE && white_eye[pos].origin == pos && (eye_to_exclude == NO_MOVE || white_eye[eye_to_exclude].origin != pos) && find_eye_dragons(pos, white_eye, WHITE, &dr, 1) == 1 && is_same_dragon(dr, d)) { TRACE("eye at %1m (%s) found for dragon at %1m--augmenting genus\n", pos, eyevalue_to_string(&white_eye[pos].value), dr); if (eye_to_exclude == NO_MOVE && (eye_move_urgency(&white_eye[pos].value) > eye_move_urgency(genus))) DRAGON2(d).heye = white_vital_points[pos].defense_points[0]; add_eyevalues(genus, &white_eye[pos].value, genus); } } } } /* Try to determine whether topologically false and half eye points * contribute to territory even if the eye doesn't solidify. The purpose * is to be able to distinguish between, e.g., these positions: * * |.OOOOO |.OOOOO * |.O.XXO |.O.OXO * |OOX.XO |OOX.XO * |O*XXXO and |O*XXXO * |OX.XOO |OX.XOO * |X.XOO. |X.XOO. * |.XXO.. |.XXO.. * +------ +------ * * In the left one the move at * is a pure dame point while in the * right one it is worth one point of territory for either player. * * In general the question is whether a topologically false eye vertex * counts as territory or not and the answer depends on whether each * string adjoining the eye is externally connected to at least one * proper eye. * * This function loops over the topologically false and half eye * vertices and calls connected_to_eye() for each adjoining string to * determine whether they all have external connection to an eye. The * result is stored in the false_eye_territory[] array. */ static void analyze_false_eye_territory(void) { int pos; int color; int eye_color; struct eye_data *eye; int k; for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (!ON_BOARD(pos)) continue; false_eye_territory[pos] = 0; /* The analysis only applies to false and half eyes. */ if (half_eye[pos].type == 0) continue; /* Determine the color of the eye. */ if (white_eye[pos].color == WHITE) { color = WHITE; eye_color = WHITE; eye = white_eye; } else if (black_eye[pos].color == BLACK) { color = BLACK; eye_color = BLACK; eye = black_eye; } else continue; /* Make sure we have a "closed" position. Positions like * * |XXXXXX. * |OOOOOXX * |.O.O*.. * +------- * * disqualify without further analysis. (* is a false eye vertex) */ for (k = 0; k < 4; k++) if (ON_BOARD(pos + delta[k]) && board[pos + delta[k]] != color && eye[pos + delta[k]].color != eye_color) break; if (k < 4) continue; /* Check that all adjoining strings have external connection to an * eye. */ for (k = 0; k < 4; k++) if (ON_BOARD(pos + delta[k]) && board[pos + delta[k]] == color && !connected_to_eye(pos, pos + delta[k], color, eye_color, eye)) break; if (k == 4) { false_eye_territory[pos] = 1; if (0) gprintf("False eye territory at %1m\n", pos); } } /* FIXME: This initialization doesn't really belong here but must be * done somewhere within examine_position(). * The array is eventually filled by the endgame() function. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) if (ON_BOARD(pos)) forced_backfilling_moves[pos] = 0; } /* * This function (implicitly) finds the connected set of strings of a * dragon, starting from (str) which is next to the analyzed halfeye * at (pos). Strings are for this purpose considered connected if and * only if they have a common liberty, which is not allowed to be the * half eye itself or one of its diagonal neighbors. For these strings * it is examined whether their liberties are parts of eyespaces worth * at least two halfeyes (again not counting the eyespace at (pos)). * * The real work is done by the recursive function * connected_to_eye_recurse() below. */ static int connected_to_eye(int pos, int str, int color, int eye_color, struct eye_data *eye) { signed char mx[BOARDMAX]; signed char me[BOARDMAX]; int k; int halfeyes; /* mx marks strings and liberties which have already been investigated. * me marks the origins of eyespaces which have already been counted. * Start by marking (pos) and the surrounding vertices in mx. */ memset(mx, 0, sizeof(mx)); memset(me, 0, sizeof(me)); mx[pos] = 1; for (k = 0; k < 8; k++) if (ON_BOARD(pos + delta[k])) mx[pos + delta[k]] = 1; halfeyes = 0; connected_to_eye_recurse(pos, str, color, eye_color, eye, mx, me, &halfeyes); if (halfeyes >= 2) return 1; return 0; } /* Recursive helper for connected_to_eye(). Stop searching when we * have found at least two halfeyes. */ static void connected_to_eye_recurse(int pos, int str, int color, int eye_color, struct eye_data *eye, signed char *mx, signed char *me, int *halfeyes) { int liberties; int libs[MAXLIBS]; int r; int k; mark_string(str, mx, 1); liberties = findlib(str, MAXLIBS, libs); /* Search the liberties of (str) for eyespaces. */ for (r = 0; r < liberties; r++) { if (eye[libs[r]].color == eye_color && libs[r] != pos && !me[eye[libs[r]].origin]) { me[eye[libs[r]].origin] = 1; *halfeyes += (min_eyes(&eye[libs[r]].value) + max_eyes(&eye[libs[r]].value)); } } if (*halfeyes >= 2) return; /* Search for new strings in the same dragon with a liberty in * common with (str), and recurse. */ for (r = 0; r < liberties; r++) { if (mx[libs[r]]) continue; mx[libs[r]] = 1; for (k = 0; k < 4; k++) { if (ON_BOARD(libs[r] + delta[k]) && board[libs[r] + delta[k]] == color && is_same_dragon(str, libs[r] + delta[k]) && !mx[libs[r] + delta[k]]) connected_to_eye_recurse(pos, libs[r] + delta[k], color, eye_color, eye, mx, me, halfeyes); if (*halfeyes >= 2) return; } } } /* print status info on all dragons. (Can be invoked from gdb) */ void show_dragons(void) { int pos; int k; for (pos = BOARDMIN; pos < BOARDMAX; pos++) { struct worm_data *w = &(worm[pos]); if (!IS_STONE(board[pos])) continue; if (w->origin == pos) { gprintf("%1m : (dragon %1m) %s string of size %d (%f), genus %d: (%d,%d,%d,%d)", pos, dragon[pos].origin, color_to_string(board[pos]), w->size, w->effective_size, w->genus, w->liberties, w->liberties2, w->liberties3, w->liberties4); if (w->cutstone == 1) gprintf("%o - is a potential cutting stone\n"); else if (w->cutstone == 2) gprintf("%o - is a cutting stone\n"); else gprintf("%o\n"); if (w->cutstone2 > 0) gprintf("- cutstone2 = %d\n", w->cutstone2); for (k = 0; k < MAX_TACTICAL_POINTS; k++) { if (w->attack_codes[k] == 0) break; gprintf("- attackable at %1m, attack code = %d\n", w->attack_points[k], w->attack_codes[k]); } for (k = 0; k < MAX_TACTICAL_POINTS; k++) { if (w->defense_codes[k] == 0) break; gprintf("- defendable at %1m, defend code = %d\n", w->defense_points[k], w->defense_codes[k]); } for (k = 0; k < MAX_TACTICAL_POINTS; k++) { if (w->attack_threat_codes[k] == 0) break; gprintf("- attack threat at %1m, attack threat code = %d\n", w->attack_threat_points[k], w->attack_threat_codes[k]); } for (k = 0; k < MAX_TACTICAL_POINTS; k++) { if (w->defense_threat_codes[k] == 0) break; gprintf("- defense threat at %1m, defense threat code = %d\n", w->defense_threat_points[k], w->defense_threat_codes[k]); } if (w->lunch != NO_MOVE) gprintf("... adjacent worm %1m is lunch\n", w->lunch); if (w->inessential) gprintf("- is inessential\n"); if (w->invincible) gprintf("- is invincible\n"); if (is_ko_point(pos)) gprintf("- is a ko stone\n"); } } gprintf("%o\n"); for (pos = BOARDMIN; pos < BOARDMAX; pos++) { struct dragon_data *dd = &(dragon[pos]); struct dragon_data2 *d2; if (!IS_STONE(board[pos])) continue; d2 = &(dragon2[dd->id]); if (dd->origin == pos) { gprintf("%1m : %s dragon size %d (%f), genus %s, escape factor %d, crude status %s, status %s, moyo size %d, moyo territory value %f, safety %s, weakness pre owl %f, weakness %f", pos, board[pos] == BLACK ? "B" : "W", dd->size, dd->effective_size, eyevalue_to_string(&d2->genus), d2->escape_route, status_to_string(dd->crude_status), status_to_string(dd->status), d2->moyo_size, d2->moyo_territorial_value, status_to_string(d2->safety), d2->weakness_pre_owl, d2->weakness); gprintf(", owl status %s\n", status_to_string(d2->owl_status)); if (d2->owl_status == CRITICAL) { gprintf("... owl attackable at %1m, code %d\n", d2->owl_attack_point, d2->owl_attack_code); gprintf("... owl defendable at %1m, code %d\n", d2->owl_defense_point, d2->owl_defense_code); } if (dd->status == CRITICAL && d2->semeais) { if (d2->semeai_defense_point) gprintf("... semeai defense move at %1m, result code %s\n", d2->semeai_defense_point, result_to_string(d2->semeai_defense_code)); if (d2->semeai_attack_point) gprintf("... semeai attack move at %1m, result code %s\n", d2->semeai_attack_point, result_to_string(d2->semeai_attack_code)); } gprintf("... neighbors"); for (k = 0; k < d2->neighbors; k++) { int d = d2->adjacent[k]; gprintf(" %1m", dragon2[d].origin); } gprintf("\n"); if (d2->lunch != NO_MOVE) gprintf("... adjacent worm %1m is lunch\n", d2->lunch); } } } static int new_dragon_origins[BOARDMAX]; /* Compute new dragons, e.g. after having made a move. This will not * affect any global state. */ void compute_new_dragons(int dragon_origins[BOARDMAX]) { int pos; int saved_cutting_points[BOARDMAX]; /* This is currently necessary in order not to mess up the * worm[].cutstone2 field. See cutstone2_helper in * patterns/helpers.c. On the other hand it shouldn't be very * interesting to recompute dragons in the original position. */ gg_assert(stackp > 0); memcpy(saved_cutting_points, cutting_points, sizeof(cutting_points)); memset(cutting_points, 0, sizeof(cutting_points)); for (pos = BOARDMIN; pos < BOARDMAX; pos++) if (ON_BOARD(pos)) { if (board[pos] == EMPTY) new_dragon_origins[pos] = NO_MOVE; else new_dragon_origins[pos] = find_origin(pos); } find_cuts(); find_connections(); memcpy(cutting_points, saved_cutting_points, sizeof(cutting_points)); memcpy(dragon_origins, new_dragon_origins, sizeof(new_dragon_origins)); } /* This gets called if we are trying to compute dragons outside of * make_dragons(), typically after a move has been made. */ static void join_new_dragons(int d1, int d2) { int pos; /* Normalize dragon coordinates. */ d1 = new_dragon_origins[d1]; d2 = new_dragon_origins[d2]; /* If d1 and d2 are the same dragon, we do nothing. */ if (d1 == d2) return; ASSERT1(board[d1] == board[d2], d1); ASSERT1(IS_STONE(board[d1]), d1); /* Don't bother to do anything fancy with dragon origins. */ for (pos = BOARDMIN; pos < BOARDMAX; pos++) if (ON_BOARD(pos) && new_dragon_origins[pos] == d2) new_dragon_origins[pos] = d1; } /* * join_dragons amalgamates the dragon at (d1) to the * dragon at (d2). */ void join_dragons(int d1, int d2) { int ii; int origin; /* new origin */ /* If not called from make_dragons(), we don't work on the main * dragon[] array. */ if (stackp > 0) { join_new_dragons(d1, d2); return; } /* Normalize dragon coordinates. */ d1 = dragon[d1].origin; d2 = dragon[d2].origin; /* If d1 and d2 are the same dragon, we do nothing. */ if (d1 == d2) return; ASSERT1(board[d1] == board[d2], d1); gg_assert(dragon2_initialized == 0); ASSERT1(IS_STONE(board[d1]), d1); /* We want to have the origin pointing to the largest string of * the dragon. If this is not unique, we take the "upper * leftmost" one. */ if (worm[d1].size > worm[d2].size || (worm[d1].size == worm[d2].size && d1 < d2)) { origin = d1; DEBUG(DEBUG_DRAGONS, "joining dragon at %1m to dragon at %1m\n", d2, d1); } else { origin = d2; DEBUG(DEBUG_DRAGONS, "joining dragon at %1m to dragon at %1m\n", d1, d2); } dragon[origin].size = dragon[d2].size + dragon[d1].size; dragon[origin].effective_size = (dragon[d2].effective_size + dragon[d1].effective_size); /* Join the second next_worm_in_dragon chain at the end of the first one. */ { int last_worm_origin_dragon = origin; while (next_worm_list[last_worm_origin_dragon] != NO_MOVE) last_worm_origin_dragon = next_worm_list[last_worm_origin_dragon]; if (origin == d1) next_worm_list[last_worm_origin_dragon] = d2; else next_worm_list[last_worm_origin_dragon] = d1; } for (ii = BOARDMIN; ii < BOARDMAX; ii++) { if (ON_BOARD(ii) && (dragon[ii].origin == d1 || dragon[ii].origin == d2)) dragon[ii].origin = origin; } } /* * compute_crude_status(pos) tries to determine whether the dragon * at (pos) is ALIVE, DEAD, or UNKNOWN. The algorithm is not perfect * and can give incorrect answers. * * The dragon is judged alive if its genus is >1. It is judged dead if * the genus is <2, it has no escape route, and no adjoining string can * be easily captured. Otherwise it is judged UNKNOWN. */ static enum dragon_status compute_crude_status(int pos) { /* FIXME: We lose information when constructing true_genus. This * code can be improved. */ struct eyevalue *genus = &DRAGON2(pos).genus; int true_genus = max_eyes(genus) + min_eyes(genus); int lunch = DRAGON2(pos).lunch; gg_assert(dragon2_initialized); /* If it has two sure eyes, everything is just dandy. */ if (true_genus > 3) return ALIVE; /* If the dragon consists of one worm, there is an attack, but * no defense and there is less than one eye and one half eye, * the situation is hopeless. */ if (dragon[pos].size == worm[pos].size && worm[pos].attack_codes[0] != 0 && worm[pos].defense_codes[0] == 0 && true_genus < 3) return DEAD; if (lunch != NO_MOVE && true_genus < 3 && worm[lunch].defense_codes[0] != 0 && DRAGON2(pos).escape_route < 5) if (true_genus == 2 || worm[lunch].size > 2) return CRITICAL; if (lunch != NO_MOVE && true_genus >= 3) return ALIVE; if (lunch == NO_MOVE || worm[lunch].cutstone < 2) { if (true_genus < 3 && DRAGON2(pos).escape_route == 0 && DRAGON2(pos).moyo_size < 5) return DEAD; if (true_genus == 3 && DRAGON2(pos).escape_route < 5) return CRITICAL; } if (DRAGON2(pos).moyo_territorial_value > 9.99) return ALIVE; return UNKNOWN; } /* The dragon escape measure. This is defined as follows. * * Let a PATH be a sequence of adjacent intersections that do nowhere * touch or include an opponent stone or touch the border. It may * include friendly stones and those are allowed to touch opponent * stones or the border). Let a DISTANCE N INTERSECTION be an * intersection connected to a dragon by a path of length N, but by no * shorter path. The connection of the path to the dragon may either * be by direct adjacency or, in the first step, diagonally if both * adjoining intersections are empty. * * It is assumed that each intersection has an escape value, which * would typically depend on influence and (preliminary) dragon * status. We define the escape potential as the sum of the escape * values over the distance four intersections of the dragon. * * Example of distance N intersections, 1 <= N <= 4: * * . . . . . . . . . . . . . . . . . . * . . . . . X . . O . . . . . X . . O * . . X . . . . . O . . X . 2 . 4 . O * X . . . . . . . . X . . 1 1 2 3 4 . * X O . O . . . . O X O 1 O 1 2 3 4 O * X O . O . . . . . X O 1 O 1 . 4 . . * X O . . . X . O O X O 1 . . X . . O * . . . X . . . . . . 1 . X . . . . . * X . . . . X . . . X . . . . X . . . * . . . . . . . . . . . . . . . . . . * * Additionally, a path may not pass a connection inhibited * intersection. */ #define ENQUEUE(pos) (queue[queue_end++] = (pos),\ mx[pos] = 1) /* Compute the escape potential described above. The dragon is marked * in the goal array. */ int dragon_escape(signed char goal[BOARDMAX], int color, signed char escape_value[BOARDMAX]) { int ii; int k; static int mx[BOARDMAX]; static int mx_initialized = 0; int queue[MAX_BOARD * MAX_BOARD]; int queue_start = 0; int queue_end = 0; int other = OTHER_COLOR(color); int distance; int escape_potential = 0; gg_assert(IS_STONE(color)); if (!mx_initialized) { memset(mx, 0, sizeof(mx)); mx_initialized = 1; } /* Enter the stones of the dragon in the queue. */ for (ii = BOARDMIN; ii < BOARDMAX; ii++) if (ON_BOARD(ii) && goal[ii]) ENQUEUE(ii); /* Find points at increasing distances from the dragon. At distance * four, sum the escape values at those points to get the escape * potential. */ for (distance = 0; distance <= 4; distance++) { int save_queue_end = queue_end; while (queue_start < save_queue_end) { ii = queue[queue_start]; queue_start++; /* Do not pass connection inhibited intersections. */ if (cut_possible(ii, OTHER_COLOR(color))) continue; if (distance == 4) escape_potential += escape_value[ii]; else { if (ON_BOARD(SOUTH(ii)) && !mx[SOUTH(ii)] && (board[SOUTH(ii)] == color || (board[SOUTH(ii)] == EMPTY && ON_BOARD(SE(ii)) && board[SE(ii)] != other && ON_BOARD(SS(ii)) && board[SS(ii)] != other && ON_BOARD(SW(ii)) && board[SW(ii)] != other))) ENQUEUE(SOUTH(ii)); if (ON_BOARD(WEST(ii)) && !mx[WEST(ii)] && (board[WEST(ii)] == color || (board[WEST(ii)] == EMPTY && ON_BOARD(SW(ii)) && board[SW(ii)] != other && ON_BOARD(WW(ii)) && board[WW(ii)] != other && ON_BOARD(NW(ii)) && board[NW(ii)] != other))) ENQUEUE(WEST(ii)); if (ON_BOARD(NORTH(ii)) && !mx[NORTH(ii)] && (board[NORTH(ii)] == color || (board[NORTH(ii)] == EMPTY && ON_BOARD(NW(ii)) && board[NW(ii)] != other && ON_BOARD(NN(ii)) && board[NN(ii)] != other && ON_BOARD(NE(ii)) && board[NE(ii)] != other))) ENQUEUE(NORTH(ii)); if (ON_BOARD(EAST(ii)) && !mx[EAST(ii)] && (board[EAST(ii)] == color || (board[EAST(ii)] == EMPTY && ON_BOARD(NE(ii)) && board[NE(ii)] != other && ON_BOARD(EE(ii)) && board[EE(ii)] != other && ON_BOARD(SE(ii)) && board[SE(ii)] != other))) ENQUEUE(EAST(ii)); /* For distance one intersections, allow kosumi to move out. I.e. * * ??.. * X.*. * ?O.? * ??X? * */ if (distance == 0) { if (board[SOUTH(ii)] == EMPTY && board[WEST(ii)] == EMPTY && !mx[SW(ii)] && (board[SW(ii)] == color || (board[SW(ii)] == EMPTY && ON_BOARD(SOUTH(SW(ii))) && board[SOUTH(SW(ii))] != other && ON_BOARD(WEST(SW(ii))) && board[WEST(SW(ii))] != other))) ENQUEUE(SW(ii)); if (board[WEST(ii)] == EMPTY && board[NORTH(ii)] == EMPTY && !mx[NW(ii)] && (board[NW(ii)] == color || (board[NW(ii)] == EMPTY && ON_BOARD(WEST(NW(ii))) && board[WEST(NW(ii))] != other && ON_BOARD(NORTH(NW(ii))) && board[NORTH(NW(ii))] != other))) ENQUEUE(NW(ii)); if (board[NORTH(ii)] == EMPTY && board[EAST(ii)] == EMPTY && !mx[NE(ii)] && (board[NE(ii)] == color || (board[NE(ii)] == EMPTY && ON_BOARD(NORTH(NE(ii))) && board[NORTH(NE(ii))] != other && ON_BOARD(EAST(NE(ii))) && board[EAST(NE(ii))] != other))) ENQUEUE(NE(ii)); if (board[EAST(ii)] == EMPTY && board[SOUTH(ii)] == EMPTY && !mx[SE(ii)] && (board[SE(ii)] == color || (board[SE(ii)] == EMPTY && ON_BOARD(EAST(SE(ii))) && board[EAST(SE(ii))] != other && ON_BOARD(SOUTH(SE(ii))) && board[SOUTH(SE(ii))] != other))) ENQUEUE(SE(ii)); } } } } /* Reset used mx cells. */ for (k = 0; k < queue_end; k++) { /* The assertion fails if the same element should have been queued * twice, which might happen if ENQUEUE() is called without * checking mx[]. */ ASSERT1(mx[queue[k]] == 1, queue[k]); mx[queue[k]] = 0; } return escape_potential; } /* Wrapper to call the function above and compute the escape potential * for the dragon at (pos). */ static int compute_escape(int pos, int dragon_status_known) { int ii; signed char goal[BOARDMAX]; signed char escape_value[BOARDMAX]; signed char safe_stones[BOARDMAX]; ASSERT1(IS_STONE(board[pos]), pos); for (ii = BOARDMIN; ii < BOARDMAX; ii++) if (ON_BOARD(ii)) goal[ii] = is_same_dragon(ii, pos); /* Compute escape_value array. Points are awarded for moyo (4), * area (2) or EMPTY (1). Values may change without notice. */ get_lively_stones(OTHER_COLOR(board[pos]), safe_stones); compute_escape_influence(board[pos], safe_stones, NULL, 0, escape_value); /* If we can reach a live group, award 6 points. */ for (ii = BOARDMIN; ii < BOARDMAX; ii++) { if (!ON_BOARD(ii)) continue; if (dragon_status_known) { if (dragon[ii].crude_status == ALIVE) escape_value[ii] = 6; else if (dragon[ii].crude_status == UNKNOWN && (DRAGON2(ii).escape_route > 5 || DRAGON2(ii).moyo_size > 5)) escape_value[ii] = 4; } else { if (board[ii] == board[pos] && !goal[ii] && worm[ii].attack_codes[0] == 0) escape_value[ii] = 2; } } return dragon_escape(goal, board[pos], escape_value); } /* * Sum up the surrounding moyo sizes for each dragon. For this * we retrieve the moyo data stored in influence_data (*q) (which must * have been computed previously) from the influence module. * We set dragon2[].moyo_size and .moyo_value if it is smaller than the * current entry. * * Currently this is implemented differently depending on whether * experimental connections are used or not. The reason why this is * needed is that most of the B patterns in conn.db are disabled for * experimental connections, which may cause the moyo segmentation to * pass through cutting points between dragons, making the surrounding * moyo size mostly useless. Instead we only use the part of the * surrounding moyo which is closest to some worm of the dragon. */ static void compute_surrounding_moyo_sizes(const struct influence_data *q) { int pos; int d; int k; int moyo_color; float moyo_sizes[BOARDMAX]; float moyo_values[BOARDMAX]; for (pos = BOARDMIN; pos < BOARDMAX; pos++) { moyo_sizes[pos] = 0.0; moyo_values[pos] = 0.0; } for (pos = BOARDMIN; pos < BOARDMAX; pos++) { if (!ON_BOARD(pos)) continue; moyo_color = whose_moyo_restricted(q, pos); if (moyo_color == board[pos]) continue; if (moyo_color == WHITE) { for (k = 0; k < number_close_white_worms[pos]; k++) { int w = close_white_worms[pos][k]; int dr = dragon[w].origin; moyo_sizes[dr] += 1.0 / number_close_white_worms[pos]; moyo_values[dr] += (gg_min(influence_territory(q, pos, WHITE), 1.0) / number_close_white_worms[pos]); } } if (moyo_color == BLACK) { for (k = 0; k < number_close_black_worms[pos]; k++) { int w = close_black_worms[pos][k]; int dr = dragon[w].origin; moyo_sizes[dr] += 1.0 / number_close_black_worms[pos]; moyo_values[dr] += (gg_min(influence_territory(q, pos, BLACK), 1.0) / number_close_black_worms[pos]); } } } for (d = 0; d < number_of_dragons; d++) { int this_moyo_size = (int) moyo_sizes[dragon2[d].origin]; float this_moyo_value = moyo_values[dragon2[d].origin]; if (this_moyo_size < dragon2[d].moyo_size) { dragon2[d].moyo_size = this_moyo_size; dragon2[d].moyo_territorial_value = this_moyo_value; } } } static struct interpolation_data moyo_value2weakness = { 5, 0.0, 15.0, {1.0, 0.65, 0.3, 0.15, 0.05, 0.0}}; static struct interpolation_data escape_route2weakness = { 5, 0.0, 25.0, {1.0, 0.6, 0.3, 0.1, 0.05, 0.0}}; static struct interpolation_data genus2weakness = { 6, 0.0, 3.0, {1.0, 0.95, 0.8, 0.5, 0.2, 0.1, 0.0}}; float crude_dragon_weakness(int safety, struct eyevalue *genus, int has_lunch, float moyo_value, float escape_route) { /* FIXME: We lose information when constructing true_genus. This * code can be improved. */ float true_genus = 0.5 * (max_eyes(genus) + min_eyes(genus) + (has_lunch != 0)); float weakness_value[3]; float weakness; int i, j; if (safety == INVINCIBLE || safety == INESSENTIAL) return 0.0; if (safety == TACTICALLY_DEAD || safety == DEAD || safety == CRITICAL) return 1.0; weakness_value[0] = gg_interpolate(&moyo_value2weakness, moyo_value); weakness_value[1] = gg_interpolate(&escape_route2weakness, escape_route); weakness_value[2] = gg_interpolate(&genus2weakness, true_genus); DEBUG(DEBUG_DRAGONS, " moyo value %f -> %f, escape %f -> %f, eyes %f -> %f\n", moyo_value, weakness_value[0], escape_route, weakness_value[1], true_genus, weakness_value[2]); for (i = 0; i < 3; i++) for (j = i + 1; j < 3; j++) if (weakness_value[j] < weakness_value[i]) { float tmp = weakness_value[i]; weakness_value[i] = weakness_value[j]; weakness_value[j] = tmp; } /* The overall weakness is mostly, but not completely determined by the * best value found so far: */ weakness = gg_min(0.7 * weakness_value[0] + 0.3 * weakness_value[1], 1.3 * weakness_value[0]); gg_assert(weakness >= 0.0 && weakness <= 1.0); return weakness; } /* This function tries to guess a coefficient measuring the weakness of * a dragon. This coefficient * the effective size of the dragon can be * used to award a strategic penalty for weak dragons. */ static float compute_dragon_weakness_value(int d) { int origin = dragon2[d].origin; float weakness; /* Possible ingredients for the computation: * '+' means currently used, '-' means not (yet?) used * - pre-owl moyo_size * + post-owl moyo_size and its territory value * + escape factor * + number of eyes * - minus number of vital attack moves? * + from owl: * + attack certain? * - number of owl nodes * - maybe reading shadow? * + threat to attack? * - possible connections to neighbour dragons */ DEBUG(DEBUG_DRAGONS, "Computing weakness of dragon at %1m:\n", origin); weakness = crude_dragon_weakness(dragon2[d].safety, &dragon2[d].genus, dragon2[d].lunch != NO_MOVE, dragon2[d].moyo_territorial_value, (float) dragon2[d].escape_route); /* Now corrections due to (uncertain) owl results resp. owl threats. */ if (!dragon2[d].owl_attack_certain) weakness += gg_min(0.25 * (1.0 - weakness), 0.25 * weakness); if (!dragon2[d].owl_defense_certain) weakness += gg_min(0.25 * (1.0 - weakness), 0.25 * weakness); if (dragon2[d].owl_threat_status == CAN_THREATEN_ATTACK) weakness += 0.15 * (1.0 - weakness); if (weakness < 0.0) weakness = 0.0; if (weakness > 1.0) weakness = 1.0; DEBUG(DEBUG_DRAGONS, " result: %f.\n", weakness); return weakness; } /* This function has to be called _after_ the owl analysis and the * subsequent re-run of the influence code. */ void compute_refined_dragon_weaknesses() { int d; /* Compute the surrounding moyo sizes. */ for (d = 0; d < number_of_dragons; d++) dragon2[d].moyo_size = 2 * BOARDMAX; /* Set moyo sizes according to initial_influence. */ compute_surrounding_moyo_sizes(&initial_black_influence); compute_surrounding_moyo_sizes(&initial_white_influence); for (d = 0; d < number_of_dragons; d++) dragon2[d].weakness = compute_dragon_weakness_value(d); } /* The strategic size is the effective size, plus a bonus for all weak * neighbouring dragons of the opponent. */ void compute_strategic_sizes() { float *bonus = calloc(number_of_dragons, sizeof(float)); int d; int k; for (d = 0; d < number_of_dragons; d++) { /* Compute bonus for all neighbors of dragon (d). The total bonus for * all neighbors is effective_size(d) * weakness(d), and it is given * to a neighbor d2 proportionally to the value of * effective_size(d2) * weakness(d2). */ float sum = 0.0; if (dragon2[d].safety == INESSENTIAL) continue; for (k = 0; k < dragon2[d].neighbors; k++) { int d2 = dragon2[d].adjacent[k]; if (board[dragon2[d2].origin] == OTHER_COLOR(board[dragon2[d].origin]) && dragon2[d2].safety != INESSENTIAL) sum += DRAGON(d2).effective_size * dragon2[d2].weakness; } if (sum == 0.0) continue; for (k = 0; k < dragon2[d].neighbors; k++) { int d2 = dragon2[d].adjacent[k]; if (board[dragon2[d2].origin] == OTHER_COLOR(board[dragon2[d].origin]) && dragon2[d2].safety != INESSENTIAL) { bonus[d2] += ((DRAGON(d2).effective_size * dragon2[d2].weakness) / sum) * DRAGON(d).effective_size * dragon2[d].weakness; if (0) gprintf("Dragon %1m receives %f effective size bonus from %1m.\n", dragon2[d2].origin, ((DRAGON(d2).effective_size * dragon2[d2].weakness) / sum) * DRAGON(d).effective_size * dragon2[d].weakness, dragon2[d].origin); } } } for (d = 0; d < number_of_dragons; d++) { if (0) gprintf("Dragon %1m gets effective size bonus of %f.\n", dragon2[d].origin, bonus[d]); /* We cap strategic size at 3 * effective_size. (This is ad hoc.) */ dragon2[d].strategic_size = gg_min(bonus[d] + DRAGON(d).effective_size, 3 * DRAGON(d).effective_size); } free(bonus); } /* * Test whether two dragons are the same. Used by autohelpers and elsewhere. */ int is_same_dragon(int d1, int d2) { if (d1 == NO_MOVE || d2 == NO_MOVE) return (d1 == d2); ASSERT_ON_BOARD1(d1); ASSERT_ON_BOARD1(d2); return (dragon[d1].origin == dragon[d2].origin); } /* Test whether two dragons are neighbors. */ int are_neighbor_dragons(int d1, int d2) { int k; d1 = dragon[d1].origin; d2 = dragon[d2].origin; for (k = 0; k < DRAGON2(d1).neighbors; k++) if (dragon2[DRAGON2(d1).adjacent[k]].origin == d2) return 1; /* Just to be make sure that this function is always symmetric, we * do it the other way round too. */ for (k = 0; k < DRAGON2(d2).neighbors; k++) if (dragon2[DRAGON2(d2).adjacent[k]].origin == d1) return 1; return 0; } /* Mark the stones of a dragon. */ void mark_dragon(int pos, signed char mx[BOARDMAX], signed char mark) { int w; for (w = first_worm_in_dragon(dragon[pos].origin); w != NO_MOVE; w = next_worm_in_dragon(w)) mark_string(w, mx, mark); } /* The following two functions allow to traverse all worms in a dragon: * for (ii = first_worm_in_dragon(pos); ii != NO_MOVE; * ii = next_worm_in_dragon(ii);) * ... * At the moment first_worm_in_dragon(pos) will always be the origin * of the dragon, but you should not rely on that. */ int first_worm_in_dragon(int d) { return dragon[d].origin; } int next_worm_in_dragon(int w) { ASSERT1(worm[w].origin == w, w); return next_worm_list[w]; } /* ================================================================ */ /* A few status functions */ /* ================================================================ */ /* * These functions are only here because then we don't need to expose * the dragon structure to the external program. */ enum dragon_status crude_status(int pos) { return dragon[pos].crude_status; } enum dragon_status dragon_status(int pos) { return dragon[pos].status; } int lively_dragon_exists(int color) { if (color == WHITE) return lively_white_dragons > 0; else return lively_black_dragons > 0; } /* Is this dragon weak? */ int dragon_weak(int pos) { ASSERT_ON_BOARD1(pos); /* FIXME: This should not happen, but avoids a crash. What is * the proper fix for calling this at stackp != 0 ? */ if (dragon[pos].id < 0 || dragon[pos].id >= number_of_dragons) return 1; return (DRAGON2(pos).weakness > 0.40001); } /* Returns the size of the biggest critical dragon on the board. */ int size_of_biggest_critical_dragon(void) { int str; int max_size = 0; for (str = BOARDMIN; str < BOARDMAX; str++) if (ON_BOARD(str)) { if (board[str] == EMPTY || dragon[str].origin != str) continue; /* Get the best available status for the dragon */ if (dragon[str].status == CRITICAL) { if (dragon[str].size >= max_size) max_size = dragon[str].size; } } return max_size; } /************************************************************************ * A list of all cuts found during connection matching * ************************************************************************/ #define MAX_CUTS 3 * MAX_BOARD * MAX_BOARD struct cut_data { int apos; int bpos; int move; }; static int num_cuts = 0; static struct cut_data cut_list[MAX_CUTS]; static void clear_cut_list() { num_cuts = 0; } /* Store in the list that (move) disconnects the two strings at * apos and bpos. */ void add_cut(int apos, int bpos, int move) { gg_assert(board[apos] == board[bpos]); if (num_cuts == MAX_CUTS) return; if (apos > bpos) { int tmp = apos; apos = bpos; bpos = tmp; } if (move == NO_MOVE) return; cut_list[num_cuts].apos = apos; cut_list[num_cuts].bpos = bpos; cut_list[num_cuts].move = move; num_cuts++; if (0) gprintf("Added %d-th cut at %1m between %1m and %1m.\n", num_cuts, move, apos, bpos); } /* For every move in the cut list disconnecting two of opponent's strings, * test whether the two strings can be connected at all. If so, add a * CUT_MOVE reason. */ void cut_reasons(int color) { int k; for (k = 0; k < num_cuts; k++) if (board[cut_list[k].apos] == OTHER_COLOR(color) && !is_same_dragon(cut_list[k].apos, cut_list[k].bpos) && string_connect(cut_list[k].apos, cut_list[k].bpos, NULL) == WIN) add_cut_move(cut_list[k].move, cut_list[k].apos, cut_list[k].bpos); } /* ================================================================ */ /* Debugger functions */ /* ================================================================ */ /* For use in gdb, print details of the dragon at (m, n). * Add this to your .gdbinit file: * * define dragon * set ascii_report_dragon("$arg0") * end * * Now 'dragon S8' will report the details of the S8 dragon. * */ void ascii_report_dragon(char *string) { int pos = string_to_location(board_size, string); if (!ON_BOARD(pos)) fprintf(stderr, "unknown position %s\n", string); else report_dragon(stderr, pos); } void report_dragon(FILE *outfile, int pos) { int w; int k; struct dragon_data *d = &(dragon[pos]); struct dragon_data2 *d2 = &(dragon2[d->id]); if (board[pos] == EMPTY) { gprintf("There is no dragon at %1m\n", pos); return; } if (d->id < 0) { gprintf("Dragon data not available at %1m\n", pos); return; } gfprintf(outfile, "color %s\n", color_to_string(d->color)); gfprintf(outfile, "origin %1m\n", d->origin); gfprintf(outfile, "size %d\n", d->size); gfprintf(outfile, "effective_size %f\n", d->effective_size); gfprintf(outfile, "strategic_size %f\n", d2->strategic_size); gfprintf(outfile, "genus %s\n", eyevalue_to_string(&d2->genus)); gfprintf(outfile, "heye %1m\n", d2->heye); gfprintf(outfile, "escape_route %d\n", d2->escape_route); gfprintf(outfile, "lunch %1m\n", d2->lunch); gfprintf(outfile, "crude_status %s\n", status_to_string(d->crude_status)); gfprintf(outfile, "owl_status %s\n", status_to_string(d2->owl_status)); gfprintf(outfile, "status %s\n", status_to_string(d->status)); gfprintf(outfile, "safety %s\n", status_to_string(d2->safety)); gfprintf(outfile, "weakness %f\n", d2->weakness); gfprintf(outfile, "weakness_pre_owl %f\n", d2->weakness_pre_owl); gfprintf(outfile, "surround_status %d\n", d2->surround_status); gfprintf(outfile, "surround_size %d\n", d2->surround_size); gfprintf(outfile, "moyo_size %d\n", d2->moyo_size); gfprintf(outfile, "moyo_territorial_value %f\n", d2->moyo_territorial_value); gfprintf(outfile, "neighbors "); for (k = 0; k < d2->neighbors; k++) gfprintf(outfile, "%1m ", DRAGON(d2->adjacent[k]).origin); gfprintf(outfile, "\nhostile_neighbors %d\n", d2->hostile_neighbors); gfprintf(outfile, "owl_attack_code %d\n", d2->owl_attack_code); gfprintf(outfile, "owl_attack_point %1m\n", d2->owl_attack_point); gfprintf(outfile, "owl_attack_certain %s\n", (d2->owl_attack_certain) ? "YES" : "NO"); gfprintf(outfile, "owl_2nd_attack_point %1m\n", d2->owl_second_attack_point); gfprintf(outfile, "owl_threat_status %s\n", status_to_string(d2->owl_threat_status)); gfprintf(outfile, "owl_defense_code %d\n", d2->owl_defense_code); gfprintf(outfile, "owl_defense_point %1m\n", d2->owl_defense_point); gfprintf(outfile, "owl_defense_certain %s\n", (d2->owl_defense_certain) ? "YES" : "NO"); gfprintf(outfile, "owl_2nd_defense_point %1m\n", d2->owl_second_defense_point); gfprintf(outfile, "owl_attack_kworm %1m\n", d2->owl_attack_kworm); gfprintf(outfile, "owl_defense_kworm %1m\n", d2->owl_defense_kworm); gfprintf(outfile, "semeais %d\n", d2->semeais); gfprintf(outfile, "semeai_defense_code %d\n", d2->semeai_defense_code); gfprintf(outfile, "semeai_defense_point %1m\n", d2->semeai_defense_point); gfprintf(outfile, "semeai_defense_certain %d\n", d2->semeai_defense_certain); gfprintf(outfile, "semeai_defense_target %1m\n", d2->semeai_defense_target); gfprintf(outfile, "semeai_attack_code %d\n", d2->semeai_attack_code); gfprintf(outfile, "semeai_attack_point %1m\n", d2->semeai_attack_point); gfprintf(outfile, "semeai_attack_certain %d\n", d2->semeai_attack_certain); gfprintf(outfile, "semeai_attack_target %1m\n", d2->semeai_attack_target); gfprintf(outfile, "strings "); for (w = first_worm_in_dragon(pos); w != NO_MOVE; w = next_worm_in_dragon(w)) gfprintf(outfile, "%1m ", w); gfprintf(outfile, "\n"); } /* * Local Variables: * tab-width: 8 * c-basic-offset: 2 * End: */