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;
int coord_edge_distance(coord_t c, struct board *b) { int x = coord_x(c, b), y = coord_y(c, b); int dx = x > board_size(b) / 2 ? board_size(b) - x : x; int dy = y > board_size(b) / 2 ? board_size(b) - y : y; return (dx < dy ? dx : dy) - 1 /* S_OFFBOARD */; }
void board_stats_print(struct board *board, struct move_stat *moves, FILE *f) { fprintf(f, "\n "); int x, y; char asdf[] = "ABCDEFGHJKLMNOPQRSTUVWXYZ"; for (x = 1; x < board_size(board) - 1; x++) fprintf(f, "%c ", asdf[x - 1]); fprintf(f, "\n +-"); for (x = 1; x < board_size(board) - 1; x++) fprintf(f, "-----"); fprintf(f, "+\n"); for (y = board_size(board) - 2; y >= 1; y--) { fprintf(f, "%2d | ", y); for (x = 1; x < board_size(board) - 1; x++) if (moves[y * board_size(board) + x].games) fprintf(f, "%0.2f ", (floating_t) moves[y * board_size(board) + x].wins / moves[y * board_size(board) + x].games); else fprintf(f, "---- "); fprintf(f, "| "); for (x = 1; x < board_size(board) - 1; x++) fprintf(f, "%4d ", moves[y * board_size(board) + x].games); fprintf(f, "|\n"); } fprintf(f, " +-"); for (x = 1; x < board_size(board) - 1; x++) fprintf(f, "-----"); fprintf(f, "+\n"); }
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); }
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 coord_t coord_transform(struct board *b, coord_t coord, int i) { #define HASH_VMIRROR 1 #define HASH_HMIRROR 2 #define HASH_XYFLIP 4 if (i & HASH_VMIRROR) { coord = coord_xy(b, coord_x(coord, b), board_size(b) - 1 - coord_y(coord, b)); } if (i & HASH_HMIRROR) { coord = coord_xy(b, board_size(b) - 1 - coord_x(coord, b), coord_y(coord, b)); } if (i & HASH_XYFLIP) { coord = coord_xy(b, coord_y(coord, b), coord_x(coord, b)); } return coord; }
void board_resize(struct board *board, int size) { #ifdef BOARD_SIZE assert(board_size(board) == size + 2); #endif assert(size <= BOARD_MAX_SIZE); board->size = size + 2 /* S_OFFBOARD margin */; board->size2 = board_size(board) * board_size(board); board->bits2 = 1; while ((1 << board->bits2) < board->size2) board->bits2++; if (board->b) free(board->b); size_t asize = board_alloc(board); memset(board->b, 0, asize); }
int main(int argc, char** argv) { int size_of_board; //BOARD'IN BUYUKLUGU char cells[21][21]; //CHAR TIPINDE 2D ARRAY int check_player_move=1; // PLAYERIN ANLAMLI HAREKETINI KONTROL int player_can_move=1;//Playerin hamlesinin olup olmadigi kontrol edilir int computer_can_move=1;//Computerin hamlesinin olup olmadigi kontrol edilir //Gecerli bir size of board degeri girilene kadar, //kullanicidan size of board degeri istenir. size_of_board=board_size(); //Hucrelerin oyun basi degerleri verilir first_view(size_of_board, cells); //Guncel board ekrana cizilir. paint_board(size_of_board, cells); //Eger player veya computer hamle yapabiliyorsa while(player_can_move==1 || computer_can_move==1) { computer_can_move=1; //Player yapacak hamleye sahip mi degil mi player_can_move=does_player_have_move(size_of_board, cells); //Eger Player Hamle yapabiliyorsa if(player_can_move) { while(check_player_move) { //Player Hareketi //Legal bir hareket girilene kadar donguden cikmaz check_player_move=for_player_move(size_of_board, cells); } check_player_move=1; //Guncel board ekrana cizilir. paint_board(size_of_board, cells); } //Computer Hareketi for_computer_move(size_of_board, cells, &computer_can_move); //Guncel board ekrana cizilir. paint_board(size_of_board, cells); cout <<"\n"; } //Skor Yazilir scoreboard(size_of_board, cells); return 0; }
static floating_t board_game_portion(struct dynkomi_adaptive *a, struct board *b) { if (!a->adapt_aport) { int total_moves = b->moves + 2 * board_estimated_moves_left(b); return (floating_t) b->moves / total_moves; } else { int brsize = board_size(b) - 2; return 1.0 - (floating_t) b->flen / (brsize * brsize); } }
char* print_score_histogram(Position *pos, int score_count[2*N*N+1]) { int cmax=0, nc=0, size2=board_size(pos)*board_size(pos); for (int i=0; i <= 2*N*N+1 ; i++) if (score_count[i] > cmax) cmax = score_count[i]; if (cmax > 0) { for (int s=-size2 ; s <= size2 ; s++) { int n = (50*score_count[s+N*N]) / cmax; char c; if (s < 0) c = '-'; else if (s == 0) c = '='; else c = '+'; nc += sprintf(buf + nc, "%5d %5d ", s, score_count[s+N*N]); for (int i=0 ; i < n ; i++) nc += sprintf(buf + nc, "%c", c); nc += sprintf(buf + nc, "\n"); } } return buf; }
bool board_stone_radar(struct board *b, coord_t coord, int distance) { int bounds[4] = { coord_x(coord, b) - distance, coord_y(coord, b) - distance, coord_x(coord, b) + distance, coord_y(coord, b) + distance }; for (int i = 0; i < 4; i++) if (bounds[i] < 1) bounds[i] = 1; else if (bounds[i] > board_size(b) - 2) bounds[i] = board_size(b) - 2; for (int x = bounds[0]; x <= bounds[2]; x++) for (int y = bounds[1]; y <= bounds[3]; y++) if (board_atxy(b, x, y) != S_NONE) { /* fprintf(stderr, "radar %d,%d,%d: %d,%d (%d)\n", coord_x(coord, b), coord_y(coord, b), distance, x, y, board_atxy(b, x, y)); */ return true; } return false; }
/* Adjust the recommended per-move time based on the current game phase. * We expect stop->worst to be total time available, stop->desired the current * per-move time allocation, and set stop->desired to adjusted per-move time. */ static void time_stop_phase_adjust(struct board *b, int fuseki_end, int yose_start, struct time_stop *stop) { int bsize = (board_size(b)-2)*(board_size(b)-2); fuseki_end = fuseki_end * bsize / 100; // move nb at fuseki end yose_start = yose_start * bsize / 100; // move nb at yose start assert(fuseki_end < yose_start); /* No adjustments in yose. */ if (b->moves >= yose_start) return; int moves_to_yose = (yose_start - b->moves) / 2; // ^- /2 because we only consider the moves we have to play ourselves int left_at_yose_start = board_estimated_moves_left(b) - moves_to_yose; if (left_at_yose_start < MIN_MOVES_LEFT) left_at_yose_start = MIN_MOVES_LEFT; /* This particular value of middlegame_time will continuously converge * to effective "yose_time" value as we approach yose_start. */ double middlegame_time = stop->worst.time / left_at_yose_start; if (middlegame_time < stop->desired.time) return; if (b->moves < fuseki_end) { assert(fuseki_end > 0); /* At the game start, use stop->desired.time (rather * conservative estimate), then gradually prolong it. */ double beta = b->moves / fuseki_end; stop->desired.time = middlegame_time * beta + stop->desired.time * (1 - beta); } else { assert(b->moves < yose_start); /* Middlegame, start with relatively large value, then * converge to the uniform-timeslice yose value. */ stop->desired.time = middlegame_time; } }
static coord_t * proof_genmove(struct engine *e, struct board *b, struct time_info *ti, enum stone color, bool pass_all_alive) { coord_t *coord; fprintf(stderr, "HELLO WORLD!"); struct board b2; board_copy(&b2, b); for (int i=0; i<=19; i++){ for (int j=0; j<=19; j++){ coord = coord_init(i,j,board_size(&b2)); if (board_is_valid_play(&b2, color, *coord)) return coord; } } *coord = -1; return coord; }
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 =) }
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 int uct_leaf_node(struct uct *u, struct board *b, enum stone player_color, struct playout_amafmap *amaf, struct uct_descent *descent, int *dlen, struct tree_node *significant[2], struct tree *t, struct tree_node *n, enum stone node_color, char *spaces) { enum stone next_color = stone_other(node_color); int parity = (next_color == player_color ? 1 : -1); if (UDEBUGL(7)) fprintf(stderr, "%s*-- UCT playout #%d start [%s] %f\n", spaces, n->u.playouts, coord2sstr(node_coord(n), t->board), tree_node_get_value(t, parity, n->u.value)); struct uct_playout_callback upc = { .uct = u, .tree = t, /* TODO: Don't necessarily restart the sequence walk when * entering playout. */ .lnode = NULL, }; struct playout_setup ps = { .gamelen = u->gamelen, .mercymin = u->mercymin, .prepolicy_hook = uct_playout_prepolicy, .postpolicy_hook = uct_playout_postpolicy, .hook_data = &upc, }; int result = play_random_game(&ps, b, next_color, u->playout_amaf ? amaf : NULL, &u->ownermap, u->playout); if (next_color == S_WHITE) { /* We need the result from black's perspective. */ result = - result; } if (UDEBUGL(7)) fprintf(stderr, "%s -- [%d..%d] %s random playout result %d\n", spaces, player_color, next_color, coord2sstr(node_coord(n), t->board), result); return result; } static floating_t scale_value(struct uct *u, struct board *b, int result) { floating_t rval = result > 0 ? 1.0 : result < 0 ? 0.0 : 0.5; if (u->val_scale && result != 0) { int vp = u->val_points; if (!vp) { vp = board_size(b) - 1; vp *= vp; vp *= 2; } floating_t sval = (floating_t) abs(result) / vp; sval = sval > 1 ? 1 : sval; if (result < 0) sval = 1 - sval; if (u->val_extra) rval += u->val_scale * sval; else rval = (1 - u->val_scale) * rval + u->val_scale * sval; // fprintf(stderr, "score %d => sval %f, rval %f\n", result, sval, rval); } return rval; }
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); }
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); }
void PropValue(void) // PropValue = "[" ValueType "]" { yyaccept('['); ValueType(); // Property Value was read : use it to modify the game int size = board_size(game->pos); if (board_nmoves(game->pos) < nmoves-1 && strcmp(prop_name[prop], "B") == 0) { if (yytext[0] != 'B') do_play(game, BLACK, parse_sgf_coord(yytext, size)); else do_play(game, BLACK, PASS_MOVE); } else if (board_nmoves(game->pos) < nmoves-1 && strcmp(prop_name[prop], "W") == 0) { if (yytext[0] != 'W') do_play(game, WHITE, parse_sgf_coord(yytext, size)); else do_play(game, WHITE, PASS_MOVE); } else if (strcmp(prop_name[prop], "AB") == 0) { Point pt = parse_sgf_coord(yytext, size); slist_push(game->placed_black_stones, pt); board_place_stone(game->pos, pt, BLACK); } else if (strcmp(prop_name[prop], "AW") == 0) { Point pt = parse_sgf_coord(yytext, size); slist_push(game->placed_white_stones, pt); board_place_stone(game->pos, pt, WHITE); } else if (strcmp(prop_name[prop], "KM") == 0) board_set_komi(game->pos, atof(yytext)); else if (strcmp(prop_name[prop], "SZ") == 0) { if (is_game_board_empty(game)) { board_set_size(game->pos, atoi(yytext)); game_clear_board(game); } else { // Can happen if SZ occurs after AB or AW Point bstones[BOARDSIZE], wstones[BOARDSIZE]; Position *pos=game->pos; slist_clear(bstones); slist_clear(wstones); slist_append(bstones, game->placed_black_stones); slist_append(wstones, game->placed_white_stones); board_set_size(game->pos, atoi(yytext)); game_clear_board(game); FORALL_IN_SLIST(bstones, pt) { board_set_color_to_play(pos, BLACK); play_move(pos, pt); slist_push(game->placed_black_stones, pt); } FORALL_IN_SLIST(wstones, pt) { board_set_color_to_play(pos, WHITE); play_move(pos, pt); slist_push(game->placed_white_stones, pt); } }
/* 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 =) }