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 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); } }
/* Set worst.time to all available remaining time (main time plus usable * byoyomi), to be spread over returned number of moves (expected game * length minus moves to be played in final byoyomi - if we would not be * able to spend more time on them in main time anyway). */ static int time_stop_set_remaining(struct time_info *ti, struct board *b, double net_lag, struct time_stop *stop) { int moves_left = board_estimated_moves_left(b); stop->worst.time = ti->len.t.main_time; if (!ti->len.t.byoyomi_time) return moves_left; /* Time for one move in byoyomi. */ assert(ti->len.t.byoyomi_stones > 0); double move_time = ti->len.t.byoyomi_time / ti->len.t.byoyomi_stones; /* (i) Plan to extend our thinking time to make use of byoyom. */ /* For Japanese byoyomi with N>1 periods, we use N-1 periods * as main time, keeping the last one as insurance against * unexpected net lag. */ if (ti->len.t.byoyomi_periods > 2) { stop->worst.time += (ti->len.t.byoyomi_periods - 2) * move_time; // Will add 1 more byoyomi_time just below } /* In case of Canadian byoyomi, include time that can be spent * on its first move. */ stop->worst.time += move_time; /* (ii) Do not play faster in main time than we would in byoyomi. */ /* Maximize the number of moves played uniformly in main time, * while not playing faster in main time than in byoyomi. * At this point, the main time remaining is stop->worst.time and * already includes the first (canadian) or N-1 byoyomi periods. */ double real_move_time = move_time - net_lag; if (real_move_time > 0) { int main_moves = stop->worst.time / real_move_time; if (moves_left > main_moves) { /* We plan to do too many moves in main time, * do the rest in byoyomi. */ moves_left = main_moves; } if (moves_left <= 0) // possible if too much lag moves_left = 1; } return moves_left; }
/* 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; } }
/* 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); }