/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
 * 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.                                            *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */


/* This file contains functions that deals with threats and, 
 * especially, combinations of threats.
 */

#include "gnugo.h"

#include <string.h>

#include "liberty.h"
#include "gg_utils.h"
#include "patterns.h"


static void find_double_threats(int color);

/* Generate move reasons for combination attacks and defenses against
 * them.
 *
 * This is one of the move generators called from genmove().
 */

void
combinations(int color)
{
  int save_verbose;
  int attack_point;
  signed char defense_points[BOARDMAX];
  int other = OTHER_COLOR(color);
  int aa_val;

  /* Find intersections with multiple threats. */
  find_double_threats(color);

  save_verbose = verbose;
  if (verbose > 0)
    verbose--;

  if (save_verbose)
    gprintf("\nlooking for combination attacks ...\n");
  
  aa_val = atari_atari(color, &attack_point, NULL, save_verbose);
  if (aa_val > 0) {
    if (save_verbose)
      gprintf("Combination attack for %C with size %d found at %1m\n",
	      color, aa_val, attack_point);
    add_my_atari_atari_move(attack_point, aa_val);
  }
  
  aa_val = atari_atari(other, &attack_point, defense_points, save_verbose);
  if (aa_val > 0) {
    int pos;
    if (save_verbose)
      gprintf("Combination attack for %C with size %d found at %1m\n",
	      other, aa_val, attack_point);
    
    for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
      if (ON_BOARD(pos) && defense_points[pos]) {
	add_your_atari_atari_move(pos, aa_val);
	if (save_verbose)
	  gprintf("- defense at %1m\n", pos);
      }
    }
  }
  verbose = save_verbose;
}


#define MAX_THREATENED_STRINGS  10  /* Should be enough for one intersection */

static void
find_double_threats(int color)
{
  int ii;
  int k;
  int l;

  for (ii = BOARDMIN; ii < BOARDMAX; ii++) {
    int num_a_threatened_groups;
    int a_threatened_groups[MAX_THREATENED_STRINGS];
#if 0
    int num_d_threatened_groups;
    int d_threatened_groups[MAX_THREATENED_STRINGS];
#endif

    if (!ON_BOARD(ii))
      continue;

    /* Generate an EITHER_MOVE move reasons for each pair of the 
     * threatened strings.  We must also remove the threats, because
     * otherwise we would get followup points for them as well.
     *
     * FIXME: 
     *   - This is perhaps not the best way to do it, but realistically
     *     it will be seldom that more than two strings are threatened
     *     at the same point.  Still, we should find a better way.
     *   - EITHER_MOVE should be generalized to more than two strings.
     */
    num_a_threatened_groups = get_attack_threats(ii, MAX_THREATENED_STRINGS,
						 a_threatened_groups);
    if (num_a_threatened_groups > 1) {
      if (trymove(ii, color, "find_double_threats-A", ii)) {
	for (k = 0; k < num_a_threatened_groups - 1; ++k)
	  for (l = k + 1; l < num_a_threatened_groups; ++l) {
	    /* Note: If we used attack_either() here instead of trymove()
	     *       and !defend_both(), we would not make use of the fact
	     *       that we already know of a common threat point for
	     *       the two strings.
	     * Besides, attack_either is currently (3.1.11) not very good.
	     *
	     * The call to attack() is intended to detect the case
	     * where the move at ii is a snapback capture.
	     */
	    if (board[a_threatened_groups[k]] == EMPTY
		|| board[a_threatened_groups[l]] == EMPTY) {
	      if (!attack(ii, NULL)) {
		TRACE("Double threat at %1m, either %1m or %1m attacked.\n",
		      ii, a_threatened_groups[k], a_threatened_groups[l]);
		add_either_move(ii, ATTACK_STRING, a_threatened_groups[k],
				ATTACK_STRING, a_threatened_groups[l]);
		remove_attack_threat_move(ii, a_threatened_groups[k]);
		remove_attack_threat_move(ii, a_threatened_groups[l]);
	      }
	    }
	    else if (!defend_both(a_threatened_groups[k],
				  a_threatened_groups[l])) {
	      TRACE("Double threat at %1m, either %1m or %1m attacked.\n",
		    ii, a_threatened_groups[k], a_threatened_groups[l]);
	      add_either_move(ii, ATTACK_STRING, a_threatened_groups[k],
			      ATTACK_STRING, a_threatened_groups[l]);
	      remove_attack_threat_move(ii, a_threatened_groups[k]);
	      remove_attack_threat_move(ii, a_threatened_groups[l]);
	    }
	  }
	popgo();
      }
    }
  }
  
  
  /* FIXME:
   *   TODO:
   *     - defense threats
   *     - combinations of owl threats and other threats
   *     - combinations of threats to cut and connect
   *     - combinations of breakins into enemy territory
   */
}


/* ================================================================ */
/*                       Combination attacks                        */
/* ================================================================ */


/* atari_atari(color, *move) looks for a series of ataris on
 * strings of the other color culminating in the capture of
 * a string which is thought to be invulnerable by the reading
 * code. Such a move can be missed since it may be that each
 * string involved individually can be rescued, but nevertheless
 * one of them can be caught. The simplest example is a double
 * atari. The return value is the size of the smallest opponent
 * worm. 
 *
 * One danger with this scheme is that the first atari
 * tried might be irrelevant to the actual combination.
 * To detect this possibility, once we've found a combination,
 * we mark that first move as forbidden, then try again. If
 * no combination of the same size or larger turns up, then
 * the first move was indeed essential.
 *
 * For the purpose of the move generation, returns the
 * size of the smallest of the worms under attack.
 */

/* Local struct to keep track of atari_atari attack moves and what
 * they threat.
 */
#define AA_MAX_TARGETS_PER_MOVE 4

#define MAX_AA_DIST 5

struct aa_move {
  int move;
  int target[AA_MAX_TARGETS_PER_MOVE];
};

#define AA_MAX_MOVES MAX_BOARD * MAX_BOARD  
static int aa_status[BOARDMAX]; /* ALIVE, DEAD or CRITICAL */
static int forbidden[BOARDMAX];
static int aa_values[BOARDMAX];
static void compute_aa_status(int color,
			      const signed char safe_stones[BOARDMAX]);
