Example #1
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;
}
Example #3
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();
}
Example #4
0
/* 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;
      }
    }
  }
}