static inline int nakade_area(struct board *b, coord_t around, enum stone color, coord_t *area) { /* First, examine the nakade area. For sure, it must be at most * six points. And it must be within color group(s). */ #define NAKADE_MAX 6 int area_n = 0; area[area_n++] = around; for (int i = 0; i < area_n; i++) { foreach_neighbor(b, area[i], { if (board_at(b, c) == stone_other(color)) return -1; if (board_at(b, c) == S_NONE) { bool dup = false; for (int j = 0; j < area_n; j++) if (c == area[j]) { dup = true; break; } if (dup) continue; if (area_n >= NAKADE_MAX) { /* Too large nakade area. */ return -1; } area[area_n++] = c; } }); }
void pattern_get(struct pattern_config *pc, struct pattern *p, struct board *b, struct move *m) { p->n = 0; struct feature *f = &p->f[0]; /* TODO: We should match pretty much all of these features * incrementally. */ /* FEAT_SPATIAL */ /* TODO */ assert(!pc->spat_max); /* FEAT_PASS */ if (is_pass(m->coord)) { f->id = FEAT_PASS; f->payload = 0; f->payload |= (b->moves > 0 && is_pass(b->last_move.coord)) << PF_PASS_LASTPASS; p->n++; return; } /* FEAT_CAPTURE */ { foreach_neighbor(b, m->coord, { if (board_at(b, c) != stone_other(m->color)) continue; group_t g = group_at(b, c); if (!g || board_group_info(b, g).libs != 1) continue; /* Capture! */ f->id = FEAT_CAPTURE; f->payload = 0; f->payload |= is_ladder(b, m->coord, g, true, true) << PF_CAPTURE_LADDER; /* TODO: is_ladder() is too conservative in some * very obvious situations, look at complete.gtp. */ /* TODO: PF_CAPTURE_RECAPTURE */ foreach_in_group(b, g) { foreach_neighbor(b, c, { assert(board_at(b, c) != S_NONE || c == m->coord); if (board_at(b, c) != m->color) continue; group_t g = group_at(b, c); if (!g || board_group_info(b, g).libs != 1) continue; /* A neighboring group of ours is in atari. */ f->payload |= 1 << PF_CAPTURE_ATARIDEF; }); } foreach_in_group_end; if (group_is_onestone(b, g) && neighbor_count_at(b, m->coord, stone_other(m->color)) + neighbor_count_at(b, m->coord, S_OFFBOARD) == 4) f->payload |= 1 << PF_CAPTURE_KO; (f++, p->n++); });
static void board_init_data(struct board *board) { int size = board_size(board); board_setup(board); board_resize(board, size - 2 /* S_OFFBOARD margin */); /* Setup neighborhood iterators */ board->nei8[0] = -size - 1; // (-1,-1) board->nei8[1] = 1; board->nei8[2] = 1; board->nei8[3] = size - 2; // (-1,0) board->nei8[4] = 2; board->nei8[5] = size - 2; // (-1,1) board->nei8[6] = 1; board->nei8[7] = 1; board->dnei[0] = -size - 1; board->dnei[1] = 2; board->dnei[2] = size*2 - 2; board->dnei[3] = 2; /* Setup initial symmetry */ if (size % 2) { board->symmetry.d = 1; board->symmetry.x1 = board->symmetry.y1 = board_size(board) / 2; board->symmetry.x2 = board->symmetry.y2 = board_size(board) - 1; board->symmetry.type = SYM_FULL; } else { /* TODO: We do not handle board symmetry on boards * with no tengen yet. */ board->symmetry.d = 0; board->symmetry.x1 = board->symmetry.y1 = 1; board->symmetry.x2 = board->symmetry.y2 = board_size(board) - 1; board->symmetry.type = SYM_NONE; } /* Set up coordinate cache */ foreach_point(board) { board->coord[c][0] = c % board_size(board); board->coord[c][1] = c / board_size(board); } foreach_point_end; /* Draw the offboard margin */ int top_row = board_size2(board) - board_size(board); int i; for (i = 0; i < board_size(board); i++) board->b[i] = board->b[top_row + i] = S_OFFBOARD; for (i = 0; i <= top_row; i += board_size(board)) board->b[i] = board->b[board_size(board) - 1 + i] = S_OFFBOARD; foreach_point(board) { coord_t coord = c; if (board_at(board, coord) == S_OFFBOARD) continue; foreach_neighbor(board, c, { inc_neighbor_count_at(board, coord, board_at(board, c)); } ); } foreach_point_end;
static void pick_random_last_move(struct board *b, enum stone to_play) { if (board_empty(b)) return; int base = fast_random(board_size2(b)); for (int i = base; i < base + board_size2(b); i++) { coord_t c = i % board_size2(b); if (board_at(b, c) == stone_other(to_play)) { b->last_move.coord = c; b->last_move.color = board_at(b, c); break; } } }
static bool test_sar(struct board *b, char *arg) { enum stone color = str2stone(arg); arg += 2; coord_t *cc = str2coord(arg, board_size(b)); coord_t c = *cc; coord_done(cc); arg += strcspn(arg, " ") + 1; int eres = atoi(arg); board_print_test(2, b); if (DEBUGL(1)) printf("sar %s %s %d...\t", stone2str(color), coord2sstr(c, b), eres); assert(board_at(b, c) == S_NONE); int rres = is_bad_selfatari(b, color, c); if (rres == eres) { if (DEBUGL(1)) printf("OK\n"); } else { if (debug_level <= 2) { board_print_test(0, b); printf("sar %s %s %d...\t", stone2str(color), coord2sstr(c, b), eres); } printf("FAILED (%d)\n", rres); } return rres == eres; }
static bool test_two_eyes(struct board *b, char *arg) { coord_t c = str2scoord(arg, board_size(b)); arg += strcspn(arg, " ") + 1; int eres = atoi(arg); board_print_test(2, b); if (DEBUGL(1)) printf("two_eyes %s %d...\t", coord2sstr(c, b), eres); enum stone color = board_at(b, c); assert(color == S_BLACK || color == S_WHITE); int rres = dragon_is_safe(b, group_at(b, c), color); if (rres == eres) { if (DEBUGL(1)) printf("OK\n"); } else { if (debug_level <= 2) { board_print_test(0, b); printf("two_eyes %s %d...\t", coord2sstr(c, b), eres); } printf("FAILED (%d)\n", rres); } return rres == eres; }
static bool test_can_countercapture(struct board *b, char *arg) { coord_t c = str2scoord(arg, board_size(b)); arg += strcspn(arg, " ") + 1; int eres = atoi(arg); board_print_test(2, b); if (DEBUGL(1)) printf("can_countercap %s %d...\t", coord2sstr(c, b), eres); enum stone color = board_at(b, c); group_t g = group_at(b, c); assert(color == S_BLACK || color == S_WHITE); int rres = can_countercapture(b, g, NULL, 0); if (rres == eres) { if (DEBUGL(1)) printf("OK\n"); } else { if (debug_level <= 2) { board_print_test(0, b); printf("can_countercap %s %d...\t", coord2sstr(c, b), eres); } printf("FAILED (%d)\n", rres); } return rres == eres; }
static bool test_useful_ladder(struct board *b, char *arg) { enum stone color = str2stone(arg); arg += 2; coord_t *cc = str2coord(arg, board_size(b)); coord_t c = *cc; coord_done(cc); arg += strcspn(arg, " ") + 1; int eres = atoi(arg); board_print_test(2, b); if (DEBUGL(1)) printf("useful_ladder %s %s %d...\t", stone2str(color), coord2sstr(c, b), eres); assert(board_at(b, c) == S_NONE); group_t atari_neighbor = board_get_atari_neighbor(b, c, color); assert(atari_neighbor); int ladder = is_ladder(b, c, atari_neighbor, true); assert(ladder); int rres = useful_ladder(b, atari_neighbor); if (rres == eres) { if (DEBUGL(1)) printf("OK\n"); } else { if (debug_level <= 2) { board_print_test(0, b); printf("useful_ladder %s %s %d...\t", stone2str(color), coord2sstr(c, b), eres); } printf("FAILED (%d)\n", rres); } return (rres == eres); }
void cfg_distances(struct board *b, coord_t start, int *distances, int maxdist) { /* Queue for d+1 spots; no two spots of the same group * should appear in the queue. */ #define qinc(x) (x = ((x + 1) >= board_size2(b) ? ((x) + 1 - board_size2(b)) : (x) + 1)) coord_t queue[board_size2(b)]; int qstart = 0, qstop = 0; foreach_point(b) { distances[c] = board_at(b, c) == S_OFFBOARD ? maxdist + 1 : -1; } foreach_point_end; queue[qstop++] = start; for (int d = 0; d <= maxdist; d++) { /* Process queued moves, while setting the queue * for new wave. */ int qa = qstart, qb = qstop; qstart = qstop; for (int q = qa; q < qb; qinc(q)) { #define cfg_one(coord, grp) do {\ distances[coord] = d; \ foreach_neighbor (b, coord, { \ if (distances[c] < 0 && (!grp || group_at(b, coord) != grp)) { \ queue[qstop] = c; \ qinc(qstop); \ } \ }); \ } while (0) coord_t cq = queue[q]; if (distances[cq] >= 0) continue; /* We already looked here. */ if (board_at(b, cq) == S_NONE) { cfg_one(cq, 0); } else { group_t g = group_at(b, cq); foreach_in_group(b, g) { cfg_one(c, g); } foreach_in_group_end; } #undef cfg_one } }
/* Note that @to_play is important; e.g. consider snapback, it's good * to play at the last liberty by attacker, but not defender. */ static __attribute__((always_inline)) bool capturable_group(struct board *b, enum stone capturer, coord_t c, enum stone to_play) { group_t g = group_at(b, c); if (likely(board_at(b, c) != stone_other(capturer) || board_group_info(b, g).libs > 1)) return false; return can_play_on_lib(b, g, to_play); }
static bool test_moggy_moves(struct board *b, char *arg) { int runs = 1000; coord_t *cc = str2coord(arg, board_size(b)); struct move last; last.coord = *cc; coord_done(cc); last.color = board_at(b, last.coord); assert(last.color == S_BLACK || last.color == S_WHITE); enum stone color = stone_other(last.color); arg += strcspn(arg, " ") + 1; b->last_move = last; board_print(b, stderr); // Always print board so we see last move char e_arg[128]; sprintf(e_arg, "runs=%i", runs); struct engine *e = engine_replay_init(e_arg, b); if (DEBUGL(1)) printf("moggy moves %s, %s to play. Sampling moves (%i runs)...\n\n", coord2sstr(last.coord, b), stone2str(color), runs); int played_[b->size2 + 2]; memset(played_, 0, sizeof(played_)); int *played = played_ + 2; // allow storing pass/resign int most_played = 0; replay_sample_moves(e, b, color, played, &most_played); /* Show moves stats */ for (int k = most_played; k > 0; k--) for (coord_t c = resign; c < b->size2; c++) if (played[c] == k) printf("%3s: %.2f%%\n", coord2str(c, b), (float)k * 100 / runs); engine_done(e); return true; // Not much of a unit test right now =) }
/* Syntax: * moggy status (last_move) coord [coord...] * Play number of random games starting from last_move * * moggy status coord [coord...] * moggy status (b) coord [coord...] * Black to play, pick random white last move * * moggy status (w) coord [coord...] * White to play, pick random black last move */ static bool test_moggy_status(struct board *board, char *arg) { int games = 4000; coord_t status_at[10]; int n = 0; enum stone color = S_BLACK; int pick_random = true; // Pick random last move for each game while (*arg && *arg != '#') { if (*arg == ' ' || *arg == '\t') { arg++; continue; } if (!strncmp(arg, "(b)", 3)) color = S_BLACK; else if (!strncmp(arg, "(w)", 3)) color = S_WHITE; else if (*arg == '(') { /* Optional "(last_move)" argument */ arg++; assert(isalpha(*arg)); pick_random = false; struct move last; last.coord = str2scoord(arg, board_size(board)); last.color = board_at(board, last.coord); assert(last.color == S_BLACK || last.color == S_WHITE); color = stone_other(last.color); board->last_move = last; } else { assert(isalpha(*arg)); status_at[n++] = str2scoord(arg, board_size(board)); } arg += strcspn(arg, " \t"); } board_print(board, stderr); if (DEBUGL(1)) { printf("moggy status "); for (int i = 0; i < n; i++) printf("%s%s", coord2sstr(status_at[i], board), (i != n-1 ? " " : "")); printf(", %s to play. Playing %i games %s...\n", stone2str(color), games, (pick_random ? "(random last move) " : "")); } struct playout_policy *policy = playout_moggy_init(NULL, board, NULL); struct playout_setup setup = { .gamelen = MAX_GAMELEN }; struct board_ownermap ownermap; ownermap.playouts = 0; ownermap.map = malloc2(board_size2(board) * sizeof(ownermap.map[0])); memset(ownermap.map, 0, board_size2(board) * sizeof(ownermap.map[0])); /* Get final status estimate after a number of moggy games */ int wr = 0; double time_start = time_now(); for (int i = 0; i < games; i++) { struct board b; board_copy(&b, board); if (pick_random) pick_random_last_move(&b, color); int score = play_random_game(&setup, &b, color, NULL, &ownermap, policy); if (color == S_WHITE) score = -score; wr += (score > 0); board_done_noalloc(&b); } double elapsed = time_now() - time_start; printf("moggy status in %.1fs, %i games/s\n\n", elapsed, (int)((float)games / elapsed)); int wr_black = wr * 100 / games; int wr_white = (games - wr) * 100 / games; if (wr_black > wr_white) printf("Winrate: [ black %i%% ] white %i%%\n\n", wr_black, wr_white); else printf("Winrate: black %i%% [ white %i%% ]\n\n", wr_black, wr_white); board_print_ownermap(board, stderr, &ownermap); for (int i = 0; i < n; i++) { coord_t c = status_at[i]; enum stone color = (ownermap.map[c][S_BLACK] > ownermap.map[c][S_WHITE] ? S_BLACK : S_WHITE); fprintf(stderr, "%3s owned by %s: %i%%\n", coord2sstr(c, board), stone2str(color), ownermap.map[c][color] * 100 / ownermap.playouts); } free(ownermap.map); playout_policy_done(policy); return true; // Not much of a unit test right now =) }
static void board_load(struct board *b, FILE *f, unsigned int size) { board_printed = false; board_resize(b, size); board_clear(b); for (int y = size - 1; y >= 0; y--) { char line[256]; if (!fgets(line, sizeof(line), f)) { fprintf(stderr, "Premature EOF.\n"); exit(EXIT_FAILURE); } line[strlen(line) - 1] = 0; // chomp if (strlen(line) != size * 2 - 1) { fprintf(stderr, "Line not %d char long: %s\n", size * 2 - 1, line); exit(EXIT_FAILURE); } for (unsigned int i = 0; i < size * 2; i++) { enum stone s; switch (line[i]) { case '.': s = S_NONE; break; case 'X': s = S_BLACK; break; case 'O': s = S_WHITE; break; default: fprintf(stderr, "Invalid stone '%c'\n", line[i]); exit(EXIT_FAILURE); } i++; if (line[i] != ' ' && i/2 < size - 1) { fprintf(stderr, "No space after stone %i: '%c'\n", i/2 + 1, line[i]); exit(EXIT_FAILURE); } if (s == S_NONE) continue; struct move m = { .color = s, .coord = coord_xy(b, i/2 + 1, y + 1) }; if (board_play(b, &m) < 0) { fprintf(stderr, "Failed to play %s %s\n", stone2str(s), coord2sstr(m.coord, b)); board_print(b, stderr); exit(EXIT_FAILURE); } } } int suicides = b->captures[S_BLACK] || b->captures[S_WHITE]; assert(!suicides); } static void set_ko(struct board *b, char *arg) { assert(isalpha(*arg)); struct move last; last.coord = str2scoord(arg, board_size(b)); last.color = board_at(b, last.coord); assert(last.color == S_BLACK || last.color == S_WHITE); b->last_move = last; /* Sanity checks */ group_t g = group_at(b, last.coord); assert(board_group_info(b, g).libs == 1); assert(group_stone_count(b, g, 2) == 1); coord_t lib = board_group_info(b, g).lib[0]; assert(board_is_eyelike(b, lib, last.color)); b->ko.coord = lib; b->ko.color = stone_other(last.color); }
static bool middle_ladder_walk(Board *b, Board *bset, group_t laddered, Coord nextmove, Stone lcolor) { assert(group_at(b, laddered)->liberties == 1); /* First, escape. */ /* if (DEBUGL(6)) fprintf(stderr, " ladder escape %s\n", coord2sstr(nextmove, b)); */ GroupId4 ids; if (!TryPlay2(b, nextmove, &ids)) error("The play should never be wrong!"); Play(b, &ids); // laddered = group_at(b, laddered); /* if (DEBUGL(8)) { board_print(b, stderr); fprintf(stderr, "%s c %d\n", coord2sstr(laddered, b), board_group_info(b, laddered).libs); } */ int laddered_libs = b->_groups[laddered].liberties; if (laddered_libs == 1) { /* if (DEBUGL(6)) fprintf(stderr, "* we can capture now\n"); */ return true; } if (laddered_libs > 2) { /* if (DEBUGL(6)) fprintf(stderr, "* we are free now\n"); */ return false; } FOR4(nextmove, _, c) { if (board_at(b, c) == OPPONENT(lcolor) && group_at(b, c)->liberties == 1) { /* We can capture one of the ladder stones * anytime later. */ /* XXX: If we were very lucky, capturing * this stone will not help us escape. * That should be pretty rate. */ /* if (DEBUGL(6)) fprintf(stderr, "* can capture chaser\n"); */ return false; } } ENDFOR4 /* Now, consider alternatives. */ int liblist[2], libs = 0; Coord tmp_libs[2]; get_nlibs_of_group(b, laddered, 2, tmp_libs); for (int i = 0; i < 2; i++) { Coord ataristone = tmp_libs[i]; Coord escape = tmp_libs[1 - i]; if (immediate_liberty_count(b, escape) > 2 + NEIGHBOR4(ataristone, escape)) { /* Too much free space, ignore. */ continue; } liblist[libs++] = i; } /* Try out the alternatives. */ bool is_ladder = false; for (int i = 0; !is_ladder && i < libs; i++) { Board *b2 = b; if (i != libs - 1) { b2 = bset++; CopyBoard(b2, b); } Coord libs_b2[2]; get_nlibs_of_group(b2, laddered, 2, libs_b2); Coord ataristone = libs_b2[liblist[i]]; // Coord escape = board_group_info(b2, laddered).lib[1 - liblist[i]]; struct move m = { ataristone, OPPONENT(lcolor) }; bool play_successful = TryPlay2(b2, ataristone, &ids); if (play_successful) Play(b2, &ids); /* If we just played self-atari, abandon ship. */ /* XXX: If we were very lucky, capturing this stone will * not help us escape. That should be pretty rate. */ /* if (DEBUGL(6)) fprintf(stderr, "(%d=%d) ladder atari %s (%d libs)\n", i, res, coord2sstr(ataristone, b2), board_group_info(b2, group_at(b2, ataristone)).libs); */ if (play_successful && group_at(b2, ataristone)->liberties > 1) { Coord last_lib = get_nlibs_of_group(b2, laddered, 1, NULL); is_ladder = middle_ladder_walk(b2, bset, laddered, last_lib, lcolor); } /* Why we need to do deallocation? if (i != libs - 1) { board_done_noalloc(b2); } */ } /* if (DEBUGL(6)) fprintf(stderr, "propagating %d\n", is_ladder); */ return is_ladder; }