static void compute_aa_values(int color);
static int get_aa_status(int pos);
static int do_atari_atari(int color, int *attack_point, int *defense_point,
			  signed char all_potential_defenses[BOARDMAX],
			  int last_friendly, int save_verbose, int minsize,
			  signed char goal[BOARDMAX]);
static int atari_atari_succeeded(int color, int *attack_point,
                                int *defense_point, int last_friendly,
                                int save_verbose, int minsize);
static void atari_atari_find_attack_moves(int color, int minsize,
					  struct aa_move attacks[AA_MAX_MOVES],
					  signed char goal[BOARDMAX]);
static void atari_atari_attack_patterns(int color, int minsize,
					struct aa_move attacks[AA_MAX_MOVES],
					signed char goal[BOARDMAX]);
static void atari_atari_attack_callback(int anchor, int color,
					struct pattern *pattern,
					int ll, void *data);
static int atari_atari_find_defense_moves(int targets[AA_MAX_TARGETS_PER_MOVE],
					  int moves[AA_MAX_MOVES]);
static int get_aa_value(int str);
static int update_aa_goal(signed char goal[BOARDMAX],
			  signed char new_goal[BOARDMAX],
			  int apos, int color);
static void aa_init_moves(struct aa_move attacks[AA_MAX_MOVES]);
static void aa_add_move(struct aa_move attacks[AA_MAX_MOVES],
			int move, int target);
static int aa_move_known(struct aa_move attacks[AA_MAX_MOVES],
			 int move, int target);
static void aa_sort_moves(struct aa_move attacks[AA_MAX_MOVES]);

/* Set to 1 if you want verbose traces from this function. */

int
atari_atari(int color, int *attack_move, signed char defense_moves[BOARDMAX],
	    int save_verbose)
{
  int other = OTHER_COLOR(color);
  int apos;
  int dpos;
  int aa_val;
  signed char saved_defense_moves[BOARDMAX];

  /* Collect worm statuses of opponent's worms. We need to
   * know this because we only want to report unexpected
   * results. For example, we do not want to report success
   * if we find we can kill a worm which is already dead.
   * The worm status of empty points is set to UNKNOWN to signal
   * that stones added along the way need special attention.
   */
  if (aa_depth < 2)
    return 0;
  memset(forbidden, 0, sizeof(forbidden));

  compute_aa_status(color, NULL);
  compute_aa_values(color);
  
  if (defense_moves)
    memset(defense_moves, 0, BOARDMAX);
  aa_val = do_atari_atari(color, &apos, &dpos, defense_moves, NO_MOVE,
			  save_verbose, 0, NULL);

  if (aa_val == 0)
    return 0;

  /* We try excluding the first atari found and see if the
   * combination still works. Repeat until failure.
   */
  while (1) {
    int new_aa_val;
    
    if (attack_move)
      *attack_move = apos;
    
    forbidden[apos] = 1;
    if (defense_moves) {
      memcpy(saved_defense_moves, defense_moves, BOARDMAX);
      memset(defense_moves, 0, BOARDMAX);
    }
    new_aa_val = do_atari_atari(color, &apos, &dpos, defense_moves, NO_MOVE,
				save_verbose, aa_val, NULL);

    /* The last do_atari_atari call fails. When do_atari_atari fails,
     * it does not change the value of (apos), so these correspond
     * to a move that works and is necessary.
     */
    if (new_aa_val == 0)
      break;
    else
      aa_val = new_aa_val;
  }

  if (defense_moves) {
    int pos;
    memcpy(defense_moves, saved_defense_moves, BOARDMAX);
    /* defense_moves[] contains potential defense moves. Now we
     * examine which of them really work.
     */
    forbidden[apos] = 0;
    for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
      if (!ON_BOARD(pos) || !defense_moves[pos])
	continue;

      if (!trymove(pos, other, "atari_atari", NO_MOVE)) {
	defense_moves[pos] = 0;
	if (save_verbose)
	  gprintf("%1m deleted defense point, illegal\n", pos);
	continue;
      }

      if (attack(pos, NULL)) {
	defense_moves[pos] = 0;
	popgo();
	if (save_verbose)
	  gprintf("%1m deleted defense point, unsafe\n", pos);
	continue;
      }
      
      if (do_atari_atari(color, &apos, &dpos, NULL, NO_MOVE,
			 save_verbose, aa_val, NULL) > 0) {
	if (save_verbose)
	  gprintf("%1m deleted defense point, didn't work\n", pos);
	defense_moves[pos] = 0;
      }
      
      popgo();
    }
  }
  return aa_val;
}


/* Wrapper around atari_atari_blunder_size. Check whether a
 * combination attack of size at least minsize appears after move
 * at (move) has been made.
 * The arrays saved_dragons[] and saved_worms[] should be one for
 * stones belonging to dragons or worms respectively, which are
 * supposedly saved by (move).
 *
 * FIXME: We probably want to change the calling convention of this
 *        function to return all defense moves.
 */
int
atari_atari_confirm_safety(int color, int move, int *defense, int minsize,
			   const signed char saved_dragons[BOARDMAX],
			   const signed char saved_worms[BOARDMAX])
{
  signed char safe_stones[BOARDMAX];
  signed char defense_moves[BOARDMAX];
  int pos;
  int blunder_size;

  mark_safe_stones(color, move, saved_dragons, saved_worms, safe_stones);
  blunder_size = atari_atari_blunder_size(color, move, defense_moves,
					  safe_stones);

  if (defense) {
    /* Return one arbitrary defense move. */
    *defense = NO_MOVE;
    for (pos = BOARDMIN; pos < BOARDMAX; pos++)
      if (ON_BOARD(pos) && defense_moves[pos]) {
	*defense = pos;
	break;
      }
  }
  
  return blunder_size >= minsize;
}


/* This function checks whether any new combination attack appears after
 * move at (move) has been made, and returns its size (in points).
 * safe_stones marks which of our stones are supposedly safe after this move.
 */
