gnugo-devel
[Top][All Lists]
Advanced

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

[gnugo-devel] breakin revisions, inessential dragons


From: Arend Bayer
Subject: [gnugo-devel] breakin revisions, inessential dragons
Date: Sun, 8 Jun 2003 10:07:22 +0200 (CEST)


- inessential dragons handled differently in estimate_territorial_value()
- use smaller goal area when trying to break into territories

The reason for the change about inessential dragons is as follows:
The inessentiality classification is, unfortunately, incorrect at times.
Fixing this would require to move that classification from examine_position()
time to each territorial evaluation of a board position.

This incorrect classification is a bit of a problem without, and a bigger
problem with --experimental-break-in: In nngs2:600, the stone at C6 gets
marked as inessential, and thus the owl attack reasons of B5 against it
is discarded. This means that it is still regarded alive after B5, and then
the "break-in" at D6 nullifies the capture of the big dragon.

I work around this by indirectly letting the influence code know why
a stone is considered safe:
* a stone marked safe in safe_stones[BOARMDAX] _and_ having strength > 0
is considered alive
* a stone marked safe in safe_stones[BOARMDAX] and having strength == 0
is a dead stone, that is however not considered a prisoner, because it
is inessential.

This is how the influence code has always worked at stackp==0.
The breakin code then only uses stones with strength > 0 as a starting
point for break-ins.

Arend



Index: engine/breakin.c
===================================================================
RCS file: /cvsroot/gnugo/gnugo/engine/breakin.c,v
retrieving revision 1.2
diff -u -p -r1.2 breakin.c
--- engine/breakin.c    4 Jun 2003 13:26:17 -0000       1.2
+++ engine/breakin.c    8 Jun 2003 07:50:51 -0000
@@ -80,6 +80,62 @@ enlarge_goal(char goal[BOARDMAX])
   }
 }

+
+/* The "smaller goal" is the intersection of the goal with what is
+ * stored in the queue of the connection_data conn.
+ */
+static void
+compute_smaller_goal(int owner, int color_to_move,
+                    const struct connection_data *conn,
+                    const char goal[BOARDMAX], 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))
+         own_stones_visited[pos] += gg_abs(I(pos) - I(coming_from))
+                                    + gg_abs(J(pos) - J(coming_from));
+       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.
+     */
+    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 (goal_neighbors > 1)
+      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.
@@ -90,48 +146,58 @@ break_in_goal_from_str(int str, char goa
                      int color_to_move)
 {
   int move = NO_MOVE;
+  char smaller_goal[BOARDMAX];
+  struct connection_data conn;

-  DEBUG(DEBUG_TERRITORY, "Trying to break in from %1m\n", str);
+  compute_connection_distances(str, NO_MOVE, 3.01, &conn);
+  compute_smaller_goal(OTHER_COLOR(board[str]), color_to_move,
+                      &conn, goal, smaller_goal);
+  DEBUG(DEBUG_TERRITORY, "Trying to break in from %1m to:\n", str);
+  if (debug & DEBUG_TERRITORY)
+    goaldump(smaller_goal);
   while ((color_to_move == board[str]
-          && break_in(str, goal, &move))
+          && break_in(str, smaller_goal, &move))
          || (color_to_move == OTHER_COLOR(board[str])
-            && !block_off(str, goal, NULL))) {
+            && !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 locaction of the break-in move.
      * FIXME: This needs improvement.
      */
-    struct connection_data conn;
     int k;
     int save_num = *num_non_territory;
-    float min_distance = 5.0;
+    float cut_off_distance = 3.5;
     if (ON_BOARD(move) && goal[move]) {
       non_territory[(*num_non_territory)++] = move;
       DEBUG(DEBUG_TERRITORY, "Erasing territory at %1m -a.\n", move);
     }

-    compute_connection_distances(str, NO_MOVE, 3.01, &conn);
     for (k = 0; k < conn.queue_end; k++) {
       int pos = conn.queue[k];
-      if (conn.distances[pos] > min_distance + 0.31)
+      if (conn.distances[pos] > cut_off_distance + 0.31)
        break;
       if (goal[pos]
          && (!ON_BOARD(conn.coming_from[pos])
              || !goal[conn.coming_from[pos]])) {
        non_territory[(*num_non_territory)++] = pos;
        DEBUG(DEBUG_TERRITORY, "Erasing territory at %1m -b.\n", pos);
-       if (conn.distances[pos] < min_distance)
-         min_distance = conn.distances[pos];
+       if (conn.distances[pos] < cut_off_distance)
+         cut_off_distance = conn.distances[pos];
       }
-      if (*num_non_territory >= save_num + 5)
+      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++)
       goal[non_territory[k]] = 0;
+
+    compute_smaller_goal(OTHER_COLOR(board[str]), color_to_move,
+                        &conn, goal, smaller_goal);
   }
   return move;
 }
