[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [gnugo-devel] bug report
From: |
Paul Pogonyshev |
Subject: |
Re: [gnugo-devel] bug report |
Date: |
Thu, 8 Jan 2004 00:32:19 +0000 |
User-agent: |
KMail/1.5.94 |
I wrote:
> > I was playing a bot on KGS which claimed to be running
> > GNU Go v3.5.2, when in the endgame it made a fairly significant blunder.
> > On move 234 in the attached SGF, it prefers to save 3 stones, preventing
> > a 6 point loss, which results in a seki. If the 3 stones had been
> > sacrificed, there would have been no seki: it would have captured 9
> > stones for a local gain of 18 points, or a net gain of 12 points
> > including the sacrifice.
>
> GNU Go is not very good with sekis. Recently, some decent improvements
> have been made, but it this position it doesn't even see A19/B17. I'll
> try to see if i can fix this particular issue.
Here is a patch. Unfortunately, it fails two existing tests (very similar
to each others), but i consider the fails acceptable, because tests used to
pass for a wrong reason.
Here is the full list of changes (some were proposed by Gunnar):
- new function compute_dragon_genus(), can optionally ignore one selected eye
- find_moves_to_make_seki() disregards the eye containing the string in
question when calculating opponent's dragon genus
- find_moves_to_make_seki() tries all liberties of the string to prevent seki
- worms of a dragon, which is found to be non-dead by semeai code, are always
considered essential
- find_more_attack_and_defense_moves() and make_worms() no longer claim a new
attack if a move makes a worm non-attackable (by pushing it into seki)
Regression breakage is
seki:206 FAIL C9 [A1]
seki:811 FAIL C9 [A1]
seki:2010 pass (failed by CVS; new test by Dan Rosen)
The two failed tests used to pass for a wrong reason: GNU Go saw false attacks
on certain worms and, hence, strategic effects. It should have seen owl
defenses instead. These should be fixed by a separate patch.
I regressed the patch without Gunnar's latest patch, but then rediffed mine on
top of Gunnar's.
Paul
Index: engine/dragon.c
===================================================================
RCS file: /cvsroot/gnugo/gnugo/engine/dragon.c,v
retrieving revision 1.128
diff -u -p -r1.128 dragon.c
--- engine/dragon.c 7 Jan 2004 10:00:19 -0000 1.128
+++ engine/dragon.c 7 Jan 2004 22:13:21 -0000
@@ -100,7 +100,6 @@ void
make_dragons(int color, int stop_before_owl)
{
int str;
- int dr;
int d;
start_timer(2);
@@ -210,41 +209,54 @@ make_dragons(int color, int stop_before_
analyze_false_eye_territory();
/* Now we compute the genus. */
- for (str = BOARDMIN; str < BOARDMAX; str++) {
- if (!ON_BOARD(str))
- continue;
-
- if (black_eye[str].color == BLACK
- && black_eye[str].origin == str
- && find_eye_dragons(black_eye[str].origin, black_eye,
- BLACK, &dr, 1) == 1) {
-
- ASSERT1(board[dr] == BLACK, dr);
- TRACE("eye at %1m found for dragon at %1m--augmenting genus\n",
- str, dr);
- if (eye_move_urgency(&black_eye[str].value)
- > eye_move_urgency(&DRAGON2(dr).genus))
- DRAGON2(dr).heye = black_eye[str].defense_point;
- add_eyevalues(&DRAGON2(dr).genus, &black_eye[str].value,
- &DRAGON2(dr).genus);
- }
-
- if (white_eye[str].color == WHITE
- && white_eye[str].origin == str
- && find_eye_dragons(white_eye[str].origin, white_eye,
- WHITE, &dr, 1) == 1) {
-
- ASSERT1(board[dr] == WHITE, dr);
- TRACE("eye at %1m found for dragon at %1m--augmenting genus\n",
- str, dr);
- if (eye_move_urgency(&white_eye[str].value)
- > eye_move_urgency(&DRAGON2(dr).genus))
- DRAGON2(dr).heye = white_eye[str].defense_point;
- add_eyevalues(&DRAGON2(dr).genus, &white_eye[str].value,
- &DRAGON2(dr).genus);
+#if 1
+
+ for (d = 0; d < number_of_dragons; d++)
+ compute_dragon_genus(dragon2[d].origin, &dragon2[d].genus, NO_MOVE);
+
+#else
+
+ {
+ int dr;
+
+ for (str = BOARDMIN; str < BOARDMAX; str++) {
+ if (!ON_BOARD(str))
+ continue;
+
+ if (black_eye[str].color == BLACK
+ && black_eye[str].origin == str
+ && find_eye_dragons(black_eye[str].origin, black_eye,
+ BLACK, &dr, 1) == 1) {
+
+ ASSERT1(board[dr] == BLACK, dr);
+ TRACE("eye at %1m found for dragon at %1m--augmenting genus\n",
+ str, dr);
+ if (eye_move_urgency(&black_eye[str].value)
+ > eye_move_urgency(&DRAGON2(dr).genus))
+ DRAGON2(dr).heye = black_eye[str].defense_point;
+ add_eyevalues(&DRAGON2(dr).genus, &black_eye[str].value,
+ &DRAGON2(dr).genus);
+ }
+
+ if (white_eye[str].color == WHITE
+ && white_eye[str].origin == str
+ && find_eye_dragons(white_eye[str].origin, white_eye,
+ WHITE, &dr, 1) == 1) {
+
+ ASSERT1(board[dr] == WHITE, dr);
+ TRACE("eye at %1m found for dragon at %1m--augmenting genus\n",
+ str, dr);
+ if (eye_move_urgency(&white_eye[str].value)
+ > eye_move_urgency(&DRAGON2(dr).genus))
+ DRAGON2(dr).heye = white_eye[str].defense_point;
+ add_eyevalues(&DRAGON2(dr).genus, &white_eye[str].value,
+ &DRAGON2(dr).genus);
+ }
}
}
+#endif
+
/* Compute the escape route measure. */
for (str = BOARDMIN; str < BOARDMAX; str++)
if (IS_STONE(board[str]) && dragon[str].origin == str)
@@ -1228,6 +1240,69 @@ compute_dragon_influence()
compute_influence(WHITE, safe_stones, strength, &initial_white_influence,
NO_MOVE, "initial white influence, dragons known");
break_territories(WHITE, &initial_white_influence, 1);
+}
+
+
+/* Compute dragon's genus, possibly excluding one given eye. To
+ * compute full genus, just set `eye_to_exclude' to NO_MOVE.
+ */
+void
+compute_dragon_genus(int d, struct eyevalue *genus, int eye_to_exclude)
+{
+ int pos;
+ int dr;
+
+ ASSERT1(IS_STONE(board[d]), d);
+ gg_assert(eye_to_exclude == NO_MOVE || ON_BOARD1(eye_to_exclude));
+
+ set_eyevalue(genus, 0, 0, 0, 0);
+
+ if (board[d] == BLACK) {
+ for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
+ if (!ON_BOARD(pos))
+ continue;
+
+ if (black_eye[pos].color == BLACK
+ && black_eye[pos].origin == pos
+ && (eye_to_exclude == NO_MOVE
+ || black_eye[eye_to_exclude].origin != pos)
+ && find_eye_dragons(pos, black_eye, BLACK, &dr, 1) == 1
+ && is_same_dragon(dr, d)) {
+ TRACE("eye at %1m (%s) found for dragon at %1m--augmenting genus\n",
+ pos, eyevalue_to_string(&black_eye[pos].value), dr);
+
+ if (eye_to_exclude == NO_MOVE
+ && (eye_move_urgency(&black_eye[pos].value)
+ > eye_move_urgency(genus)))
+ DRAGON2(d).heye = black_eye[pos].defense_point;
+
+ add_eyevalues(genus, &black_eye[pos].value, genus);
+ }
+ }
+ }
+ else {
+ for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
+ if (!ON_BOARD(pos))
+ continue;
+
+ if (white_eye[pos].color == WHITE
+ && white_eye[pos].origin == pos
+ && (eye_to_exclude == NO_MOVE
+ || white_eye[eye_to_exclude].origin != pos)
+ && find_eye_dragons(pos, white_eye, WHITE, &dr, 1) == 1
+ && is_same_dragon(dr, d)) {
+ TRACE("eye at %1m (%s) found for dragon at %1m--augmenting genus\n",
+ pos, eyevalue_to_string(&white_eye[pos].value), dr);
+
+ if (eye_to_exclude == NO_MOVE
+ && (eye_move_urgency(&white_eye[pos].value)
+ > eye_move_urgency(genus)))
+ DRAGON2(d).heye = white_eye[pos].defense_point;
+
+ add_eyevalues(genus, &white_eye[pos].value, genus);
+ }
+ }
+ }
}
Index: engine/liberty.h
===================================================================
RCS file: /cvsroot/gnugo/gnugo/engine/liberty.h,v
retrieving revision 1.205
diff -u -p -r1.205 liberty.h
--- engine/liberty.h 18 Nov 2003 08:55:46 -0000 1.205
+++ engine/liberty.h 7 Jan 2004 22:13:31 -0000
@@ -290,9 +290,12 @@ int does_secure(int color, int move, int
void join_dragons(int d1, int d2);
int dragon_escape(char goal[BOARDMAX], int color, char escape_value[BOARDMAX]);
void compute_refined_dragon_weaknesses(void);
+
struct eyevalue;
+void compute_dragon_genus(int d, struct eyevalue *genus, int eye_to_exclude);
float crude_dragon_weakness(int safety, struct eyevalue *genus, int has_lunch,
float moyo_value, float escape_route);
+
int is_same_dragon(int d1, int d2);
int are_neighbor_dragons(int d1, int d2);
void mark_dragon(int pos, char mx[BOARDMAX], char mark);
Index: engine/semeai.c
===================================================================
RCS file: /cvsroot/gnugo/gnugo/engine/semeai.c,v
retrieving revision 1.66
diff -u -p -r1.66 semeai.c
--- engine/semeai.c 7 Jan 2004 10:00:20 -0000 1.66
+++ engine/semeai.c 7 Jan 2004 22:13:31 -0000
@@ -246,7 +246,8 @@ find_moves_to_make_seki()
int color = board[str];
int opponent = NO_MOVE;
int certain;
-
+ struct eyevalue reduced_genus;
+
for (k = 0; k < DRAGON2(str).neighbors; k++) {
opponent = dragon2[DRAGON2(str).adjacent[k]].origin;
if (board[opponent] != color)
@@ -257,15 +258,14 @@ find_moves_to_make_seki()
if (dragon[opponent].status != ALIVE)
continue;
-
- /* FIXME: These heuristics are not very precise. What we really
- * want to check is whether str is inside the eyespace of the
- * opponent, for an interpretation of eyespace that includes
- * lunches, while it doesn't already have more than one eye
- * elsewhere.
+
+ /* FIXME: These heuristics are used for optimization. We don't
+ * want to call expensive semeai code if the opponent
+ * dragon has more than one eye elsewhere. However, the
+ * heuristics might still need improvement.
*/
- if (DRAGON2(opponent).moyo_size > 10
- || min_eyes(&DRAGON2(opponent).genus) > 1)
+ compute_dragon_genus(opponent, &reduced_genus, str);
+ if (DRAGON2(opponent).moyo_size > 10 || min_eyes(&reduced_genus) > 1)
continue;
owl_analyze_semeai_after_move(defend_move, color, opponent, str,
@@ -283,10 +283,39 @@ find_moves_to_make_seki()
update_status(str, CRITICAL, CRITICAL);
dragon2[d].semeai_defense_point = defend_move;
dragon2[d].semeai_defense_certain = certain;
- /* FIXME: The assumption that the attack move coincides with
- * the defense move is somewhat optimistic.
+
+ /* We need to determine a proper attack move (the one that
+ * prevents seki). Currently we try the defense move first,
+ * and if it doesn't work -- all liberties of the string.
*/
- dragon2[d].semeai_attack_point = defend_move;
+ owl_analyze_semeai_after_move(defend_move, OTHER_COLOR(color),
+ str, opponent, &resulta, NULL,
+ NULL, 1, NULL);
+ if (resulta != WIN)
+ dragon2[d].semeai_attack_point = defend_move;
+ else {
+ int k;
+ int libs[MAXLIBS];
+ int liberties = findlib(str, MAXLIBS, libs);
+
+ for (k = 0; k < liberties; k++) {
+ owl_analyze_semeai_after_move(libs[k], OTHER_COLOR(color),
+ str, opponent, &resulta, NULL,
+ NULL, 1, NULL);
+ if (resulta != WIN) {
+ dragon2[d].semeai_attack_point = libs[k];
+ break;
+ }
+ }
+
+ /* FIXME: What should we do if none of the tried attacks worked? */
+ if (k == liberties)
+ dragon2[d].semeai_attack_point = defend_move;
+ }
+
+ DEBUG(DEBUG_SEMEAI, "Move to prevent seki at %1m (%1m vs %1m)\n",
+ dragon2[d].semeai_attack_point, opponent, str);
+
dragon2[d].semeai_attack_certain = certain;
dragon2[d].semeai_target = opponent;
}
@@ -435,8 +464,10 @@ semeai_move_reasons(int color)
}
-/* Change the status and safety of a dragon */
-
+/* Change the status and safety of a dragon. In addition, if the new
+ * status is not DEAD, make all worms of the dragon essential, so that
+ * results found by semeai code don't get ignored.
+ */
static void
update_status(int dr, enum dragon_status new_status,
enum dragon_status new_safety)
@@ -449,8 +480,11 @@ update_status(int dr, enum dragon_status
status_to_string(dragon[dr].status),
status_to_string(new_status));
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
- if (IS_STONE(board[pos]) && is_same_dragon(dr, pos))
+ if (IS_STONE(board[pos]) && is_same_dragon(dr, pos)) {
dragon[pos].status = new_status;
+ if (new_status != DEAD)
+ worm[pos].inessential = 0;
+ }
}
if (DRAGON2(dr).safety != new_safety
Index: engine/value_moves.c
===================================================================
RCS file: /cvsroot/gnugo/gnugo/engine/value_moves.c,v
retrieving revision 1.115
diff -u -p -r1.115 value_moves.c
--- engine/value_moves.c 7 Jan 2004 10:00:20 -0000 1.115
+++ engine/value_moves.c 7 Jan 2004 22:14:02 -0000
@@ -232,19 +232,27 @@ find_more_attack_and_defense_moves(int c
if (dcode < worm[aa].defense_codes[0]) {
/* Maybe find_defense() doesn't find the defense. Try to
* defend with the stored defense move.
+ *
+ * Another option is maybe there is no attack anymore
+ * (e.g. we pushed the worm into seki), find_defense()
+ * could easily fail in that case.
*/
int attack_works = 1;
-
- if (trymove(worm[aa].defense_points[0], other,
- "find_more_attack_and_defense_moves", 0, EMPTY, 0)) {
- int this_dcode = REVERSE_RESULT(attack(aa, NULL));
- if (this_dcode > dcode) {
- dcode = this_dcode;
- if (dcode >= worm[aa].defense_codes[0])
- attack_works = 0;
+
+ if (attack(aa, NULL) >= worm[aa].attack_codes[0]) {
+ if (trymove(worm[aa].defense_points[0], other,
+ "find_more_attack_and_defense_moves", 0, EMPTY, 0)) {
+ int this_dcode = REVERSE_RESULT(attack(aa, NULL));
+ if (this_dcode > dcode) {
+ dcode = this_dcode;
+ if (dcode >= worm[aa].defense_codes[0])
+ attack_works = 0;
+ }
+ popgo();
}
- popgo();
}
+ else
+ attack_works = 0;
if (attack_works) {
if (!cursor_at_start_of_line)
Index: engine/worm.c
===================================================================
RCS file: /cvsroot/gnugo/gnugo/engine/worm.c,v
retrieving revision 1.58
diff -u -p -r1.58 worm.c
--- engine/worm.c 24 Aug 2003 03:04:11 -0000 1.58
+++ engine/worm.c 7 Jan 2004 22:14:07 -0000
@@ -301,21 +301,30 @@ make_worms(void)
int dcode = find_defense(str, NULL);
if (dcode < worm[str].defense_codes[0]) {
int attack_works = 1;
+
/* Sometimes find_defense() fails to find a
* defense which has been found by other means.
* Try if the old defense move still works.
+ *
+ * However, we first check if the _attack_ still exists,
+ * because we could, for instance, drive the worm into
+ * seki with our move.
*/
- if (worm[str].defense_codes[0] != 0
- && trymove(worm[str].defense_points[0],
- OTHER_COLOR(color), "make_worms", 0, EMPTY, 0)) {
- int this_dcode = REVERSE_RESULT(attack(str, NULL));
- if (this_dcode > dcode) {
- dcode = this_dcode;
- if (dcode >= worm[str].defense_codes[0])
- attack_works = 0;
+ if (attack(str, NULL) >= worm[str].attack_codes[0]) {
+ if (worm[str].defense_codes[0] != 0
+ && trymove(worm[str].defense_points[0],
+ OTHER_COLOR(color), "make_worms", 0, EMPTY, 0)) {
+ int this_dcode = REVERSE_RESULT(attack(str, NULL));
+ if (this_dcode > dcode) {
+ dcode = this_dcode;
+ if (dcode >= worm[str].defense_codes[0])
+ attack_works = 0;
+ }
+ popgo();
}
- popgo();
}
+ else
+ attack_works = 0;
/* ...then add an attack point of that worm at pos. */
if (attack_works) {
Index: regression/seki.tst
===================================================================
RCS file: /cvsroot/gnugo/gnugo/regression/seki.tst,v
retrieving revision 1.4
diff -u -p -r1.4 seki.tst
--- regression/seki.tst 31 Dec 2003 07:05:11 -0000 1.4
+++ regression/seki.tst 7 Jan 2004 22:14:17 -0000
@@ -498,3 +498,7 @@ play black C2
1106 reg_genmove white
#? [C1]*
+
+loadsgf games/FSGCBot-dr.sgf 234
+2010 reg_genmove white
+#? [A19|B17]
Index: regression/games/FSGCBot-dr.sgf
===================================================================
RCS file: regression/games/FSGCBot-dr.sgf
diff -N regression/games/FSGCBot-dr.sgf
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ regression/games/FSGCBot-dr.sgf 7 Jan 2004 22:18:02 -0000
@@ -0,0 +1,246 @@
+(;GM[1]FF[4]CA[UTF-8]AP[CGoban:2]ST[2]
+RU[Japanese]SZ[19]KM[5.50]TM[1500]OT[5x30 byo-yomi]
+PW[FSGCBot]PB[dr]BR[13k]DT[2004-01-06]PC[The Kiseido Go Server (KGS) at
http://kgs.kiseido.com/]C[FSGCBot [?\]: GTP Engine for FSGCBot (white): GNU Go
version 3.5.2
+]RE[B+13.50]
+;B[pd]BL[1645.506]CR[pd]
+;W[dp]WL[1649.882]CR[dp]
+;B[pp]BL[1641.677]CR[pp]
+;W[dd]WL[1649.661]CR[dd]
+;B[pj]BL[1638.671]CR[pj]
+;W[nq]WL[1649.258]CR[nq]
+;B[qn]BL[1636]CR[qn]
+;W[pr]WL[1648.844]CR[pr]
+;B[qq]BL[1635.03]CR[qq]
+;W[kq]WL[1648.214]CR[kq]PL[B]
+;B[fc]BL[1623.422]CR[fc]
+;W[nd]WL[1646.74]CR[nd]
+;B[pf]BL[1620.08]CR[pf]
+;W[pc]WL[1645.91]CR[pc]
+;B[qc]BL[1619.068]CR[qc]
+;W[oc]WL[1644.088]CR[oc]
+;B[qd]BL[1607.735]CR[qd]
+;W[fd]WL[1643.499]CR[fd]
+;B[gd]BL[1605.076]CR[gd]
+;W[fe]WL[1642.379]CR[fe]
+;B[dc]BL[1591.304]CR[dc]
+;W[cc]WL[1640.516]CR[cc]
+;B[ec]BL[1586.362]CR[ec]
+;W[cd]WL[1638.323]CR[cd]
+;B[jc]BL[1574.654]CR[jc]
+;W[ch]WL[1637.412]CR[ch]
+;B[cn]BL[1570.625]CR[cn]
+;W[fq]WL[1636.369]CR[fq]
+;B[bp]BL[1568.468]CR[bp]
+;W[cq]WL[1635.337]CR[cq]
+;B[ck]BL[1565.377]CR[ck]
+;W[bq]WL[1634.245]CR[bq]
+;B[bo]BL[1558.647]CR[bo]
+;W[en]WL[1633.111]CR[en]
+;B[mc]BL[1545.713]CR[mc]
+;W[no]WL[1629.854]CR[no]
+;B[om]BL[1542.51]CR[om]
+;W[mm]WL[1628.439]CR[mm]
+;B[le]BL[1519.841]CR[le]
+;W[nf]WL[1627.113]CR[nf]
+;B[ne]BL[1518.56]CR[ne]
+;W[nh]WL[1624.338]CR[nh]
+;B[oe]BL[1500.82]CR[oe]
+;W[kg]WL[1621.179]CR[kg]
+;B[ph]BL[1493.956]CR[ph]
+;W[fk]WL[1618.879]CR[fk]
+;B[ej]BL[1467.929]CR[ej]
+;W[mk]WL[1614.475]CR[mk]
+;B[eh]BL[1450.242]CR[eh]
+;W[gh]WL[1610.9]CR[gh]
+;B[gf]BL[1436.223]CR[gf]
+;W[ig]WL[1607.516]CR[ig]
+;B[cf]BL[1413.312]CR[cf]
+;W[dg]WL[1599.691]CR[dg]
+;B[bi]BL[1400.82]CR[bi]
+;W[bh]WL[1593.575]CR[bh]
+;B[dh]BL[1392.803]CR[dh]
+;W[df]WL[1579.512]CR[df]
+;B[ci]BL[1379.183]CR[ci]
+;W[ah]WL[1572.823]CR[ah]
+;B[ai]BL[1373.727]CR[ai]
+;W[ce]WL[1568.14]CR[ce]
+;B[gi]BL[1343.127]CR[gi]
+;W[hj]WL[1563.582]CR[hj]
+;B[hi]BL[1340.581]CR[hi]
+;W[ii]WL[1559.688]CR[ii]
+;B[gj]BL[1337.164]CR[gj]
+;W[gk]WL[1555.366]CR[gk]
+;B[ij]BL[1327.406]CR[ij]
+;W[hk]WL[1550.405]CR[hk]
+;B[mg]BL[1300.758]CR[mg]
+;W[mf]WL[1544.867]CR[mf]
+;B[ng]BL[1298.208]CR[ng]
+;W[lg]WL[1535.834]CR[lg]
+;B[mh]BL[1279.759]CR[mh]
+;W[ji]WL[1528.644]CR[ji]
+;B[lf]BL[1273.89]CR[lf]
+;W[ge]WL[1523.104]CR[ge]
+;B[hd]BL[1264.843]CR[hd]
+;W[fg]WL[1520.567]CR[fg]
+;B[hf]BL[1245.826]CR[hf]
+;W[he]WL[1518.379]CR[he]
+;B[if]BL[1211.964]CR[if]
+;W[ie]WL[1515.554]CR[ie]
+;B[jf]BL[1205.669]CR[jf]
+;W[jg]WL[1512.432]CR[jg]
+;B[id]BL[1193.1]CR[id]
+;W[qr]WL[1509.277]CR[qr]
+;B[rr]BL[1191.161]CR[rr]
+;W[ni]WL[1506.255]CR[ni]
+;B[mi]BL[1172.48]CR[mi]
+;W[mj]WL[1502.416]CR[mj]
+;B[nj]BL[1143.269]CR[nj]
+;W[cb]WL[1498.427]CR[cb]
+;B[db]BL[1141.299]CR[db]
+;W[dl]WL[1495.968]CR[dl]
+;B[cl]BL[1138.949]CR[cl]
+;W[nk]WL[1492.944]CR[nk]
+;B[oj]BL[1135.68]CR[oj]
+;W[je]WL[1491.101]CR[je]
+;B[kf]BL[1132.007]CR[kf]
+;W[kd]WL[1488.26]CR[kd]
+;B[kc]BL[1115.489]CR[kc]
+;W[rs]WL[1484.564]CR[rs]
+;B[rq]BL[1108.369]CR[rq]
+;W[ap]WL[1482.446]CR[ap]
+;B[ao]BL[1106.245]CR[ao]
+;W[aq]WL[1480.453]CR[aq]
+;B[el]BL[1072.353]CR[el]
+;W[gm]WL[1478.912]CR[gm]
+;B[ca]BL[1028.171]CR[ca]
+;W[dm]WL[1477.564]CR[dm]
+;B[ek]BL[1015.285]CR[ek]
+;W[ba]WL[1476.088]CR[ba]
+;B[da]BL[1013.669]CR[da]
+;W[ok]WL[1474.024]CR[ok]
+;B[pk]BL[1004.646]CR[pk]
+;W[ld]WL[1472.134]CR[ld]
+;B[md]BL[998.826]CR[md]
+;W[bb]WL[1469.858]CR[bb]
+;B[jk]BL[963.552]CR[jk]
+;W[hh]WL[1468.57]CR[hh]
+;B[fh]BL[940.652]CR[fh]
+;W[op]WL[1467.028]CR[op]
+;B[gg]BL[931.154]CR[gg]
+;W[pq]WL[1465.406]CR[pq]
+;B[po]BL[926.618]CR[po]
+;W[lc]WL[1463.442]CR[lc]
+;B[lb]BL[921.9]CR[lb]
+;W[cm]WL[1460.436]CR[cm]
+;B[bm]BL[919.422]CR[bm]
+;W[on]WL[1458.651]CR[on]
+;B[ih]BL[890.776]CR[ih]
+;W[hg]WL[1455.611]CR[hg]
+;B[ki]BL[874.944]CR[ki]
+;W[jj]WL[1451.981]CR[jj]
+;B[ik]BL[865.067]CR[ik]
+;W[kj]WL[1448.094]CR[kj]
+;B[lj]BL[829.7]CR[lj]
+;W[lk]WL[1443.867]CR[lk]
+;B[li]BL[820.685]CR[li]
+;W[kk]WL[1439.235]CR[kk]
+;B[jm]BL[789.7]CR[jm]
+;W[pm]WL[1436.538]CR[pm]
+;B[pn]BL[780.657]CR[pn]
+;W[pl]WL[1434.788]CR[pl]
+;B[ql]BL[777.372]CR[ql]
+;W[ol]WL[1433.343]CR[ol]
+;B[qm]BL[770.988]CR[qm]
+;W[oo]WL[1431.996]CR[oo]
+;B[gl]BL[751.847]CR[gl]
+;W[hl]WL[1430.237]CR[hl]
+;B[fl]BL[742.006]CR[fl]
+;W[jl]WL[1425.857]CR[jl]
+;B[ff]BL[714.285]CR[ff]
+;W[eg]WL[1422.476]CR[eg]
+;B[ed]BL[709.275]CR[ed]
+;W[ee]WL[1419.41]CR[ee]
+;B[ef]BL[693.738]CR[ef]
+;W[cg]WL[1415.954]CR[cg]
+;B[jd]BL[682.202]CR[jd]
+;W[fj]WL[1412.697]CR[fj]
+;B[fi]BL[678.37]CR[fi]
+;W[im]WL[1410.065]CR[im]
+;B[bf]BL[655.421]CR[bf]
+;W[dk]WL[1405.372]CR[dk]
+;B[dj]BL[649.313]CR[dj]
+;W[kh]WL[1403.315]CR[kh]
+;B[of]BL[630.717]CR[of]
+;W[do]WL[1401.317]CR[do]
+;B[dn]BL[623.322]CR[dn]
+;W[em]WL[1399.855]CR[em]
+;B[fm]BL[616.498]CR[fm]
+;W[fn]WL[1398.501]CR[fn]
+;B[co]BL[524.22]CR[co]
+;W[sr]WL[1397.731]CR[sr]
+;B[sq]BL[521.841]CR[sq]
+;W[qs]WL[1397.104]CR[qs]
+;B[ss]BL[506.026]CR[ss]
+;W[cp]WL[1396.257]CR[cp]
+;B[bl]BL[494.456]CR[bl]
+;W[sr]WL[1395.787]CR[sr]
+;B[gn]BL[472.701]CR[gn]
+;W[hm]WL[1393.223]CR[hm]
+;B[ss]BL[470.589]CR[ss]
+;W[gc]WL[1392.209]CR[gc]
+;B[gb]BL[443.845]CR[gb]
+;W[sr]WL[1391.66]CR[sr]
+;B[kl]BL[425.405]CR[kl]
+;W[jn]WL[1390.055]CR[jn]
+;B[ss]BL[414.688]CR[ss]
+;W[og]WL[1389.039]CR[og]
+;B[me]BL[406.214]CR[me]
+;W[sr]WL[1387.957]CR[sr]
+;B[il]BL[396.522]CR[il]
+;W[jl]WL[1384.896]CR[jl]
+;B[ss]BL[395.251]CR[ss]
+;W[di]WL[1384.112]CR[di]
+;B[ei]BL[386.89]CR[ei]
+;W[sr]WL[1383.494]CR[sr]
+;B[eo]BL[376.775]CR[eo]
+;W[fo]WL[1381.954]CR[fo]
+;B[ss]BL[375.272]CR[ss]
+;W[cj]WL[1381.135]CR[cj]
+;B[bj]BL[372.36]CR[bj]
+;W[sr]WL[1380.536]CR[sr]
+;B[fp]BL[362.495]CR[fp]
+;W[ep]WL[1378.953]CR[ep]
+;B[ss]BL[360.762]CR[ss]
+;W[bn]WL[1378.292]CR[bn]
+;B[an]BL[358.601]CR[an]
+;W[sr]WL[1377.741]CR[sr]
+;B[go]BL[357.218]CR[go]
+;W[gp]WL[1376.762]CR[gp]
+;B[ss]BL[355.389]CR[ss]
+;W[am]WL[1376.098]CR[am]
+;B[al]BL[353.459]CR[al]
+;W[sr]WL[1375.579]CR[sr]
+;B[ad]BL[333.896]CR[ad]
+;W[af]WL[1374.827]CR[af]
+;B[ss]BL[332.016]CR[ss]
+;W[jh]WL[1374.199]CR[jh]
+;B[ab]BL[302.94]CR[ab]
+;W[ae]WL[1372.057]CR[ae]
+;B[be]BL[294.707]CR[be]
+;W[bd]WL[1370.284]CR[bd]
+;B[ag]BL[288.836]CR[ag]
+;W[ae]WL[1368.412]CR[ae]
+;B[af]BL[258.075]CR[af]
+;W[de]WL[1367.78]CR[de]
+;B[ac]BL[233.94]CR[ac]
+;W[bg]WL[1367.174]CR[bg]
+;B[ae]BL[231.943]CR[ae]
+(;W[ke]WL[1366.452]CR[ke]
+;B[sr]BL[198.784]CR[sr]
+;W[lh]WL[1365.791]CR[lh]C[Split [-\]: retarded robot....a19 not L15....
+]
+;B[]BL[181.323]
+;W[]WL[1365.791]TW[ih][ij][ik][jk][il][kl][ll][ml][nl][jm][km][lm][nm][om][gn][hn][in][kn][ln][mn][nn][eo][go][ho][io][jo][ko][lo][mo][fp][hp][ip][jp][kp][lp][mp][np][dq][eq][gq][hq][iq][jq][lq][mq][oq][ar][br][cr][dr][er][fr][gr][hr][ir][jr][kr][lr][mr][nr][or][as][bs][cs][ds][es][fs][gs][hs][is][js][ks][ls][ms][ns][os][ps]TB[ea][fa][ga][ha][ia][ja][ka][la][ma][na][oa][pa][qa][ra][sa][eb][fb][hb][ib][jb][kb][mb][nb][ob][pb][qb][rb][sb][gc][hc][ic][nc][oc][pc][rc][sc][nd][od][rd][sd][pe][qe][re][se][mf][nf][qf][rf][sf][og][pg][qg][rg][sg][nh][oh][qh][rh][sh][di][ni][oi][pi][qi][ri][si][aj][cj][qj][rj][sj][ak][bk][qk][rk][sk][rl][sl][am][rm][sm][bn][rn][sn][qo][ro][so][qp][rp][sp])
+(;W[aa]CR[aa]
+;B[ke]CR[ke]
+;W[bc]CR[bc]))