int
atari_atari_blunder_size(int color, int move,
			 signed char defense_moves[BOARDMAX],
			 const signed char safe_stones[BOARDMAX])
{
  int apos;
  int defense_point = NO_MOVE;
  int aa_val, after_aa_val;
  int other = OTHER_COLOR(color);
  signed char defense_points[BOARDMAX];
  int last_forbidden = NO_MOVE;

  /* If aa_depth is too small, we can't see any combination attacks,
   * so in this respect the move is safe enough.
   */
  if (aa_depth < 2)
    return 0;

  memset(forbidden, 0, sizeof(forbidden));
  memset(defense_points, 0, sizeof(defense_points));

  compute_aa_status(other, safe_stones);
  compute_aa_values(other);

  /* Accept illegal ko capture here. */
  if (!tryko(move, color, NULL))
    /* Really shouldn't happen. */
    abortgo(__FILE__, __LINE__, "trymove", move);

  increase_depth_values();

  aa_val = do_atari_atari(other, &apos, &defense_point, defense_points,
			  NO_MOVE, 0, 0, NULL);
  after_aa_val = aa_val;

  if (aa_val == 0 || defense_point == NO_MOVE) {

    /* No sufficiently large combination attack, so the move is safe from
     * this danger.
     *
     * On rare occasions do_atari_atari might find a combination
     * but no defense. In this case we assume that the combination
     * is illusory.
     */

    popgo();
    decrease_depth_values();
    return 0;
  }

  while (aa_val >= after_aa_val && defense_point != NO_MOVE) {
    /* Try dropping moves from the combination and see if it still
     * works. What we really want is to get the proper defense move
     * into (*defense).
     */
    forbidden[apos] = 1;
    last_forbidden = apos;
    aa_val = do_atari_atari(other, &apos, &defense_point, NULL,
			    NO_MOVE, 0, aa_val, NULL);
  }

  popgo();
  decrease_depth_values();
  /* We know that a combination exists, but we don't know if
   * the original move at (aa) was really relevant. So we
   * try omitting it and see if a combination is still found.
   */
  compute_aa_status(other, NULL);
  compute_aa_values(other);
  forbidden[last_forbidden] = 0;
  aa_val = do_atari_atari(other, NULL, NULL, NULL, NO_MOVE, 0, 0, NULL);
  if (aa_val >= after_aa_val)
    return 0;

  /* Try the potential defense moves to see which are effective. */
  if (defense_moves) {
    int pos;
    /* defense_points[] contains potential defense moves. Now we
     * examine which of them really work.
     */
    
    /* FIXME: Maybe these should be moved after the tryko() below? */
    compute_aa_status(other, safe_stones);
    compute_aa_values(other);
      
    memcpy(defense_moves, defense_points, sizeof(defense_points));
    for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
      if (!ON_BOARD(pos) || !defense_moves[pos] || pos == move)
	continue;

      if (!trymove(pos, color, "atari_atari", NO_MOVE)) {
	defense_moves[pos] = 0;
	continue;
      }
      increase_depth_values();

      if (attack(pos, NULL)) {
	defense_moves[pos] = 0;
	decrease_depth_values();
	popgo();
	continue;
      }
      
      /* Accept illegal ko capture here. */
      if (!tryko(move, color, NULL))
	/* Really shouldn't happen. */
	abortgo(__FILE__, __LINE__, "trymove", move);
      increase_depth_values();
      
      if (do_atari_atari(other, &apos, &defense_point, NULL, NO_MOVE,
			 0, after_aa_val, NULL) >= after_aa_val)
	defense_moves[pos] = 0;

      decrease_depth_values();
      popgo();
      decrease_depth_values();
      popgo();
    }
  }
  
  return after_aa_val - aa_val;
}


/* ---------------------------------------------------------------- */
/*                Helper functions for atari_atari.                 */
/* ---------------------------------------------------------------- */


/* Helper function for computing the aa_status for all opponent's strings.
 * If safe_stones is given, we just copy the information from there.
 * If called at stackp > 0, safe_stones must be provided since the
 * dragon_data is not valid then.
 */

static void
compute_aa_status(int color, const signed char safe_stones[BOARDMAX])
{
  int other = OTHER_COLOR(color);
  int pos;
  SGFTree *save_sgf_dumptree = sgf_dumptree;
  int save_count_variations = count_variations;
  int save_verbose = verbose;

  gg_assert(safe_stones || stackp == 0);
  
  sgf_dumptree = NULL;
  count_variations = 0;
  if (verbose)
    verbose--;
  
  /* Collect worm statuses of opponent's worms. We need to
   * know this because we only want to report unexpected
   * results. For example, we do not want to report success
   * if we find we can kill a worm which is already dead.
   * The worm status of empty points is set to UNKNOWN to signal
   * that stones added along the way need special attention.
   */
  for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
    if (board[pos] == other) {
      if (safe_stones) {
	if (safe_stones[pos])
	  aa_status[pos] = ALIVE;
	else
	  aa_status[pos] = DEAD;
      }
      else {
	if (dragon[pos].status == DEAD)
	  aa_status[pos] = DEAD;
	else if (dragon[pos].status == CRITICAL)
	  aa_status[pos] = CRITICAL;
	else if (worm[pos].attack_codes[0] != 0) {
	  if (worm[pos].defense_codes[0] != 0)
	    aa_status[pos] = CRITICAL;
	  else
	    aa_status[pos] = DEAD;
	}
	else
	  aa_status[pos] = ALIVE;
      }
    }
    else if (ON_BOARD(pos))
      aa_status[pos] = UNKNOWN;
  }
  
  /* reclassify a worm with 2 liberties as INSUBSTANTIAL if capturing
   * it does not result in a live group.
   */
  for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
    if (board[pos] == other
	&& find_origin(pos) == pos
	&& countlib(pos) == 2
	&& aa_status[pos] == ALIVE) {
      int libs[2];
      findlib(pos, 2, libs);
      /* Don't waste time running owl_substantial() if we can't safely
       * atari anyway.
       */
      if (is_self_atari(libs[0], color)
	  && is_self_atari(libs[1], color))
	continue;
      
      if (!owl_substantial(pos)) {
	int pos2;
	for (pos2 = BOARDMIN; pos2 < BOARDMAX; pos2++)
	  if (board[pos2] == other && find_origin(pos2) == pos)
	    aa_status[pos2] = INSUBSTANTIAL;
      }
    }
  }
    
  if (debug & DEBUG_ATARI_ATARI) {
    gprintf("compute_aa_status() for %C\n", color);
    gprintf("aa_status: (ALIVE worms not listed)\n");
    for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
      if (board[pos] == other && is_worm_origin(pos, pos)) {
	const char *status = "UNKNOWN (shouldn't happen)";
	if (aa_status[pos] == DEAD)
	  status = "DEAD";
	else if (aa_status[pos] == CRITICAL)
	  status = "CRITICAL";
	else if (aa_status[pos] == INSUBSTANTIAL)
	  status = "INSUBSTANTIAL";
	
	if (aa_status[pos] != ALIVE)
	  gprintf("%1M: %s\n", pos, status);
      }
    }
  }

  sgf_dumptree = save_sgf_dumptree;
  count_variations = save_count_variations;
  verbose = save_verbose;
}


