gnugo-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [gnugo-devel] unconditional status


From: Gunnar Farneback
Subject: Re: [gnugo-devel] unconditional status
Date: Fri, 23 Jan 2004 23:22:26 +0100
User-agent: EMH/1.14.1 SEMI/1.14.3 (Ushinoya) FLIM/1.14.2 (Yagi-Nishiguchi) APEL/10.3 Emacs/20.7 (sparc-sun-solaris2.7) (with unibyte mode)

I wrote:
> This patch adds a new GTP command unconditional_status and a new
> regression test suite unconditional.tst, which includes extensive
> tests for the misclassified sekis. Those will be fixed in a later
> patch.

The patch below solves the 183 tests in unconditional.tst which
currently fail. Read the long comments in the patch to see how.

- new static function capture_non_invincible_strings() broken out of
  unconditional_life()
- unconditional_life() revised

/Gunnar

Index: engine/utils.c
===================================================================
RCS file: /cvsroot/gnugo/gnugo/engine/utils.c,v
retrieving revision 1.85
diff -u -r1.85 utils.c
--- engine/utils.c      12 Jan 2004 12:37:05 -0000      1.85
+++ engine/utils.c      23 Jan 2004 14:51:45 -0000
@@ -1330,60 +1330,17 @@
 }
     
 
-/* Find those worms of the given color that can never be captured,
- * even if the opponent is allowed an arbitrary number of consecutive
- * moves. The coordinates of the origins of these worms are written to
- * the worm arrays and the number of non-capturable worms is
- * returned.
- *
- * The algorithm is to cycle through the worms until none remains or
- * no more can be captured. A worm is removed when it is found to be
- * capturable, by letting the opponent try to play on all its
- * liberties. If the attack fails, the moves are undone. When no more
- * worm can be removed in this way, the remaining ones are
- * unconditionally alive.
- *
- * After this, unconditionally dead opponent worms and unconditional
- * territory are identified. To find these, we continue from the
- * position obtained at the end of the previous operation (only
- * unconditionally alive strings remain for color) with the following
- * steps:
- *
- * 1. Play opponent stones on all liberties of the unconditionally
- *    alive strings except where illegal. (That the move order may
- *    determine exactly which liberties can be played legally is not
- *    important. Just pick an arbitrary order).
- * 2. Recursively extend opponent strings in atari, except where this
- *    would be suicide.
- * 3. Play an opponent stone anywhere it can get two empty
- *    neighbors. (I.e. split big eyes into small ones).
- * 4. Play an opponent stone anywhere it can get one empty
- *    neighbor. (I.e. reduce two space eyes to one space eyes.)
- *
- * Remaining opponent strings in atari and remaining liberties of the
- * unconditionally alive strings constitute the unconditional
- * territory.
- *
- * Opponent strings from the initial position placed on
- * unconditional territory are unconditionally dead.
- *
- * On return, unconditional_territory[][] is 1 where color has
- * unconditionally alive stones, 2 where it has unconditional
- * territory, and 0 otherwise.
- */
-
-void
-unconditional_life(int unconditional_territory[BOARDMAX], int color)
+static int
+capture_non_invincible_strings(int color, int exceptions[BOARDMAX])
 {
+  int other = OTHER_COLOR(color);
   int something_captured = 1; /* To get into the first turn of the loop. */
-  int found_one;
   int moves_played = 0;
   int save_moves;
-  int pos;
-  int k;
   int libs[MAXLIBS];
   int liberties;
-  int other = OTHER_COLOR(color);
+  int pos;
+  int k;
   
   while (something_captured) {
     /* Nothing captured so far in this turn of the loop. */
@@ -1391,10 +1348,13 @@
 
     /* Visit all friendly strings on the board. */
     for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
-      if (board[pos] != color || !is_worm_origin(pos, pos))
+      if (board[pos] != color || find_origin(pos) != pos)
        continue;
-       
-      /* Try to capture the worm at (m, n). */
+
+      if (exceptions && exceptions[pos])
+       continue;
+      
+      /* Try to capture the string at pos. */
       liberties = findlib(pos, MAXLIBS, libs);
       save_moves = moves_played;
       for (k = 0; k < liberties; k++) {
@@ -1422,7 +1382,7 @@
        int success = tryko(libs[0], other, "unconditional_life", EMPTY, 0);
        gg_assert(success);
        moves_played++;
-       something_captured++;
+       something_captured = 1;
       }
       else
        while (moves_played > save_moves) {
@@ -1431,15 +1391,262 @@
        }
     }
   }
