/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
 * This is GNU Go, a Go program. Contact gnugo@gnu.org, or see       *
 * http://www.gnu.org/software/gnugo/ for more information.          *
 *                                                                   *
 * Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,   *
 * 2008 and 2009 by the Free Software Foundation.                    *
 *                                                                   *
 * This program is free software; you can redistribute it and/or     *
 * modify it under the terms of the GNU General Public License as    *
 * published by the Free Software Foundation - version 3 or          *
 * (at your option) any later version.                               *
 *                                                                   *
 * This program is distributed in the hope that it will be useful,   *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of    *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the     *
 * GNU General Public License in file COPYING for more details.      *
 *                                                                   *
 * You should have received a copy of the GNU General Public         *
 * License along with this program; if not, write to the Free        *
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor,       *
 * Boston, MA 02111, USA.                                            *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "gnugo.h"
#include "liberty.h"
#include "readconnect.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


/* This module looks for break-ins into territories that require
 * deeper tactical reading and are thus impossible to detect for the
 * influence module. It gets run after the influence module and revises
 * its territory valuations.
 *
 * The procedure is as follows: We look at all big (>= 10) territory regions
 * as detected by the influence code. Using the computation of
 * connection distances from readconnect.c, we compute all nearby vertices
 * of this territory. We look for the closest safe stones belonging to
 * the opponent.
 * For each such string (str) we call
 * - break_in(str, territory) if the opponent is assumed to be next to move,
 *   or
 * - block_off(str, territory) if the territory owner is next.
 * If the break in is successful resp. the blocking unsuccessful, we
 * shrink the territory, and see whether the opponent can still break in.
 * We repeat this until the territory is shrunk so much that the opponent
 * can no longer reach it.
 */


/* Store possible break-ins in initial position to generate move reasons
 * later.
 */
struct break_in_data {
  int str;
  int move;
};

#define MAX_BREAK_INS 50
static struct break_in_data break_in_list[MAX_BREAK_INS];
static int num_break_ins;


/* Adds all empty intersections that have two goal neighbors to the goal. */
static void
enlarge_goal(signed char goal[BOARDMAX])
{
  int pos;
  for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
    if (board[pos] == EMPTY && !goal[pos]) {
      int k;
      int goal_neighbors = 0;
      for (k = 0; k < 4; k++)
	if (board[pos + delta[k]] == EMPTY && goal[pos + delta[k]] == 1)
	  goal_neighbors++;
      if (goal_neighbors >= 2)
	goal[pos] = 2;
    }
  }
}


/* The "smaller goal" is the intersection of the goal with what is
 * stored in the queue of the connection_data conn.
 * Plus we need a couple of extra careful modifications in the case
 * of "blocking off", i.e. when color_to_move == owner.
 */