/* Helper function for retrieving the aa_status for a string. We can't
 * reliably do this simply by looking up aa_status[pos] since this is
 * only valid at vertices which were non-empty at the start of the
 * reading. For later added stones, we need to find their aa_status by
 * locating a part of the string which was a worm at the beginning of
 * the reading.
 */

static int
get_aa_status(int pos)
{
  int stones[MAX_BOARD * MAX_BOARD];
  int num_stones;
  int k;

  if (aa_status[pos] != UNKNOWN)
    return aa_status[pos];

  num_stones = findstones(pos, MAX_BOARD * MAX_BOARD, stones);
  for (k = 0; k < num_stones; k++)
    if (aa_status[stones[k]] != UNKNOWN)
      return aa_status[stones[k]];

  return UNKNOWN;
}



/* Helper function for atari_atari. Here worms is the number of
 * opponent worms involved in the combination, and (last_friendly) is
 * the location of the last friendly move played. Moves marked
 * with the forbidden array are not tried. If no move is found,
 * the values of *attack_point and *defense_point are not changed.
 *
 * If not NULL, *attack_point is left pointing to the location of the
 * attacking move, and *defense_point points to a move defending the
 * combination. In rare cases a defensive move might not be found. If
 * a non-static function calling do_atari_atari gets a return value of
 * 1 but NO_MOVE as the defense point, this should be treated as
 * equivalent to a return value of 0.
 *
 * The goal array limits where we are allowed to consider threats.
 * Only strings for which goal is set to 1 may be threatened. If goal
 * is NULL, anything may be attacked. Thus goal is typically NULL when
 * do_atari_atari() is called from an external function. After the
 * first threat has been made, the goal array is set to one in a
 * neighborhood of the move and after subsequent threats it is
 * expanded with neighborhoods of those moves. The details of this can
 * be found in the function update_aa_goal().
 */

static int
do_atari_atari(int color, int *attack_point, int *defense_point,
	       signed char all_potential_defenses[BOARDMAX], int last_friendly,
	       int save_verbose, int minsize, signed char goal[BOARDMAX])
{
  int other = OTHER_COLOR(color);
  int k;
  struct aa_move attacks[AA_MAX_MOVES];
  int num_defense_moves;
  int defense_moves[AA_MAX_MOVES];
  int pos;
  SGFTree *save_sgf_dumptree;
  int save_count_variations;
  
  if (debug & DEBUG_ATARI_ATARI) {
    gprintf("%odo_atari_atari: ");
    dump_stack();
    gprintf("%oforbidden moves: ");
    for (pos = BOARDMIN; pos < BOARDMAX; pos++)
      if (ON_BOARD(pos) && forbidden[pos])
	gprintf("%o%1m ", pos);
    gprintf("\n");
    gprintf("%ogoal: ");
    if (!goal)
      gprintf("none");
    else {
      for (pos = BOARDMIN; pos < BOARDMAX; pos++)
	if (ON_BOARD(pos) && goal[pos])
	  gprintf("%o%1m ", pos);
    }
    gprintf("\n");
  }

  /* First look for strings adjacent to the last friendly move played
   * (or to another stone in the same string) which can be
   * unexpectedly attacked.  If so, the combination attack
   * has succeeded.
   */
  if (last_friendly != NO_MOVE) {
    int retval;
    save_sgf_dumptree = sgf_dumptree;
    save_count_variations = count_variations;
    sgf_dumptree = NULL;
    count_variations = 0;
    retval = atari_atari_succeeded(color, attack_point, defense_point,
				   last_friendly, save_verbose, minsize);
    sgf_dumptree = save_sgf_dumptree;
    count_variations = save_count_variations;
    if (retval != 0) {
      if (sgf_dumptree)
	/* FIXME: Better message. */
	sgftreeAddComment(sgf_dumptree, "attack found");
      return retval;
    }
  }

  if (stackp > aa_depth)
    return 0;

  /* Find attack moves. These are typically ataris but may also be
   * more general.
   */
  save_sgf_dumptree = sgf_dumptree;
  save_count_variations = count_variations;
  sgf_dumptree = NULL;
  count_variations = 0;
  atari_atari_find_attack_moves(color, minsize, attacks, goal);
  sgf_dumptree = save_sgf_dumptree;
  count_variations = save_count_variations;

  /* Try the attacking moves and let the opponent defend. Then call
   * ourselves recursively.
   */
  for (k = 0; attacks[k].move != NO_MOVE; k++) {
    int aa_val;
    int str = attacks[k].target[0];
    int apos = attacks[k].move;
    int bpos;
    int r;
    
    if (!trymove(apos, color, "do_atari_atari-A", str))
      continue;
    
    if (all_potential_defenses) {
      all_potential_defenses[apos] = 1;
      if (countlib(apos) <= 2) {
	int libs[2];
	int num_libs = findlib(apos, 2, libs);
	all_potential_defenses[libs[0]] = 1;
	if (num_libs == 2)
	  all_potential_defenses[libs[1]] = 1;
      }
    }

    if (!IS_STONE(board[str])) {
      /* Error situation. This could be caused by a wrong matcher status. */
      if (save_verbose || (debug & DEBUG_ATARI_ATARI))
	gprintf("%oError condition found by atari_atari\n");
      popgo();
      return 0;
    }

    /* Try to defend the stone (str) which is threatened. */
    aa_val = get_aa_value(str);

    /* Pick up defense moves. */
    save_sgf_dumptree = sgf_dumptree;
    save_count_variations = count_variations;
    sgf_dumptree = NULL;
    count_variations = 0;
    num_defense_moves = atari_atari_find_defense_moves(attacks[k].target,
						       defense_moves);
    sgf_dumptree = save_sgf_dumptree;
    count_variations = save_count_variations;
    
    for (r = 0; r < num_defense_moves; r++) {
      bpos = defense_moves[r];

      if (all_potential_defenses)
	all_potential_defenses[bpos] = 1;

      if (trymove(bpos, other, "do_atari_atari-B", str)) {
	int new_aa_val;
	signed char new_goal[BOARDMAX];
	/* These moves may have been irrelevant for later
	 * reading, so in order to avoid horizon problems, we
	 * need to temporarily increase the depth values.
	 */
	modify_depth_values(2);
	update_aa_goal(goal, new_goal, apos, color);
	new_aa_val = do_atari_atari(color, NULL, defense_point,
				    all_potential_defenses,
				    apos, save_verbose, minsize, new_goal);
	modify_depth_values(-2);
	if (new_aa_val < aa_val)
	  aa_val = new_aa_val;
	popgo();
      }

      /* Defense successful, no need to try any further. */
      if (aa_val == 0)
	break;
    }

    /* Undo the attacking move. */
    popgo();

    if (aa_val == 0)
      continue;

    /* atari_atari successful */
    if (num_defense_moves == 0) {
      if (save_verbose || (debug & DEBUG_ATARI_ATARI)) {
	gprintf("%oThe worm %1m can be attacked at %1m after ", str, apos);
	dump_stack();
      }
      if (sgf_dumptree)
	/* FIXME: Better message. */
	sgftreeAddComment(sgf_dumptree, "attack found");
    }

    if (attack_point)
      *attack_point = apos;

    if (defense_point) {
      save_sgf_dumptree = sgf_dumptree;
      save_count_variations = count_variations;
      sgf_dumptree = NULL;
      count_variations = 0;

      if (!find_defense(str, defense_point))
	*defense_point = NO_MOVE;

      /* If no defense point is known and (apos) is a safe
       * move for other, it probably defends the combination.
       */
      if ((*defense_point == NO_MOVE || !safe_move(*defense_point, other))
	  && safe_move(apos, other))
	*defense_point = apos;

      sgf_dumptree = save_sgf_dumptree;
      count_variations = save_count_variations;
    }
    
    DEBUG(DEBUG_ATARI_ATARI, "%oreturn value:%d (%1m)\n", aa_val, str);
    return aa_val;
  }
    
  /* No atari_atari attack. */
  return 0;
}


