int main(int argc, char * argv[]){ Board * board; int x, y; Pos pos; UCTNode * uct_tree = 0; UCTNode * ptr; board_initialize(); uct_initialize(); board = board_new(); while(1) { board_print(board, stdout); printf("\nBlack: "); if(scanf("%d %d", &x, &y) == -1) break; board_play(board, to_pos(x, y), BLACK); board_print(board, stdout); pos = uct_search(board, WHITE); board_play(board, pos, WHITE); } //for(ptr = uct_tree->child; ptr; ptr = ptr->next) // printf("%d ", ptr->move); puts(""); board_destroy(); uct_destroy(); return 0; }
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); }
UCTNode * uct_select(UCTNode * self, Board * board, Color color) { UCTNode * ptr, * best_ptr; float uct, best_uct = 0; while(1) { best_uct = 0; best_ptr = 0; for(ptr = self->child; ptr; ptr = ptr->next) { if(ptr->move == nil) continue; if(ptr->visits == 0) uct = 10000. + (rand() & 0xFF); else { uct = UCT_K * sqrtf( logf(self->visits) / ptr->visits ) + 1.0 * ptr->wins / ptr->visits; } if(uct > best_uct) { best_uct = uct; best_ptr = ptr; } } if(board_play(board, best_ptr->move, color)) break; else best_ptr->move = nil; } return best_ptr; }
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; }
struct fbook * fbook_init(char *filename, struct board *b) { if (fbcache && fbcache->bsize == board_size(b) && fbcache->handicap == b->handicap) return fbcache; FILE *f = fopen(filename, "r"); if (!f) { perror(filename); return NULL; } struct fbook *fbook = calloc(1, sizeof(*fbook)); fbook->bsize = board_size(b); fbook->handicap = b->handicap; /* We do not set handicap=1 in case of too low komi on purpose; * we want to go with the no-handicap fbook for now. */ for (int i = 0; i < 1<<fbook_hash_bits; i++) fbook->moves[i] = pass; if (DEBUGL(1)) fprintf(stderr, "Loading opening fbook %s...\n", filename); /* Scratch board where we lay out the sequence; * one for each transposition. */ struct board *bs[8]; for (int i = 0; i < 8; i++) { bs[i] = board_init(NULL); board_resize(bs[i], fbook->bsize - 2); } char linebuf[1024]; while (fgets(linebuf, sizeof(linebuf), f)) { char *line = linebuf; linebuf[strlen(linebuf) - 1] = 0; // chop /* Format of line is: * BSIZE COORD COORD COORD... | COORD * BSIZE/HANDI COORD COORD COORD... | COORD * We descend up to |, then add the new node * with value minimax(1000), forcing UCT to * always pick that node immediately. */ int bsize = strtol(line, &line, 10); if (bsize != fbook->bsize - 2) continue; int handi = 0; if (*line == '/') { line++; handi = strtol(line, &line, 10); } if (handi != fbook->handicap) continue; while (isspace(*line)) line++; for (int i = 0; i < 8; i++) { board_clear(bs[i]); bs[i]->last_move.color = S_WHITE; } while (*line != '|') { coord_t *c = str2coord(line, fbook->bsize); for (int i = 0; i < 8; i++) { coord_t coord = coord_transform(b, *c, i); struct move m = { .coord = coord, .color = stone_other(bs[i]->last_move.color) }; int ret = board_play(bs[i], &m); assert(ret >= 0); } coord_done(c); while (!isspace(*line)) line++; while (isspace(*line)) line++; } line++; while (isspace(*line)) line++; /* In case of multiple candidates, pick one with * exponentially decreasing likelihood. */ while (strchr(line, ' ') && fast_random(2)) { line = strchr(line, ' '); while (isspace(*line)) line++; // fprintf(stderr, "<%s> skip to %s\n", linebuf, line); } coord_t *c = str2coord(line, fbook->bsize); for (int i = 0; i < 8; i++) { coord_t coord = coord_transform(b, *c, i); #if 0 char conflict = is_pass(fbook->moves[bs[i]->hash & fbook_hash_mask]) ? '+' : 'C'; if (conflict == 'C') for (int j = 0; j < i; j++) if (bs[i]->hash == bs[j]->hash) conflict = '+'; if (conflict == 'C') { hash_t hi = bs[i]->hash; while (!is_pass(fbook->moves[hi & fbook_hash_mask]) && fbook->hashes[hi & fbook_hash_mask] != bs[i]->hash) hi++; if (fbook->hashes[hi & fbook_hash_mask] == bs[i]->hash) hi = 'c'; } fprintf(stderr, "%c %"PRIhash":%"PRIhash" (<%d> %s)\n", conflict, bs[i]->hash & fbook_hash_mask, bs[i]->hash, i, linebuf); #endif hash_t hi = bs[i]->hash; while (!is_pass(fbook->moves[hi & fbook_hash_mask]) && fbook->hashes[hi & fbook_hash_mask] != bs[i]->hash) hi++; fbook->moves[hi & fbook_hash_mask] = coord; fbook->hashes[hi & fbook_hash_mask] = bs[i]->hash; fbook->movecnt++; } coord_done(c); } for (int i = 0; i < 8; i++) { board_done(bs[i]); } fclose(f); if (!fbook->movecnt) { /* Empty book is not worth the hassle. */ fbook_done(fbook); return NULL; } struct fbook *fbold = fbcache; fbcache = fbook; if (fbold) fbook_done(fbold); return fbook; } void fbook_done(struct fbook *fbook) { if (fbook != fbcache) free(fbook); }
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); }