static void
compute_smaller_goal(int owner, int color_to_move,
    		     const struct connection_data *conn,
    		     const signed char goal[BOARDMAX],
		     signed char smaller_goal[BOARDMAX])
{
  int k, j;
  int own_stones_visited[BOARDMAX];
  memset(smaller_goal, 0, BOARDMAX);
  for (k = 0; k < conn->queue_end; k++) {
    int pos = conn->queue[k];
    int goal_neighbors = 0;
    /* If we are trying to block-off, we need to be extra careful: We only
     * can block intrusions coming directly from the string in question.
     * Therefore, we discard the area if we have traversed more than two
     * stones of the color breaking in on the way to the goal.
     */
    if (owner == color_to_move) {
      int coming_from = conn->coming_from[pos];
      if (coming_from == NO_MOVE)
	own_stones_visited[pos] = 0;
      else {
	own_stones_visited[pos] = own_stones_visited[coming_from];
	/* How many stones have we used to jump from coming_from to pos?
	 * Use Manhattan metric as a guess.
	 */
	if (!goal[pos] && board[pos] == OTHER_COLOR(owner)) {
	  int i;
	  int stones[MAX_BOARD * MAX_BOARD];
	  int num_stones = findstones(pos, MAX_BOARD * MAX_BOARD, stones);
	  int smallest_distance = 3;

	  for (i = 0; i < num_stones; i++) {
	    int distance = (gg_abs(I(stones[i]) - I(coming_from))
			    + gg_abs(J(stones[i]) - J(coming_from)));

	    if (distance < smallest_distance)
	      smallest_distance = distance;
	  }

	  own_stones_visited[pos] += smallest_distance;
	}

	if (own_stones_visited[pos] > 2)
	  continue;
      }
    }

    if (!goal[pos])
      continue;

    /* We don't want vertices that are at the border of the territory, and
     * from which a break-in is unlikely; these often lead to false
     * positives.
     * So we throw out every vertex that has only one neighbor in the goal,
     * or that is on an edge and has only two goal neighbors.
     */
    for (j = 0; j < 4; j++)
      if (ON_BOARD(pos + delta[j])
	  && goal[pos + delta[j]]
	  && (board[pos] == EMPTY || goal[pos] == OTHER_COLOR(owner)))
	goal_neighbors++;
#if 0
    if (goal_neighbors > 2
	|| goal_neighbors == 2 && !is_edge_vertex(pos))
#else
    if (goal_neighbors >= 2)
      smaller_goal[pos] = 1;
#endif
  }

  /* Finally, in the case of blocking off, we only want one connected
   * component.
   */
  if (owner == color_to_move) {
    signed char marked[BOARDMAX];
    int sizes[BOARDMAX / 2];
    signed char mark = 0;
    int biggest_region = 1;
    memset(marked, 0, BOARDMAX);
    for (k = 0; k < conn->queue_end; k++) {
      int pos = conn->queue[k];
      if (ON_BOARD(pos) && smaller_goal[pos] && !marked[pos]) {
	/* Floodfill the connected component of (pos) in the goal. */
	int queue_start = 0;
	int queue_end = 1;
	int queue[BOARDMAX];
	mark++;
	sizes[(int) mark] = 1;
	marked[pos] = mark;
	queue[0] = pos;
	while (queue_start < queue_end) {
	  test_gray_border();
	  for (j = 0; j < 4; j++) {
	    int pos2 = queue[queue_start] + delta[j];
	    if (!ON_BOARD(pos2))
	      continue;
	    ASSERT1(marked[pos2] == 0 || marked[pos2] == mark, pos2);
	    if (smaller_goal[pos2]
		&& !marked[pos2]) {
	      sizes[(int) mark]++;
	      marked[pos2] = mark;
	      queue[queue_end++] = pos2;
	    }
	  }
	  queue_start++;
	}
      }
    }
    /* Now selected the biggest connected component. (In case of
     * equality, take the first one.
     */
    for (k = 1; k <= mark; k++) {
      if (sizes[k] > sizes[biggest_region])
	biggest_region = k;
    }
    memset(smaller_goal, 0, BOARDMAX);
    for (k = 0; k < conn->queue_end; k++) {
      int pos = conn->queue[k];
      if (marked[pos] == biggest_region)
	smaller_goal[pos] = 1;
    }
  }
}


/* Try to intrude from str into goal. If successful, we shrink the goal,
 * store the non-territory fields in the non_territory array, and
 * try again.
 */