static int
atari_atari_succeeded(int color, int *attack_point, int *defense_point,
		      int last_friendly, int save_verbose, int minsize)
{
  int pos;
  int apos;
  int other = OTHER_COLOR(color);

  for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
    if (board[pos] != other)
      continue;
    
    if (pos != find_origin(pos))
      continue;
    
    if (minsize > 0
	&& get_aa_value(pos) < minsize)
      continue;
    
    if (get_aa_status(pos) != ALIVE)
      continue;
    
    if (board[last_friendly] != EMPTY
	&& !adjacent_strings(last_friendly, pos))
      continue;
    
    if (board[last_friendly] == EMPTY
	&& !liberty_of_string(last_friendly, pos))
      continue;
    
    if (debug & DEBUG_ATARI_ATARI)
      gprintf("Considering attack of %1m. depth = %d.\n", pos, depth);
    
    if (attack(pos, &apos) && !forbidden[apos]) {
      if (save_verbose || (debug & DEBUG_ATARI_ATARI)) {
	gprintf("%oThe worm %1m can be attacked at %1m after ", pos, apos);
	dump_stack();
      }
      if (attack_point)
	*attack_point = apos;
      
      /* We look for a move defending the combination.
       * Normally this is found by find_defense but failing
       * that, if the attacking move is a safe move for color,
       * it probably defends.
       */
      if (defense_point) {
	if (!find_defense(pos, defense_point)) {
	  if (safe_move(apos, other))
	    *defense_point = apos;
	  else
	    *defense_point = NO_MOVE;
	}
      }
      
      DEBUG(DEBUG_ATARI_ATARI, "%oreturn value:%d (%1m)\n",
	    get_aa_value(pos), pos);
      return get_aa_value(pos);
    }
  }
  
  return 0;
}

#define MAX_THREAT_MOVES  MAX_TACTICAL_POINTS

static void
atari_atari_find_attack_moves(int color, int minsize,
			      struct aa_move attacks[AA_MAX_MOVES],
			      signed char goal[BOARDMAX])
{
  int k;
  int r;

  aa_init_moves(attacks);

  atari_atari_attack_patterns(color, minsize, attacks, goal);

  /* Sort the attack moves. */
  aa_sort_moves(attacks);
  
  if (debug & DEBUG_ATARI_ATARI) {
    gprintf("Attack moves:");
    for (k = 0; k < AA_MAX_MOVES && attacks[k].move != NO_MOVE; k++) {
      gprintf("%o %1m(", attacks[k].move);
      for (r = 0; r < AA_MAX_TARGETS_PER_MOVE; r++) {
	if (attacks[k].target[r] == NO_MOVE)
	  break;
	gprintf("%o%s%1m", r == 0 ? "" : ",", attacks[k].target[r]);
      }
      gprintf("%o)");
    }
    gprintf("%o\n");
  }
}

/* FIXME: Move these to a struct and pass to callback through the
 * *data parameter.
 */
static int current_minsize;
static struct aa_move *current_attacks;
static int conditional_attack_point[BOARDMAX];