@@ -170,7 +236,7 @@ break_in_goal(int color_to_move, int own
     if (conn.distances[pos] > min_distance + 1.001)
       break;
     if (board[pos] == intruder
-       && influence_considered_safe(q, pos)
+       && influence_considered_lively(q, pos)
        && !used[pos]) {
       /* Discard this string in case the shortest path goes via a string
        * that we have in the candidate list already.
Index: engine/dragon.c
===================================================================
RCS file: /cvsroot/gnugo/gnugo/engine/dragon.c,v
retrieving revision 1.114
diff -u -p -r1.114 dragon.c
--- engine/dragon.c     4 Jun 2003 01:41:28 -0000       1.114
+++ engine/dragon.c     8 Jun 2003 07:50:54 -0000
@@ -1185,10 +1185,10 @@ set_dragon_strengths(const char safe_sto
     }
 }

-/* Marks all inessential stones with INFLUENCE_SAVE_STONE, leaves
+/* Marks all inessential stones with INFLUENCE_SAFE_STONE, leaves
  * everything else unchanged.
  */
-static void
+void
 mark_inessential_stones(int color, char safe_stones[BOARDMAX])
 {
   int ii;
Index: engine/hash.c
===================================================================
RCS file: /cvsroot/gnugo/gnugo/engine/hash.c,v
retrieving revision 1.15
diff -u -p -r1.15 hash.c
--- engine/hash.c       4 Jun 2003 12:48:50 -0000       1.15
+++ engine/hash.c       8 Jun 2003 07:50:54 -0000
@@ -396,7 +396,7 @@ hashdata_compare(Hash_data *hd1, Hash_da

 /* Compute hash value to identify the goal area. */
 Hash_data
-goal_to_hashvalue(char *goal)
+goal_to_hashvalue(const char *goal)
 {
   int i, pos;
   Hash_data return_value;
Index: engine/hash.h
===================================================================
RCS file: /cvsroot/gnugo/gnugo/engine/hash.h,v
retrieving revision 1.10
diff -u -p -r1.10 hash.h
--- engine/hash.h       4 Jun 2003 12:48:50 -0000       1.10
+++ engine/hash.h       8 Jun 2003 07:50:55 -0000
@@ -134,7 +134,7 @@ typedef struct {
 } Hash_data;

 Hash_data xor_hashvalues(Hash_data *key1, Hash_data *key2);
-Hash_data goal_to_hashvalue(char *goal);
+Hash_data goal_to_hashvalue(const char *goal);

 void hash_init(void);
 #if FULL_POSITION_IN_HASH
Index: engine/influence.c
===================================================================
RCS file: /cvsroot/gnugo/gnugo/engine/influence.c,v
retrieving revision 1.82
diff -u -p -r1.82 influence.c
--- engine/influence.c  4 Jun 2003 01:41:28 -0000       1.82
+++ engine/influence.c  8 Jun 2003 07:50:56 -0000
@@ -907,8 +907,10 @@ influence_erase_territory(struct influen
   q->territory_value[pos] = 0.0;
   influence_mark_non_territory(pos, color);
   for (k = 0; k < 4; k++) {
-    q->territory_value[pos + delta[k]] = 0.0;
-    influence_mark_non_territory(pos + delta[k], color);
+    if (ON_BOARD(pos + delta[k])) {
+      q->territory_value[pos + delta[k]] = 0.0;
+      influence_mark_non_territory(pos + delta[k], color);
+    }
   }
 }

@@ -1506,9 +1508,13 @@ influence_territory(const struct influen
 }

 int
-influence_considered_safe(const struct influence_data *q, int pos)
+influence_considered_lively(const struct influence_data *q, int pos)
 {
-  return q->safe[pos];
+  int color = board[pos];
+  ASSERT1(IS_STONE(color), pos);
+  return (q->safe[pos]
+          && ((color == WHITE && q->white_strength[pos] > 0)
+             || (color == BLACK && q->black_strength[pos] > 0)));
 }


@@ -1536,6 +1542,7 @@ compute_followup_influence(const struct
   int save_debug = debug;

   memcpy(q, base, sizeof(*q));
+  ASSERT1(IS_STONE(q->color_to_move), move);

   /* We mark the saved stones and their neighbors in the goal array.
    */
Index: engine/liberty.h
===================================================================
RCS file: /cvsroot/gnugo/gnugo/engine/liberty.h,v
retrieving revision 1.178
diff -u -p -r1.178 liberty.h
--- engine/liberty.h    4 Jun 2003 19:16:46 -0000       1.178
+++ engine/liberty.h    8 Jun 2003 07:50:58 -0000
@@ -355,8 +355,8 @@ int string_connect(int str1, int str2, i
 int disconnect(int str1, int str2, int *move);
 int non_transitivity(int str1, int str2, int str3, int *move);

-int break_in(int str, char goal[BOARDMAX], int *move);
-int block_off(int str1, char goal[BOARDMAX], int *move);
+int break_in(int str, const char goal[BOARDMAX], int *move);
+int block_off(int str1, const char goal[BOARDMAX], int *move);

 /* board.c */
 int liberty_of_string(int pos, int str);
@@ -414,6 +414,7 @@ int lively_dragon_exists(int color);
 void compute_dragon_influence(void);
 void set_strength_data(int color, char safe_stones[BOARDMAX],
                       float strength[BOARDMAX]);
+void mark_inessential_stones(int color, char safe_stones[BOARDMAX]);

 void get_lively_stones(int color, char safe_stones[BOARDMAX]);
 int is_same_worm(int w1, int w2);
@@ -717,7 +718,7 @@ float influence_score(const struct influ
 float game_status(int color);
 void resegment_initial_influence(void);
 void influence_mark_non_territory(int pos, int color);
-int influence_considered_safe(const struct influence_data *q, int pos);
+int influence_considered_lively(const struct influence_data *q, int pos);
 void influence_erase_territory(struct influence_data *q, int pos, int color);

 void break_territories(int color_to_move, struct influence_data *q,
@@ -738,7 +739,7 @@ int analyze_eyegraph(const char *coded_e


 /* debugging support */
-void goaldump(char goal[BOARDMAX]);
+void goaldump(const char goal[BOARDMAX]);
 void move_considered(int move, float value);


Index: engine/move_reasons.c
===================================================================
RCS file: /cvsroot/gnugo/gnugo/engine/move_reasons.c,v
retrieving revision 1.113
diff -u -p -r1.113 move_reasons.c
--- engine/move_reasons.c       8 May 2003 18:34:11 -0000       1.113
+++ engine/move_reasons.c       8 Jun 2003 07:51:00 -0000
@@ -1872,16 +1872,10 @@ static struct discard_rule discard_rules
   { { ALL_MOVE, -1 },
     one_of_both_attackable, REDUNDANT,
     "  %1m: 0.0 - 'defend both' is redundant at %1m (direct att./def. as 
well)\n"},
-  { { ATTACK_MOVE, ATTACK_MOVE_GOOD_KO,
-      ATTACK_MOVE_BAD_KO, ATTACK_THREAT,
-      DEFEND_MOVE, DEFEND_MOVE_GOOD_KO,
-      DEFEND_MOVE_BAD_KO, DEFEND_THREAT, -1 },
+  { { ATTACK_THREAT, DEFEND_THREAT, -1 },
     concerns_inessential_worm, TERRITORY_REDUNDANT,
-    "  %1m: 0.0 - attack/defense of %1m (inessential)\n"},
-  { { OWL_ATTACK_MOVE, OWL_ATTACK_MOVE_GOOD_KO,
-      OWL_ATTACK_MOVE_BAD_KO, OWL_ATTACK_THREAT,
-      OWL_DEFEND_MOVE, OWL_DEFEND_MOVE_GOOD_KO,
-      OWL_DEFEND_MOVE_BAD_KO, UNCERTAIN_OWL_DEFENSE, -1 },
+    "  %1m: 0.0 - attack/defense threat of %1m (inessential)\n"},
+  { { OWL_ATTACK_THREAT, UNCERTAIN_OWL_DEFENSE, -1 },
     concerns_inessential_dragon, REDUNDANT,
     "  %1m: 0.0 - (uncertain) owl attack/defense of %1m (inessential)\n"},
   { { ATTACK_MOVE, ATTACK_MOVE_GOOD_KO, ATTACK_MOVE_BAD_KO,
Index: engine/owl.c
===================================================================
RCS file: /cvsroot/gnugo/gnugo/engine/owl.c,v
retrieving revision 1.167
diff -u -p -r1.167 owl.c
--- engine/owl.c        7 Jun 2003 19:43:09 -0000       1.167
+++ engine/owl.c        8 Jun 2003 07:51:04 -0000
@@ -3813,7 +3813,7 @@ owl_update_boundary_marks(int pos, struc
  */

 void
-goaldump(char goal[BOARDMAX])
+goaldump(const char goal[BOARDMAX])
 {
   int pos;
   for (pos = BOARDMIN; pos < BOARDMAX; pos++)
Index: engine/readconnect.c
===================================================================
RCS file: /cvsroot/gnugo/gnugo/engine/readconnect.c,v
retrieving revision 1.51
diff -u -p -r1.51 readconnect.c
--- engine/readconnect.c        4 Jun 2003 13:26:17 -0000       1.51
+++ engine/readconnect.c        8 Jun 2003 07:51:07 -0000
@@ -47,10 +47,10 @@ static int recursive_connect2(int str1,
                              int komaster, int kom_pos, int has_passed);
 static int recursive_disconnect2(int str1, int str2, int *move,
                                 int komaster, int kom_pos, int has_passed);
-static int recursive_break(int str, char goal[BOARDMAX], int *move,
+static int recursive_break(int str, const char goal[BOARDMAX], int *move,
                           int komaster, int kom_pos, int has_passed,
                           Hash_data *goal_hash);
-static int recursive_block(int str, char goal[BOARDMAX], int *move,
+static int recursive_block(int str, const char goal[BOARDMAX], int *move,
                           int komaster, int kom_pos, int has_passed,
                           Hash_data *goal_hash);

@@ -2529,32 +2529,47 @@ find_string_connection_moves(int str1, i
   return num_moves;
 }

+
+static void
+add_to_start_queue(int pos, float dist, struct connection_data *conn)
+{
+  conn->queue[conn->queue_end++] = pos;
+  conn->distances[pos] = dist;
+  conn->deltas[pos] = dist;
+  conn->coming_from[pos] = NO_MOVE;
+  conn->vulnerable1[pos] = NO_MOVE;
+  conn->vulnerable2[pos] = NO_MOVE;
+}
+
+
 void
 init_connection_data(int color, const char goal[BOARDMAX],
                     struct connection_data *conn)
 {
   int pos;
+  char mark[BOARDMAX];
+  memset(mark, 0, BOARDMAX);
   clear_connection_data(conn);
   for (pos = BOARDMIN; pos < BOARDMAX; pos++)
-    if ((board[pos] == EMPTY || board[pos] == color)
+    if (!mark[pos]
+       && (board[pos] == EMPTY || board[pos] == color)
        && goal[pos]) {
-      conn->queue[conn->queue_end++] = pos;
       if (board[pos] == color) {
-       conn->distances[pos] = 0.0;
-       conn->deltas[pos] = 0.0;
+       /* In this case, add the whole string to the start queue. */
+       int stones[MAX_BOARD * MAX_BOARD];
+       int num_stones = findstones(pos, MAX_BOARD * MAX_BOARD, stones);
+       int k;
+       for (k = 0; k < num_stones; k++)
+         add_to_start_queue(stones[k], 0.0, conn);
+       mark_string(pos, mark, 1);
       }
-      else {
-       conn->distances[pos] = 1.0;
-       conn->deltas[pos] = 1.0;
-      }
-      conn->coming_from[pos] = NO_MOVE;
-      conn->vulnerable1[pos] = NO_MOVE;
-      conn->vulnerable2[pos] = NO_MOVE;
+      else
+        add_to_start_queue(pos, 1.0, conn);
     }
 }

 static int
-find_break_moves(int str, char goal[BOARDMAX], int color_to_move,
+find_break_moves(int str, const char goal[BOARDMAX], int color_to_move,
                 int moves[MAX_MOVES], float *total_distance)
 {
   struct connection_data conn1;
@@ -2602,6 +2617,10 @@ find_break_moves(int str, char goal[BOAR
   max_dist2 = conn2.distances[str];
   *total_distance = gg_min(max_dist1, max_dist2);

+  /* Turn the sgf traces back on. */
+  sgf_dumptree = save_sgf_dumptree;
+  count_variations = save_count_variations;
+
   if (verbose > 0) {
     gprintf("%oVariation %d\n", save_count_variations);
     dump_stack();
@@ -2610,10 +2629,6 @@ find_break_moves(int str, char goal[BOAR
     print_connection_distances(&conn2);
   }

-  /* Turn the sgf traces back on. */
-  sgf_dumptree = save_sgf_dumptree;
-  count_variations = save_count_variations;
-
   num_moves = find_connection_moves(str, str2, color_to_move,
                                    &conn1, &conn2, max_dist1, max_dist2,
                                    moves, *total_distance);
@@ -2639,7 +2654,7 @@ static int break_in_depth;

 /* Can (str) connect to goal[] if the other color moves first? */
 static int
-recursive_break(int str, char goal[BOARDMAX], int *move,
+recursive_break(int str, const char goal[BOARDMAX], int *move,
                int komaster, int kom_pos, int has_passed,
                Hash_data *goal_hash)
 {
@@ -2756,7 +2771,7 @@ recursive_break(int str, char goal[BOARD

 /* Can (str) connect to goal[] if the other color moves first? */
 static int
-recursive_block(int str, char goal[BOARDMAX], int *move,
+recursive_block(int str, const char goal[BOARDMAX], int *move,
                int komaster, int kom_pos, int has_passed,
                Hash_data *goal_hash)
 {
@@ -2884,7 +2899,7 @@ recursive_block(int str, char goal[BOARD
  * not contain stones), if he gets the first move.
  */
 int
-break_in(int str, char goal[BOARDMAX], int *move)
+break_in(int str, const char goal[BOARDMAX], int *move)
 {
   int dummy_move;
   int save_verbose;
@@ -2919,6 +2934,7 @@ break_in(int str, char goal[BOARDMAX], i
     gprintf("%obreak_in    %1M, result %d %1M (%d, %d nodes, %f seconds)\n",
            str, result, *move,
            nodes_connect, tactical_nodes, gg_cputime() - start);
+    goaldump(goal);
     dump_stack();
   }
   if (0) {
@@ -2936,7 +2952,7 @@ break_in(int str, char goal[BOARDMAX], i
  * not contain stones), if the other color moves first.
  */
 int
-block_off(int str, char goal[BOARDMAX], int *move)
+block_off(int str, const char goal[BOARDMAX], int *move)
 {
   int dummy_move;
   int result;
@@ -2967,18 +2983,18 @@ block_off(int str, char goal[BOARDMAX],
   verbose = save_verbose;
   tactical_nodes = get_reading_node_counter() - reading_nodes_when_called;

-#if 0
   if (0) {
-    gprintf("%odisconnect %1m %1m, result %d %1m (%d, %d nodes, %f seconds)\n",
-           str1, str2, result, *move,
+    gprintf("%oblock_off %1m, result %d %1m (%d, %d nodes, %f seconds)\n",
+           str, result, *move,
            nodes_connect, tactical_nodes, gg_cputime() - start);
+    goaldump(goal);
     dump_stack();
   }
   if (0) {
-    gprintf("%odisconnect %1m %1m %d %1m ", str1, str2, result, *move);
+    gprintf("%oblock_off %1m %d %1m ", str, result, *move);
+    goaldump(goal);
     dump_stack();
   }
-#endif

   return result;
 }
Index: engine/value_moves.c
===================================================================
RCS file: /cvsroot/gnugo/gnugo/engine/value_moves.c,v
retrieving revision 1.98
diff -u -p -r1.98 value_moves.c
--- engine/value_moves.c        4 Jun 2003 19:16:46 -0000       1.98
+++ engine/value_moves.c        8 Jun 2003 07:51:11 -0000
@@ -1926,6 +1926,8 @@ estimate_territorial_value(int pos, int
    */
   this_value = 0.0;

+  mark_inessential_stones(OTHER_COLOR(color), safe_stones);
+
   if (move[pos].move_safety == 1 && safe_move(pos, color) == WIN) {
     safe_stones[pos] = INFLUENCE_SAVED_STONE;
     strength[pos] = DEFAULT_STRENGTH;
Index: regression/owl1.tst
===================================================================
RCS file: /cvsroot/gnugo/gnugo/regression/owl1.tst,v
retrieving revision 1.66
diff -u -p -r1.66 owl1.tst
--- regression/owl1.tst 3 Jun 2003 12:56:21 -0000       1.66
+++ regression/owl1.tst 8 Jun 2003 07:51:11 -0000
@@ -233,6 +233,12 @@ loadsgf games/owl46.sgf
 317 owl_attack J4
 #? [1 (J7|J6|J8)]*

+# See also lazarus:10
+loadsgf games/nngs/Lazarus-gnugo-3.1.19-200201092246.sgf 258
+318 owl_does_defend F10 D10
+#? [0]
+
+
 ########### end of tests #####################

 # Report number of nodes visited by the tactical reading
Index: regression/trevora.tst
===================================================================
RCS file: /cvsroot/gnugo/gnugo/regression/trevora.tst,v
retrieving revision 1.30
diff -u -p -r1.30 trevora.tst
--- regression/trevora.tst      19 May 2003 16:00:20 -0000      1.30
+++ regression/trevora.tst      8 Jun 2003 07:51:11 -0000
@@ -328,10 +328,11 @@ loadsgf games/trevor/auto/a033.sgf 26

 # games/trevor/auto/a034.sgf problems:

-#Must connect groups.
-loadsgf games/trevor/auto/a034.sgf 12
-510 gg_genmove white
-#? [F4]*
+# Duplicate of trevora:150. /ab
+##Must connect groups.
+#loadsgf games/trevor/auto/a034.sgf 12
+#510 gg_genmove white
+##? [F4]*








reply via email to

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