/* Generate a move. */ static void generate_move(int *i, int *j, int color) { int moves[MAX_BOARD * MAX_BOARD]; int num_moves = 0; int move; int ai, aj; int k; memset(moves, 0, sizeof(moves)); for (ai = 0; ai < board_size; ai++) for (aj = 0; aj < board_size; aj++) { /* Consider moving at (ai, aj) if it is legal and not suicide. */ if (legal_move(ai, aj, color) && !suicide(ai, aj, color)) { /* Further require the move not to be suicide for the opponent... */ if (!suicide(ai, aj, OTHER_COLOR(color))) moves[num_moves++] = POS(ai, aj); else { /* ...however, if the move captures at least one stone, * consider it anyway. */ for (k = 0; k < 4; k++) { int bi = ai + deltai[k]; int bj = aj + deltaj[k]; if (on_board(bi, bj) && get_board(bi, bj) == OTHER_COLOR(color)) { moves[num_moves++] = POS(ai, aj); break; } } } } } /* Choose one of the considered moves randomly with uniform * distribution. (Strictly speaking the moves with smaller 1D * coordinates tend to have a very slightly higher probability to be * chosen, but for all practical purposes we get a uniform * distribution.) */ if (num_moves > 0) { move = moves[xor_randn(num_moves)]; *i = I(move); *j = J(move); } else { /* But pass if no move was considered. */ *i = -1; *j = -1; } }
bool stone::can_live(chain* itor) { int visited[BOARD_SIZE*BOARD_SIZE]; memset(visited, 0, sizeof(visited)); int a = 0; list <int> q1; q1.push_back(POS(this->row, this->col)); while (q1.size() != EMPTY) { int top = q1.front(); q1.pop_front(); if (visited[top] == 1){ continue; } visited[top] = 1; for (int k = 0; k < 4; k++) { if (stone_board->on_board(I(top) + deltai[k], J(top) + deltaj[k])) { int index = POS(I(top) + deltai[k], J(top) + deltaj[k]); if (stone_board->main_board[index]->color == EMPTY) { return true; } else if (stone_board->main_board[index]->color == (itor)->chain_block->color) { continue; } else if (stone_board->main_board[index]->color == OTHER_COLOR((itor)->chain_block->color)) { q1.push_back(index); } } } } return false; }
//PART 1 void stone::evaluating_empty(int *arr) { //arr[4] -> [my, enemy, empty, outside] int countforshared = 0; for (int k = 0; k < 4; k++) { int index = POS(row + deltai[k], col + deltaj[k]); if (!stone_board->on_board(row + deltai[k], col + deltaj[k])) { arr[3]++; } else if (stone_board->main_board[index]->color == stone_block->color) { arr[0]++; this->shared[countforshared] = stone_board->main_board[index]->stone_chain; countforshared++; //record the chain if we find the same color } else if (stone_board->main_board[index]->color == OTHER_COLOR(stone_block->color)) { arr[1]++; } else if (stone_board->main_board[index]->color == EMPTY) { arr[2]++; } } }
int GoBoard::is_anti_dian33_available(int color, int rival_move) { int star[4] = { 3 * board_size + 3, 3 * board_size + 9, 9 * board_size + 3, 9 * board_size + 9 }; //stars position int other = OTHER_COLOR(color); int dian, anti; dian = POS(I(star[0]) - 1, J(star[0]) - 1); anti = POS(I(star[0]) - 0, J(star[0]) - 1); if (board[star[0]] == color && board[dian] == other && board[anti] == EMPTY && heavy_policy(anti, color)) return anti; dian = POS(I(star[1]) - 1, J(star[1]) + 1); anti = POS(I(star[1]) + 0, J(star[1]) + 1); if (board[star[1]] == color && board[dian] == other && board[anti] == EMPTY && heavy_policy(anti, color)) return anti; dian = POS(I(star[2]) + 1, J(star[2]) - 1); anti = POS(I(star[2]) - 0, J(star[2]) - 1); if (board[star[2]] == color && board[dian] == other && board[anti] == EMPTY && heavy_policy(anti, color)) return anti; dian = POS(I(star[3]) + 1, J(star[3]) + 1); anti = POS(I(star[3]) + 0, J(star[3]) + 1); if (board[star[3]] == color && board[dian] == other && board[anti] == EMPTY && heavy_policy(anti, color)) return anti; return -1; }
void updateboard(int i, int j, int color) { int other = OTHER_COLOR(color); assert(i >= 0 && i < board_size && j >= 0 && j < board_size); if (p[i][j] != EMPTY) { gprintf("Stone overlay problem at %m!\n", i, j); gprintf("Try reducing the move range with -s and -e.\n"); abort(); } p[i][j] = color; if (1) DEBUG("Update board : %m = %d\n", i,j, color); if (i > 0 && p[i-1][j] == other) check_for_capture(i-1, j, other); if (i < board_size-1 && p[i+1][j] == other) check_for_capture(i+1, j, other); if (j > 0 && p[i][j-1] == other) check_for_capture(i, j-1, other); if (j < board_size-1 && p[i][j+1] == other) check_for_capture(i, j+1, other); }
static int do_pass(Gameinfo *gameinfo, int *passes, int force) { (*passes)++; init_sgf(gameinfo); gnugo_play_move(PASS_MOVE, gameinfo->to_move); sgffile_add_debuginfo(sgftree.lastnode, 0.0); sgftreeAddPlay(&sgftree, gameinfo->to_move, -1, -1); sgffile_output(&sgftree); gameinfo->to_move = OTHER_COLOR(gameinfo->to_move); if (force) { gameinfo->computer_player = OTHER_COLOR(gameinfo->computer_player); sgftreeAddComment(&sgftree, "forced"); return 0; } return computer_move(gameinfo, passes); }
int GoBoard::is_xiaomu_available(int color, int rival_move) { int star[4] = { 3 * board_size + 3, 3 * board_size + 9, 9 * board_size + 3, 9 * board_size + 9 }; //stars position int anti, xiaomu; int other = OTHER_COLOR(color); if (board[star[0]] != color) { anti = POS(I(star[0]) + 1, J(star[0]) - 1); xiaomu = POS(I(star[0]) - 1, J(star[0]) - 0); if (board[xiaomu] == other && board[anti] == EMPTY && heavy_policy(anti, color)) return anti; anti = POS(I(star[0]) - 1, J(star[0]) + 1); xiaomu = POS(I(star[0]) - 0, J(star[0]) - 1); if (board[xiaomu] == other && board[anti] == EMPTY && heavy_policy(anti, color)) return anti; } if (board[star[1]] != color) { anti = POS(I(star[1]) + 1, J(star[1]) + 1); xiaomu = POS(I(star[1]) - 1, J(star[1]) - 0); if (board[xiaomu] == other && board[anti] == EMPTY && heavy_policy(anti, color)) return anti; anti = POS(I(star[1]) - 1, J(star[1]) - 1); xiaomu = POS(I(star[1]) + 0, J(star[1]) + 1); if (board[xiaomu] == other && board[anti] == EMPTY && heavy_policy(anti, color)) return anti; } if (board[star[2]] != color) { anti = POS(I(star[2]) - 1, J(star[2]) - 1); xiaomu = POS(I(star[2]) + 1, J(star[2]) + 0); if (board[xiaomu] == other && board[anti] == EMPTY && heavy_policy(anti, color)) return anti; anti = POS(I(star[2]) + 1, J(star[2]) + 1); xiaomu = POS(I(star[2]) - 0, J(star[2]) - 1); if (board[xiaomu] == other && board[anti] == EMPTY && heavy_policy(anti, color)) return anti; } if (board[star[3]] != color) { anti = POS(I(star[3]) + 1, J(star[3]) - 1); xiaomu = POS(I(star[3]) + 0, J(star[3]) + 1); if (board[xiaomu] == other && board[anti] == EMPTY && heavy_policy(anti, color)) return anti; anti = POS(I(star[3]) - 1, J(star[3]) + 1); xiaomu = POS(I(star[3]) + 1, J(star[3]) + 0); if (board[xiaomu] == other && board[anti] == EMPTY && heavy_policy(anti, color)) return anti; } return -1; }
static int autohelperread_attack5(int trans, int move, int color, int action) { int a, b; UNUSED(color); UNUSED(action); a = AFFINE_TRANSFORM(647, trans, move); b = AFFINE_TRANSFORM(685, trans, move); return 0 && rgoal[a] == 1 && accuratelib(b, OTHER_COLOR(color), MAX_LIBERTIES, NULL) <= 2 && accuratelib(move, color, MAX_LIBERTIES, NULL) > 1; }
static int do_move(Gameinfo *gameinfo, char *command, int *passes, int force) { int move = string_to_location(board_size, command); if (move == NO_MOVE) { printf("\nInvalid move: %s\n", command); return 0; } if (!is_allowed_move(move, gameinfo->to_move)) { printf("\nIllegal move: %s", command); return 0; } *passes = 0; TRACE("\nyour move: %1m\n\n", move); init_sgf(gameinfo); gnugo_play_move(move, gameinfo->to_move); sgffile_add_debuginfo(sgftree.lastnode, 0.0); sgftreeAddPlay(&sgftree, gameinfo->to_move, I(move), J(move)); sgffile_output(&sgftree); if (opt_showboard) { ascii_showboard(); printf("GNU Go is thinking...\n"); } if (force) { gameinfo->computer_player = OTHER_COLOR(gameinfo->computer_player); gameinfo->to_move = OTHER_COLOR(gameinfo->to_move); sgftreeAddComment(&sgftree, "forced"); return 0; } gameinfo->to_move = OTHER_COLOR(gameinfo->to_move); return computer_move(gameinfo, passes); }
static void benchmark(int num_games_per_point) { int i, j; unsigned int random_seed = 1U; board_size = 9; komi = 0.5; init_brown(random_seed); for (i = 0; i < board_size; i++) { for (j = 0; j < board_size; j++) { int white_wins = 0; int black_wins = 0; int k; for (k = 0; k < num_games_per_point; k++) { int passes = 0; int num_moves = 1; int color = WHITE; clear_board(); play_move(i, j, BLACK); while (passes < 3 && num_moves < 600) { int m, n; generate_move(&m, &n, color); play_move(m, n, color); if (pass_move(m, n)) { passes++; } else { passes = 0; } num_moves++; color = OTHER_COLOR(color); } if (passes == 3) { if (compute_score() > 0) { white_wins++; } else { black_wins++; } } } /* printf("%d %d %f\n", i, j, (float) black_wins / (float) (black_wins + white_wins)); */ } } }
int GoBoard::is_anti_yijianjia_available(int color, int rival_move) { { int star[4] = { 3 * board_size + 3, 3 * board_size + 9, 9 * board_size + 3, 9 * board_size + 9 }; //stars position int kakari1, kakari2; int other = OTHER_COLOR(color); kakari1 = POS(I(star[0]) + 0, J(star[0]) + 2); kakari2 = POS(I(star[0]) + 2, J(star[0]) + 0); if (board[star[0]] == color) { if (board[kakari1] == other && board[kakari2] == EMPTY && heavy_policy(kakari2, color)) return kakari2; if (board[kakari2] == other && board[kakari1] == EMPTY && heavy_policy(kakari1, color)) return kakari1; } kakari1 = POS(I(star[1]) + 0, J(star[1]) - 2); kakari2 = POS(I(star[1]) + 2, J(star[1]) - 0); if (board[star[1]] == color) { if (board[kakari1] == other && board[kakari2] == EMPTY && heavy_policy(kakari2, color)) return kakari2; if (board[kakari2] == other && board[kakari1] == EMPTY && heavy_policy(kakari1, color)) return kakari1; } kakari1 = POS(I(star[2]) + 0, J(star[2]) + 2); kakari2 = POS(I(star[2]) - 2, J(star[2]) - 0); if (board[star[2]] == color) { if (board[kakari1] == other && board[kakari2] == EMPTY && heavy_policy(kakari2, color)) return kakari2; if (board[kakari2] == other && board[kakari1] == EMPTY && heavy_policy(kakari1, color)) return kakari1; } kakari1 = POS(I(star[3]) - 0, J(star[3]) - 2); kakari2 = POS(I(star[3]) - 2, J(star[3]) - 0); if (board[star[3]] == color) { if (board[kakari1] == other && board[kakari2] == EMPTY && heavy_policy(kakari2, color)) return kakari2; if (board[kakari2] == other && board[kakari1] == EMPTY && heavy_policy(kakari1, color)) return kakari1; } return -1; } }
static int autohelperread_attack4(int trans, int move, int color, int action) { int a, b; UNUSED(color); a = AFFINE_TRANSFORM(647, trans, move); b = AFFINE_TRANSFORM(685, trans, move); if (!action) return rgoal[a] == 1 && accuratelib(b, OTHER_COLOR(color), MAX_LIBERTIES, NULL)==1; if (goallib == 2) ((read_attack + 4)->value) = 71; else ((read_attack + 4)->value) = 55;; return 0; }
bool stone::eat_this_chain(chain* itor) { for (int k = 0; k < 4; k++) { int ai = row + deltai[k]; int aj = col + deltaj[k]; if (stone_board->on_board(ai, aj) && stone_board->get_board(ai, aj)->color == OTHER_COLOR(color) && !stone_board->has_additional_liberty(ai, aj, row, col)) { if (stone_board->main_board[POS(ai,aj)]->stone_chain == itor ) return 1; } } return 0; }
/* Preliminary function for playing through the aftermath. */ static void do_play_aftermath(int color, struct aftermath_data *a) { int move; int pass = 0; int moves = 0; int color_to_play = color; DEBUG(DEBUG_AFTERMATH, "The aftermath starts.\n"); /* Disable computing worm and owl threats. */ disable_threat_computation = 1; /* Disable matching of endgame patterns. */ disable_endgame_patterns = 1; while (pass < 2 && moves < board_size * board_size) { int reading_nodes = get_reading_node_counter(); int owl_nodes = get_owl_node_counter(); int move_val = reduced_genmove(&move, color_to_play); if (move_val < 0) { int save_verbose = verbose; if (verbose > 0) verbose--; move_val = aftermath_genmove(&move, color_to_play, (color_to_play == WHITE ? a->white_control : a->black_control), 0); verbose = save_verbose; } play_move(move, color_to_play); if (aftermath_sgftree) sgftreeAddPlay(aftermath_sgftree, color_to_play, I(move), J(move)); moves++; DEBUG(DEBUG_AFTERMATH, "%d %C move %1m (nodes %d, %d total %d, %d)\n", movenum, color_to_play, move, get_owl_node_counter() - owl_nodes, get_reading_node_counter() - reading_nodes, get_owl_node_counter(), get_reading_node_counter()); if (move != PASS_MOVE) pass = 0; else pass++; color_to_play = OTHER_COLOR(color_to_play); } /* Reenable worm and dragon threats and endgame patterns. */ disable_threat_computation = 0; disable_endgame_patterns = 0; }
static int computer_move(Gameinfo *gameinfo, int *passes) { int move; float move_value; int resign; int resignation_declined = 0; float upper_bound, lower_bound; init_sgf(gameinfo); /* Generate computer move. */ if (autolevel_on) adjust_level_offset(gameinfo->to_move); move = genmove(gameinfo->to_move, &move_value, &resign); if (resignation_allowed && resign) { int state = ascii_endgame(gameinfo, 2); if (state != -1) return state; /* The opponent declined resignation. Remember not to resign again. */ resignation_allowed = 0; resignation_declined = 1; } if (showscore) { gnugo_estimate_score(&upper_bound, &lower_bound); current_score_estimate = (int) ((lower_bound + upper_bound) / 2.0); } mprintf("%s(%d): %1m\n", color_to_string(gameinfo->to_move), movenum + 1, move); if (is_pass(move)) (*passes)++; else *passes = 0; gnugo_play_move(move, gameinfo->to_move); sgffile_add_debuginfo(sgftree.lastnode, move_value); sgftreeAddPlay(&sgftree, gameinfo->to_move, I(move), J(move)); if (resignation_declined) sgftreeAddComment(&sgftree, "GNU Go resignation was declined"); sgffile_output(&sgftree); gameinfo->to_move = OTHER_COLOR(gameinfo->to_move); return 0; }
void decide_semeai(int apos, int bpos) { SGFTree tree; int resulta, resultb, move, result_certain; int color = board[apos]; if (color == EMPTY || board[bpos] != OTHER_COLOR(color)) { gprintf("gnugo: --decide-semeai called on invalid data\n"); return; } /* Prepare pattern matcher and reading code. */ reset_engine(); silent_examine_position(EXAMINE_DRAGONS_WITHOUT_OWL); gprintf("finished examine_position\n"); count_variations = 1; if (*outfilename) sgffile_begindump(&tree); gprintf("Analyzing semeai between %1m and %1m, %C moves first\n", apos, bpos, board[apos]); owl_analyze_semeai(apos, bpos, &resulta, &resultb, &move, &result_certain); gprintf("Semeai defense of %1m: result %s %1m\n", apos, result_to_string(resulta), move); gprintf("Semeai attack of %1m: result %s %1m\n", bpos, result_to_string(resultb), move); gprintf("%d nodes%s\n\n", count_variations, result_certain ? "" : ", uncertain result"); gprintf("Analyzing semeai between %1m and %1m, %C moves first\n", bpos, apos, board[bpos]); owl_analyze_semeai(bpos, apos, &resultb, &resulta, &move, &result_certain); gprintf("Semeai defense of %1m: result %s %1m\n", bpos, result_to_string(resultb), move); gprintf("Semeai attack of %1m: result %s %1m\n", apos, result_to_string(resulta), move); gprintf("%d nodes%s\n", count_variations, result_certain ? "" : ", uncertain result"); sgffile_enddump(outfilename); count_variations = 0; }
void decide_tactical_semeai(int apos, int bpos) { SGFTree tree; int resulta, resultb, move; int color = board[apos]; if (color == EMPTY || board[bpos] != OTHER_COLOR(color)) { gprintf("gnugo: --decide-semeai called on invalid data\n"); return; } /* Prepare pattern matcher and reading code. */ reset_engine(); silent_examine_position(board[apos], EXAMINE_DRAGONS_WITHOUT_OWL); gprintf("finished examine_position\n"); count_variations = 1; /* We want to see the reading performed, not just a result picked * from the cache. Thus we clear the cache here. */ reading_cache_clear(); if (*outfilename) sgffile_begindump(&tree); owl_analyze_semeai(apos, bpos, &resulta, &resultb, &move, 0); gprintf("After %s at %1m, %1m is %s, %1m is %s (%d nodes)\n", color == BLACK ? "black" : "white", move, apos, safety_to_string(resulta), bpos, safety_to_string(resultb), count_variations); owl_analyze_semeai(bpos, apos, &resultb, &resulta, &move, 0); gprintf("After %s at %1m, %1m is %s, %1m is %s (%d nodes)\n", color == BLACK ? "white" : "black", move, apos, safety_to_string(resulta), bpos, safety_to_string(resultb), count_variations); sgffile_enddump(outfilename); count_variations = 0; }
static int legal_move(int i, int j, int color) { int other = OTHER_COLOR(color); /* Pass is always legal. */ if (pass_move(i, j)) return 1; /* Already occupied. */ if (get_board(i, j) != EMPTY) return 0; /* Illegal ko recapture. It is not illegal to fill the ko so we must * check the color of at least one neighbor. */ if (i == ko_i && j == ko_j && ((on_board(i - 1, j) && get_board(i - 1, j) == other) || (on_board(i + 1, j) && get_board(i + 1, j) == other))) return 0; return 1; }
bool GoBoard::is_virtual_eye(int point, int color) { if (!is_surrounded(point, color)) return false; int nopponent = 0; int ai = I(point); int aj = J(point); bool at_edge = false; for (int i = 0; i < 4; i++) { int bi = ai + diag_i[i]; int bj = aj + diag_j[i]; if (!on_board(bi,bj)) { at_edge = true; continue; } if( get_board(bi,bj) == OTHER_COLOR(color) ) { nopponent++; } } if(at_edge) ++nopponent; return nopponent < 2; }
/* This function adds the semeai related move reasons, using the information * stored in the dragon2 array. * * If the semeai had an uncertain result, and there is a owl move with * certain result doing the same, we don't trust the semeai move. */ void semeai_move_reasons(int color) { int other = OTHER_COLOR(color); int d; int liberties; int libs[MAXLIBS]; int r; int resulta, resultb, semeai_move, s_result_certain; for (d = 0; d < number_of_dragons; d++) if (dragon2[d].semeais && DRAGON(d).status == CRITICAL) { if (DRAGON(d).color == color && dragon2[d].semeai_defense_point && (dragon2[d].owl_defense_point == NO_MOVE || dragon2[d].semeai_defense_certain >= dragon2[d].owl_defense_certain)) { /* My dragon can be defended. */ add_semeai_move(dragon2[d].semeai_defense_point, dragon2[d].origin); DEBUG(DEBUG_SEMEAI, "Adding semeai defense move for %1m at %1m\n", DRAGON(d).origin, dragon2[d].semeai_defense_point); if (neighbor_of_dragon(dragon2[d].semeai_defense_point, dragon2[d].semeai_defense_target) && !neighbor_of_dragon(dragon2[d].semeai_defense_point, dragon2[d].origin) && !is_self_atari(dragon2[d].semeai_defense_point, color)) { /* If this is a move to fill the non-common liberties of the * target, and is not a ko or snap-back, then we try all * non-common liberties of the target and add all winning * moves to the move list. */ liberties = findlib(dragon2[d].semeai_defense_target, MAXLIBS, libs); for (r = 0; r < liberties; r++) { if (!neighbor_of_dragon(libs[r], dragon2[d].origin) && !is_self_atari(libs[r], color) && libs[r] != dragon2[d].semeai_defense_point) { owl_analyze_semeai_after_move(libs[r], color, dragon2[d].semeai_defense_target, dragon2[d].origin, &resulta, &resultb, &semeai_move, 1, &s_result_certain, 0); if (resulta == 0 && resultb == 0) { add_semeai_move(libs[r], dragon2[d].origin); DEBUG(DEBUG_SEMEAI, "Adding semeai defense move for %1m at %1m\n", DRAGON(d).origin, libs[r]); } } } } } else if (DRAGON(d).color == other && dragon2[d].semeai_attack_point && (dragon2[d].owl_attack_point == NO_MOVE || dragon2[d].owl_defense_point == NO_MOVE || dragon2[d].semeai_attack_certain >= dragon2[d].owl_attack_certain)) { /* Your dragon can be attacked. */ add_semeai_move(dragon2[d].semeai_attack_point, dragon2[d].origin); DEBUG(DEBUG_SEMEAI, "Adding semeai attack move for %1m at %1m\n", DRAGON(d).origin, dragon2[d].semeai_attack_point); if (neighbor_of_dragon(dragon2[d].semeai_attack_point, dragon2[d].origin) && !neighbor_of_dragon(dragon2[d].semeai_attack_point, dragon2[d].semeai_attack_target) && !is_self_atari(dragon2[d].semeai_attack_point, color)) { liberties = findlib(dragon2[d].origin, MAXLIBS, libs); for (r = 0; r < liberties; r++) { if (!neighbor_of_dragon(libs[r], dragon2[d].semeai_attack_target) && !is_self_atari(libs[r], color) && libs[r] != dragon2[d].semeai_attack_point) { owl_analyze_semeai_after_move(libs[r], color, dragon2[d].origin, dragon2[d].semeai_attack_target, &resulta, &resultb, &semeai_move, 1, &s_result_certain, 0); if (resulta == 0 && resultb == 0) { add_semeai_move(libs[r], dragon2[d].origin); DEBUG(DEBUG_SEMEAI, "Adding semeai attack move for %1m at %1m\n", DRAGON(d).origin, libs[r]); } } } } } } }
/* Find moves turning supposed territory into seki. This is not * detected above since it either involves an ALIVE dragon adjacent to * a CRITICAL dragon, or an ALIVE dragon whose eyespace can be invaded * and turned into a seki. * * Currently we only search for tactically critical strings with * dragon status dead, which are neighbors of only one opponent * dragon, which is alive. Through semeai analysis we then determine * whether such a string can in fact live in seki. Relevant testcases * include gunnar:42 and gifu03:2. */ static void find_moves_to_make_seki() { int str; int defend_move; int resulta, resultb; for (str = BOARDMIN; str < BOARDMAX; str++) { if (IS_STONE(board[str]) && is_worm_origin(str, str) && attack_and_defend(str, NULL, NULL, NULL, &defend_move) && dragon[str].status == DEAD && DRAGON2(str).hostile_neighbors == 1) { int k; int color = board[str]; int opponent = NO_MOVE; int certain; struct eyevalue reduced_genus; for (k = 0; k < DRAGON2(str).neighbors; k++) { opponent = dragon2[DRAGON2(str).adjacent[k]].origin; if (board[opponent] != color) break; } ASSERT1(opponent != NO_MOVE, opponent); if (dragon[opponent].status != ALIVE) continue; /* FIXME: These heuristics are used for optimization. We don't * want to call expensive semeai code if the opponent * dragon has more than one eye elsewhere. However, the * heuristics might still need improvement. */ compute_dragon_genus(opponent, &reduced_genus, str); if (DRAGON2(opponent).moyo_size > 10 || min_eyes(&reduced_genus) > 1) continue; owl_analyze_semeai_after_move(defend_move, color, opponent, str, &resulta, &resultb, NULL, 1, &certain, 0); /* Do not trust uncertain results. In fact it should only take a * few nodes to determine the semeai result, if it is a proper * potential seki position. */ if (resultb != WIN && certain) { int d = dragon[str].id; DEBUG(DEBUG_SEMEAI, "Move to make seki at %1m (%1m vs %1m)\n", defend_move, str, opponent); dragon2[d].semeais++; update_status(str, CRITICAL, CRITICAL); dragon2[d].semeai_defense_point = defend_move; dragon2[d].semeai_defense_certain = certain; dragon2[d].semeai_defense_target = opponent; /* We need to determine a proper attack move (the one that * prevents seki). Currently we try the defense move first, * and if it doesn't work -- all liberties of the string. */ owl_analyze_semeai_after_move(defend_move, OTHER_COLOR(color), str, opponent, &resulta, NULL, NULL, 1, NULL, 0); if (resulta != WIN) dragon2[d].semeai_attack_point = defend_move; else { int k; int libs[MAXLIBS]; int liberties = findlib(str, MAXLIBS, libs); for (k = 0; k < liberties; k++) { owl_analyze_semeai_after_move(libs[k], OTHER_COLOR(color), str, opponent, &resulta, NULL, NULL, 1, NULL, 0); if (resulta != WIN) { dragon2[d].semeai_attack_point = libs[k]; break; } } /* FIXME: What should we do if none of the tried attacks worked? */ if (k == liberties) dragon2[d].semeai_attack_point = defend_move; } DEBUG(DEBUG_SEMEAI, "Move to prevent seki at %1m (%1m vs %1m)\n", dragon2[d].semeai_attack_point, opponent, str); dragon2[d].semeai_attack_certain = certain; dragon2[d].semeai_attack_target = opponent; } } } }
void play_solo(Gameinfo *gameinfo, int moves) { SGFTree sgftree; int passes = 0; /* num. consecutive passes */ int move_val; double t1, t2; int save_moves = moves; int boardsize = gnugo_get_boardsize(); struct stats_data totalstats; int total_owl_count = 0; /* It tends not to be very imaginative in the opening, * so we scatter a few stones randomly to start with. * We add two random numbers to reduce the probability * of playing stones near the edge. */ int n = 6 + 2*gg_rand()%5; int i, j; gnugo_set_komi(5.5); sgftree_clear(&sgftree); sgftreeCreateHeaderNode(&sgftree, gnugo_get_boardsize(), gnugo_get_komi()); sgf_write_header(sgftree.root, 1, random_seed, 5.5, level, chinese_rules); /* Generate some random moves. */ if (boardsize > 6) { do { do { i = (gg_rand() % 4) + (gg_rand() % (boardsize - 4)); j = (gg_rand() % 4) + (gg_rand() % (boardsize - 4)); } while (!gnugo_is_legal(i, j, gameinfo->to_move)); gnugo_play_move(i, j, gameinfo->to_move); sgftreeAddPlay(&sgftree, gameinfo->to_move, i, j); sgftreeAddComment(&sgftree, "random move"); gameinfo->to_move = OTHER_COLOR(gameinfo->to_move); } while (--n > 0); } t1 = gg_cputime(); memset(&totalstats, '\0', sizeof(totalstats)); while (passes < 2 && --moves >= 0) { reset_owl_node_counter(); move_val = gnugo_genmove(&i, &j, gameinfo->to_move); gnugo_play_move(i, j, gameinfo->to_move); sgffile_add_debuginfo(sgftree.lastnode, move_val); sgftreeAddPlay(&sgftree, gameinfo->to_move, i, j); sgffile_output(&sgftree); gameinfo->to_move = OTHER_COLOR(gameinfo->to_move); if (move_val < 0) { ++passes; printf("%s(%d): Pass\n", gameinfo->to_move == BLACK ? "Black" : "White", movenum); } else { passes = 0; gprintf("%s(%d): %m\n", gameinfo->to_move == BLACK ? "Black" : "White", movenum, i, j); } totalstats.nodes += stats.nodes; totalstats.position_entered += stats.position_entered; totalstats.position_hits += stats.position_hits; totalstats.read_result_entered += stats.read_result_entered; totalstats.hash_collisions += stats.hash_collisions; total_owl_count += get_owl_node_counter(); } t2 = gg_cputime(); /* Two passes and it's over. (EMPTY == BOTH) */ gnugo_who_wins(EMPTY, stdout); score = gnugo_estimate_score(&lower_bound, &upper_bound); sgfWriteResult(sgftree.root, score, 1); sgffile_output(&sgftree); #if 0 if (t2 == t1) printf("%.3f moves played\n", (double) (save_moves-moves)); else printf("%.3f moves/sec\n", (save_moves-moves)/(t2-t1)); #else printf("%10d moves played in %0.3f seconds\n", save_moves-moves, t2-t1); if (save_moves != moves) printf("%10.3f seconds/move\n", (t2-t1)/(save_moves-moves)); printf("%10d nodes\n", totalstats.nodes); printf("%10d positions entered\n", totalstats.position_entered); printf("%10d position hits\n", totalstats.position_hits); printf("%10d read results entered\n", totalstats.read_result_entered); printf("%10d hash collisions\n", totalstats.hash_collisions); printf("%10d owl nodes\n", total_owl_count); #endif }
void load_and_score_sgf_file(SGFTree *tree, Gameinfo *gameinfo, const char *scoringmode) { int i, j, move_val; float result; char *tempc = NULL; char dummy; char text[250]; char winner; int next; int pass = 0; SGFTree score_tree; sgftree_clear(&score_tree); sgftreeCreateHeaderNode(&score_tree, board_size, komi); sgffile_printboard(&score_tree); next = gameinfo->to_move; doing_scoring = 1; reset_engine(); if (!strcmp(scoringmode, "finish") || !strcmp(scoringmode, "aftermath")) { do { move_val = genmove_conservative(&i, &j, next); if (move_val >= 0) { pass = 0; gprintf("%d %s move %m\n", movenum, next == WHITE ? "white (O)" : "black (X)", i, j); } else { ++pass; gprintf("%d %s move : PASS!\n", movenum, next == WHITE ? "white (O)" : "black (X)"); } play_move(POS(i, j), next); sgffile_add_debuginfo(score_tree.lastnode, move_val); sgftreeAddPlay(&score_tree, next, i, j); sgffile_output(&score_tree); next = OTHER_COLOR(next); } while (movenum <= 10000 && pass < 2); if (pass >= 2) { /* Calculate the score */ if (!strcmp(scoringmode, "aftermath")) score = aftermath_compute_score(next, komi, &score_tree); else score = gnugo_estimate_score(&lower_bound, &upper_bound); if (score < 0.0) { sprintf(text, "Black wins by %1.1f points\n", -score); winner = 'B'; } else if (score > 0.0) { sprintf(text, "White wins by %1.1f points\n", score); winner = 'W'; } else { sprintf(text, "Jigo\n"); winner = '0'; } fputs(text, stdout); sgftreeAddComment(&score_tree, text); if (sgfGetCharProperty(tree->root, "RE", &tempc)) { if (sscanf(tempc, "%1c%f", &dummy, &result) == 2) { fprintf(stdout, "Result from file: %1.1f\n", result); fputs("GNU Go result and result from file are ", stdout); if (result == fabs(score) && winner == dummy) fputs("identical\n", stdout); else fputs("different\n", stdout); } else { if (tempc[2] == 'R') { fprintf(stdout, "Result from file: Resign\n"); fputs("GNU Go result and result from file are ", stdout); if (tempc[0] == winner) fputs("identical\n", stdout); else fputs("different\n", stdout); } } } sgfWriteResult(score_tree.root, score, 1); sgffile_output(&score_tree); } } doing_scoring = 0; if (strcmp(scoringmode, "aftermath")) { /* Before we call estimate_score() we must make sure that the dragon * status is computed. Therefore the call to examine_position(). */ examine_position(next, EXAMINE_ALL); score = estimate_score(NULL, NULL); fprintf(stdout, "\n%s seems to win by %1.1f points\n", score < 0 ? "B" : "W", score < 0 ? -score : score); } }
/* --------------------------------------------------------------*/ void play_gmp(Gameinfo *gameinfo, int simplified) { SGFTree sgftree; Gmp *ge; GmpResult message; const char *error; int i, j; int passes = 0; /* two passes and its over */ int to_move; /* who's turn is next ? */ int mycolor = -1; /* who has which color */ int yourcolor; if (gameinfo->computer_player == WHITE) mycolor = 1; else if (gameinfo->computer_player == BLACK) mycolor = 0; sgftree_clear(&sgftree); sgftreeCreateHeaderNode(&sgftree, board_size, komi, gameinfo->handicap); ge = gmp_create(0, 1); TRACE("board size=%d\n", board_size); /* * The specification of the go modem protocol doesn't even discuss * komi. So we have to guess the komi. If the komi is set on the * command line, keep it. Otherwise, its value will be 0.0 and we * use 5.5 in an even game, 0.5 otherwise. */ if (komi == 0.0) { if (gameinfo->handicap == 0) komi = 5.5; else komi = 0.5; } if (!simplified) { /* Leave all the -1's so the client can negotiate the game parameters. */ if (chinese_rules) gmp_startGame(ge, -1, -1, 5.5, -1, mycolor, 0); else gmp_startGame(ge, -1, -1, 5.5, 0, mycolor, 0); } else { gmp_startGame(ge, board_size, gameinfo->handicap, komi, chinese_rules, mycolor, 1); } do { message = gmp_check(ge, 1, NULL, NULL, &error); } while (message == gmp_nothing || message == gmp_reset); if (message == gmp_err) { fprintf(stderr, "gnugo-gmp: Error \"%s\" occurred.\n", error); exit(EXIT_FAILURE); } else if (message != gmp_newGame) { fprintf(stderr, "gnugo-gmp: Expecting a newGame, got %s\n", gmp_resultString(message)); exit(EXIT_FAILURE); } gameinfo->handicap = gmp_handicap(ge); if (!check_boardsize(gmp_size(ge), stderr)) exit(EXIT_FAILURE); gnugo_clear_board(gmp_size(ge)); /* Let's pretend GMP knows about komi in case something will ever change. */ komi = gmp_komi(ge); #if ORACLE if (metamachine && oracle_exists) oracle_clear_board(board_size); #endif sgfOverwritePropertyInt(sgftree.root, "SZ", board_size); TRACE("size=%d, handicap=%d, komi=%f\n", board_size, gameinfo->handicap, komi); if (gameinfo->handicap) to_move = WHITE; else to_move = BLACK; if (gmp_iAmWhite(ge)) { mycolor = WHITE; /* computer white */ yourcolor = BLACK; /* human black */ } else { mycolor = BLACK; yourcolor = WHITE; } gameinfo->computer_player = mycolor; sgf_write_header(sgftree.root, 1, get_random_seed(), komi, gameinfo->handicap, get_level(), chinese_rules); gameinfo->handicap = gnugo_sethand(gameinfo->handicap, sgftree.root); sgfOverwritePropertyInt(sgftree.root, "HA", gameinfo->handicap); /* main GMP loop */ while (passes < 2) { if (to_move == yourcolor) { int move; /* Get opponent's move from gmp client. */ message = gmp_check(ge, 1, &j, &i, &error); if (message == gmp_err) { fprintf(stderr, "GNU Go: Sorry, error from gmp client\n"); sgftreeAddComment(&sgftree, "got error from gmp client"); sgffile_output(&sgftree); return; } if (message == gmp_undo) { int k; assert(j > 0); for (k = 0; k < j; k++) { if (!undo_move(1)) { fprintf(stderr, "GNU Go: play_gmp UNDO: can't undo %d moves\n", j - k); break; } sgftreeAddComment(&sgftree, "undone"); sgftreeBack(&sgftree); to_move = OTHER_COLOR(to_move); } continue; } if (message == gmp_pass) { passes++; move = PASS_MOVE; } else { passes = 0; move = POS(i, j); } TRACE("\nyour move: %1m\n\n", move); sgftreeAddPlay(&sgftree, to_move, I(move), J(move)); gnugo_play_move(move, yourcolor); sgffile_output(&sgftree); } else { /* Generate my next move. */ float move_value; int move; if (autolevel_on) adjust_level_offset(mycolor); move = genmove(mycolor, &move_value, NULL); gnugo_play_move(move, mycolor); sgffile_add_debuginfo(sgftree.lastnode, move_value); if (is_pass(move)) { /* pass */ sgftreeAddPlay(&sgftree, to_move, -1, -1); gmp_sendPass(ge); ++passes; } else { /* not pass */ sgftreeAddPlay(&sgftree, to_move, I(move), J(move)); gmp_sendMove(ge, J(move), I(move)); passes = 0; TRACE("\nmy move: %1m\n\n", move); } sgffile_add_debuginfo(sgftree.lastnode, 0.0); sgffile_output(&sgftree); } to_move = OTHER_COLOR(to_move); } /* two passes: game over */ gmp_sendPass(ge); if (!quiet) fprintf(stderr, "Game over - waiting for client to shut us down\n"); who_wins(mycolor, stderr); if (showtime) { gprintf("\nSLOWEST MOVE: %d at %1m ", slowest_movenum, slowest_move); fprintf(stderr, "(%.2f seconds)\n", slowest_time); fprintf(stderr, "\nAVERAGE TIME: %.2f seconds per move\n", total_time / movenum); fprintf(stderr, "\nTOTAL TIME: %.2f seconds\n", total_time); } /* play_gmp() does not return to main(), therefore the score * writing code is here. */ { float score = gnugo_estimate_score(NULL, NULL); sgfWriteResult(sgftree.root, score, 1); } sgffile_output(&sgftree); if (!simplified) { /* We hang around here until cgoban asks us to go, since * sometimes cgoban crashes if we exit first. * * FIXME: Check if this is still needed. I made it dependand on * `simplifed' just to avoid changes in GMP mode. */ while (1) { message = gmp_check(ge, 1, &j, &i, &error); if (!quiet) fprintf(stderr, "Message %d from gmp\n", message); if (message == gmp_err) break; } } #if ORACLE if (metamachine && oracle_exists) dismiss_oracle(); #endif if (!quiet) fprintf(stderr, "gnugo going down\n"); }
/* Play at (i, j) for color. No legality check is done here. We need * to properly update the board array, the next_stone array, and the * ko point. */ static void play_move(int i, int j, int color) { int pos = POS(i, j); int captured_stones = 0; int k; /* Reset the ko point. */ ko_i = -1; ko_j = -1; /* Nothing more happens if the move was a pass. */ if (pass_move(i, j)) return; /* If the move is a suicide we only need to remove the adjacent * friendly stones. */ if (suicide(i, j, color)) { for (k = 0; k < 4; k++) { int ai = i + deltai[k]; int aj = j + deltaj[k]; if (on_board(ai, aj) && get_board(ai, aj) == color) remove_string(ai, aj); } return; } /* Not suicide. Remove captured opponent strings. */ for (k = 0; k < 4; k++) { int ai = i + deltai[k]; int aj = j + deltaj[k]; if (on_board(ai, aj) && get_board(ai, aj) == OTHER_COLOR(color) && !has_additional_liberty(ai, aj, i, j)) captured_stones += remove_string(ai, aj); } /* Put down the new stone. Initially build a single stone string by * setting next_stone[pos] pointing to itself. */ board[pos] = color; next_stone[pos] = pos; /* If we have friendly neighbor strings we need to link the strings * together. */ for (k = 0; k < 4; k++) { int ai = i + deltai[k]; int aj = j + deltaj[k]; int pos2 = POS(ai, aj); /* Make sure that the stones are not already linked together. This * may happen if the same string neighbors the new stone in more * than one direction. */ if (on_board(ai, aj) && board[pos2] == color && !same_string(pos, pos2)) { /* The strings are linked together simply by swapping the the * next_stone pointers. */ int tmp = next_stone[pos2]; next_stone[pos2] = next_stone[pos]; next_stone[pos] = tmp; } } /* If we have captured exactly one stone and the new string is a * single stone it may have been a ko capture. */ if (captured_stones == 1 && next_stone[pos] == pos) { int ai, aj; /* Check whether the new string has exactly one liberty. If so it * would be an illegal ko capture to play there immediately. We * know that there must be a liberty immediately adjacent to the * new stone since we captured one stone. */ for (k = 0; k < 4; k++) { ai = i + deltai[k]; aj = j + deltaj[k]; if (on_board(ai, aj) && get_board(ai, aj) == EMPTY) break; } if (!has_additional_liberty(i, j, ai, aj)) { ko_i = ai; ko_j = aj; } } }
/* This function adds the semeai related move reasons, using the information * stored in the dragon2 array. * * If the semeai had an uncertain result, and there is a owl move with * certain result doing the same, we don't trust the semeai move. */ void semeai_move_reasons(int color) { int other = OTHER_COLOR(color); int d; int liberties; int libs[MAXLIBS]; int r; for (d = 0; d < number_of_dragons; d++) if (dragon2[d].semeais && DRAGON(d).status == CRITICAL) { if (DRAGON(d).color == color && dragon2[d].semeai_defense_point && (dragon2[d].owl_defense_point == NO_MOVE || dragon2[d].semeai_defense_certain >= dragon2[d].owl_defense_certain)) { /* My dragon can be defended. */ add_semeai_move(dragon2[d].semeai_defense_point, dragon2[d].origin); DEBUG(DEBUG_SEMEAI, "Adding semeai defense move for %1m at %1m\n", DRAGON(d).origin, dragon2[d].semeai_defense_point); if (neighbor_of_dragon(dragon2[d].semeai_defense_point, dragon2[d].semeai_defense_target) && !neighbor_of_dragon(dragon2[d].semeai_defense_point, dragon2[d].origin) && !is_self_atari(dragon2[d].semeai_defense_point, color)) { /* If this is a move to fill the non-common liberties of the * target, and is not a ko or snap-back, then we mark all * non-common liberties of the target as potential semeai moves. */ liberties = findlib(dragon2[d].semeai_defense_target, MAXLIBS, libs); for (r = 0; r < liberties; r++) { if (!neighbor_of_dragon(libs[r], dragon2[d].origin) && !is_self_atari(libs[r], color) && libs[r] != dragon2[d].semeai_defense_point) add_potential_semeai_defense(libs[r], dragon2[d].origin, dragon2[d].semeai_defense_target); } } } else if (DRAGON(d).color == other && dragon2[d].semeai_attack_point && (dragon2[d].owl_attack_point == NO_MOVE || dragon2[d].owl_defense_point == NO_MOVE || dragon2[d].semeai_attack_certain >= dragon2[d].owl_attack_certain)) { /* Your dragon can be attacked. */ add_semeai_move(dragon2[d].semeai_attack_point, dragon2[d].origin); DEBUG(DEBUG_SEMEAI, "Adding semeai attack move for %1m at %1m\n", DRAGON(d).origin, dragon2[d].semeai_attack_point); if (neighbor_of_dragon(dragon2[d].semeai_attack_point, dragon2[d].origin) && !neighbor_of_dragon(dragon2[d].semeai_attack_point, dragon2[d].semeai_attack_target) && !is_self_atari(dragon2[d].semeai_attack_point, color)) { liberties = findlib(dragon2[d].origin, MAXLIBS, libs); for (r = 0; r < liberties; r++) { if (!neighbor_of_dragon(libs[r], dragon2[d].semeai_attack_target) && !is_self_atari(libs[r], color) && libs[r] != dragon2[d].semeai_attack_point) add_potential_semeai_attack(libs[r], dragon2[d].origin, dragon2[d].semeai_attack_target); } } } } }
void semeai() { int semeai_results_first[MAX_DRAGONS][MAX_DRAGONS]; int semeai_results_second[MAX_DRAGONS][MAX_DRAGONS]; int semeai_move[MAX_DRAGONS][MAX_DRAGONS]; signed char semeai_certain[MAX_DRAGONS][MAX_DRAGONS]; int d1, d2; int k; int num_dragons = number_of_dragons; if (num_dragons > MAX_DRAGONS) { TRACE("Too many dragons!!! Semeai analysis disabled."); return; } for (d1 = 0; d1 < num_dragons; d1++) for (d2 = 0; d2 < num_dragons; d2++) { semeai_results_first[d1][d2] = -1; semeai_results_second[d1][d2] = -1; } for (d1 = 0; d1 < num_dragons; d1++) for (k = 0; k < dragon2[d1].neighbors; k++) { int apos = DRAGON(d1).origin; int bpos = DRAGON(dragon2[d1].adjacent[k]).origin; int result_certain; d2 = dragon[bpos].id; /* Look for semeais */ if (dragon[apos].color == dragon[bpos].color || (dragon[apos].status != DEAD && dragon[apos].status != CRITICAL) || (dragon[bpos].status != DEAD && dragon[bpos].status != CRITICAL)) continue; /* Ignore inessential worms or dragons */ if (worm[apos].inessential || DRAGON2(apos).safety == INESSENTIAL || worm[bpos].inessential || DRAGON2(bpos).safety == INESSENTIAL) continue; /* Sometimes the dragons are considered neighbors but are too * distant to constitute a proper semeai, e.g. in nngs4:650, P2 * vs. R3. Then the result of semeai reading may be meaningless * and can confuse the analysis. In order to avoid this we check * that the dragons either are directly adjacent or at least * have one common liberty. */ if (!close_enough_for_proper_semeai(apos, bpos)) continue; /* The array semeai_results_first[d1][d2] will contain the status * of d1 after the d1 d2 semeai, giving d1 the first move. * The array semeai_results_second[d1][d2] will contain the status * of d1 after the d1 d2 semeai, giving d2 the first move. */ DEBUG(DEBUG_SEMEAI, "Considering semeai between %1m and %1m\n", apos, bpos); owl_analyze_semeai(apos, bpos, &(semeai_results_first[d1][d2]), &(semeai_results_second[d1][d2]), &(semeai_move[d1][d2]), 1, &result_certain); DEBUG(DEBUG_SEMEAI, "results if %s moves first: %s %s, %1m%s\n", board[apos] == BLACK ? "black" : "white", result_to_string(semeai_results_first[d1][d2]), result_to_string(semeai_results_second[d1][d2]), semeai_move[d1][d2], result_certain ? "" : " (uncertain)"); semeai_certain[d1][d2] = result_certain; } /* Look for dragons which lose all their semeais outright. The * winners in those semeais are considered safe and further semeais * they are involved in are disregarded. See semeai:81-86 and * nicklas5:1211 for examples of where this is useful. * * Note: To handle multiple simultaneous semeais properly we would * have to make simultaneous semeai reading. Lacking that we can * only get rough guesses of the correct status of the involved * dragons. This code is not guaranteed to be correct in all * situations but should usually be an improvement. */ for (d1 = 0; d1 < num_dragons; d1++) { int involved_in_semeai = 0; int all_lost = 1; for (d2 = 0; d2 < num_dragons; d2++) { if (semeai_results_first[d1][d2] != -1) { involved_in_semeai = 1; if (semeai_results_first[d1][d2] != 0) { all_lost = 0; break; } } } if (involved_in_semeai && all_lost) { /* Leave the status changes to the main loop below. Here we just * remove the presumably irrelevant semeai results. */ for (d2 = 0; d2 < num_dragons; d2++) { if (semeai_results_first[d1][d2] == 0) { int d3; for (d3 = 0; d3 < num_dragons; d3++) { if (semeai_results_second[d3][d2] > 0) { semeai_results_first[d3][d2] = -1; semeai_results_second[d3][d2] = -1; semeai_results_first[d2][d3] = -1; semeai_results_second[d2][d3] = -1; } } } } } } for (d1 = 0; d1 < num_dragons; d1++) { int semeais_found = 0; int best_defense = 0; int best_attack = 0; int defense_move = PASS_MOVE; int attack_move = PASS_MOVE; int defense_certain = -1; int attack_certain = -1; int semeai_attack_target = NO_MOVE; int semeai_defense_target = NO_MOVE; for (d2 = 0; d2 < num_dragons; d2++) { if (semeai_results_first[d1][d2] == -1) continue; gg_assert(semeai_results_second[d1][d2] != -1); semeais_found++; if (best_defense < semeai_results_first[d1][d2] || (best_defense == semeai_results_first[d1][d2] && defense_certain < semeai_certain[d1][d2])) { best_defense = semeai_results_first[d1][d2]; defense_move = semeai_move[d1][d2]; defense_certain = semeai_certain[d1][d2]; gg_assert(board[dragon2[d2].origin] == OTHER_COLOR(board[dragon2[d1].origin])); semeai_defense_target = dragon2[d2].origin; } if (best_attack < semeai_results_second[d2][d1] || (best_attack == semeai_results_second[d2][d1] && attack_certain < semeai_certain[d2][d1])) { best_attack = semeai_results_second[d2][d1]; attack_move = semeai_move[d2][d1]; attack_certain = semeai_certain[d2][d1]; semeai_attack_target = dragon2[d2].origin; } } if (semeais_found) { dragon2[d1].semeais = semeais_found; if (best_defense != 0 && best_attack != 0) update_status(DRAGON(d1).origin, CRITICAL, CRITICAL); else if (best_attack == 0 && attack_certain) update_status(DRAGON(d1).origin, ALIVE, ALIVE); dragon2[d1].semeai_defense_code = best_defense; dragon2[d1].semeai_defense_point = defense_move; dragon2[d1].semeai_defense_certain = defense_certain; ASSERT1(board[semeai_defense_target] == OTHER_COLOR(board[dragon2[d1].origin]), dragon2[d1].origin); dragon2[d1].semeai_defense_target = semeai_defense_target; dragon2[d1].semeai_attack_code = best_attack; dragon2[d1].semeai_attack_point = attack_move; dragon2[d1].semeai_attack_certain = attack_certain; dragon2[d1].semeai_attack_target = semeai_attack_target; } } find_moves_to_make_seki(); }
/* Find moves turning supposed territory into seki. This is not * detected above since it either involves an ALIVE dragon adjacent to * a CRITICAL dragon, or an ALIVE dragon whose eyespace can be invaded * and turned into a seki. * * Currently we only search for tactically critical strings with * dragon status dead, which are neighbors of only one opponent * dragon, which is alive. Through semeai analysis we then determine * whether such a string can in fact live in seki. Relevant testcases * include gunnar:42 and gifu03:2. */ static void find_moves_to_make_seki() { int str; int defend_move; int resulta, resultb; for (str = BOARDMIN; str < BOARDMAX; str++) { if (IS_STONE(board[str]) && is_worm_origin(str, str) && attack_and_defend(str, NULL, NULL, NULL, &defend_move) && dragon[str].status == DEAD && DRAGON2(str).hostile_neighbors == 1) { int k; int color = board[str]; int opponent = NO_MOVE; int certain; struct eyevalue reduced_genus; for (k = 0; k < DRAGON2(str).neighbors; k++) { opponent = dragon2[DRAGON2(str).adjacent[k]].origin; if (board[opponent] != color) break; } ASSERT1(opponent != NO_MOVE, opponent); if (dragon[opponent].status != ALIVE) continue; /* FIXME: These heuristics are used for optimization. We don't * want to call expensive semeai code if the opponent * dragon has more than one eye elsewhere. However, the * heuristics might still need improvement. */ compute_dragon_genus(opponent, &reduced_genus, str); if (min_eyes(&reduced_genus) > 1 || DRAGON2(opponent).moyo_size > 10 || DRAGON2(opponent).moyo_territorial_value > 2.999 || DRAGON2(opponent).escape_route > 0 || DRAGON2(str).escape_route > 0) continue; owl_analyze_semeai_after_move(defend_move, color, opponent, str, &resulta, &resultb, NULL, 1, &certain, 0); if (resultb == WIN) { owl_analyze_semeai(str, opponent, &resultb, &resulta, &defend_move, 1, &certain); resulta = REVERSE_RESULT(resulta); resultb = REVERSE_RESULT(resultb); } /* Do not trust uncertain results. In fact it should only take a * few nodes to determine the semeai result, if it is a proper * potential seki position. */ if (resultb != WIN && certain) { int d = dragon[str].id; DEBUG(DEBUG_SEMEAI, "Move to make seki at %1m (%1m vs %1m)\n", defend_move, str, opponent); dragon2[d].semeais++; update_status(str, CRITICAL, CRITICAL); dragon2[d].semeai_defense_code = REVERSE_RESULT(resultb); dragon2[d].semeai_defense_point = defend_move; dragon2[d].semeai_defense_certain = certain; gg_assert(board[opponent] == OTHER_COLOR(board[dragon2[d].origin])); dragon2[d].semeai_defense_target = opponent; /* We need to determine a proper attack move (the one that * prevents seki). Currently we try the defense move first, * and if it doesn't work -- all liberties of the string. */ owl_analyze_semeai_after_move(defend_move, OTHER_COLOR(color), str, opponent, &resulta, NULL, NULL, 1, NULL, 0); if (resulta != WIN) { dragon2[d].semeai_attack_code = REVERSE_RESULT(resulta); dragon2[d].semeai_attack_point = defend_move; } else { int k; int libs[MAXLIBS]; int liberties = findlib(str, MAXLIBS, libs); for (k = 0; k < liberties; k++) { owl_analyze_semeai_after_move(libs[k], OTHER_COLOR(color), str, opponent, &resulta, NULL, NULL, 1, NULL, 0); if (resulta != WIN) { dragon2[d].semeai_attack_code = REVERSE_RESULT(resulta); dragon2[d].semeai_attack_point = libs[k]; break; } } if (k == liberties) { DEBUG(DEBUG_SEMEAI, "No move to attack in semeai (%1m vs %1m), seki assumed.\n", str, opponent); dragon2[d].semeai_attack_code = 0; dragon2[d].semeai_attack_point = NO_MOVE; update_status(str, ALIVE, ALIVE_IN_SEKI); } } DEBUG(DEBUG_SEMEAI, "Move to prevent seki at %1m (%1m vs %1m)\n", dragon2[d].semeai_attack_point, opponent, str); dragon2[d].semeai_attack_certain = certain; dragon2[d].semeai_attack_target = opponent; } } } /* Now look for dead strings inside a single eyespace of a living dragon. * * FIXME: Clearly this loop should share most of its code with the * one above. It would also be good to reimplement so that * moves invading a previously empty single eyespace to make * seki can be found. */ for (str = BOARDMIN; str < BOARDMAX; str++) { if (IS_STONE(board[str]) && is_worm_origin(str, str) && !find_defense(str, NULL) && dragon[str].status == DEAD && DRAGON2(str).hostile_neighbors == 1) { int k; int color = board[str]; int opponent = NO_MOVE; int certain; struct eyevalue reduced_genus; for (k = 0; k < DRAGON2(str).neighbors; k++) { opponent = dragon2[DRAGON2(str).adjacent[k]].origin; if (board[opponent] != color) break; } ASSERT1(opponent != NO_MOVE, opponent); if (dragon[opponent].status != ALIVE) continue; /* FIXME: These heuristics are used for optimization. We don't * want to call expensive semeai code if the opponent * dragon has more than one eye elsewhere. However, the * heuristics might still need improvement. */ compute_dragon_genus(opponent, &reduced_genus, str); if (DRAGON2(opponent).moyo_size > 10 || min_eyes(&reduced_genus) > 1) continue; owl_analyze_semeai(str, opponent, &resulta, &resultb, &defend_move, 1, &certain); /* Do not trust uncertain results. In fact it should only take a * few nodes to determine the semeai result, if it is a proper * potential seki position. */ if (resulta != 0 && certain) { int d = dragon[str].id; DEBUG(DEBUG_SEMEAI, "Move to make seki at %1m (%1m vs %1m)\n", defend_move, str, opponent); dragon2[d].semeais++; update_status(str, CRITICAL, CRITICAL); dragon2[d].semeai_defense_code = resulta; dragon2[d].semeai_defense_point = defend_move; dragon2[d].semeai_defense_certain = certain; gg_assert(board[opponent] == OTHER_COLOR(board[dragon2[d].origin])); dragon2[d].semeai_defense_target = opponent; /* We need to determine a proper attack move (the one that * prevents seki). Currently we try the defense move first, * and if it doesn't work -- all liberties of the string. */ owl_analyze_semeai_after_move(defend_move, OTHER_COLOR(color), str, opponent, &resulta, NULL, NULL, 1, NULL, 0); if (resulta != WIN) { dragon2[d].semeai_attack_code = REVERSE_RESULT(resulta); dragon2[d].semeai_attack_point = defend_move; } else { int k; int libs[MAXLIBS]; int liberties = findlib(str, MAXLIBS, libs); for (k = 0; k < liberties; k++) { owl_analyze_semeai_after_move(libs[k], OTHER_COLOR(color), str, opponent, &resulta, NULL, NULL, 1, NULL, 0); if (resulta != WIN) { dragon2[d].semeai_attack_code = REVERSE_RESULT(resulta); dragon2[d].semeai_attack_point = libs[k]; break; } } if (k == liberties) { DEBUG(DEBUG_SEMEAI, "No move to attack in semeai (%1m vs %1m), seki assumed.\n", str, opponent); dragon2[d].semeai_attack_code = 0; dragon2[d].semeai_attack_point = NO_MOVE; update_status(str, ALIVE, ALIVE_IN_SEKI); } } DEBUG(DEBUG_SEMEAI, "Move to prevent seki at %1m (%1m vs %1m)\n", dragon2[d].semeai_attack_point, opponent, str); dragon2[d].semeai_attack_certain = certain; dragon2[d].semeai_attack_target = opponent; } } } }
int compute_surroundings(int pos, int apos, int showboard, int *surround_size) { int i, j; int m, n; int k; int dpos; int surrounded; int left_corner[MAX_BOARD]; int right_corner[MAX_BOARD]; int corner[BOARDMAX]; int left_corners = 0, right_corners = 0; int corners = 0; int top_row, bottom_row; int color = board[pos]; int other = OTHER_COLOR(color); int gi = 0; int gj = 0; int stones = 0; int found_some; signed char mf[BOARDMAX]; /* friendly dragon */ signed char mn[BOARDMAX]; /* neighbor dragons */ int sd[BOARDMAX]; /* distances to the goal */ if (DRAGON2(pos).hostile_neighbors == 0) return(0); memset(mf, 0, sizeof(mf)); memset(mn, 0, sizeof(mn)); memset(sd, 0, sizeof(sd)); mark_dragon(pos, mf, 1); /* mark hostile neighbors */ for (k = 0; k < DRAGON2(pos).neighbors; k++) { int nd = DRAGON(DRAGON2(pos).adjacent[k]).origin; if (board[nd] != color) { if (0) gprintf("neighbor: %1m\n", nd); mark_dragon(nd, mn, 1); } } /* descend markings from stones lying on the 2nd and third lines */ for (dpos = BOARDMIN; dpos < BOARDMAX; dpos++) if (ON_BOARD(dpos) && mn[dpos]) { for (k = 0; k < 4; k++) { int d = delta[k]; if (!ON_BOARD(dpos + d)) continue; if (!ON_BOARD(dpos + 2*d)) { if (board[dpos + d] == EMPTY) mn[dpos + d] = 1; } else if (!ON_BOARD(dpos + 3*d)) { if (board[dpos + d] == EMPTY && board[dpos + 2*d] == EMPTY) mn[dpos + 2*d] = 1; } } } /* compute minimum distances to the goal */ for (dpos = BOARDMIN; dpos < BOARDMAX; dpos++) if (ON_BOARD(dpos) && mn[dpos]) sd[dpos] = goal_dist(dpos, mf); /* revise markings */ do { found_some = 0; for (dpos = BOARDMIN; dpos < BOARDMAX; dpos++) if (ON_BOARD(dpos) && mn[dpos] && sd[dpos] > 8) { /* discard markings if we can find 2 stones * that verify : * - it is closer to the goal than we are * - it is closer to us than the goal is * - they are closer to each other than we are to the goal */ for (i = BOARDMIN; i < BOARDMAX; i++) if (ON_BOARD(i) && mn[i] && i != dpos && sd[i] < sd[dpos] && square_dist(i, dpos) < sd[dpos]) { for (j = i + 1; j < BOARDMAX; j++) if (ON_BOARD(j) && mn[j] && j != dpos && sd[j] < sd[dpos] && square_dist(j, dpos) < sd[dpos] && square_dist(i, j) < sd[dpos]) { mn[dpos] = 0; found_some = 1; break; } if (mn[dpos] == 0) break; } } } while (found_some); /* prepare corner array */ for (dpos = BOARDMIN; dpos < BOARDMAX; dpos++) if (ON_BOARD(dpos) && mn[dpos]) corner[corners++] = dpos; /* compute gravity center of the goal */ for (dpos = BOARDMIN; dpos < BOARDMAX; dpos++) if (ON_BOARD(dpos) && mf[dpos]) { gi += I(dpos); gj += J(dpos); stones++; } gi /= stones; gj /= stones; gg = POS(gi, gj); /* sort the corner array */ gg_sort(corner, corners, sizeof(int), compare_angles); /* if apos is not NO_MOVE, mark it. */ if (apos != NO_MOVE) { ASSERT_ON_BOARD1(apos); mn[apos] = 1; } if (showboard == 1) { show_surround_map(mf, mn); } /* find top row of surrounding polyhedron */ top_row = -1; for (m = 0; m < board_size; m++) { if (top_row != -1) break; for (n = 0; n < board_size; n++) if (mn[POS(m, n)]) { left_corner[0] = POS(m, n); top_row = m; break; } } /* find bottom row */ bottom_row = -1; for (m = board_size - 1; m >= 0; m--) { if (bottom_row != -1) break; for (n = 0; n < board_size; n++) if (mn[POS(m, n)]) { bottom_row = m; break; } } /* find the corners on the left side */ for (left_corners = 1; I(left_corner[left_corners-1]) < bottom_row; left_corners++) { int best_found = 0; float best_slope = 0.; int m = I(left_corner[left_corners-1]); int n = J(left_corner[left_corners-1]); for (i = m + 1; i <= bottom_row; i++) for (j = 0; j < board_size; j++) if (mn[POS(i, j)]) { float slope = ((float) (j - n))/((float) (i - m)); if (0) gprintf("(left) at %m, last %m, slope=%f\n", i, j, m, n, slope); if (!best_found || slope < best_slope) { best_found = POS(i, j); best_slope = slope; } } ASSERT_ON_BOARD1(best_found); left_corner[left_corners] = best_found; } for (n = board_size-1; n >= 0; n--) if (mn[POS(top_row, n)]) { right_corner[0] = POS(top_row, n); break; } /* find the corners on the right side */ for (right_corners = 1; I(right_corner[right_corners-1]) < bottom_row; right_corners++) { int best_found = 0; float best_slope = 0.; int m = I(right_corner[right_corners-1]); int n = J(right_corner[right_corners-1]); for (i = m + 1; i <= bottom_row; i++) { for (j = board_size - 1; j >= 0; j--) { if (mn[POS(i, j)]) { float slope = ((float) (j - n))/((float) (i - m)); if (0) gprintf("(right) at %m, last %m, slope=%f\n", i, j, m, n, slope); if (!best_found || slope > best_slope) { best_found = POS(i, j); best_slope = slope; } } } } ASSERT_ON_BOARD1(best_found); right_corner[right_corners] = best_found; } if (0) { for (k = 0; k < left_corners; k++) gprintf("left corner %d: %1m\n", k, left_corner[k]); for (k = 0; k < right_corners; k++) gprintf("right corner %d: %1m\n", k, right_corner[k]); } /* Now mark the interior of the convex hull */ for (n = J(left_corner[0]); n <= J(right_corner[0]); n++) mn[POS(top_row, n)] = 1; for (n = J(left_corner[left_corners-1]); n <= J(right_corner[right_corners-1]); n++) mn[POS(bottom_row, n)] = 1; for (m = top_row+1; m < bottom_row; m++) { int left_boundary = -1, right_boundary = -1; for (k = 1; k < left_corners; k++) { if (I(left_corner[k]) > m) { float ti = I(left_corner[k-1]); float tj = J(left_corner[k-1]); float bi = I(left_corner[k]); float bj = J(left_corner[k]); if (0) gprintf("(left) %d: %1m %1m\n", m, left_corner[k-1], left_corner[k]); /* left edge in this row is on segment (ti,tj) -> (bi, bj) */ /* FIXME: Rewrite this to avoid floating point arithmetic */ left_boundary = ceil(tj + (m - ti) * (bj - tj) / (bi - ti)); break; } } for (k = 1; k < right_corners; k++) { if (I(right_corner[k]) > m) { float ti = I(right_corner[k-1]); float tj = J(right_corner[k-1]); float bi = I(right_corner[k]); float bj = J(right_corner[k]); if (0) gprintf("(right) %d: %1m %1m\n", m, right_corner[k-1], right_corner[k]); /* FIXME: Rewrite this to avoid floating point arithmetic */ right_boundary = floor(tj + (m - ti) * (bj - tj) / (bi - ti)); break; } } for (n = left_boundary; n <= right_boundary; n++) mn[POS(m, n)] = 1; } /* mark the expanded region */ for (dpos = BOARDMIN; dpos < BOARDMAX; dpos++) if (ON_BOARD(dpos) && mn[dpos] == 1) for (k = 0; k < 4; k++) if (ON_BOARD(dpos + delta[k]) && !mn[dpos + delta[k]]) mn[dpos + delta[k]] = 2; /* Mark allied dragons that intersect the (unexpanded) hull. * These must all lie entirely within the hull for the * dragon to be considered surrounded. * * Only neighbor dragons are considered since dragons that * are not neighbors are less likely to be helpful. */ for (dpos = BOARDMIN; dpos < BOARDMAX; dpos++) { int mpos; if (ON_BOARD(dpos) && mn[dpos] == 1 && board[dpos] == color && are_neighbor_dragons(pos, dpos) && !mf[dpos]) { for (mpos = BOARDMIN; mpos < BOARDMAX; mpos++) if (ON_BOARD(mpos) && is_same_dragon(mpos, dpos)) mf[mpos] = 2; } /* A special case * * . X X . * X O . X * X . O O * . O . . * * The O stone hasn't been amalgamated and the surround computations * might think this single stone dragon is surrounded, which in turn * can generate overvaluation of moves around this stone. * Consequently, we allow inclusion of the stones at kosumi distance * in the mf (friendly) array. */ if (ON_BOARD(dpos) && mn[dpos] == 2 && board[dpos] == color && are_neighbor_dragons(pos, dpos) && !mf[dpos]) { for (k = 4; k < 8; k++) if (ON_BOARD(dpos + delta[k]) && board[dpos + delta[k]] == color && mn[dpos + delta[k]] == 1 && board[dpos + delta[k-4]] == EMPTY && board[dpos + delta[(k-3)%4]] == EMPTY) { for (mpos = BOARDMIN; mpos < BOARDMAX; mpos++) if (ON_BOARD(mpos) && is_same_dragon(mpos, dpos)) mf[mpos] = 2; } } } /* determine the surround status of the dragon */ surrounded = SURROUNDED; /* Compute the maximum surround status awarded * If distances between enclosing stones are large, reduce to * WEAKLY_SURROUNDED. If (really) too large, then reduce to 0 * FIXME: constants chosen completely ad hoc. Possibly better tunings * can be found. */ for (k = 0; k < corners - 1; k++) { if (is_edge_vertex(corner[k]) && is_edge_vertex(corner[k+1])) continue; if (square_dist(corner[k], corner[k+1]) > 60) { surrounded = 0; break; } else if (square_dist(corner[k], corner[k+1]) > 27) surrounded = WEAKLY_SURROUNDED; } if (surrounded && (!is_edge_vertex(corner[0]) || !is_edge_vertex(corner[corners-1]))) { if (square_dist(corner[0], corner[corners-1]) > 60) surrounded = 0; else if (square_dist(corner[0], corner[corners-1]) > 27) surrounded = WEAKLY_SURROUNDED; } if (surrounded) for (dpos = BOARDMIN; dpos < BOARDMAX; dpos++) if (mf[dpos]) { if (mn[dpos] == 0) { surrounded = 0; break; } else if (mn[dpos] == 2) surrounded = WEAKLY_SURROUNDED; } /* revise the status for single stone dragons. */ if (stones == 1 && surrounded == WEAKLY_SURROUNDED && mn[pos] == 2) surrounded = 0; /* revise the status if an ikken tobi jumps out. */ if (surrounded) { for (dpos = BOARDMIN; dpos < BOARDMAX && surrounded; dpos++) { if (!ON_BOARD(dpos) || !mf[dpos]) continue; for (k = 0; k < 4; k++) { int up = delta[k]; int right = delta[(k + 1) % 4]; if (board[dpos + up] == EMPTY && board[dpos + 2*up] == color && mn[dpos + 2*up] != 1 && ON_BOARD(dpos + up + right) && board[dpos + up + right] != other && ON_BOARD(dpos + up - right) && board[dpos + up - right] != other) { surrounded = 0; break; } } } } if (showboard == 1 || (showboard == 2 && surrounded)) { show_surround_map(mf, mn); } if (!apos && surrounded && surround_pointer < MAX_SURROUND) { memcpy(surroundings[surround_pointer].surround_map, mn, sizeof(mn)); surroundings[surround_pointer].dragon_number = dragon[pos].id; surround_pointer++; } if (surround_size) { int pos; *surround_size = 0; for (pos = BOARDMIN; pos < BOARDMAX; pos++) if (ON_BOARD(pos) && mn[pos] == 1) (*surround_size)++; } return surrounded; }
static void cut_connect_callback(int m, int n, int color, struct pattern *pattern, int ll) { int stari, starj; int k; int first_dragoni=-1, first_dragonj=-1; int second_dragoni=-1, second_dragonj=-1; int other=OTHER_COLOR(color); if ((pattern->movei) == -1) { stari = -1; starj = -1; } else { TRANSFORM(pattern->movei, pattern->movej, &stari, &starj, ll); stari += m; starj += n; if (!safe_move(stari, starj, other)) return; } if (pattern->helper) { if (!pattern->helper(pattern, ll, stari, starj, color)) return; } if (pattern->klass & CLASS_B) { /* Require that the X stones in the pattern are tactically safe. */ for (k = 0; k < pattern->patlen; ++k) { /* match each point */ if (pattern->patn[k].att == ATT_X) { /* transform pattern real coordinate */ int x, y; TRANSFORM(pattern->patn[k].x,pattern->patn[k].y,&x,&y,ll); x += m; y += n; if ((worm[x][y].attacki != -1) && ((stari == -1) || !does_defend(stari, starj, x, y))) return; /* Match failed */ } } } /* get here => pattern matches */ if (pattern->klass & CLASS_B) { TRACE("Cutting pattern %s+%d found at %m\n", pattern->name, ll, m, n); if (stari != -1) TRACE("cutting point %m\n", stari, starj); } else TRACE("Connecting pattern %s+%d found at %m\n", pattern->name, ll, m, n); /* If it is a B pattern, set cutting point in worm data and make eye * space marginal. */ if (pattern->klass & CLASS_B) { if (stari != -1) { if (color == WHITE) white_eye[stari][starj].cut = 1; else black_eye[stari][starj].cut = 1; if (color == WHITE && white_eye[stari][starj].color == WHITE_BORDER) white_eye[stari][starj].marginal = 1; else if (color == BLACK && black_eye[stari][starj].color == BLACK_BORDER) black_eye[stari][starj].marginal = 1; } } else if (!(pattern->klass & CLASS_C)) return; /* Nothing more to do, up to the helper or autohelper to amalgamate dragons. */ /* If it is a C pattern, find the dragons to connect. * If it is a B pattern, find eye space points to inhibit connection * through. */ for (k = 0; k < pattern->patlen; ++k) { /* match each point */ int x, y; /* absolute (board) co-ords of (transformed) pattern element */ /* transform pattern real coordinate */ TRANSFORM(pattern->patn[k].x,pattern->patn[k].y,&x,&y,ll); x+=m; y+=n; /* Look for dragons to amalgamate. Never amalgamate stones which * can be attacked. */ if ((pattern->klass & CLASS_C) && (p[x][y] == color) && (worm[x][y].attacki == -1)) { if (first_dragoni == -1) { first_dragoni = dragon[x][y].origini; first_dragonj = dragon[x][y].originj; } else if ((second_dragoni == -1) && ((dragon[x][y].origini != first_dragoni) || (dragon[x][y].originj != first_dragonj))) { second_dragoni = dragon[x][y].origini; second_dragonj = dragon[x][y].originj; /* A second dragon found, we amalgamate them at once. */ join_dragons(second_dragoni, second_dragonj, first_dragoni, first_dragonj); TRACE("Joining dragon %m to %m\n", second_dragoni, second_dragonj, first_dragoni, first_dragonj); /* Now look for another second dragon */ second_dragoni = -1; second_dragonj = -1; ////assert(dragon[x][y].origini == first_dragoni && //dragon[x][y].originj == first_dragonj); } } /* Inhibit connections */ if (pattern->klass & CLASS_B) { if (pattern->patn[k].att != ATT_not) break; /* The inhibition points are guaranteed to come first. */ if (color == WHITE && white_eye[x][y].color == WHITE_BORDER) white_eye[x][y].type |= INHIBIT_CONNECTION; else if (color == BLACK && black_eye[x][y].color == BLACK_BORDER) black_eye[x][y].type |= INHIBIT_CONNECTION; } } /* loop over elements */ }