+  return moves_played;
+}
+
+/* Find those worms of the given color that can never be captured,
+ * even if the opponent is allowed an arbitrary number of consecutive
+ * moves. The coordinates of the origins of these worms are written to
+ * the worm arrays and the number of non-capturable worms is
+ * returned.
+ *
+ * The algorithm is to cycle through the worms until none remains or
+ * no more can be captured. A worm is removed when it is found to be
+ * capturable, by letting the opponent try to play on all its
+ * liberties. If the attack fails, the moves are undone. When no more
+ * worm can be removed in this way, the remaining ones are
+ * unconditionally alive.
+ *
+ * After this, unconditionally dead opponent worms and unconditional
+ * territory are identified. This is almost, but only almost,
+ * straightforward. We first present a simple but only almost correct
+ * solution, then show how to patch up its deficiencies.
+ *
+ * - - - - - - -
+ *
+ * Algorithm 1, simple but slightly incorrect.
+ *
+ * To find unconditionally dead opponent worms and unconditional
+ * territory, we continue from the position obtained at the end of the
+ * previous operation (only unconditionally alive strings remain for
+ * color) with the following steps:
+ *
+ * 1. Play opponent stones on all liberties of the unconditionally
+ *    alive strings except where illegal. (That the move order may
+ *    determine exactly which liberties can be played legally is not
+ *    important. Just pick an arbitrary order).
+ * 2. Recursively extend opponent strings in atari, except where this
+ *    would be suicide.
+ * 3. Play an opponent stone anywhere it can get two empty
+ *    neighbors. (I.e. split big eyes into small ones).
+ * 4. Play an opponent stone anywhere it can get one empty
+ *    neighbor. (I.e. reduce two space eyes to one space eyes.)
+ *
+ * Remaining opponent strings in atari and remaining liberties of the
+ * unconditionally alive strings constitute the unconditional
+ * territory.
+ *
+ * Opponent strings from the initial position placed on
+ * unconditional territory are unconditionally dead.
+ *
+ * - - - - - - -
+ *
+ * The deficiency with this algorithm is that a certain class of sekis
+ * are considered as dead, e.g. this position:
+ *
+ * .OOOOO.
+ * OOXXXOO
+ * OXX.XXO
+ * OX.O.XO
+ * OX.O.XO
+ * OXX.XXO
+ * OOXXXOO
+ * .OOOOO.
+ *
+ * The problem is that while removing the two O stones, X is reduced
+ * to a single small eye. Still O cannot capture these stones under
+ * alternating play since the eyespace is too big.
+ *
+ * Before discussing this seki further we make a preliminary
+ * modification of the algorithm.
+ *
+ * - - - - - - -
+ *
+ * Algorithm 2. More complex but still slightly incorrect algorithm:
+ *
+ * 1. Run algorithm 1.
+ * 2. Return to the original position.
+ * 3. Capture all capturable O strings which according to algorithm 1
+ *    do not belong to unconditional territory.
+ * 4. Play opponent stones on all liberties of the unconditionally
+ *    alive strings except where illegal. (That the move order may
+ *    determine exactly which liberties can be played legally is not
+ *    important. Just pick an arbitrary order).
+ * 5. Recursively extend opponent strings in atari, except where this
+ *    would be suicide.
+ * 6. Capture all remaining capturable O strings.
+ * 7. Repeat 4 and 5 once.
+ * 8. Play an opponent stone anywhere it can get two empty
+ *    neighbors. (I.e. split big eyes into small ones).
+ * 9. Play an opponent stone anywhere it can get one empty
+ *    neighbor. (I.e. reduce two space eyes to one space eyes.)
+ *
+ * Remaining opponent strings in atari and remaining liberties of the
+ * unconditionally alive strings constitute the unconditional
+ * territory.
+ *
+ * Opponent strings from the initial position placed on
+ * unconditional territory are unconditionally dead.
+ *
+ * - - - - - - -
+ *
+ * We can observe that, after step 5, an X group with at least two
+ * distinct eyespaces would not risk being reduced to a single small
+ * eye. Similarly an X group with a capturable O string of size at
+ * least three would allow the formation of two distinct small eyes
+ * after being captured. Thus it is easy to see that the only X groups
+ * which would live in seki but could not be transformed into
+ * unconditionally alive groups would have a single eyespace with a
+ * capturable O string of size at most 2. Furthermore the eyespace
+ * would not be possible to subdivide. Then if the capturable string
+ * would be of size 1 it would in all cases form a nakade and we would
+ * not have a seki. The plausible seki positions would all be
+ * reducable to the following eyeshape:
+ *
+ * .OOOOO.
+ * OOXXXO.
+ * OXX.XOO
+ * OX.OXXO
+ * OXXO.XO
+ * OOX.XXO
+ * .OXXXOO
+ * .OOOOO.
+ *
+ * The remaining question is what effects cutting points in the X
+ * group would have. For example these X groups are dead:
+ *
+ * .OOOOO.   .OOOOO.   .OOOOO.   .OOOOO.   ..OOOO.   ..OOOO.
+ * .OXXXO.   .OXXXO.   .OXXXO.  .OXXXO.   OOOXXO.   OOOXXO.
+ * OOX.XO.   OOX.XOO   OOX.XOO  OOX.XOO   OXX.XO.   OXX.XOO
+ * OX.OXOO   OX.OXXO   OX.OXXO  OX.OXXO   OX.OXOO   OX.OXXO
+ * OXXO.XO   OXXO.XO   OXXO.XO  OXXO.XO   OXXO.XO   OXXO.XO
+ * OOX.XXO   OOX.XOO   OOX.XXO  OOX.XXO   OOX.XXO   OOX.XXO
+ * .OXXXOO   .OXXXO.   .OXXOOO  .OOXXOO   .OXXXOO   .OXXOOO
+ * .OOOOO.   .OOOOO.   .OOOO..  ..OOOO.   .OOOOO.   .OOOO..
+ *
+ * while these are alive in seki
+ *
+ * ..OOOO.   .OOOO..   .OOOO..   ..OOOO.   ..OOOO.
+ * OOOXXO.   .OXXOO.   OOXXOO.  .OOXXO.   OOOXXO.
+ * OXX.XOO   OOX.XOO   OXX.XOO  OOX.XOO   OXX.XOO
+ * OX.OXXO   OX.OXXO   OX.OXXO  OX.OXXO   OX.OXXO
+ * OXXO.XO   OXXO.XO   OOXO.XO  OXXO.XO   OOXO.XO
+ * OOX.XXO   OOX.XXO   .OX.XXO  OOX.XXO   .OX.XXO
+ * .OXXXOO   .OXXXOO   .OXXOOO  .OXXXOO   .OXXXOO
+ * .OOOOO.   .OOOOO.   .OOOO..  ..OOOO.   .OOOOO.
+ *
+ * The critical distinction between the dead ones and the seki ones is
+ * that the stones marked a and b below,
+ *
+ * .OOOOO.
+ * OOXXXO.
+ * OXX.XOO
+ * OX.ObXO
+ * OXaO.XO
+ * OOX.XXO
+ * .OXXXOO
+ * .OOOOO.
+ *
+ * belong to different strings for the dead groups and to the same
+ * string for the seki groups.
+ *
+ * The trick to avoid misclassifying areas where the opponent can form
+ * a seki group but not an invincible group as unconditional territory
+ * is thus to detect the formation above and add a third stone to the
+ * O group before the capturing in step 6 above.
+ *
+ * This leads to the final algorithm.
+ *
+ * - - - - - - -
+ *
+ * Algorithm 3. Final and correct algorithm:
+ *
+ * 1. Run algorithm 1.
+ * 2. Return to the original position.
+ * 3. Capture all capturable O strings which according to algorithm 1
+ *    do not belong to unconditional territory.
+ * 4. Play opponent stones on all liberties of the unconditionally
+ *    alive strings except where illegal. (That the move order may
+ *    determine exactly which liberties can be played legally is not
+ *    important. Just pick an arbitrary order).
+ * 5. Recursively extend opponent strings in atari, except where this
+ *    would be suicide.
+ * 6. Identify eyespaces of the kind described above and extend any
+ *    matching two-stone string with a third stone.
+ * 7. Capture all remaining capturable O strings.
+ * 8. Repeat 4 and 5 once.
+ * 9. Play an opponent stone anywhere it can get two empty
+ *    neighbors. (I.e. split big eyes into small ones).
+ * 10. Play an opponent stone anywhere it can get one empty
+ *    neighbor. (I.e. reduce two space eyes to one space eyes.)
+ *
+ * Remaining opponent strings in atari and remaining liberties of the
+ * unconditionally alive strings constitute the unconditional
+ * territory.
+ *
+ * Opponent strings from the initial position placed on
+ * unconditional territory are unconditionally dead.
+ *
+ * - - - - - - -
+ *
+ * On return, unconditional_territory[][] is 1 where color has
+ * unconditionally alive stones, 2 where it has unconditional
+ * territory, and 0 otherwise.
+ */
+
+void
+unconditional_life(int unconditional_territory[BOARDMAX], int color)
+{
+  int found_one;
+  int other = OTHER_COLOR(color);
+  int libs[MAXLIBS];
+  int liberties;
+  int pos;
+  int k, r;
+  int moves_played;
+  int potential_sekis[BOARDMAX];
+
+  /* Find isolated two-stone strings which might be involved in the
+   * kind of seki described in the comments.
+   */
+  memset(potential_sekis, 0, sizeof(potential_sekis));
+  for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
+    int isolated = 1;
+    int stones[2];
+    int pos2;
+    
+    if (board[pos] != color
+       || find_origin(pos) != pos
+       || countstones(pos) != 2)
+      continue;
+    
+    findstones(pos, 2, stones);
+    for (k = 0; k < 1 && isolated; k++) {
+      for (r = 0; r < 8 && isolated; r++) {
+       pos2 = stones[k] + delta[r];
+       if (!ON_BOARD(pos2)
+           || (board[pos2] == color
+               && !same_string(pos, pos2)))
+         isolated = 0;
+      }
+    }
+
+    if (isolated) {
+      potential_sekis[stones[0]] = 1;
+      potential_sekis[stones[1]] = 1;
+    }
+  }
   
