diff --git a/board.c b/board.c index 267a59e3b..ae95d66ca 100644 --- a/board.c +++ b/board.c @@ -10,6 +10,7 @@ #include "fbook.h" #include "mq.h" #include "random.h" +#include "ownermap.h" #ifdef BOARD_PAT3 #include "pattern3.h" @@ -1494,17 +1495,17 @@ board_play_random(struct board *b, enum stone color, coord_t *coord, ppr_permit } +/* XXX: We attempt false eye detection but we will yield false + * positives in case of http://senseis.xmp.net/?TwoHeadedDragon :-( */ bool board_is_false_eyelike(struct board *board, coord_t coord, enum stone eye_color) { enum stone color_diag_libs[S_MAX] = {0, 0, 0, 0}; - /* XXX: We attempt false eye detection but we will yield false - * positives in case of http://senseis.xmp.net/?TwoHeadedDragon :-( */ - foreach_diag_neighbor(board, coord) { color_diag_libs[(enum stone) board_at(board, c)]++; } foreach_diag_neighbor_end; + /* For false eye, we need two enemy stones diagonally in the * middle of the board, or just one enemy stone at the edge * or in the corner. */ @@ -1513,21 +1514,18 @@ board_is_false_eyelike(struct board *board, coord_t coord, enum stone eye_color) } bool -board_is_one_point_eye(struct board *board, coord_t coord, enum stone eye_color) +board_is_one_point_eye(struct board *b, coord_t c, enum stone eye_color) { - return board_is_eyelike(board, coord, eye_color) - && !board_is_false_eyelike(board, coord, eye_color); + return (board_is_eyelike(b, c, eye_color) && + !board_is_false_eyelike(b, c, eye_color)); } enum stone -board_get_one_point_eye(struct board *board, coord_t coord) +board_eye_color(struct board *b, coord_t c) { - if (board_is_one_point_eye(board, coord, S_WHITE)) - return S_WHITE; - else if (board_is_one_point_eye(board, coord, S_BLACK)) - return S_BLACK; - else - return S_NONE; + if (board_is_eyelike(b, c, S_WHITE)) return S_WHITE; + if (board_is_eyelike(b, c, S_BLACK)) return S_BLACK; + return S_NONE; } floating_t @@ -1538,7 +1536,7 @@ board_fast_score(struct board *board) foreach_point(board) { enum stone color = board_at(board, c); if (color == S_NONE && board->rules != RULES_STONES_ONLY) - color = board_get_one_point_eye(board, c); + color = board_eye_color(board, c); scores[color]++; // fprintf(stderr, "%d, %d ++%d = %d\n", coord_x(c, board), coord_y(c, board), color, scores[color]); } foreach_point_end; @@ -1622,8 +1620,12 @@ board_score(struct board *b, int scores[S_MAX]) int handi_comp = board_score_handicap_compensation(b); floating_t score = b->komi + handi_comp + scores[S_WHITE] - scores[S_BLACK]; - /* Aja's formula for converting playouts area scoring to territory. - * http://computer-go.org/pipermail/computer-go/2010-April/000209.html */ + /* Aja's formula for converting area scoring to territory: + * http://computer-go.org/pipermail/computer-go/2010-April/000209.html + * Under normal circumstances there's a relationship between area + * and territory scoring so we can derive one from the other. If + * the board has been artificially edited however the relationship + * is broken and japanese score will be off. */ if (b->rules == RULES_JAPANESE) score += (b->last_move.color == S_BLACK) + (b->passes[S_WHITE] - b->passes[S_BLACK]); return score; @@ -1644,9 +1646,11 @@ board_print_official_ownermap(struct board *b, int *final_ownermap) } /* Official score after removing dead groups and Tromp-Taylor counting. - * Number of dames is saved in @dames, final ownermap in @ownermap. */ + * Returns number of dames, sekis, final ownermap in @dame, @seki, @ownermap. + * (only distinguishes between dames/sekis if @po is not NULL) */ floating_t -board_official_score_details(struct board *board, struct move_queue *dead, int *dames, int *ownermap) +board_official_score_details(struct board *b, struct move_queue *dead, + int *dame, int *seki, int *ownermap, struct ownermap *po) { /* A point P, not colored C, is said to reach C, if there is a path of * (vertically or horizontally) adjacent points of P's color from P to @@ -1657,16 +1661,16 @@ board_official_score_details(struct board *board, struct move_queue *dead, int * int s[4] = {0}; const int o[4] = {0, 1, 2, 0}; - foreach_point(board) { - ownermap[c] = o[board_at(board, c)]; - s[board_at(board, c)]++; + foreach_point(b) { + ownermap[c] = o[board_at(b, c)]; + s[board_at(b, c)]++; } foreach_point_end; if (dead) { /* Process dead groups. */ for (unsigned int i = 0; i < dead->moves; i++) { - foreach_in_group(board, dead->move[i]) { - enum stone color = board_at(board, c); + foreach_in_group(b, dead->move[i]) { + enum stone color = board_at(b, c); ownermap[c] = o[stone_other(color)]; s[color]--; s[stone_other(color)]++; } foreach_in_group_end; @@ -1675,28 +1679,43 @@ board_official_score_details(struct board *board, struct move_queue *dead, int * /* We need to special-case empty board. */ if (!s[S_BLACK] && !s[S_WHITE]) - return board->komi; + return b->komi; - while (board_tromp_taylor_iter(board, ownermap)) + while (board_tromp_taylor_iter(b, ownermap)) /* Flood-fill... */; int scores[S_MAX] = { 0, }; - foreach_point(board) { - assert(board_at(board, c) == S_OFFBOARD || ownermap[c] != 0); + foreach_point(b) { + assert(board_at(b, c) == S_OFFBOARD || ownermap[c] != 0); scores[ownermap[c]]++; } foreach_point_end; - *dames = scores[3]; + *dame = scores[3]; + *seki = 0; + + if (po) { + foreach_point(b) { + if (ownermap_judge_point(po, c, GJ_THRES) != PJ_SEKI) continue; + (*seki)++; (*dame)--; + } foreach_point_end; + } - return board_score(board, scores); + return board_score(b, scores); } floating_t board_official_score(struct board *b, struct move_queue *dead) { - int dame; + int dame, seki; int ownermap[board_size2(b)]; - return board_official_score_details(b, dead, &dame, ownermap); + return board_official_score_details(b, dead, &dame, &seki, ownermap, NULL); +} + +floating_t +board_official_score_color(struct board *b, struct move_queue *dead, enum stone color) +{ + floating_t score = board_official_score(b, dead); + return (color == S_WHITE ? score : -score); } bool diff --git a/board.h b/board.h index 16902c459..5d3beda05 100644 --- a/board.h +++ b/board.h @@ -15,7 +15,7 @@ #include "mq.h" struct fbook; - +struct ownermap; /* Maximum supported board size. (Without the S_OFFBOARD edges.) */ #define BOARD_MAX_SIZE 19 @@ -382,10 +382,6 @@ static bool board_is_valid_play(struct board *b, enum stone color, coord_t coord static bool board_is_valid_move(struct board *b, struct move *m); /* Returns true if ko was just taken. */ static bool board_playing_ko_threat(struct board *b); -/* Returns 0 or ID of neighboring group in atari. */ -static group_t board_get_atari_neighbor(struct board *b, coord_t coord, enum stone group_color); -/* Get all neighboring groups in atari */ -static void board_get_atari_neighbors(struct board *b, coord_t coord, enum stone group_color, struct move_queue *q); /* Returns true if the move is not obvious self-atari. */ static bool board_safe_to_play(struct board *b, coord_t coord, enum stone color); @@ -408,8 +404,8 @@ bool board_is_false_eyelike(struct board *board, coord_t coord, enum stone eye_c /* Returns true if given coordinate is a 1-pt eye (checks against false eyes, or * at least tries to). */ bool board_is_one_point_eye(struct board *board, coord_t c, enum stone eye_color); -/* Returns color of a 1pt eye owner, S_NONE if not an eye. */ -enum stone board_get_one_point_eye(struct board *board, coord_t c); +/* Returns 1pt eye color (can be false-eye) */ +enum stone board_eye_color(struct board *board, coord_t c); /* board_official_score() is the scoring method for yielding score suitable * for external presentation. For fast scoring of entirely filled boards @@ -421,7 +417,8 @@ floating_t board_score(struct board *b, int scores[S_MAX]); /* Tromp-Taylor scoring, assuming given groups are actually dead. */ struct move_queue; floating_t board_official_score(struct board *board, struct move_queue *dead); -floating_t board_official_score_details(struct board *board, struct move_queue *dead, int *dame, int *final_ownermap); +floating_t board_official_score_color(struct board *board, struct move_queue *dead, enum stone color); +floating_t board_official_score_details(struct board *board, struct move_queue *dead, int *dame, int *seki, int *ownermap, struct ownermap *po); void board_print_official_ownermap(struct board *b, int *final_ownermap); /* Set board rules according to given string. Returns false in case @@ -551,8 +548,8 @@ void board_quick_undo(struct board *b, struct move *m, struct board_undo *u); static inline bool board_is_eyelike(struct board *board, coord_t coord, enum stone eye_color) { - return (neighbor_count_at(board, coord, eye_color) - + neighbor_count_at(board, coord, S_OFFBOARD)) == 4; + return (neighbor_count_at(board, coord, eye_color) + + neighbor_count_at(board, coord, S_OFFBOARD)) == 4; } /* Group suicides allowed */ @@ -616,44 +613,6 @@ board_playing_ko_threat(struct board *b) return !is_pass(b->ko.coord); } -static inline group_t -board_get_atari_neighbor(struct board *b, coord_t coord, enum stone group_color) -{ - assert(coord != pass); - foreach_neighbor(b, coord, { - group_t g = group_at(b, c); - if (g && board_at(b, c) == group_color && board_group_info(b, g).libs == 1) - return g; - /* We return first match. */ - }); - return 0; -} - -static inline void -board_get_atari_neighbors(struct board *b, coord_t c, enum stone group_color, struct move_queue *q) -{ - assert(c != pass); - q->moves = 0; - foreach_neighbor(b, c, { - group_t g = group_at(b, c); - if (g && board_at(b, c) == group_color && board_group_info(b, g).libs == 1) { - mq_add(q, g, 0); - mq_nodup(q); - } - }); -} - -#define foreach_atari_neighbor(b, c, group_color) \ - do { \ - struct move_queue __q; \ - board_get_atari_neighbors(b, (c), (group_color), &__q); \ - for (unsigned int __i = 0; __i < __q.moves; __i++) { \ - group_t g = __q.move[__i]; - -#define foreach_atari_neighbor_end \ - } \ - } while (0) - static inline bool board_safe_to_play(struct board *b, coord_t coord, enum stone color) diff --git a/gogui.c b/gogui.c index c14417487..eee13c7c2 100644 --- a/gogui.c +++ b/gogui.c @@ -438,9 +438,9 @@ cmd_gogui_final_score(struct board *b, struct engine *e, struct time_info *ti, g struct move_queue q = { .moves = 0 }; if (e->dead_group_list) e->dead_group_list(e, b, &q); - int dame; + int dame, seki; int ownermap[board_size2(b)]; - floating_t score = board_official_score_details(b, &q, &dame, ownermap); + floating_t score = board_official_score_details(b, &q, &dame, &seki, ownermap, NULL); char buffer[5000]; strbuf_t strbuf; strbuf_t *buf = strbuf_init(&strbuf, buffer, sizeof(buffer)); diff --git a/gtp.c b/gtp.c index 249bb642f..fbaacae03 100644 --- a/gtp.c +++ b/gtp.c @@ -643,7 +643,7 @@ cmd_final_status_list_seki(char *arg, struct board *b, struct engine *e, gtp_t * struct move_queue sekis = { .moves = 0 }; foreach_point(b) { if (board_at(b, c) == S_OFFBOARD) continue; - if (ownermap_judge_point(ownermap, c, 0.80) != PJ_DAME) continue; + if (ownermap_judge_point(ownermap, c, 0.80) != PJ_SEKI) continue; foreach_neighbor(b, c, { group_t g = group_at(b, c); diff --git a/ownermap.c b/ownermap.c index 834dd0599..af8b516a6 100644 --- a/ownermap.c +++ b/ownermap.c @@ -25,17 +25,14 @@ printhook(struct board *board, coord_t c, strbuf_t *buf, void *data) sbprintf(buf, "Score Est: %s", ownermap_score_est_str(board, ownermap)); return; } - - if (!ownermap) { - sbprintf(buf, ". "); - return; - } - const char chr[] = ":XO,"; // dame, black, white, unclear + + if (!ownermap) { sbprintf(buf, ". "); return; } + + const char chr[] = ":XO,"; // seki, black, white, unclear const char chm[] = ":xo,"; char ch = chr[ownermap_judge_point(ownermap, c, GJ_THRES)]; - if (ch == ',') { // less precise estimate then? - ch = chm[ownermap_judge_point(ownermap, c, 0.67)]; - } + if (ch == ',') // less precise estimate then? + ch = chm[ownermap_judge_point(ownermap, c, 0.67)]; sbprintf(buf, "%c ", ch); } @@ -52,8 +49,7 @@ ownermap_fill(struct ownermap *ownermap, struct board *b) foreach_point(b) { enum stone color = board_at(b, c); if (color == S_OFFBOARD) continue; - if (color == S_NONE) - color = board_get_one_point_eye(b, c); + if (color == S_NONE) color = board_eye_color(b, c); ownermap->map[c][color]++; } foreach_point_end; } @@ -87,14 +83,10 @@ ownermap_judge_point(struct ownermap *ownermap, coord_t c, floating_t thres) int b = ownermap->map[c][S_BLACK]; int w = ownermap->map[c][S_WHITE]; int total = ownermap->playouts; - if (n >= total * thres) - return PJ_DAME; - else if (n + b >= total * thres) - return PJ_BLACK; - else if (n + w >= total * thres) - return PJ_WHITE; - else - return PJ_UNKNOWN; + if (n >= total * thres) return PJ_SEKI; + else if (n + b >= total * thres) return PJ_BLACK; + else if (n + w >= total * thres) return PJ_WHITE; + else return PJ_UNKNOWN; } enum stone @@ -115,35 +107,26 @@ ownermap_judge_groups(struct board *b, struct ownermap *ownermap, struct group_j foreach_point(b) { enum stone color = board_at(b, c); group_t g = group_at(b, c); - if (!g) continue; - + if (!g) continue; enum point_judgement pj = ownermap_judge_point(ownermap, c, judge->thres); - // assert(judge->gs[g] == GS_NONE || judge->gs[g] == pj); + if (pj == PJ_UNKNOWN) { - /* Fate is uncertain. */ judge->gs[g] = GS_UNKNOWN; - - } else if (judge->gs[g] != GS_UNKNOWN) { - /* Update group state. */ - enum gj_state new; - - // Comparing enum types, casting (int) avoids compiler warnings - if ((int)pj == (int)color) { - new = GS_ALIVE; - } else if ((int)pj == (int)stone_other(color)) { - new = GS_DEAD; - } else { assert(pj == PJ_DAME); - /* Exotic! */ - new = GS_UNKNOWN; - } - - if (judge->gs[g] == GS_NONE) { - judge->gs[g] = new; - } else if (judge->gs[g] != new) { - /* Contradiction. :( */ - judge->gs[g] = GS_UNKNOWN; - } + continue; } + + if (judge->gs[g] == GS_UNKNOWN) + continue; + + /* Update group state. + * Comparing enum types, casting (int) avoids compiler warnings */ + enum gj_state new; + if ((int)pj == (int)color) new = GS_ALIVE; + else if ((int)pj == (int)stone_other(color)) new = GS_DEAD; + else { assert(pj == PJ_SEKI); new = GS_UNKNOWN; /* Exotic! */ } + + if (judge->gs[g] == GS_NONE) judge->gs[g] = new; + else if (judge->gs[g] != new) judge->gs[g] = GS_UNKNOWN; /* Contradiction. :( */ } foreach_point_end; } @@ -170,6 +153,24 @@ get_dead_groups(struct board *b, struct ownermap *ownermap, struct move_queue *d if (unclear) { unclear->moves = 0; groups_of_status(b, &gj, GS_UNKNOWN, unclear); } } +void +ownermap_scores(struct board *b, struct ownermap *ownermap, int *scores) +{ + foreach_point(b) { + if (board_at(b, c) == S_OFFBOARD) continue; + enum point_judgement j = ownermap_judge_point(ownermap, c, 0.67); + scores[j]++; + } foreach_point_end; +} + +int +ownermap_dames(struct board *b, struct ownermap *ownermap) +{ + int scores[S_MAX] = { 0, }; + ownermap_scores(b, ownermap, scores); + return scores[PJ_UNKNOWN]; +} + enum point_judgement ownermap_score_est_coord(struct board *b, struct ownermap *ownermap, coord_t c) { @@ -225,19 +226,18 @@ board_position_final(struct board *b, struct ownermap *ownermap, char **msg) floating_t score_est = ownermap_score_est(b, ownermap); - int final_dames; + int dame, seki; int final_ownermap[board_size2(b)]; - floating_t final_score = board_official_score_details(b, &dead, &final_dames, final_ownermap); + floating_t final_score = board_official_score_details(b, &dead, &dame, &seki, final_ownermap, ownermap); return board_position_final_full(b, ownermap, &dead, &unclear, score_est, - final_ownermap, final_dames, final_score, msg, true); + final_ownermap, dame, final_score, msg); } bool board_position_final_full(struct board *b, struct ownermap *ownermap, struct move_queue *dead, struct move_queue *unclear, float score_est, - int *final_ownermap, int final_dames, float final_score, - char **msg, bool extra_checks) + int *final_ownermap, int final_dames, float final_score, char **msg) { *msg = "too early to pass"; if (b->moves < board_earliest_pass(b)) @@ -266,7 +266,7 @@ board_position_final_full(struct board *b, struct ownermap *ownermap, foreach_point(b) { if (board_at(b, c) == S_OFFBOARD) continue; if (final_ownermap[c] != 3) continue; - if (ownermap_judge_point(ownermap, c, GJ_THRES) == PJ_DAME) continue; + if (ownermap_judge_point(ownermap, c, GJ_THRES) == PJ_SEKI) continue; coord_t dame = c; int around[4] = { 0, }; @@ -284,17 +284,15 @@ board_position_final_full(struct board *b, struct ownermap *ownermap, /* If ownermap and official score disagree position is likely not final. * If too many dames also. */ - if (extra_checks) { - int max_dames = (board_large(b) ? 20 : 7); - *msg = "non-final position: too many dames"; - if (final_dames > max_dames) return false; - - /* Can disagree up to dame points, as long as there are not too many. - * For example a 1 point difference with 1 dame is quite usual... */ - int max_diff = MIN(final_dames, 4); - *msg = "non-final position: score est and official score don't agree"; - if (fabs(final_score - score_est) > max_diff) return false; - } + int max_dames = (board_large(b) ? 15 : 7); + *msg = "non-final position: too many dames"; + if (final_dames > max_dames) return false; + + /* Can disagree up to dame points, as long as there are not too many. + * For example a 1 point difference with 1 dame is quite usual... */ + int max_diff = MIN(final_dames, 4); + *msg = "non-final position: score est and official score don't agree"; + if (fabs(final_score - score_est) > max_diff) return false; return true; } diff --git a/ownermap.h b/ownermap.h index 08f49bb5f..dd58bd732 100644 --- a/ownermap.h +++ b/ownermap.h @@ -15,7 +15,7 @@ struct move_queue; #define GJ_THRES 0.8 enum point_judgement { - PJ_DAME = S_NONE, + PJ_SEKI = S_NONE, PJ_BLACK = S_BLACK, PJ_WHITE = S_WHITE, PJ_UNKNOWN = 3, @@ -70,12 +70,15 @@ float ownermap_score_est_color(struct board *b, struct ownermap *ownermap, enum char *ownermap_score_est_str(struct board *b, struct ownermap *ownermap); enum point_judgement ownermap_score_est_coord(struct board *b, struct ownermap *ownermap, coord_t c); +/* Raw count for each color. */ +void ownermap_scores(struct board *b, struct ownermap *ownermap, int *scores); +int ownermap_dames(struct board *b, struct ownermap *ownermap); + /* Is board position final ? */ bool board_position_final(struct board *b, struct ownermap *ownermap, char **msg); bool board_position_final_full(struct board *b, struct ownermap *ownermap, struct move_queue *dead, struct move_queue *unclear, float score_est, - int *final_ownermap, int final_dames, float final_score, - char **msg, bool extra_checks); + int *final_ownermap, int final_dames, float final_score, char **msg); /* Don't allow passing earlier than that: * 19x19: 120 15x15: 56 13x13: 33 9x9: 16 */ diff --git a/pattern.c b/pattern.c index 9fe6cb03d..5c084ad51 100644 --- a/pattern.c +++ b/pattern.c @@ -927,10 +927,11 @@ pattern_match_spatial(struct pattern_config *pc, } static int -pattern_match_mcowner(struct board *b, struct move *m, struct ownermap *ownermap) +pattern_match_mcowner(struct board *b, struct move *m, struct ownermap *o) { - assert(ownermap->playouts >= MM_MINGAMES); - return (ownermap->map[m->coord][m->color] * 8 / (ownermap->playouts + 1)); + assert(o->playouts >= MM_MINGAMES); + int r = o->map[m->coord][m->color] * 8 / (o->playouts + 1); + return MIN(r, 8); // multi-threads count not exact, can reach 9 sometimes... } static void diff --git a/t-regress/dead_stones/japanese_rules.gtp b/t-regress/dead_stones/japanese_rules.gtp new file mode 100644 index 000000000..730bae927 --- /dev/null +++ b/t-regress/dead_stones/japanese_rules.gtp @@ -0,0 +1,223 @@ +kgs-rules japanese +boardsize 19 +clear_board +komi 0.5 +set_free_handicap q4 d4 d16 q16 +play W r14 +play B o17 +play W s16 +play B r17 +play W r11 +play B k3 +play W c14 +play B d14 +play W d13 +play B e14 +play W c15 +play B d15 +play W r6 +play B s4 +play W c9 +play B c7 +play W c3 +play B d3 +play W c4 +play B d5 +play W c5 +play B d6 +play W c12 +play B o5 +play W d2 +play B e2 +play W c2 +play B f3 +play W o7 +play B n6 +play W n7 +play B m6 +play W m7 +play B l6 +play W l7 +play B k7 +play W k8 +play B j7 +play W j8 +play B h7 +play W h8 +play B g8 +play W g9 +play B f8 +play W k15 +play B k17 +play W l17 +play B k16 +play W l16 +play B s17 +play W p14 +play B j15 +play W k14 +play B j14 +play W k13 +play B j13 +play W j12 +play B h12 +play W h11 +play B g12 +play W k18 +play B j18 +play W l18 +play B h17 +play W j16 +play B j17 +play W c16 +play B c17 +play W b17 +play B c18 +play W b18 +play B c19 +play W n18 +play B o18 +play W n17 +play B o16 +play W o15 +play B n16 +play W n15 +play B m16 +play W m15 +play B t7 +play W s7 +play B t6 +play W s5 +play B t5 +play W r4 +play B r3 +play W r5 +play B s3 +play W q3 +play B q2 +play W p3 +play B p5 +play W p2 +play B r2 +play W k2 +play B m2 +play W m3 +play B l2 +play W s8 +play B t2 +play W f13 +play B g14 +play W e13 +play B g13 +play W f14 +play B f15 +play W b6 +play B b8 +play W g16 +play B h16 +play W g15 +play B f16 +play W l3 +play B j2 +play W k4 +play B k1 +play W k6 +play B k5 +play W j6 +play B j5 +play W h6 +play B g7 +play W f9 +play B e8 +play W e9 +play B t4 +play W o19 +play B p19 +play W q5 +play B p4 +play W d17 +play B e17 +play W c8 +play B b7 +play W p6 +play B n2 +play W o6 +play B n4 +play W n5 +play B m5 +play W d8 +play B d7 +play W b9 +play B t8 +play W s9 +play B t9 +play W e7 +play B g6 +play W g11 +play B f12 +play W s15 +play B t11 +play W s10 +play B t10 +play W n19 +play B q18 +play W t17 +play B t18 +play W t16 +play B s18 +play W b19 +play B d18 +play W s12 +play B t12 +play W s13 +play B t13 +play W j19 +play B h19 +play W e12 +play B f11 +play W e11 +play B f10 +play W e10 +play B g10 +play W h10 +play B g17 +play W e1 +play B f1 +play W d1 +play B c6 +play W b5 +play B a8 +play W f2 +play B g2 +play W a9 +play B k19 +play W q15 +play B r16 +play W p16 +play B p17 +play W m17 +play B t14 +play W l19 +play B j19 +play W p15 +play B s14 +play W r15 +play B a6 +play W a5 +play B a7 +play W s11 +play B t15 +play W s6 +play B k12 +play W j11 +play B n12 +play W b2 +play B m9 +play W o10 +play B l14 +play W l15 +play B pass +play W q13 +genmove b + +echo pass only move under japanese rules diff --git a/t-regress/dead_stones/japanese_rules.sgf b/t-regress/dead_stones/japanese_rules.sgf new file mode 100644 index 000000000..976dbb826 --- /dev/null +++ b/t-regress/dead_stones/japanese_rules.sgf @@ -0,0 +1,223 @@ +(;RU[Japanese]SZ[19] +KM[0.5] +HA[4] +;B[pp] +;B[dp] +;B[dd] +;B[pd] +;W[qf] +;B[nc] +;W[rd] +;B[qc] +;W[qi] +;B[jq] +;W[cf] +;B[df] +;W[dg] +;B[ef] +;W[ce] +;B[de] +;W[qn] +;B[rp] +;W[ck] +;B[cm] +;W[cq] +;B[dq] +;W[cp] +;B[do] +;W[co] +;B[dn] +;W[ch] +;B[no] +;W[dr] +;B[er] +;W[cr] +;B[fq] +;W[nm] +;B[mn] +;W[mm] +;B[ln] +;W[lm] +;B[kn] +;W[km] +;B[jm] +;W[jl] +;B[im] +;W[il] +;B[hm] +;W[hl] +;B[gl] +;W[gk] +;B[fl] +;W[je] +;B[jc] +;W[kc] +;B[jd] +;W[kd] +;B[rc] +;W[of] +;B[ie] +;W[jf] +;B[if] +;W[jg] +;B[ig] +;W[ih] +;B[hh] +;W[hi] +;B[gh] +;W[jb] +;B[ib] +;W[kb] +;B[hc] +;W[id] +;B[ic] +;W[cd] +;B[cc] +;W[bc] +;B[cb] +;W[bb] +;B[ca] +;W[mb] +;B[nb] +;W[mc] +;B[nd] +;W[ne] +;B[md] +;W[me] +;B[ld] +;W[le] +;B[sm] +;W[rm] +;B[sn] +;W[ro] +;B[so] +;W[qp] +;B[qq] +;W[qo] +;B[rq] +;W[pq] +;B[pr] +;W[oq] +;B[oo] +;W[or] +;B[qr] +;W[jr] +;B[lr] +;W[lq] +;B[kr] +;W[rl] +;B[sr] +;W[fg] +;B[gf] +;W[eg] +;B[gg] +;W[ff] +;B[fe] +;W[bn] +;B[bl] +;W[gd] +;B[hd] +;W[ge] +;B[fd] +;W[kq] +;B[ir] +;W[jp] +;B[js] +;W[jn] +;B[jo] +;W[in] +;B[io] +;W[hn] +;B[gm] +;W[fk] +;B[el] +;W[ek] +;B[sp] +;W[na] +;B[oa] +;W[po] +;B[op] +;W[dc] +;B[ec] +;W[cl] +;B[bm] +;W[on] +;B[mr] +;W[nn] +;B[mp] +;W[mo] +;B[lo] +;W[dl] +;B[dm] +;W[bk] +;B[sl] +;W[rk] +;B[sk] +;W[em] +;B[gn] +;W[gi] +;B[fh] +;W[re] +;B[si] +;W[rj] +;B[sj] +;W[ma] +;B[pb] +;W[sc] +;B[sb] +;W[sd] +;B[rb] +;W[ba] +;B[db] +;W[rh] +;B[sh] +;W[rg] +;B[sg] +;W[ia] +;B[ha] +;W[eh] +;B[fi] +;W[ei] +;B[fj] +;W[ej] +;B[gj] +;W[hj] +;B[gc] +;W[es] +;B[fs] +;W[ds] +;B[cn] +;W[bo] +;B[al] +;W[fr] +;B[gr] +;W[ak] +;B[ja] +;W[pe] +;B[qd] +;W[od] +;B[oc] +;W[lc] +;B[sf] +;W[ka] +;B[ia] +;W[oe] +;B[rf] +;W[qe] +;B[an] +;W[ao] +;B[am] +;W[ri] +;B[se] +;W[rn] +;B[jh] +;W[ii] +;B[mh] +;W[br] +;B[lk] +;W[nj] +;B[kf] +;W[ke] +;B[] +;W[pg] +) diff --git a/t-unit/moggy.t b/t-unit/moggy.t index 9805eb579..f70f428e0 100644 --- a/t-unit/moggy.t +++ b/t-unit/moggy.t @@ -29,6 +29,20 @@ moggy status h8 ? moggy status h8 x b5 X a9 O # Check h8, b5 and a9 status +% Two-headed dragon, false eyes should be b +boardsize 9 +. . . . . . . . . +. O O O O O . . . +. O X X X O O O O +. O X . X X X X O +. O O X O O O X O +. . O X O . O X X +. . O X X O . O X +. . O O X O O O X +. . . O)X X X X . + +moggy status d6 X + % Raw playout speed benchmark boardsize 19 diff --git a/t-unit/test.c b/t-unit/test.c index 68a5bf20f..0eecec647 100644 --- a/t-unit/test.c +++ b/t-unit/test.c @@ -11,6 +11,7 @@ #include "tactics/dragon.h" #include "tactics/ladder.h" #include "tactics/1lib.h" +#include "tactics/2lib.h" #include "tactics/seki.h" #include "util.h" #include "random.h" @@ -295,17 +296,6 @@ test_ladder_any(struct board *b, char *arg) } -static group_t -get_2lib_neighbor(struct board *b, coord_t c, enum stone color) -{ - foreach_neighbor(b, c, { - group_t g = group_at(b, c); - if (board_at(b, c) == color && board_group_info(b, g).libs == 2) - return g; - }); - return 0; -} - static bool test_wouldbe_ladder(struct board *b, char *arg) { @@ -320,7 +310,7 @@ test_wouldbe_ladder(struct board *b, char *arg) PRINT_TEST(b, "wouldbe_ladder %s %s %d...\t", stone2str(color), coord2sstr(c, b), eres); assert(board_at(b, c) == S_NONE); - group_t g = get_2lib_neighbor(b, c, stone_other(color)); + group_t g = board_get_2lib_neighbor(b, c, stone_other(color)); assert(g); assert(board_at(b, g) == stone_other(color)); coord_t chaselib = c; int rres = wouldbe_ladder(b, g, chaselib); @@ -343,7 +333,7 @@ test_wouldbe_ladder_any(struct board *b, char *arg) PRINT_TEST(b, "wouldbe_ladder_any %s %s %d...\t", stone2str(color), coord2sstr(c, b), eres); assert(board_at(b, c) == S_NONE); - group_t g = get_2lib_neighbor(b, c, stone_other(color)); + group_t g = board_get_2lib_neighbor(b, c, stone_other(color)); assert(g); assert(board_at(b, g) == stone_other(color)); coord_t chaselib = c; int rres = wouldbe_ladder_any(b, g, chaselib); @@ -484,7 +474,7 @@ test_moggy_status(struct board *b, char *arg) if (!strcmp(arg, "X") || !strcmp(arg, "O" )) thres[n] = 80; if (!strcasecmp(arg, "x")) expected[n] = PJ_BLACK; else if (!strcasecmp(arg, "o")) expected[n] = PJ_WHITE; - else if (!strcasecmp(arg, ":")) expected[n] = PJ_DAME; + else if (!strcasecmp(arg, ":")) expected[n] = PJ_SEKI; else if (!strcasecmp(arg, "?")) { expected[n] = PJ_BLACK; thres[n] = 0; } else die("Expected x/o/X/O/: after coord %s\n", coord2sstr(status_at[n], b)); next_arg(arg); @@ -542,7 +532,7 @@ test_moggy_status(struct board *b, char *arg) int pc = ownermap.map[c][color] * 100 / ownermap.playouts; int passed = (!thres[i] || (j == expected[i] && pc >= thres[i])); - char *colorstr = (j == PJ_DAME ? "dame" : stone2str(color)); + char *colorstr = (j == PJ_SEKI ? "seki" : stone2str(color)); PRINT_TEST(b, "moggy status %3s %-5s -> %3i%% ", coord2sstr(c, b), colorstr, pc); if (!passed) ret = false; diff --git a/tactics/1lib.h b/tactics/1lib.h index 40a2a001c..6647e7e2e 100644 --- a/tactics/1lib.h +++ b/tactics/1lib.h @@ -25,4 +25,50 @@ bool can_countercapture_any(struct board *b, group_t group, struct move_queue *q void group_atari_check(unsigned int alwaysccaprate, struct board *b, group_t group, enum stone to_play, struct move_queue *q, coord_t *ladder, bool middle_ladder, int tag); + +/* Returns 0 or ID of neighboring group in atari. */ +static group_t board_get_atari_neighbor(struct board *b, coord_t coord, enum stone group_color); +/* Get all neighboring groups in atari */ +static void board_get_atari_neighbors(struct board *b, coord_t coord, enum stone group_color, struct move_queue *q); + + +static inline group_t +board_get_atari_neighbor(struct board *b, coord_t coord, enum stone group_color) +{ + assert(coord != pass); + foreach_neighbor(b, coord, { + group_t g = group_at(b, c); + if (g && board_at(b, c) == group_color && board_group_info(b, g).libs == 1) + return g; + /* We return first match. */ + }); + return 0; +} + +static inline void +board_get_atari_neighbors(struct board *b, coord_t c, enum stone group_color, struct move_queue *q) +{ + assert(c != pass); + q->moves = 0; + foreach_neighbor(b, c, { + group_t g = group_at(b, c); + if (g && board_at(b, c) == group_color && board_group_info(b, g).libs == 1) { + mq_add(q, g, 0); + mq_nodup(q); + } + }); +} + +#define foreach_atari_neighbor(b, c, group_color) \ + do { \ + struct move_queue __q; \ + board_get_atari_neighbors(b, (c), (group_color), &__q); \ + for (unsigned int __i = 0; __i < __q.moves; __i++) { \ + group_t g = __q.move[__i]; + +#define foreach_atari_neighbor_end \ + } \ + } while (0) + + #endif diff --git a/tactics/2lib.h b/tactics/2lib.h index a75709858..19fbe343f 100644 --- a/tactics/2lib.h +++ b/tactics/2lib.h @@ -15,5 +15,20 @@ void group_2lib_check(struct board *b, group_t group, enum stone to_play, struct bool can_capture_2lib_group(struct board *b, group_t g, struct move_queue *q, int tag); void group_2lib_capture_check(struct board *b, group_t group, enum stone to_play, struct move_queue *q, int tag, bool use_miaisafe, bool use_def_no_hopeless); +/* Returns 0 or ID of neighboring group with 2 libs. */ +static group_t board_get_2lib_neighbor(struct board *b, coord_t c, enum stone color); + + +static inline group_t +board_get_2lib_neighbor(struct board *b, coord_t c, enum stone color) +{ + foreach_neighbor(b, c, { + group_t g = group_at(b, c); + if (board_at(b, c) == color && board_group_info(b, g).libs == 2) + return g; + }); + return 0; +} + #endif diff --git a/tools/sgf2gtp.pl b/tools/sgf2gtp.pl index e42d0dfcf..5c34248e4 100755 --- a/tools/sgf2gtp.pl +++ b/tools/sgf2gtp.pl @@ -45,12 +45,12 @@ sub sgf2gtp local $/ = undef; my $sgf = <>; $sgf =~ s/\\]//gs; # remove escaped brackets (in comments) $sgf =~ s/\bC\[.*?\]//gs; # no comments +$sgf =~ s/[ \n\t]//gs; # no whitespaces #$sgf =~ s/\).*//gs; # cut at end of principal branch my @m = split(/;/, $sgf); if (shift @m ne "(") { die "doesn't look like valid sgf, aborting\n"; } my $h = shift @m; # game header -$h =~ s/[ \n\t]//gs; # no whitespaces my $rules = "chinese"; if ($h =~ /RU\[([^]]+)\]/) { $rules = lc($1); } else { warn "WARNING: no rules specified, assuming $rules ...\n"; } diff --git a/uct/prior.c b/uct/prior.c index eb19ab4ed..233f74f3a 100644 --- a/uct/prior.c +++ b/uct/prior.c @@ -11,6 +11,7 @@ #include "move.h" #include "random.h" #include "engine.h" +#include "tactics/1lib.h" #include "tactics/ladder.h" #include "tactics/util.h" #include "uct/internal.h" @@ -60,11 +61,12 @@ get_prior_best_moves(struct prior_map *map, coord_t *best_c, float *best_r, int best_c[i] = pass; best_r[i] = 0; } - float max = 0.0; + float max = map->prior[pass].playouts; foreach_free_point(map->b) { max = MAX(max, map->prior[c].playouts); } foreach_free_point_end; + best_moves_add(pass, (float)map->prior[pass].playouts / max, best_c, best_r, nbest); foreach_free_point(map->b) { best_moves_add(c, (float)map->prior[c].playouts / max, best_c, best_r, nbest); } foreach_free_point_end; @@ -211,7 +213,7 @@ uct_prior_cfgd(struct uct *u, struct tree_node *node, struct prior_map *map) } foreach_free_point_end; } -static int +static void uct_prior_joseki(struct uct *u, struct tree_node *node, struct prior_map *map) { /* Q_{joseki} */ @@ -232,7 +234,6 @@ uct_prior_joseki(struct uct *u, struct tree_node *node, struct prior_map *map) get_joseki_best_moves(b, coords, ratings, matches, best_c, best_r, 20); print_joseki_best_moves(b, best_c, best_r, 20); } - return matches; } static void @@ -274,7 +275,6 @@ void uct_prior(struct uct *u, struct tree_node *node, struct prior_map *map) { struct board *b = map->b; - int joseki_matches = 0; if (u->prior->prune_ladders && !board_playing_ko_threat(b)) { foreach_free_point(b) { @@ -296,6 +296,9 @@ uct_prior(struct uct *u, struct tree_node *node, struct prior_map *map) } foreach_free_point_end; } + if (u->prior->boost_pass) /* Endgame with japanese rules, pass can be hard to find. */ + add_prior_value(map, pass, 1.0, u->prior->pattern_eqex * 3 / 4); + if (u->prior->even_eqex) uct_prior_even(u, node, map); /* Use dcnn for root priors */ @@ -310,15 +313,14 @@ uct_prior(struct uct *u, struct tree_node *node, struct prior_map *map) if (u->prior->cfgd_eqex) uct_prior_cfgd(u, node, map); } - if (u->prior->joseki_eqex) joseki_matches = uct_prior_joseki(u, node, map); + if (u->prior->joseki_eqex) uct_prior_joseki(u, node, map); #ifdef PACHI_PLUGINS if (u->prior->plugin_eqex) plugin_prior(u->plugins, node, map, u->prior->plugin_eqex); #endif - /* Show final prior mix in case there are joseki matches. */ - if (DEBUGL(3) && !node->parent && joseki_matches) - print_prior_best_moves(map->b, map); + /* Show final prior mix. */ + if (DEBUGL(3) && !node->parent) print_prior_best_moves(map->b, map); } struct uct_prior * diff --git a/uct/prior.h b/uct/prior.h index 6333c6c25..cb33b9a75 100644 --- a/uct/prior.h +++ b/uct/prior.h @@ -21,6 +21,7 @@ struct uct_prior { int joseki_eqex, joseki_eqex_far, pattern_eqex, dcnn_eqex; int cfgdn; int *cfgd_eqex; bool prune_ladders; + bool boost_pass; }; struct prior_map { diff --git a/uct/search.c b/uct/search.c index 6d4700db1..21d154743 100644 --- a/uct/search.c +++ b/uct/search.c @@ -18,6 +18,8 @@ #include "joseki.h" #include "random.h" #include "timeinfo.h" +#include "tactics/1lib.h" +#include "tactics/2lib.h" #include "uct/dynkomi.h" #include "uct/internal.h" #include "uct/search.h" @@ -103,36 +105,44 @@ spawn_worker(void *ctx_) /* Setup */ struct uct_thread_ctx *ctx = ctx_; struct uct *u = ctx->u; + struct board *b = ctx->b; + enum stone color = ctx->color; fast_srandom(ctx->seed); /* Fill ownermap for mcowner pattern feature. */ if (using_patterns()) { double time_start = time_now(); - uct_mcowner_playouts(ctx->u, ctx->b, ctx->color); + uct_mcowner_playouts(u, b, color); if (!ctx->tid) { if (DEBUGL(2)) fprintf(stderr, "mcowner %.2fs\n", time_now() - time_start); //fprintf(stderr, "\npattern ownermap:\n"); - //board_print_ownermap(ctx->b, stderr, &u->ownermap); + //board_print_ownermap(b, stderr, &u->ownermap); } } + /* Close endgame with japanese rules ? Boost pass prior. */ + if (!ctx->tid && b->rules == RULES_JAPANESE) { + int dames = ownermap_dames(b, &u->ownermap); + float score = ownermap_score_est(b, &u->ownermap); + u->prior->boost_pass = (dames < 10 && fabs(score) <= 3); + } + /* Expand root node (dcnn). Other threads wait till it's ready. * For dcnn pondering we also need dcnn values for opponent's best moves. */ struct tree *t = ctx->t; struct tree_node *n = t->root; if (!ctx->tid) { - enum stone player_color = ctx->color; - enum stone node_color = stone_other(player_color); + enum stone node_color = stone_other(color); assert(node_color == t->root_color); if (tree_leaf_node(n) && !__sync_lock_test_and_set(&n->is_expanded, 1)) { - tree_expand_node(t, n, ctx->b, player_color, u, 1); - if (u->genmove_pondering && using_dcnn(ctx->b)) - uct_expand_next_best_moves(u, t, ctx->b, player_color); + tree_expand_node(t, n, b, color, u, 1); + if (u->genmove_pondering && using_dcnn(b)) + uct_expand_next_best_moves(u, t, b, color); } else if (DEBUGL(2)) { /* Show previously computed priors */ - print_joseki_moves(joseki_dict, ctx->b, ctx->color); - print_node_prior_best_moves(ctx->b, n); + print_joseki_moves(joseki_dict, b, color); + print_node_prior_best_moves(b, n); } u->tree_ready = true; } @@ -630,13 +640,39 @@ uct_search_pass_is_safe(struct uct *u, struct board *b, enum stone color, bool p return res; } +static bool +uct_pass_first(struct uct *u, struct board *b, enum stone color, bool pass_all_alive, coord_t coord) +{ + /* For kgs: must not pass first in main game phase. */ + bool can_pass_first = (!nopassfirst || pass_all_alive); + if (!can_pass_first) return false; + + if (is_pass(coord) || is_pass(b->last_move.coord)) return false; + + enum stone other_color = stone_other(color); + int capturing = board_get_atari_neighbor(b, coord, other_color); + int atariing = board_get_2lib_neighbor(b, coord, other_color); + if (capturing || atariing || board_playing_ko_threat(b)) return false; + + /* Find dames left */ + struct move_queue dead, unclear; + uct_mcowner_playouts(u, b, color); + get_dead_groups(b, &u->ownermap, &dead, &unclear); + if (unclear.moves) return false; + int final_ownermap[board_size2(b)]; + int dame, seki; + board_official_score_details(b, &dead, &dame, &seki, final_ownermap, &u->ownermap); + + enum stone move_owner = ownermap_color(&u->ownermap, coord, 0.80); + return (!dame && move_owner == other_color); /* play in opponent territory */ +} + struct tree_node * uct_search_result(struct uct *u, struct board *b, enum stone color, bool pass_all_alive, int played_games, int base_playouts, coord_t *best_coord) { /* Choose the best move from the tree. */ - enum stone other_color = stone_other(color); struct tree_node *best = u->policy->choose(u->policy, u->t->root, b, color, resign); if (!best) { *best_coord = pass; @@ -667,22 +703,15 @@ uct_search_result(struct uct *u, struct board *b, enum stone color, } bool opponent_passed = is_pass(b->last_move.coord); - bool pass_first = false; - if (!is_pass(*best_coord)) { - enum stone move_owner = ownermap_color(&u->ownermap, *best_coord, 0.80); - int capturing = board_get_atari_neighbor(b, *best_coord, other_color); - floating_t score = ownermap_score_est_color(b, &u->ownermap, color); - bool can_pass_first = (!nopassfirst || pass_all_alive); /* For kgs: must not pass first in main game phase. */ - pass_first = (can_pass_first && (move_owner == other_color) && /* play in opponent territory */ - !capturing && !board_playing_ko_threat(b) && - winrate > 0.80 && score > 1.0); - } + bool pass_first = uct_pass_first(u, b, color, pass_all_alive, *best_coord); + if (UDEBUGL(2) && pass_first) fprintf(stderr, "\n"); /* If the opponent just passed and we win counting, always pass as well. * Pass also instead of playing in opponent territory if winning. * For option stones_only, we pass only when there is nothing else to do, * to show how to maximize score. */ if ((opponent_passed || pass_first) && + !is_pass(*best_coord) && b->moves > 10 && b->rules != RULES_STONES_ONLY) { char *msg; if (uct_search_pass_is_safe(u, b, color, pass_all_alive, &msg)) { @@ -694,7 +723,7 @@ uct_search_result(struct uct *u, struct board *b, enum stone color, *best_coord = pass; return NULL; } - if (UDEBUGL(0)) fprintf(stderr, "Refusing to pass: %s\n", msg); + if (UDEBUGL(2)) fprintf(stderr, "Refusing to pass: %s\n", msg); } return best; diff --git a/uct/uct.c b/uct/uct.c index 82198be62..58d619415 100644 --- a/uct/uct.c +++ b/uct/uct.c @@ -110,31 +110,34 @@ uct_pass_is_safe(struct uct *u, struct board *b, enum stone color, bool pass_all struct move_queue dead, unclear; uct_mcowner_playouts(u, b, color); get_dead_groups(b, &u->ownermap, &dead, &unclear); - + + bool check_score = !u->allow_losing_pass; + if (pass_all_alive) { *msg = "need to remove opponent dead groups first"; for (unsigned int i = 0; i < dead.moves; i++) if (board_at(b, dead.move[i]) == stone_other(color)) return false; dead.moves = 0; // our dead stones are alive when pass_all_alive is true + + float final_score = board_official_score_color(b, &dead, color); + *msg = "losing on official score"; + return (check_score ? final_score >= 0 : true); } /* Check score estimate first, official score is off if position is not final */ *msg = "losing on score estimate"; - bool check_score = !u->allow_losing_pass; floating_t score_est = ownermap_score_est_color(b, &u->ownermap, color); if (check_score && score_est < 0) return false; int final_ownermap[board_size2(b)]; - int dames; - floating_t final_score = board_official_score_details(b, &dead, &dames, final_ownermap); + int dame, seki; + floating_t final_score = board_official_score_details(b, &dead, &dame, &seki, final_ownermap, &u->ownermap); if (color == S_BLACK) final_score = -final_score; - /* Don't go to counting if position is not final. - * Skip extra checks for pass_all_alive in case there are - * positions which don't pass them (too many sekis for example). */ + /* Don't go to counting if position is not final. */ if (!board_position_final_full(b, &u->ownermap, &dead, &unclear, score_est, - final_ownermap, dames, final_score, msg, !pass_all_alive)) + final_ownermap, dame, final_score, msg)) return false; *msg = "losing on official score"; @@ -504,7 +507,8 @@ genmove(struct engine *e, struct board *b, struct time_info *ti, enum stone colo uct_pondering_stop(u); - if (u->genmove_reset_tree && u->t) { + if (u->t && (u->genmove_reset_tree || + (using_dcnn(b) && !(u->t->root->hints & TREE_HINT_DCNN)))) { u->initial_extra_komi = u->t->extra_komi; reset_state(u); }