static void
atari_atari_attack_patterns(int color, int minsize,
			    struct aa_move attacks[AA_MAX_MOVES],
			    signed char goal[BOARDMAX])
{
  signed char revised_goal[BOARDMAX];
  current_minsize = minsize;
  current_attacks = attacks;
  memset(conditional_attack_point, 0, sizeof(conditional_attack_point));

  /* If goal is NULL and there are forbidden moves we need to compute
   * a new goal around the forbidden moves.
   */
  if (goal == NULL && update_aa_goal(goal, revised_goal, NO_MOVE, color))
    goal = revised_goal;

#if 0
  if (goal != NULL) {
    int pos;
    gprintf("goal:");
    for (pos = BOARDMIN; pos < BOARDMAX; pos++)
      if (ON_BOARD(pos) && goal[pos])
	gprintf("%o %1m", pos);
    gprintf("%o\n");
  }
#endif
  
  matchpat(atari_atari_attack_callback, color, &aa_attackpat_db, NULL, goal);
}

/* Try to attack every X string in the pattern, whether there is an attack
 * before or not. Only exclude already known attacking moves.
 */
static void
atari_atari_attack_callback(int anchor, int color,
			    struct pattern *pattern, int ll, void *data)
{
  int move;
  int k;
  UNUSED(data);

  move = AFFINE_TRANSFORM(pattern->move_offset, ll, anchor);

  if (forbidden[move])
    return;
  
  /* If the pattern has a constraint, call the autohelper to see
   * if the pattern must be rejected.
   */
  if (pattern->autohelper_flag & HAVE_CONSTRAINT)
    if (!pattern->autohelper(ll, move, color, 0))
      return;

  /* If the pattern has a helper, call it to see if the pattern must
   * be rejected.
   */
  if (pattern->helper)
    if (!pattern->helper(pattern, ll, move, color))
      return;

  /* Loop through pattern elements in search of X strings to
   * threaten to attack.
   */
  for (k = 0; k < pattern->patlen; ++k) { /* match each point */
    if (pattern->patn[k].att == ATT_X) {
      /* transform pattern real coordinate */
      int str = find_origin(AFFINE_TRANSFORM(pattern->patn[k].offset,
					     ll, anchor));

      if (current_minsize > 0
	  && get_aa_value(str) < current_minsize)
	continue;

      if (aa_move_known(current_attacks, move, str))
	continue;

      if (get_aa_status(str) != ALIVE)
	continue;

      /* Usually we don't want to play self atari. However, if we
       * capture in snapback it's okay. For s class patterns we don't
       * have this requirement.
       */
      if (!(pattern->class & CLASS_s) && is_self_atari(move, color)) {
	if (countlib(str) > 2)
	  continue;

	if (!safe_move(move, color))
	  continue;
      }
      
      /*
       * Play (move) and see if there is an attack.
       */
      if (trymove(move, color, "attack_callback", str)) {
	int acode;
	int attack_point = NO_MOVE;

	if (!board[str])
	  acode = WIN;
	else
	  acode = attack(str, &attack_point);

	popgo();

	if (acode != 0) {
	  if ((pattern->class & CLASS_c)
	      && !aa_move_known(current_attacks, move, NO_MOVE)) {
	    /* Conditional pattern. */
	    DEBUG(DEBUG_ATARI_ATARI,
		  "aa_attack pattern %s+%d (conditional) found threat on %1m at %1m with code %d\n",
		  pattern->name, ll, str, move, acode);
	    if (conditional_attack_point[move] == NO_MOVE)
	      conditional_attack_point[move] = str;
	    else if (conditional_attack_point[move] != str) {
	      aa_add_move(current_attacks, move,
			  conditional_attack_point[move]);
	      aa_add_move(current_attacks, move, str);
	    }
	  }
	  else {
	    aa_add_move(current_attacks, move, str);
	    DEBUG(DEBUG_ATARI_ATARI,
		  "aa_attack pattern %s+%d found threat on %1m at %1m with code %d\n",
		  pattern->name, ll, str, move, acode);
	  }
	}
      }
    }
  }
}


static int
atari_atari_find_defense_moves(int targets[AA_MAX_TARGETS_PER_MOVE],
			       int moves[AA_MAX_MOVES])
{
  int num_moves = 0;
  int move;
  int k;
  int liberties;
  int libs[4];
  int neighbors;
  int adjs[MAXCHAIN];
  int mx[BOARDMAX];
  int r, s;

  memset(mx, 0, sizeof(mx));

  for (r = 0; r < AA_MAX_TARGETS_PER_MOVE && targets[r] != NO_MOVE; r++) {
    int str = targets[r];

    /* If the attack move happened to remove (str), there's no defense. */
    if (board[str] == EMPTY)
      continue;
    
    /* Because we know (str) is threatened there is an
     * attack and we can be sure find_defense() will give a
     * useful defense point if it returns non-zero. Usually we
     * would need to call attack_and_defend() to be certain of
     * this.
     */
    if (!find_defense(str, &move))
      continue;
    moves[num_moves++] = move;
    if (num_moves == AA_MAX_MOVES)
      return num_moves;
    mx[move] = 1;
    
    /* Consider all moves to attack a neighbor or to play on a liberty. */
    liberties = findlib(str, 4, libs);
    for (k = 0; k < liberties; k++) {
      if (!mx[libs[k]]
	  && trymove(libs[k], board[str], "aa_defend-A", str)) {
	if (attack(str, NULL) == 0) {
	  moves[num_moves++] = libs[k];
	  mx[libs[k]] = 1;
	}
	popgo();
	if (num_moves == AA_MAX_MOVES)
	  return num_moves;
      }
    }
    
    neighbors = chainlinks(str, adjs);
    for (k = 0; k < neighbors; k++) {
      int attack_point;
      if (attack(adjs[k], &attack_point) == WIN
	  && !mx[attack_point]) {
	moves[num_moves++] = attack_point;
	if (num_moves == AA_MAX_MOVES)
	  return num_moves;
	mx[attack_point] = 1;
      }

      /* If the neighbor has at most three liberties, try all of them
       * for defense, except self-ataris.
       */
      liberties = findlib(adjs[k], 3, libs);
      if (liberties <= 3) {
	for (s = 0; s < liberties; s++) {
	  if (!mx[libs[s]]
	      && !is_self_atari(libs[s], board[str])
	      && trymove(libs[s], board[str], "aa_defend-B", str)) {
	    if (attack(str, NULL) == 0) {
	      moves[num_moves++] = libs[s];
	      mx[libs[s]] = 1;
	    }
	    popgo();
	    if (num_moves == AA_MAX_MOVES)
	      return num_moves;
	  }
	}
      }
    }
    
    if (debug & DEBUG_ATARI_ATARI) {
      gprintf("Defense moves for %1m:", str);
      for (k = 0; k < num_moves; k++)
	gprintf("%o %1m", moves[k]);
      gprintf("%o\n");
    }
  }

  return num_moves;
}