static int
break_in_goal_from_str(int str, signed char goal[BOARDMAX],
    		      int *num_non_territory, int non_territory[BOARDMAX],
    		      int color_to_move, int info_pos)
{
  int move = NO_MOVE;
  int saved_move = NO_MOVE;
  signed char smaller_goal[BOARDMAX];
  struct connection_data conn;

  /* When blocking off, we use a somewhat smaller goal area. */
  if (color_to_move == board[str])
    compute_connection_distances(str, NO_MOVE, FP(3.01), &conn, 1);
  else
    compute_connection_distances(str, NO_MOVE, FP(2.81), &conn, 1);

  sort_connection_queue_tail(&conn);
  expand_connection_queue(&conn);
  compute_smaller_goal(OTHER_COLOR(board[str]), color_to_move,
      		       &conn, goal, smaller_goal);
  if (0 && (debug & DEBUG_BREAKIN))
    print_connection_distances(&conn);
  DEBUG(DEBUG_BREAKIN, "Trying to break in from %1m to:\n", str);
  if (debug & DEBUG_BREAKIN)
    goaldump(smaller_goal);
  while ((color_to_move == board[str]
          && break_in(str, smaller_goal, &move))
         || (color_to_move == OTHER_COLOR(board[str])
	     && !block_off(str, smaller_goal, NULL))) { 
    /* Successful break-in/unsuccessful block. Now where exactly can we
     * erase territory? This is difficult, and the method here is very
     * crude: Wherever we enter the territory when computing the closest
     * neighbors of (str). Plus at the location of the break-in move.
     * FIXME: This needs improvement.
     */
    int k;
    int save_num = *num_non_territory;
    int affected_size = 0;
    int cut_off_distance = FP(3.5);
    if (ON_BOARD(move) && goal[move]) {
      non_territory[(*num_non_territory)++] = move;
      if (info_pos)
	DEBUG(DEBUG_TERRITORY | DEBUG_BREAKIN,
	      "%1m: Erasing territory at %1m -a.\n", info_pos, move);
      else
	DEBUG(DEBUG_TERRITORY | DEBUG_BREAKIN,
	      "Erasing territory at %1m -a.\n", move);
    }

    for (k = 0; k < conn.queue_end; k++) {
      int pos = conn.queue[k];
      if (conn.distances[pos] > cut_off_distance + FP(0.31))
	break;
      if (goal[pos]
	  && (!ON_BOARD(conn.coming_from[pos])
	      || !goal[conn.coming_from[pos]])) {
	non_territory[(*num_non_territory)++] = pos;
	if (info_pos)
	  DEBUG(DEBUG_TERRITORY | DEBUG_BREAKIN,
		"%1m: Erasing territory at %1m -b.\n", info_pos, pos);
	else
	  DEBUG(DEBUG_TERRITORY | DEBUG_BREAKIN,
	        "Erasing territory at %1m -b.\n", pos);
	if (conn.distances[pos] < cut_off_distance)
	  cut_off_distance = conn.distances[pos];
      }
      if (*num_non_territory >= save_num + 4)
	break;
    }

    /* Shouldn't happen, but it does. */
    if (*num_non_territory == save_num)
      break;

    for (k = save_num; k < *num_non_territory; k++) {
      int j;
      int pos = non_territory[k];
      if (goal[pos]) {
	affected_size++;
	goal[pos] = 0;
      }
      for (j = 0; j < 4; j++)
	if (ON_BOARD(pos + delta[j]) && goal[pos + delta[j]])
	  affected_size++;
      /* Don't kill too much territory at a time. */
      if (affected_size >= 5) {
	*num_non_territory = k;
	break;
      }
    }

    compute_smaller_goal(OTHER_COLOR(board[str]), color_to_move,
			 &conn, goal, smaller_goal);
    DEBUG(DEBUG_BREAKIN, "Now trying to break to smaller goal:\n", str);
    if (debug & DEBUG_BREAKIN)
      goaldump(smaller_goal);

    if (saved_move == NO_MOVE)
      saved_move = move;
  }
  return saved_move;
}

#define MAX_TRIES 10

static void
break_in_goal(int color_to_move, int owner, signed char goal[BOARDMAX],
    	      struct influence_data *q, int store, int info_pos)
{
  struct connection_data conn;
  int k;
  int intruder = OTHER_COLOR(owner);
  signed char used[BOARDMAX];
  int non_territory[BOARDMAX];
  int num_non_territory = 0;
  int candidate_strings[MAX_TRIES];
  int candidates = 0;
  int min_distance = FP(5.0);

