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 coord_t * replay_genmove(struct engine *e, struct board *b, struct time_info *ti, enum stone color, bool pass_all_alive) { struct replay *r = e->data; struct playout_setup s; memset(&s, 0, sizeof(s)); coord_t coord = r->playout->choose(r->playout, &s, b, color); if (!is_pass(coord)) { struct move m; m.coord = coord; m.color = color; if (board_play(b, &m) >= 0) goto have_move; if (DEBUGL(2)) { fprintf(stderr, "Pre-picked move %d,%d is ILLEGAL:\n", coord_x(coord, b), coord_y(coord, b)); board_print(b, stderr); } } /* Defer to uniformly random move choice. */ board_play_random(b, color, &coord, (ppr_permit) r->playout->permit, r->playout); have_move: if (!group_at(b, coord)) { /* This was suicide. Just pass. */ /* XXX: We should check for non-suicide alternatives. */ return coord_pass(); } return coord_copy(coord); }
static coord_t * random_genmove(struct engine *e, struct board *b, struct time_info *ti, enum stone color, bool pass_all_alive) { /* Play a random coordinate. However, we must also guard * against suicide moves; repeat playing while it's a suicide * unless we keep suiciding; in that case, we probably don't * have any other moves available and we pass. */ coord_t coord; int i = 0; bool suicide = false; do { /* board_play_random() actually plays the move too; * this is desirable for MC simulations but not within * the genmove. Make a scratch new board for it. */ struct board b2; board_copy(&b2, b); board_play_random(&b2, color, &coord, NULL, NULL); suicide = (coord != pass && !group_at(&b2, coord)); board_done_noalloc(&b2); } while (suicide && i++ < 100); return coord_copy(suicide ? pass : coord); }
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; }
/* 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); }
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 } }
bool can_countercapture(struct board *b, enum stone owner, group_t g, enum stone to_play, struct move_queue *q, int tag) { if (b->clen < 2) return false; unsigned int qmoves_prev = q ? q->moves : 0; foreach_in_group(b, g) { foreach_neighbor(b, c, { if (!capturable_group(b, owner, c, to_play)) continue; if (!q) { return true; } mq_add(q, board_group_info(b, group_at(b, c)).lib[0], tag); mq_nodup(q); });
int uct_playout(struct uct *u, struct board *b, enum stone player_color, struct tree *t) { struct board b2; board_copy(&b2, b); struct playout_amafmap amaf; amaf.gamelen = amaf.game_baselen = 0; /* Walk the tree until we find a leaf, then expand it and do * a random playout. */ struct tree_node *n = t->root; enum stone node_color = stone_other(player_color); assert(node_color == t->root_color); /* Make sure the root node is expanded. */ if (tree_leaf_node(n) && !__sync_lock_test_and_set(&n->is_expanded, 1)) tree_expand_node(t, n, &b2, player_color, u, 1); /* Tree descent history. */ /* XXX: This is somewhat messy since @n and descent[dlen-1].node are * redundant. */ struct uct_descent descent[DESCENT_DLEN]; descent[0].node = n; descent[0].lnode = NULL; int dlen = 1; /* Total value of the sequence. */ struct move_stats seq_value = { .playouts = 0 }; /* The last "significant" node along the descent (i.e. node * with higher than configured number of playouts). For black * and white. */ struct tree_node *significant[2] = { NULL, NULL }; if (n->u.playouts >= u->significant_threshold) significant[node_color - 1] = n; int result; int pass_limit = (board_size(&b2) - 2) * (board_size(&b2) - 2) / 2; int passes = is_pass(b->last_move.coord) && b->moves > 0; /* debug */ static char spaces[] = "\0 "; /* /debug */ if (UDEBUGL(8)) fprintf(stderr, "--- UCT walk with color %d\n", player_color); while (!tree_leaf_node(n) && passes < 2) { spaces[dlen - 1] = ' '; spaces[dlen] = 0; /*** Choose a node to descend to: */ /* Parity is chosen already according to the child color, since * it is applied to children. */ node_color = stone_other(node_color); int parity = (node_color == player_color ? 1 : -1); assert(dlen < DESCENT_DLEN); descent[dlen] = descent[dlen - 1]; if (u->local_tree && (!descent[dlen].lnode || descent[dlen].node->d >= u->tenuki_d)) { /* Start new local sequence. */ /* Remember that node_color already holds color of the * to-be-found child. */ descent[dlen].lnode = node_color == S_BLACK ? t->ltree_black : t->ltree_white; } if (!u->random_policy_chance || fast_random(u->random_policy_chance)) u->policy->descend(u->policy, t, &descent[dlen], parity, b2.moves > pass_limit); else u->random_policy->descend(u->random_policy, t, &descent[dlen], parity, b2.moves > pass_limit); /*** Perform the descent: */ if (descent[dlen].node->u.playouts >= u->significant_threshold) { significant[node_color - 1] = descent[dlen].node; } seq_value.playouts += descent[dlen].value.playouts; seq_value.value += descent[dlen].value.value * descent[dlen].value.playouts; n = descent[dlen++].node; assert(n == t->root || n->parent); if (UDEBUGL(7)) fprintf(stderr, "%s+-- UCT sent us to [%s:%d] %d,%f\n", spaces, coord2sstr(node_coord(n), t->board), node_coord(n), n->u.playouts, tree_node_get_value(t, parity, n->u.value)); /* Add virtual loss if we need to; this is used to discourage * other threads from visiting this node in case of multiple * threads doing the tree search. */ if (u->virtual_loss) stats_add_result(&n->u, node_color == S_BLACK ? 0.0 : 1.0, u->virtual_loss); assert(node_coord(n) >= -1); record_amaf_move(&amaf, node_coord(n)); struct move m = { node_coord(n), node_color }; int res = board_play(&b2, &m); if (res < 0 || (!is_pass(m.coord) && !group_at(&b2, m.coord)) /* suicide */ || b2.superko_violation) { if (UDEBUGL(4)) { for (struct tree_node *ni = n; ni; ni = ni->parent) fprintf(stderr, "%s<%"PRIhash"> ", coord2sstr(node_coord(ni), t->board), ni->hash); fprintf(stderr, "marking invalid %s node %d,%d res %d group %d spk %d\n", stone2str(node_color), coord_x(node_coord(n),b), coord_y(node_coord(n),b), res, group_at(&b2, m.coord), b2.superko_violation); } n->hints |= TREE_HINT_INVALID; result = 0; goto end; } if (is_pass(node_coord(n))) passes++; else passes = 0; enum stone next_color = stone_other(node_color); /* We need to make sure only one thread expands the node. If * we are unlucky enough for two threads to meet in the same * node, the latter one will simply do another simulation from * the node itself, no big deal. t->nodes_size may exceed * the maximum in multi-threaded case but not by much so it's ok. * The size test must be before the test&set not after, to allow * expansion of the node later if enough nodes have been freed. */ if (tree_leaf_node(n) && n->u.playouts - u->virtual_loss >= u->expand_p && t->nodes_size < u->max_tree_size && !__sync_lock_test_and_set(&n->is_expanded, 1)) tree_expand_node(t, n, &b2, next_color, u, -parity); } amaf.game_baselen = amaf.gamelen; if (t->use_extra_komi && u->dynkomi->persim) { b2.komi += round(u->dynkomi->persim(u->dynkomi, &b2, t, n)); } if (passes >= 2) { /* XXX: No dead groups support. */ floating_t score = board_official_score(&b2, NULL); /* Result from black's perspective (no matter who * the player; black's perspective is always * what the tree stores. */ result = - (score * 2); if (UDEBUGL(5)) fprintf(stderr, "[%d..%d] %s p-p scoring playout result %d (W %f)\n", player_color, node_color, coord2sstr(node_coord(n), t->board), result, score); if (UDEBUGL(6)) board_print(&b2, stderr); board_ownermap_fill(&u->ownermap, &b2); } else { // assert(tree_leaf_node(n)); /* In case of parallel tree search, the assertion might * not hold if two threads chew on the same node. */ result = uct_leaf_node(u, &b2, player_color, &amaf, descent, &dlen, significant, t, n, node_color, spaces); } if (u->policy->wants_amaf && u->playout_amaf_cutoff) { unsigned int cutoff = amaf.game_baselen; cutoff += (amaf.gamelen - amaf.game_baselen) * u->playout_amaf_cutoff / 100; amaf.gamelen = cutoff; } /* Record the result. */ assert(n == t->root || n->parent); floating_t rval = scale_value(u, b, result); u->policy->update(u->policy, t, n, node_color, player_color, &amaf, &b2, rval); if (t->use_extra_komi) { stats_add_result(&u->dynkomi->score, result / 2, 1); stats_add_result(&u->dynkomi->value, rval, 1); } if (u->local_tree && n->parent && !is_pass(node_coord(n)) && dlen > 0) { /* Get the local sequences and record them in ltree. */ /* We will look for sequence starts in our descent * history, then run record_local_sequence() for each * found sequence start; record_local_sequence() may * pick longer sequences from descent history then, * which is expected as it will create new lnodes. */ enum stone seq_color = player_color; /* First move always starts a sequence. */ record_local_sequence(u, t, &b2, descent, dlen, 1, seq_color); seq_color = stone_other(seq_color); for (int dseqi = 2; dseqi < dlen; dseqi++, seq_color = stone_other(seq_color)) { if (u->local_tree_allseq) { /* We are configured to record all subsequences. */ record_local_sequence(u, t, &b2, descent, dlen, dseqi, seq_color); continue; } if (descent[dseqi].node->d >= u->tenuki_d) { /* Tenuki! Record the fresh sequence. */ record_local_sequence(u, t, &b2, descent, dlen, dseqi, seq_color); continue; } if (descent[dseqi].lnode && !descent[dseqi].lnode) { /* Record result for in-descent picked sequence. */ record_local_sequence(u, t, &b2, descent, dlen, dseqi, seq_color); continue; } } } end: /* We need to undo the virtual loss we added during descend. */ if (u->virtual_loss) { floating_t loss = node_color == S_BLACK ? 0.0 : 1.0; for (; n->parent; n = n->parent) { stats_rm_result(&n->u, loss, u->virtual_loss); loss = 1.0 - loss; } } board_done_noalloc(&b2); return result; } int uct_playouts(struct uct *u, struct board *b, enum stone color, struct tree *t, struct time_info *ti) { int i; if (ti && ti->dim == TD_GAMES) { for (i = 0; t->root->u.playouts <= ti->len.games && !uct_halt; i++) uct_playout(u, b, color, t); } else { for (i = 0; !uct_halt; i++) uct_playout(u, b, color, t); } return i; }
static coord_t * montecarlo_genmove(struct engine *e, struct board *b, struct time_info *ti, enum stone color, bool pass_all_alive) { struct montecarlo *mc = e->data; if (ti->dim == TD_WALLTIME) { fprintf(stderr, "Warning: TD_WALLTIME time mode not supported, resetting to defaults.\n"); ti->period = TT_NULL; } if (ti->period == TT_NULL) { ti->period = TT_MOVE; ti->dim = TD_GAMES; ti->len.games = MC_GAMES; } struct time_stop stop; time_stop_conditions(ti, b, 20, 40, 3.0, &stop); /* resign when the hope for win vanishes */ coord_t top_coord = resign; floating_t top_ratio = mc->resign_ratio; /* We use [0] for pass. Normally, this is an inaccessible corner * of board margin. */ struct move_stat moves[board_size2(b)]; memset(moves, 0, sizeof(moves)); int losses = 0; int i, superko = 0, good_games = 0; for (i = 0; i < stop.desired.playouts; i++) { assert(!b->superko_violation); struct board b2; board_copy(&b2, b); coord_t coord; board_play_random(&b2, color, &coord, NULL, NULL); if (!is_pass(coord) && !group_at(&b2, coord)) { /* Multi-stone suicide. We play chinese rules, * so we can't consider this. (Note that we * unfortunately still consider this in playouts.) */ if (DEBUGL(4)) { fprintf(stderr, "SUICIDE DETECTED at %d,%d:\n", coord_x(coord, b), coord_y(coord, b)); board_print(b, stderr); } continue; } if (DEBUGL(3)) fprintf(stderr, "[%d,%d color %d] playing random game\n", coord_x(coord, b), coord_y(coord, b), color); struct playout_setup ps = { .gamelen = mc->gamelen }; int result = play_random_game(&ps, &b2, color, NULL, NULL, mc->playout); board_done_noalloc(&b2); if (result == 0) { /* Superko. We just ignore this playout. * And play again. */ if (unlikely(superko > 2 * stop.desired.playouts)) { /* Uhh. Triple ko, or something? */ if (MCDEBUGL(0)) fprintf(stderr, "SUPERKO LOOP. I will pass. Did we hit triple ko?\n"); goto pass_wins; } /* This playout didn't count; we should not * disadvantage moves that lead to a superko. * And it is supposed to be rare. */ i--, superko++; continue; } if (MCDEBUGL(3)) fprintf(stderr, "\tresult for other player: %d\n", result); int pos = is_pass(coord) ? 0 : coord; good_games++; moves[pos].games++; losses += result > 0; moves[pos].wins += 1 - (result > 0); if (unlikely(!losses && i == mc->loss_threshold)) { /* We played out many games and didn't lose once yet. * This game is over. */ break; } } if (!good_games) { /* No moves to try??? */ if (MCDEBUGL(0)) { fprintf(stderr, "OUT OF MOVES! I will pass. But how did this happen?\n"); board_print(b, stderr); } pass_wins: top_coord = pass; top_ratio = 0.5; goto move_found; } foreach_point(b) { if (b->moves < 3) { /* Simple heuristic: avoid opening too low. Do not * play on second or first line as first white or * first two black moves.*/ if (coord_x(c, b) < 3 || coord_x(c, b) > board_size(b) - 4 || coord_y(c, b) < 3 || coord_y(c, b) > board_size(b) - 4) continue; } floating_t ratio = (floating_t) moves[c].wins / moves[c].games; /* Since pass is [0,0], we will pass only when we have nothing * better to do. */ if (ratio >= top_ratio) { top_ratio = ratio; top_coord = c == 0 ? pass : c; } } foreach_point_end; if (MCDEBUGL(2)) { board_stats_print(b, moves, stderr); } move_found: if (MCDEBUGL(1)) fprintf(stderr, "*** WINNER is %d,%d with score %1.4f (%d games, %d superko)\n", coord_x(top_coord, b), coord_y(top_coord, b), top_ratio, i, superko); return coord_copy(top_coord); } static void montecarlo_done(struct engine *e) { struct montecarlo *mc = e->data; playout_policy_done(mc->playout); joseki_done(mc->jdict); } struct montecarlo * montecarlo_state_init(char *arg, struct board *b) { struct montecarlo *mc = calloc2(1, sizeof(struct montecarlo)); mc->debug_level = 1; mc->gamelen = MC_GAMELEN; mc->jdict = joseki_load(b->size); if (arg) { char *optspec, *next = arg; while (*next) { optspec = next; next += strcspn(next, ","); if (*next) { *next++ = 0; } else { *next = 0; } char *optname = optspec; char *optval = strchr(optspec, '='); if (optval) *optval++ = 0; if (!strcasecmp(optname, "debug")) { if (optval) mc->debug_level = atoi(optval); else mc->debug_level++; } else if (!strcasecmp(optname, "gamelen") && optval) { mc->gamelen = atoi(optval); } else if (!strcasecmp(optname, "playout") && optval) { char *playoutarg = strchr(optval, ':'); if (playoutarg) *playoutarg++ = 0; if (!strcasecmp(optval, "moggy")) { mc->playout = playout_moggy_init(playoutarg, b, mc->jdict); } else if (!strcasecmp(optval, "light")) { mc->playout = playout_light_init(playoutarg, b); } else { fprintf(stderr, "MonteCarlo: Invalid playout policy %s\n", optval); } } else { fprintf(stderr, "MonteCarlo: Invalid engine argument %s or missing value\n", optname); } } } if (!mc->playout) mc->playout = playout_light_init(NULL, b); mc->playout->debug_level = mc->debug_level; mc->resign_ratio = 0.1; /* Resign when most games are lost. */ mc->loss_threshold = 5000; /* Stop reading if no loss encountered in first 5000 games. */ return mc; } struct engine * engine_montecarlo_init(char *arg, struct board *b) { struct montecarlo *mc = montecarlo_state_init(arg, b); struct engine *e = calloc2(1, sizeof(struct engine)); e->name = "MonteCarlo"; e->comment = "I'm playing in Monte Carlo. When we both pass, I will consider all the stones on the board alive. If you are reading this, write 'yes'. Please bear with me at the game end, I need to fill the whole board; if you help me, we will both be happier. Filling the board will not lose points (NZ rules)."; e->genmove = montecarlo_genmove; e->done = montecarlo_done; e->data = mc; return e; }
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; }
bool is_middle_ladder(Board *b, Coord coord, group_t laddered, Stone lcolor) { /* TODO: Remove the redundant parameters. */ assert(group_at(b, laddered)->liberties == 1); Coord last_lib = get_nlibs_of_group(b, laddered, 1, NULL); assert(last_lib == coord); assert(group_at(b, laddered)->color == lcolor); /* If we can move into empty space or do not have enough space * to escape, this is obviously not a ladder. */ if (immediate_liberty_count(b, coord) != 2) { /* if (DEBUGL(5)) fprintf(stderr, "no ladder, wrong free space\n"); */ return false; } /* A fair chance for a ladder. Group in atari, with some but limited * space to escape. Time for the expensive stuff - set up a temporary * board and start selective 2-liberty search. */ Board *bset = (Board *)malloc(BOARD_MAX_SIZE * 2 * sizeof(Board)); struct move_queue ccq = { .moves = 0 }; if (can_countercapture(b, lcolor, laddered, lcolor, &ccq, 0)) { /* We could escape by countercapturing a group. * Investigate. */ assert(ccq.moves > 0); for (unsigned int i = 0; i < ccq.moves; i++) { Board b2; CopyBoard(&b2, b); bool is_ladder = middle_ladder_walk(&b2, bset, laddered, ccq.move[i], lcolor); // board_done_noalloc(&b2); if (!is_ladder) { free(bset); return false; } } } Board b2; CopyBoard(&b2, b); Coord last_lib2 = get_nlibs_of_group(&b2, laddered, 1, NULL); bool is_ladder = middle_ladder_walk(&b2, bset, laddered, last_lib2, lcolor); // board_done_noalloc(&b2); free(bset); return is_ladder; } bool wouldbe_ladder(Board *b, group_t group, Coord escapelib, Coord chaselib, Stone lcolor) { assert(b->_groups[group].liberties == 2); assert(b->_groups[group].color == lcolor); /* if (DEBUGL(6)) fprintf(stderr, "would-be ladder check - does %s %s play out chasing move %s?\n", stone2str(lcolor), coord2sstr(escapelib, b), coord2sstr(chaselib, b)); */ if (!NEIGHBOR8(escapelib, chaselib)) { /* if (DEBUGL(5)) fprintf(stderr, "cannot determine ladder for remote simulated stone\n"); */ return false; } if (neighbor_count_at(b, chaselib, lcolor) != 1 || immediate_liberty_count(b, chaselib) != 2) { /* if (DEBUGL(5)) fprintf(stderr, "overly trivial for a ladder\n"); */ return false; } bool is_ladder = false; Board *bset = (Board *)malloc(BOARD_MAX_SIZE * 2 * sizeof(Board)); Board b2; CopyBoard(&b2, b); GroupId4 ids; if (TryPlay(&b2, X(chaselib), Y(chaselib), OPPONENT(lcolor), &ids)) { Play(&b2, &ids); Coord last_lib2 = get_nlibs_of_group(&b2, group, 1, NULL); is_ladder = middle_ladder_walk(&b2, bset, group, last_lib2, lcolor); } // board_done_noalloc(&b2); free(bset); return is_ladder; }