/* Try to guess the value of the strings. We do this by adding twice
 * the number of stones to the number of liberties and second order
 * liberties within the moyo around the string. This is of course
 * quite crude since it doesn't take into account any strategic
 * effects, e.g. a string being cutting stones. 
 */
static void
compute_aa_values(int color)
{
  int other = OTHER_COLOR(color);
  int pos;
  int value;
  int liberties;
  int libs[MAXLIBS];
  int mx[BOARDMAX];
  int r, k;

  for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
    if (board[pos] != other
	|| pos != find_origin(pos)
	|| aa_status[pos] != ALIVE) {
      aa_values[pos] = 0;
      continue;
    }
      
    memset(mx, 0, sizeof(mx));
    liberties = findlib(pos, MAXLIBS, libs);
    value = 2 * countstones(pos);

    for (r = 0; r < liberties; r++) {
      if (!mx[libs[r]]
	  && (whose_moyo(&initial_black_influence, libs[r]) == other
	      || whose_moyo(&initial_white_influence, libs[r]) == other)) {
	mx[libs[r]] = 1;
	value++;
      }
      for (k = 0; k < 4; k++) {
	int librd = libs[r] + delta[k];
	if (!ON_BOARD1(librd) || mx[librd])
	  continue;
	mx[librd] = 1;
	if (board[librd] == EMPTY
	    && (whose_moyo(&initial_black_influence, librd) == other
		|| (whose_moyo(&initial_white_influence, librd) == other)))
	  value++;
      }
    }

    aa_values[pos] = value;
    if (1)
      DEBUG(DEBUG_ATARI_ATARI, "aa_value for %1m = %d\n", pos, value);
  }
}

/* The aa_value for a string is the sum of the aa_values for all
 * included strings in the original position. This will systematically
 * overvalue strings which consist of multiple original strings, but
 * this is okay since the defender very rarely should defend a string
 * first and then sacrifice it later.
 */
static int
get_aa_value(int str)
{
  int stones[MAX_BOARD * MAX_BOARD];
  int k;
  int num_stones = findstones(str, MAX_BOARD * MAX_BOARD, stones);
  int value = 0;
  
  for (k = 0; k < num_stones; k++)
    value += aa_values[stones[k]];

  return value;
}


/* update_aa_goal(goal, new_goal, apos, color) extends the goal array
 * with vertices in a neighborhood of apos. The algorithm is that
 * starting at apos, a distance measure is computed to nearby
 * vertices. The distance increases with one for each step through
 * empty vertices and by a liberty depending number when passing
 * through strings of the attacked color. Strings with 3 or fewer
 * liberties are free to pass through while strings with more
 * liberties cost (libs - 3) to pass through. Stones with a distance
 * of 5 or less are included in the goal.
 *
 * Additionally neighborhoods of the moves in the forbidden array are
 * included in the goal, to make it possible to limit the goal to a
 * specific area from the beginning. This is needed when trying to
 * decide which moves are relevant to the combination.
 */

#define ENQUEUE(pos, dist) \
    do { \
      if ((dist) <= MAX_AA_DIST) { \
        if (dists[pos] == 0) { \
          queue[queue_end++] = (pos); \
          dists[pos] = (dist); \
        } \
        else if (dists[pos] < (dist)) \
          dists[pos] = (dist); \
      } \
    } while (0);

static int
update_aa_goal(signed char goal[BOARDMAX], signed char new_goal[BOARDMAX],
	       int apos, int color)
{
  int other = OTHER_COLOR(color);
  int dists[BOARDMAX];
  int queue[MAX_BOARD * MAX_BOARD];
  int queue_end = 0;
  int k, r, s;
  int pos;
  
  if (goal == NULL)
    memset(new_goal, 0, BOARDMAX);
  else
    memcpy(new_goal, goal, BOARDMAX);

  memset(dists, 0, sizeof(dists));

  if (apos != NO_MOVE) {
    dists[apos] = 1;
    queue[queue_end++] = apos;
  }

#if 0
  /* Disabled for now, since it does nothing but break atari_atari:16
   * and trevorc:1540. It could be reactivated when the rest of the
   * function would be modified in order to garanty that a forbidden
   * move is strictly equivalent to a played move in terms of goal
   * mapping. I doubt it would be anything worth though...
   */
  for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
    if (ON_BOARD(pos) && forbidden[pos]) {
      dists[pos] = 1;
      queue[queue_end++] = pos;
    }
  }
#endif

  if (queue_end == 0)
    return 0;
  
  for (r = 0; r < queue_end; r++) {
    int smallest_dist = MAX_BOARD * MAX_BOARD;
    int best_index = -1;

    gg_assert(queue_end < MAX_BOARD * MAX_BOARD);
    
    for (k = r; k < queue_end; k++) {
      if (dists[queue[k]] < smallest_dist) {
	smallest_dist = dists[queue[k]];
	best_index = k;
      }
    }
    
    if (best_index != r) {
      int tmp = queue[r];
      queue[r] = queue[best_index];
      queue[best_index] = tmp;
    }
    
    pos = queue[r];
    if (board[pos] == other)
      new_goal[pos] = 1;

    /* FIXME: We shouldn't let dead opponent stones stop the
     * propagation of distance.
     *
     * As a partial fix we include pos == apos in a test below.
     */
    for (k = 0; k < 4; k++) {
      int pos2 = pos + delta[k];
      if (!ON_BOARD(pos2))
       continue;
      if ((board[pos] != color || pos == apos) && board[pos2] == EMPTY) {
        ENQUEUE(pos2, dists[pos] + 1);
      }
      else if (board[pos] != other && board[pos2] == other) {
	int stones[MAX_BOARD * MAX_BOARD];
	int size = findstones(pos2, MAX_BOARD * MAX_BOARD, stones);
	int libs = countlib(pos2);
	int deltadist = libs - 3;
	if (deltadist < 0)
	  deltadist = 0;
	for (s = 0; s < size; s++)
	  ENQUEUE(stones[s], dists[pos] + deltadist);
      }
    }
  }
  return 1;
}