  DEBUG(DEBUG_BREAKIN,
        "Trying to break (%C to move) %C's territory ", color_to_move, owner);
  if (debug & DEBUG_BREAKIN)
    goaldump(goal);
  /* Compute nearby fields of goal. */
  init_connection_data(intruder, goal, NO_MOVE, FP(3.01), &conn, 1);
  k = conn.queue_end;
  spread_connection_distances(intruder, &conn);
  sort_connection_queue_tail(&conn);
  if (0 && (debug & DEBUG_BREAKIN))
    print_connection_distances(&conn);

  /* Look for nearby stones. */
  memset(used, 0, BOARDMAX);
  for (; k < conn.queue_end; k++) {
    int pos = conn.queue[k];
    if (conn.distances[pos] > min_distance + FP(1.001))
      break;
    if (board[pos] == intruder
	&& influence_considered_lively(q, pos)) {
      /* Discard this string in case the shortest path goes via a string
       * that we have in the candidate list already.
       */
      int pos2 = pos;
      while (ON_BOARD(pos2)) {
        pos2 = conn.coming_from[pos2];
	if (IS_STONE(board[pos2]))
	  pos2 = find_origin(pos2);

	if (used[pos2])
	  break;
      }

      used[pos] = 1;
      if (ON_BOARD(pos2))
	continue;
      if (candidates == 0)
	min_distance = conn.distances[pos];
      candidate_strings[candidates++] = pos;
      if (candidates == MAX_TRIES)
	break;
    }
  }

  /* Finally, try the break-ins. */
  memset(non_territory, 0, BOARDMAX);
  for (k = 0; k < candidates; k++) {
    int move = break_in_goal_from_str(candidate_strings[k], goal,
  		                     &num_non_territory, non_territory,
				     color_to_move, info_pos);
    if (store && ON_BOARD(move) && num_break_ins < MAX_BREAK_INS) {
      /* Remember the move as a possible move candidate for later. */
      break_in_list[num_break_ins].str = candidate_strings[k];
      break_in_list[num_break_ins].move = move;
      num_break_ins++;
    }
  }

  for (k = 0; k < num_non_territory; k++)
    influence_erase_territory(q, non_territory[k], owner);
  if (0 && num_non_territory > 0 && (debug & DEBUG_BREAKIN))
    showboard(0);
}


/* The main function of this module. color_to_move is self-explanatory,
 * and the influence_data refers to the influence territory evaluation that
 * we are analyzing (and will be correcting). store indicates whether
 * the successful break-ins should be stored in the break_in_list[] (which
 * later gets used to generate move reasons).
 */
void
break_territories(int color_to_move, struct influence_data *q, int store,
    		  int info_pos)
{
  struct moyo_data territories;
  int k;

  if (!experimental_break_in || get_level() < 10)
    return;

  influence_get_territory_segmentation(q, &territories);
  for (k = 1; k <= territories.number; k++) {
    signed char goal[BOARDMAX];
    int pos;
    int size = 0;

    memset(goal, 0, BOARDMAX);
    for (pos = BOARDMIN; pos < BOARDMAX; pos++)
      if (ON_BOARD(pos) && territories.segmentation[pos] == k) {
	goal[pos] = 1;
	if (board[pos] != territories.owner[k])
	  size++;
      }
    if (size < 10)
      continue;

    if (color_to_move == OTHER_COLOR(territories.owner[k]))
      enlarge_goal(goal);
    break_in_goal(color_to_move, territories.owner[k], goal, q, store,
		  info_pos);
  }
}

void
clear_break_in_list()
{
  num_break_ins = 0;
}

/* The blocking moves should usually already have a move reason.
 *
 * The EXPAND_TERRITORY move reason ensures a territory evaluation of
 * this move, without setting the move.safety field. (I.e. the move will
 * be treated as a sacrifice move unless another move reasons tells us
 * otherwise.)
 */
void
break_in_move_reasons(int color)
{
  int k;
  for (k = 0; k < num_break_ins; k++)
    if (board[break_in_list[k].str] == color)
      add_expand_territory_move(break_in_list[k].move);
}
