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_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 floating_t adaptive_permove(struct uct_dynkomi *d, struct board *b, struct tree *tree) { struct dynkomi_adaptive *a = d->data; enum stone color = stone_other(tree->root_color); /* We do not use extra komi at the game end - we are not * to fool ourselves at this point. */ if (a->no_komi_at_game_end && board_estimated_moves_left(b) <= MIN_MOVES_LEFT) { tree->use_extra_komi = false; return 0; } if (DEBUGL(4)) fprintf(stderr, "m %d/%d ekomi %f permove %f/%d\n", b->moves, a->lead_moves, tree->extra_komi, d->score.value, d->score.playouts); if (b->moves <= a->lead_moves) return bounded_komi(a, b, color, board_effective_handicap(b, 7 /* XXX */), a->max_losing_komi); floating_t komi = a->indicator(d, b, tree, color); if (DEBUGL(4)) fprintf(stderr, "dynkomi: %f -> %f\n", tree->extra_komi, komi); return bounded_komi(a, b, color, komi, a->max_losing_komi); }
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; }
/* Check if we can make a move along the fbook right away. * Otherwise return pass. */ coord_t fbook_check(struct board *board) { if (!board->fbook) return pass; hash_t hi = board->hash; coord_t cf = pass; while (!is_pass(board->fbook->moves[hi & fbook_hash_mask])) { if (board->fbook->hashes[hi & fbook_hash_mask] == board->hash) { cf = board->fbook->moves[hi & fbook_hash_mask]; break; } hi++; } if (!is_pass(cf)) { if (DEBUGL(1)) fprintf(stderr, "fbook match %"PRIhash":%"PRIhash"\n", board->hash, board->hash & fbook_hash_mask); } else { /* No match, also prevent further fbook usage * until the next clear_board. */ if (DEBUGL(4)) fprintf(stderr, "fbook out %"PRIhash":%"PRIhash"\n", board->hash, board->hash & fbook_hash_mask); fbook_done(board->fbook); board->fbook = NULL; } return cf; }
static floating_t linear_permove(struct uct_dynkomi *d, struct board *b, struct tree *tree) { struct dynkomi_linear *l = d->data; enum stone color = d->uct->pondering ? tree->root_color : stone_other(tree->root_color); int lmoves = l->moves[color]; floating_t extra_komi; if (b->moves < lmoves) { floating_t base_komi = board_effective_handicap(b, l->handicap_value[color]); extra_komi = base_komi * (lmoves - b->moves) / lmoves; return extra_komi; } else { extra_komi = floor(tree->extra_komi); } /* Do not take decisions on unstable value. */ if (tree->root->u.playouts < GJ_MINGAMES) return extra_komi; floating_t my_value = tree_node_get_value(tree, 1, tree->root->u.value); /* We normalize komi as in komi_by_value(), > 0 when winning. */ extra_komi = komi_by_color(extra_komi, color); if (extra_komi < 0 && DEBUGL(3)) fprintf(stderr, "XXX: extra_komi %.3f < 0 (color %s tree ek %.3f)\n", extra_komi, stone2str(color), tree->extra_komi); // assert(extra_komi >= 0); floating_t orig_komi = extra_komi; if (my_value < 0.5 && l->komi_ratchet > 0 && l->komi_ratchet != INFINITY) { if (DEBUGL(0)) fprintf(stderr, "losing %f extra komi %.1f ratchet %.1f -> 0\n", my_value, extra_komi, l->komi_ratchet); /* Disable dynkomi completely, too dangerous in this game. */ extra_komi = l->komi_ratchet = 0; } else if (my_value < l->orange_zone && extra_komi > 0) { extra_komi = l->komi_ratchet = fmax(extra_komi - l->drop_step, 0.0); if (extra_komi != orig_komi && DEBUGL(3)) fprintf(stderr, "dropping to %f, extra komi %.1f -> ratchet %.1f\n", my_value, orig_komi, extra_komi); } else if (my_value > l->green_zone && extra_komi + 1 <= l->komi_ratchet) { extra_komi += 1; if (extra_komi != orig_komi && DEBUGL(3)) fprintf(stderr, "winning %f extra_komi %.1f -> %.1f, ratchet %.1f\n", my_value, orig_komi, extra_komi, l->komi_ratchet); } return komi_by_color(extra_komi, color); }
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); }
void showSubst (Term t) { #ifdef DEBUG if (!DEBUGL (5)) return; indent (); eprintf ("Substituting "); termPrint (t); eprintf (", typed "); termlistPrint (t->stype); if (realTermLeaf (t->subst)) { eprintf ("->"); termlistPrint (t->subst->stype); } else { eprintf (", composite term"); } if (t->type != VARIABLE) { eprintf (" (bound roleconstant)"); } eprintf ("\n"); #endif }
/* Wait for at least one new reply. Return when at least * min_replies slaves have already replied, or when the * given absolute time is passed. * The replies are returned in gtp_replies[0..reply_count-1] * slave_lock is held on entry and on return. */ void get_replies(double time_limit, int min_replies) { for (;;) { if (reply_count > 0) { struct timespec ts; double sec; ts.tv_nsec = (int)(modf(time_limit, &sec)*1000000000.0); ts.tv_sec = (int)sec; pthread_cond_timedwait(&reply_cond, &slave_lock, &ts); } else { pthread_cond_wait(&reply_cond, &slave_lock); } if (reply_count == 0) continue; if (reply_count >= min_replies || reply_count >= active_slaves) return; if (time_now() >= time_limit) break; } if (DEBUGL(1)) { char buf[1024]; snprintf(buf, sizeof(buf), "get_replies timeout %.3f >= %.3f, replies %d < min %d, active %d\n", time_now() - start_time, time_limit - start_time, reply_count, min_replies, active_slaves); logline(NULL, "? ", buf); } assert(reply_count > 0); }
static void board_print_test(int level, struct board *b) { if (!DEBUGL(level) || board_printed) return; board_print(b, stderr); board_printed = true; }
int ausb_bulk_write(ausb_dev_handle *ah, int ep, char *bytes, int size, int timeout) { DEBUGL(ah, "Write:", bytes, size); if (ah->bulkWriteFn) return ah->bulkWriteFn(ah, ep, bytes, size, timeout); return -1; }
/* Send the gtp command to_send and get a reply from the slave machine. * Write the reply in buf which must have at least CMDS_SIZE bytes. * If *bin_size > 0, send bin_buf after the gtp command. * Return any binary reply in bin_buf and set its size in bin_size. * bin_buf is private to the slave and need not be copied. * Return the gtp command id, or -1 if error. * slave_lock is held on both entry and exit of this function. */ static int send_command(char *to_send, void *bin_buf, int *bin_size, FILE *f, struct slave_state *sstate, char *buf) { assert(to_send && gtp_cmd && bin_buf && bin_size); strncpy(buf, to_send, CMDS_SIZE); bool resend = to_send != gtp_cmd; pthread_mutex_unlock(&slave_lock); if (DEBUGL(1) && resend) logline(&sstate->client, "? ", to_send == gtp_cmds ? "resend all\n" : "partial resend\n"); double start = time_now(); fputs(buf, f); if (*bin_size) fwrite(bin_buf, 1, *bin_size, f); fflush(f); if (DEBUGV(strchr(buf, '@'), 2)) { double ms = (time_now() - start) * 1000.0; if (!DEBUGL(3)) { char *s = strchr(buf, '\n'); if (s) s[1] = '\0'; } logline(&sstate->client, ">>", buf); if (*bin_size) { char b[1024]; snprintf(b, sizeof(b), "sent cmd %d+%d bytes in %.4fms\n", (int)strlen(buf), *bin_size, ms); logline(&sstate->client, "= ", b); } } /* Reuse the buffers for the reply. */ *bin_size = sstate->max_buf_size; int reply_id = get_reply(f, sstate->client, buf, bin_buf, bin_size); pthread_mutex_lock(&slave_lock); return reply_id; }
bool is_border_ladder(Board *b, Coord coord, Stone lcolor) { int x = X(coord), y = Y(coord); /* if (DEBUGL(5)) fprintf(stderr, "border ladder\n"); */ /* Direction along border; xd is horiz. border, yd vertical. */ int xd = 0, yd = 0; if (b->_infos[L(coord)].color == S_OFFBOARD || b->_infos[R(coord)].color == S_OFFBOARD) yd = 1; else xd = 1; /* Direction from the border; -1 is above/left, 1 is below/right. */ int dd = (board_atxy(b, x + yd, y + xd) == S_OFFBOARD) ? 1 : -1; if (DEBUGL(6)) fprintf(stderr, "xd %d yd %d dd %d\n", xd, yd, dd); /* | ? ? * | . O # * | c X # * | . O # * | ? ? */ /* This is normally caught, unless we have friends both above * and below... */ if (board_atxy(b, x + xd * 2, y + yd * 2) == lcolor && board_atxy(b, x - xd * 2, y - yd * 2) == lcolor) return false; /* ...or can't block where we need because of shortage * of liberties. */ group_t g1 = group_atxy(b, x + xd - yd * dd, y + yd - xd * dd); int libs1 = b->_groups[g1].liberties; group_t g2 = group_atxy(b, x - xd - yd * dd, y - yd - xd * dd); int libs2 = b->_groups[g2].liberties; /* if (DEBUGL(6)) fprintf(stderr, "libs1 %d libs2 %d\n", libs1, libs2); */ /* Already in atari? */ if (libs1 < 2 || libs2 < 2) return false; Coord libs_g1[2], libs_g2[2]; get_nlibs_of_group(b, g1, 2, libs_g1); get_nlibs_of_group(b, g2, 2, libs_g2); /* Would be self-atari? */ if (libs1 < 3 && (board_atxy(b, x + xd * 2, y + yd * 2) != S_NONE || NEIGHBOR4(libs_g1[0], libs_g1[1]))) return false; if (libs2 < 3 && (board_atxy(b, x - xd * 2, y - yd * 2) != S_NONE || NEIGHBOR4(libs_g2[0], libs_g2[1]))) return false; return true; }
/* Thread sending gtp commands to one slave machine, and * reading replies. If a slave machine dies, this thread waits * for a connection from another slave. * The large buffers are allocated only once we get a first * connection, to avoid wasting memory if max_slaves is too large. * We do not invalidate the received buffers if a slave disconnects; * they are still useful for other slaves. */ static void * slave_thread(void *arg) { struct slave_state sstate = default_sstate; sstate.thread_id = (long)arg; assert(sstate.slave_sock >= 0); char reply_buf[CMDS_SIZE]; bool resend = false; for (;;) { /* Wait for a connection from any slave. */ struct in_addr client; int conn = open_server_connection(sstate.slave_sock, &client); FILE *f = fdopen(conn, "r+"); if (DEBUGL(2)) { snprintf(reply_buf, sizeof(reply_buf), "new slave, id %d\n", sstate.thread_id); logline(&client, "= ", reply_buf); } if (!is_pachi_slave(f, &client)) continue; if (!resend) slave_state_alloc(&sstate); sstate.client = client; pthread_mutex_lock(&slave_lock); active_slaves++; slave_loop(f, reply_buf, &sstate, resend); assert(active_slaves > 0); active_slaves--; // Unblock main thread if it was waiting for this slave. pthread_cond_signal(&reply_cond); pthread_mutex_unlock(&slave_lock); resend = true; if (DEBUGL(2)) logline(&client, "= ", "lost slave\n"); fclose(f); } }
void prob_dict_init(char *filename, pattern_config_t *pc) { assert(!prob_dict); if (!filename) filename = "patterns_mm.gamma"; FILE *f = fopen_data_file(filename, "r"); if (!f) { if (DEBUGL(1)) fprintf(stderr, "%s not found, will not use mm patterns.\n", filename); return; } prob_dict = calloc2(1, prob_dict_t); prob_dict->table = calloc2(spat_dict->nspatials + 1, pattern_prob_t*); int i = 0; char sbuf[1024]; while (fgets(sbuf, sizeof(sbuf), f)) { pattern_prob_t *pb = calloc2(1, pattern_prob_t); //int c, o; char *buf = sbuf; if (buf[0] == '#') continue; while (isspace(*buf)) buf++; float gamma = strtof(buf, &buf); pb->gamma = gamma; while (isspace(*buf)) buf++; str2pattern(buf, &pb->p); assert(pb->p.n == 1); /* One gamma per feature, please ! */ uint32_t spi = feature2spatial(pc, &pb->p.f[0]); assert(spi <= spat_dict->nspatials); /* Bad patterns.spat / patterns.prob ? */ if (feature_has_gamma(pc, &pb->p.f[0])) die("%s: multiple gammas for feature %s\n", filename, pattern2sstr(&pb->p)); pb->next = prob_dict->table[spi]; prob_dict->table[spi] = pb; i++; } fclose(f); if (DEBUGL(1)) fprintf(stderr, "Loaded %d gammas.\n", i); }
/* Clear the receive queue. The buffer pointers do not have to be cleared * here, this is done as each buffer is recycled. * slave_lock is held on both entry and exit of this function. */ void clear_receive_queue(void) { if (DEBUGL(3)) { char buf[1024]; snprintf(buf, sizeof(buf), "clear queue, old length %d age %d\n", queue_length, queue_age); logline(NULL, "? ", buf); } queue_length = 0; queue_age++; }
static bool can_play_on_lib(struct board *b, group_t g, enum stone to_play) { coord_t capture = board_group_info(b, g).lib[0]; if (DEBUGL(6)) fprintf(stderr, "can capture group %d (%s)?\n", g, coord2sstr(capture, b)); /* Does playing on the liberty usefully capture the group? */ if (board_is_valid_play(b, to_play, capture) && !is_bad_selfatari(b, to_play, capture)) return true; return false; }
int ausb_bulk_read(ausb_dev_handle *ah, int ep, char *bytes, int size, int timeout) { if (ah->bulkReadFn) { int rv; DEBUGP(ah, "Reading up to %d bytes", size); rv=ah->bulkReadFn(ah, ep, bytes, size, timeout); if (rv>=0) { DEBUGL(ah, "Read:", bytes, rv); } return rv; } return -1; }
/* Get a reply to one gtp command. Return the gtp command id, * or -1 if error. reply must have at least CMDS_SIZE bytes. * The ascii reply ends with an empty line; if the first line * contains "@size", a binary reply of size bytes follows the * empty line. @size is not standard gtp, it is only used * internally by Pachi for the genmoves command; it must be the * last parameter on the line. * *bin_size is the maximum size upon entry, actual size on return. * slave_lock is not held on either entry or exit of this function. */ static int get_reply(FILE *f, struct in_addr client, char *reply, void *bin_reply, int *bin_size) { double start = time_now(); int reply_id = -1; *reply = '\0'; if (!fgets(reply, CMDS_SIZE, f)) return -1; /* Check for binary reply. */ char *s = strchr(reply, '@'); int size = 0; if (s) size = atoi(s+1); assert(size <= *bin_size); *bin_size = size; if (DEBUGV(s, 2)) logline(&client, "<<", reply); if ((*reply == '=' || *reply == '?') && isdigit(reply[1])) reply_id = atoi(reply+1); /* Read the rest of the ascii reply */ char *line = reply + strlen(reply); while (fgets(line, reply + CMDS_SIZE - line, f) && *line != '\n') { if (DEBUGL(3)) logline(&client, "<<", line); line += strlen(line); } if (*line != '\n') return -1; /* Read the binary reply if any. */ int len; while (size && (len = fread(bin_reply, 1, size, f)) > 0) { bin_reply = (char *)bin_reply + len; size -= len; } if (*bin_size && DEBUGVV(2)) { char buf[1024]; snprintf(buf, sizeof(buf), "read reply %d+%d bytes in %.4fms\n", (int)strlen(reply), *bin_size, (time_now() - start)*1000); logline(&client, "= ", buf); } return size ? -1 : reply_id; }
static floating_t komi_by_score(struct uct_dynkomi *d, struct board *b, struct tree *tree, enum stone color) { struct dynkomi_adaptive *a = d->data; if (d->score.playouts < TRUSTWORTHY_KOMI_PLAYOUTS) return tree->extra_komi; struct move_stats score = d->score; /* Almost-reset tree->score to gather fresh stats. */ d->score.playouts = 1; /* Look at average score and push extra_komi in that direction. */ floating_t p = a->adapter(d, b); p = a->adapt_base + p * (1 - a->adapt_base); if (p > 0.9) p = 0.9; // don't get too eager! floating_t extra_komi = tree->extra_komi + p * score.value; if (DEBUGL(3)) fprintf(stderr, "mC += %f * %f\n", p, score.value); return extra_komi; }
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 main(int argc, char *argv[]) { enum engine_id engine = E_UCT; struct time_info ti_default = { .period = TT_NULL }; char *testfile = NULL; char *gtp_port = NULL; char *log_port = NULL; int gtp_sock = -1; char *chatfile = NULL; char *fbookfile = NULL; char *ruleset = NULL; seed = time(NULL) ^ getpid(); int opt; while ((opt = getopt(argc, argv, "c:e:d:Df:g:l:r:s:t:u:")) != -1) { switch (opt) { case 'c': chatfile = strdup(optarg); break; case 'e': if (!strcasecmp(optarg, "random")) { engine = E_RANDOM; } else if (!strcasecmp(optarg, "replay")) { engine = E_REPLAY; } else if (!strcasecmp(optarg, "montecarlo")) { engine = E_MONTECARLO; } else if (!strcasecmp(optarg, "uct")) { engine = E_UCT; } else if (!strcasecmp(optarg, "distributed")) { engine = E_DISTRIBUTED; } else if (!strcasecmp(optarg, "patternscan")) { engine = E_PATTERNSCAN; } else if (!strcasecmp(optarg, "patternplay")) { engine = E_PATTERNPLAY; } else if (!strcasecmp(optarg, "joseki")) { engine = E_JOSEKI; } else { fprintf(stderr, "%s: Invalid -e argument %s\n", argv[0], optarg); exit(1); } break; case 'd': debug_level = atoi(optarg); break; case 'D': debug_boardprint = false; break; case 'f': fbookfile = strdup(optarg); break; case 'g': gtp_port = strdup(optarg); break; case 'l': log_port = strdup(optarg); break; case 'r': ruleset = strdup(optarg); break; case 's': seed = atoi(optarg); break; case 't': /* Time settings to follow; if specified, * GTP time information is ignored. Useful * e.g. when you want to force your bot to * play weaker while giving the opponent * reasonable time to play, or force play * by number of simulations in timed games. */ /* Please see timeinfo.h:time_parse() * description for syntax details. */ if (!time_parse(&ti_default, optarg)) { fprintf(stderr, "%s: Invalid -t argument %s\n", argv[0], optarg); exit(1); } ti_default.ignore_gtp = true; assert(ti_default.period != TT_NULL); break; case 'u': testfile = strdup(optarg); break; default: /* '?' */ usage(argv[0]); exit(1); } } if (log_port) open_log_port(log_port); fast_srandom(seed); if (DEBUGL(0)) fprintf(stderr, "Random seed: %d\n", seed); struct board *b = board_init(fbookfile); if (ruleset) { if (!board_set_rules(b, ruleset)) { fprintf(stderr, "Unknown ruleset: %s\n", ruleset); exit(1); } } struct time_info ti[S_MAX]; ti[S_BLACK] = ti_default; ti[S_WHITE] = ti_default; chat_init(chatfile); char *e_arg = NULL; if (optind < argc) e_arg = argv[optind]; struct engine *e = init_engine(engine, e_arg, b); if (testfile) { unittest(testfile); return 0; } if (gtp_port) { open_gtp_connection(>p_sock, gtp_port); } for (;;) { char buf[4096]; while (fgets(buf, 4096, stdin)) { if (DEBUGL(1)) fprintf(stderr, "IN: %s", buf); enum parse_code c = gtp_parse(b, e, ti, buf); if (c == P_ENGINE_RESET) { ti[S_BLACK] = ti_default; ti[S_WHITE] = ti_default; if (!e->keep_on_clear) { b->es = NULL; done_engine(e); e = init_engine(engine, e_arg, b); } } else if (c == P_UNKNOWN_COMMAND && gtp_port) { /* The gtp command is a weak identity check, * close the connection with a wrong peer. */ break; } } if (!gtp_port) break; open_gtp_connection(>p_sock, gtp_port); } done_engine(e); chat_done(); free(testfile); free(gtp_port); free(log_port); free(chatfile); free(fbookfile); free(ruleset); return 0; }
/* 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 floating_t komi_by_value(struct uct_dynkomi *d, struct board *b, struct tree *tree, enum stone color) { struct dynkomi_adaptive *a = d->data; if (d->value.playouts < TRUSTWORTHY_KOMI_PLAYOUTS) return tree->extra_komi; struct move_stats value = d->value; /* Almost-reset tree->value to gather fresh stats. */ d->value.playouts = 1; /* Correct color POV. */ if (color == S_WHITE) value.value = 1 - value.value; /* We have three "value zones": * red zone | yellow zone | green zone * ~45% ~60% * red zone: reduce komi * yellow zone: do not touch komi * green zone: enlage komi. * * Also, at some point komi will be tuned in such way * that it will be in green zone but increasing it will * be unfeasible. Thus, we have a _ratchet_ - we will * remember the last komi that has put us into the * red zone, and not use it or go over it. We use the * ratchet only when giving extra komi, we always want * to try to reduce extra komi we take. * * TODO: Make the ratchet expire after a while. */ /* We use komi_by_color() first to normalize komi * additions/subtractions, then apply it again on * return value to restore original komi parity. */ /* Positive extra_komi means that we are _giving_ * komi (winning), negative extra_komi is _taking_ * komi (losing). */ floating_t extra_komi = komi_by_color(tree->extra_komi, color); int score_step_red = -a->score_step; int score_step_green = a->score_step; if (a->score_step_byavg != 0) { struct move_stats score = d->score; /* Almost-reset tree->score to gather fresh stats. */ d->score.playouts = 1; /* Correct color POV. */ if (color == S_WHITE) score.value = - score.value; if (score.value > 0) score_step_green = round(score.value * a->score_step_byavg); else score_step_red = round(-score.value * a->score_step_byavg); if (score_step_green < 0 || score_step_red > 0) { /* The steps are in bad direction - keep still. */ return komi_by_color(extra_komi, color); } } /* Wear out the ratchet. */ if (a->use_komi_ratchet && a->komi_ratchet_maxage > 0) { a->komi_ratchet_age += value.playouts; if (a->komi_ratchet_age > a->komi_ratchet_maxage) { a->komi_ratchet = 1000; a->komi_ratchet_age = 0; } } if (value.value < a->zone_red) { /* Red zone. Take extra komi. */ if (DEBUGL(3)) fprintf(stderr, "[red] %f, step %d | komi ratchet %f age %d/%d -> %f\n", value.value, score_step_red, a->komi_ratchet, a->komi_ratchet_age, a->komi_ratchet_maxage, extra_komi); if (a->losing_komi_ratchet || extra_komi > 0) { a->komi_ratchet = extra_komi; a->komi_ratchet_age = 0; } extra_komi += score_step_red; return komi_by_color(extra_komi, color); } else if (value.value < a->zone_green) { /* Yellow zone, do nothing. */ return komi_by_color(extra_komi, color); } else { /* Green zone. Give extra komi. */ if (DEBUGL(3)) fprintf(stderr, "[green] %f, step %d | komi ratchet %f age %d/%d\n", value.value, score_step_green, a->komi_ratchet, a->komi_ratchet_age, a->komi_ratchet_maxage); extra_komi += score_step_green; if (a->use_komi_ratchet && extra_komi >= a->komi_ratchet) extra_komi = a->komi_ratchet - 1; return komi_by_color(extra_komi, 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); }
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; }
/* Pre-process time_info for search control and sets the desired stopping conditions. */ void time_stop_conditions(struct time_info *ti, struct board *b, int fuseki_end, int yose_start, floating_t max_maintime_ratio, struct time_stop *stop) { /* We must have _some_ limits by now, be it random default values! */ assert(ti->period != TT_NULL); /* Special-case limit by number of simulations. */ if (ti->dim == TD_GAMES) { if (ti->period == TT_TOTAL) { ti->period = TT_MOVE; ti->len.games /= board_estimated_moves_left(b); } stop->desired.playouts = ti->len.games; /* We force worst == desired, so note that we will NOT loop * until best == winner. */ stop->worst.playouts = ti->len.games; return; } assert(ti->dim == TD_WALLTIME); /* Minimum net lag (seconds) to be reserved in the time for move. */ double net_lag = MAX_NET_LAG; net_lag += time_now() - ti->len.t.timer_start; // TODO: keep statistics to get good estimate of lag not just current move if (ti->period == TT_TOTAL && time_in_byoyomi(ti)) { /* Technically, we are still in main time, but we can * effectively switch to byoyomi scheduling since we * have less time available than one byoyomi move takes. */ ti->period = TT_MOVE; } if (ti->period == TT_MOVE) { /* We are in byoyomi, or almost! */ /* The period can still include some tiny remnant of main * time if we are just switching to byoyomi. */ double period_len = ti->len.t.byoyomi_time + ti->len.t.main_time; stop->worst.time = period_len; assert(ti->len.t.byoyomi_stones > 0); stop->desired.time = period_len / ti->len.t.byoyomi_stones; /* Use a larger safety margin if we risk losing on time on * this move; it makes no sense to have 30s byoyomi and wait * until 28s to play our move). */ if (stop->desired.time >= period_len - net_lag) { double safe_margin = RESERVED_BYOYOMI_PERCENT * stop->desired.time / 100; if (safe_margin > net_lag) net_lag = safe_margin; } /* Make recommended_old == average(recommended_new, max) */ double worst_time = stop->desired.time * MAX_BYOYOMI_TIME_RATIO; if (worst_time < stop->worst.time) stop->worst.time = worst_time; stop->desired.time *= (2 - MAX_BYOYOMI_TIME_RATIO); } else { assert(ti->period == TT_TOTAL); /* We are in main time. */ assert(ti->len.t.main_time > 0); /* Set worst.time to all available remaining time, to be spread * over returned number of moves. */ int moves_left = time_stop_set_remaining(ti, b, net_lag, stop); /* Allocate even slice of the remaining time for next move. */ stop->desired.time = stop->worst.time / moves_left; assert(stop->desired.time > 0 && stop->worst.time > 0); assert(stop->desired.time <= stop->worst.time + 0.001); /* Furthermore, tweak the slice based on the game phase. */ time_stop_phase_adjust(b, fuseki_end, yose_start, stop); /* Put final upper bound on maximal time spent on the move. * Keep enough time for sudden death (or near SD) games. */ double worst_time = stop->desired.time; if (ti->len.t.byoyomi_time_max > ti->len.t.byoyomi_stones_max) { worst_time *= max_maintime_ratio; } else { worst_time *= MAX_SUDDEN_DEATH_RATIO; } if (worst_time < stop->worst.time) stop->worst.time = worst_time; if (stop->desired.time > stop->worst.time) stop->desired.time = stop->worst.time; } if (DEBUGL(1)) fprintf(stderr, "desired %0.2f, worst %0.2f, clock [%d] %0.2f + %0.2f/%d*%d, lag %0.2f\n", stop->desired.time, stop->worst.time, ti->dim, ti->len.t.main_time, ti->len.t.byoyomi_time, ti->len.t.byoyomi_stones, ti->len.t.byoyomi_periods, net_lag); /* Account for lag. */ lag_adjust(&stop->desired.time, net_lag); lag_adjust(&stop->worst.time, net_lag); }