/* Initialize an array with atari_atari attacks. The convention is that
 * the array ends when a NO_MOVE is encountered in the move field.
 */
static void
aa_init_moves(struct aa_move attacks[AA_MAX_MOVES])
{
  attacks[0].move = NO_MOVE;
}


/* Add an atari_atari attack move to a struct aa_move array. If the
 * move already is included in the array, we check whether the target
 * also is known for that move and add it if not.
 */
static void
aa_add_move(struct aa_move attacks[AA_MAX_MOVES], int move, int target)
{
  int k;
  int r;

  for (k = 0; k < AA_MAX_MOVES; k++)
    if (attacks[k].move == move || attacks[k].move == NO_MOVE)
      break;

  /* If the array is full, give up. */
  if (k == AA_MAX_MOVES)
    return;

  target = find_origin(target);

  /* New move. */
  if (attacks[k].move == NO_MOVE) {
    attacks[k].move = move;
    attacks[k].target[0] = target;
    if (AA_MAX_TARGETS_PER_MOVE > 0)
      attacks[k].target[1] = NO_MOVE;

    if (k < AA_MAX_MOVES - 1)
      attacks[k+1].move = NO_MOVE;

    return;
  }

  /* Known move, maybe new target. */
  for (r = 0; r < AA_MAX_TARGETS_PER_MOVE; r++)
    if (attacks[k].target[r] == target || attacks[k].target[r] == NO_MOVE)
      break;

  /* No place for more targets. */
  if (r == AA_MAX_TARGETS_PER_MOVE)
    return;

  /* Target known. */
  if (attacks[k].target[r] == target)
    return;

  /* Add target. */
  attacks[k].target[r] = target;
  if (r < AA_MAX_TARGETS_PER_MOVE - 1)
    attacks[k].target[r + 1] = NO_MOVE;
}

/* Check whether an atari_atari attack move is included in an struct
 * aa_move array. If target is not NO_MOVE, we also require that the
 * target is known for the move.
 */
static int
aa_move_known(struct aa_move attacks[AA_MAX_MOVES], int move, int target)
{
  int k;
  int r;

  for (k = 0; k < AA_MAX_MOVES; k++)
    if (attacks[k].move == move || attacks[k].move == NO_MOVE)
      break;

  /* If the array is full, give up and claim the move to be known. */
  if (k == AA_MAX_MOVES)
    return 1;

  /* Unknown move. */
  if (attacks[k].move == NO_MOVE)
    return 0;

  /* Move known, but how about the target?
   * If no target specified, just return 1.
   */
  if (target == NO_MOVE)
    return 1;

  target = find_origin(target);
  for (r = 0; r < AA_MAX_TARGETS_PER_MOVE; r++)
    if (attacks[k].target[r] == target || attacks[k].target[r] == NO_MOVE)
      break;

  /* No place for more targets. Give up and claim the target to be known. */
  if (r == AA_MAX_TARGETS_PER_MOVE)
    return 1;

  /* Target known. */
  if (attacks[k].target[r] == target)
    return 1;

  /* Unknown target. */
  return 0;
}


/* Auxiliary function for aa_sort_moves(). */
static int
target_comp_func(const void *a, const void *b)
{
  int asize = get_aa_value(*((const int *) a));
  int bsize = get_aa_value(*((const int *) b));
  return asize - bsize;
}


/* Auxiliary function for aa_sort_moves(). */
static int
move_comp_func(const void *a, const void *b)
{
  const struct aa_move *aa = a;
  const struct aa_move *bb = b;
  int asize = get_aa_value(aa->target[0]);
  int bsize = get_aa_value(bb->target[0]);
  return asize - bsize;
}

/* Sort the attack moves. For each move the targets are sorted in
 * decreasing size. Then the moves are sorted with increasing size
 * of their first target.
 */
static void
aa_sort_moves(struct aa_move attacks[AA_MAX_MOVES])
{
  int k;
  int r;
  int number_of_attacks;
  int number_of_targets;

  for (k = 0; k < AA_MAX_MOVES && attacks[k].move != NO_MOVE; k++) {
    for (r = 0; r < AA_MAX_TARGETS_PER_MOVE; r++)
      if (attacks[k].target[r] == NO_MOVE)
	break;
    number_of_targets = r;
    gg_sort(attacks[k].target, number_of_targets,
	    sizeof(attacks[k].target[0]), target_comp_func);
  }
  number_of_attacks = k;
  gg_sort(attacks, number_of_attacks, sizeof(attacks[0]), move_comp_func);
}


#if 0

/* Returns true if a move by (color) at (pos) is atari on something.
 * Currently unused.
 */

static int
is_atari(int pos, int color)
{
  int other = OTHER_COLOR(color);

  if (!is_legal(pos, color))
    return 0;
  
  if (board[SOUTH(pos)] == other 
      && countlib(SOUTH(pos)) == 2)
    return 1;
  
  if (board[WEST(pos)] == other 
      && countlib(WEST(pos)) == 2)
    return 1;
  
  if (board[NORTH(pos)] == other 
      && countlib(NORTH(pos)) == 2)
    return 1;
  
  if (board[EAST(pos)] == other 
      && countlib(EAST(pos)) == 2)
    return 1;
  
  return 0;
}

#endif


/*
 * Local Variables:
 * tab-width: 8
 * c-basic-offset: 2
 * End:
 */
