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; }
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]; 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!!! Might disregard some semeais."); num_dragons = MAX_DRAGONS; } 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 = 0; int attack_certain = 0; 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]; 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_point = defense_move; dragon2[d1].semeai_defense_certain = defense_certain; dragon2[d1].semeai_defense_target = semeai_defense_target; 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; } } } }