-  /* The strings still remaining are uncapturable. Now see which
-   * opponent strings can survive.
+  moves_played = capture_non_invincible_strings(color, potential_sekis);
+  /* The strings still remaining except those marked in
+   * potential_sekis[] are uncapturable. Now see which opponent
+   * strings can survive.
    *
    * 1. Play opponent stones on all liberties of the unconditionally
    *    alive strings except where illegal.
    */
   for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
-    if (board[pos] != color || !is_worm_origin(pos, pos))
+    if (board[pos] != color || potential_sekis[pos] || find_origin(pos) != pos)
       continue;
       
     /* Play as many liberties as we can. */
@@ -1471,6 +1678,71 @@
     }
   }
 
+  /* Now see whether there are any significant sekis on the board. */
+  for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
+    if (!potential_sekis[pos]
+       || board[pos] == EMPTY
+       || find_origin(pos) != pos)
+      continue;
+    for (r = 0; r < 4; r++) {
+      int up = delta[r];
+      int right = delta[(r + 1) % 4];
+      int locally_played_moves = 0;
+      if (board[pos + up] != color
+         || board[pos + up + up] != EMPTY
+         || board[pos - up] != EMPTY)
+       continue;
+      for (k = 0; k < 2; k++) {
+       if (k == 1)
+         right = -right;
+       if (board[pos + right] != EMPTY || board[pos + up - right] != EMPTY)
+         continue;
+       if (board[pos - right] == EMPTY
+           && trymove(pos - right, other, "unconditional_life", pos,
+                      EMPTY, NO_MOVE))
+         locally_played_moves++;
+       if (board[pos + up + right] == EMPTY
+           && trymove(pos + up + right, other, "unconditional_life", pos,
+                      EMPTY, NO_MOVE))
+         locally_played_moves++;
+       if (board[pos - right] == other && board[pos + up + right] == other
+           && same_string(pos - right, pos + up + right)) {
+         /* This is a critical seki. Extend the string with one stone
+           * in an arbitrary direction to break the seki.
+          */
+         while (locally_played_moves > 0) {
+           popgo();
+           locally_played_moves--;
+         }
+         trymove(pos - up, color, "unconditional_life", pos, EMPTY, NO_MOVE);
+         moves_played++;
+         break;
+       }
+       else {
+         while (locally_played_moves > 0) {
+           popgo();
+           locally_played_moves--;
+         }
+       }
+      }
+      if (countstones(pos) > 2)
+       break;
+    }
+  }
+
+  /* Capture the strings involved in potential sekis. */
+  for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
+    if (!potential_sekis[pos] || board[pos] == EMPTY)
+      continue;
+    /* Play as many liberties as we can. */
+    liberties = findlib(pos, MAXLIBS, libs);
+    for (k = 0; k < liberties; k++) {
+      if (trymove(libs[k], other, "unconditional_life", pos, EMPTY, NO_MOVE))
+       moves_played++;
+    }
+  }
+    
+
   for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
     int apos;
     int bpos;
@@ -1515,9 +1787,9 @@
   /* Identify unconditionally alive stones and unconditional territory. */
   memset(unconditional_territory, 0, sizeof(int) * BOARDMAX);
   for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
-    if (board[pos] == color) {
+    if (board[pos] == color && !potential_sekis[pos]) {
       unconditional_territory[pos] = 1;
-      if (is_worm_origin(pos, pos)) {
+      if (find_origin(pos) == pos) {
        liberties = findlib(pos, MAXLIBS, libs);
        for (k = 0; k < liberties; k++)
          unconditional_territory[libs[k]] = 2;




reply via email to

[Prev in Thread] Current Thread [Next in Thread]