/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ * This is GNU Go, a Go program. Contact gnugo@gnu.org, or see * * http://www.gnu.org/software/gnugo/ for more information. * * * * Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, * * 2008 and 2009 by the Free Software Foundation. * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation - version 3 or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License in file COPYING for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02111, USA. * \* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* ============================================================= *\ * Time handling * * for GNU Go * * __ __ * * < > < > * * +--++-------++--+ * * | .'11 12 1'. | * * | :10 \ 2: | * * | :9 @-> 3: | * * | :8 4; | * * | '..7 6 5..' | * * |_______________| * * * \* ============================================================= */ #include "clock.h" #include "gg_utils.h" #include "board.h" /* Level data */ static int level = DEFAULT_LEVEL; /* current level */ static int level_offset = 0; static int min_level = 0; static int max_level = gg_max(DEFAULT_LEVEL, 10); /*************************/ /* Datas and other stuff */ /*************************/ /* clock parameters */ static int main_time = -1; static int byoyomi_time = -1; static int byoyomi_stones = -1; /* <= 0 if no byo-yomi */ /* Keep track of the remaining time left. * If stones_left is zero, .._time_left is the remaining main time. * Otherwise, the remaining time for this byoyomi period. */ struct remaining_time_data { double time_left; double time_for_last_move; int stones; int movenum; int in_byoyomi; }; struct timer_data { struct remaining_time_data official; struct remaining_time_data estimated; int time_out; }; static struct timer_data black_time_data; static struct timer_data white_time_data; /* Echo a time value in STANDARD format */ static void timeval_print(FILE *outfile, double tv) { int min; double sec; min = (int) tv / 60; sec = tv - min*60; fprintf(outfile, "%3dmin %.2fsec ", min, sec); } /* Print the clock status for one side. */ void clock_print(int color) { struct timer_data *const td = (color == BLACK) ? &black_time_data : &white_time_data; fprintf(stderr, "clock: "); fprintf(stderr, "%s ", color_to_string(color)); if (td->time_out) fprintf(stderr, "TIME OUT! "); else { if (td->estimated.in_byoyomi) { fprintf(stderr, "byoyomi"); timeval_print(stderr, td->estimated.time_left); fprintf(stderr, "for %d stones.", td->estimated.stones); } else timeval_print(stderr, td->estimated.time_left); } fprintf(stderr, "\n"); } /******************************/ /* Initialization functions */ /******************************/ /* * Initialize the time settings for this game. * -1 means "do not modify this value". * * byo_time > 0 and byo_stones == 0 means no time settings. */ void clock_settings(int time, int byo_time, int byo_stones) { if (time >= 0) main_time = time; if (byo_time >= 0) byoyomi_time = byo_time; if (byo_stones >= 0) byoyomi_stones = byo_stones; init_timers(); } /* Get time settings. Returns 1 if any time settings have been made, * 0 otherwise. */ int have_time_settings(void) { /* According to the semantics of the GTP command 'time_settings', the * following signifies no time limits. */ if (byoyomi_time > 0 && byoyomi_stones == 0) return 0; else return (main_time >= 0 || byoyomi_time >= 0); } /* Initialize all timers. */ void init_timers() { white_time_data.official.time_left = main_time; white_time_data.official.time_for_last_move = -1.0; white_time_data.official.stones = 0; white_time_data.official.movenum = 0; white_time_data.official.in_byoyomi = 0; white_time_data.estimated = white_time_data.official; white_time_data.time_out = 0; black_time_data = white_time_data; level_offset = 0; } /*****************************/ /* Clock access functions. */ /*****************************/ void update_time_left(int color, int time_left, int stones) { struct timer_data *const td = ((color == BLACK) ? &black_time_data : &white_time_data); int time_used = td->official.time_left - time_left; if (time_left > 0) td->time_out = 0; else td->time_out = 1; /* Did our estimate for time usage go wrong? */ if (time_used > 0 && gg_abs(time_used - td->estimated.time_for_last_move) >= 1.0) td->estimated.time_for_last_move = time_used; td->estimated.stones = stones; td->estimated.movenum = movenum; /* Did our clock go wrong? */ if (gg_abs(td->estimated.time_left - time_left) >= 1.0) td->estimated.time_left = time_left; if (stones > 0) td->estimated.in_byoyomi = 1; else td->estimated.in_byoyomi = 0; td->official.stones = stones; td->official.movenum = movenum; td->official.time_for_last_move = td->official.time_for_last_move - time_left; td->official.time_left = time_left; td->official.in_byoyomi = td->estimated.in_byoyomi; } /* * Update the estimated timer after a move has been made. */ void clock_push_button(int color) { static double last_time = -1.0; static int last_movenum = -1; struct timer_data *const td = (color == BLACK) ? &black_time_data : &white_time_data; double now = gg_gettimeofday(); if (!have_time_settings()) return; if (last_movenum >= 0 && movenum == last_movenum + 1 && movenum > td->estimated.movenum) { double time_used = now - last_time; td->estimated.time_left -= time_used; td->estimated.movenum = movenum; td->estimated.time_for_last_move = time_used; if (td->estimated.time_left < 0) { if (td->estimated.in_byoyomi || byoyomi_stones == 0) { DEBUG(DEBUG_TIME, "%s ran out of time.\n", color_to_string(color)); if (debug & DEBUG_TIME) clock_print(color); td->time_out = 1; } else { /* Entering byoyomi. */ gg_assert(!(td->estimated.in_byoyomi)); td->estimated.in_byoyomi = 1; td->estimated.stones = byoyomi_stones - 1; td->estimated.time_left += byoyomi_time; if (td->estimated.time_left < 0) td->time_out = 1; } } else if (td->estimated.stones > 0) { gg_assert(td->estimated.in_byoyomi); td->estimated.stones = td->estimated.stones - 1; if (td->estimated.stones == 0) { td->estimated.time_left = byoyomi_time; td->estimated.stones = byoyomi_stones; } } } last_movenum = movenum; last_time = now; /* Update main timer. */ if (debug & DEBUG_TIME) clock_print(color); } /**********************/ /* Autolevel system */ /**********************/ /* Analyze the two most recent time reports and determine the time * spent on the last moves, the (effective) number of stones left and * the (effective) remaining time. */ static int analyze_time_data(int color, double *time_for_last_move, double *time_left, int *stones_left) { struct remaining_time_data *const timer = (color == BLACK) ? &black_time_data.estimated : &white_time_data.estimated; /* Do we have any time limits. */ if (!have_time_settings()) return 0; /* If we don't have consistent time information yet, just return. */ if (timer->time_for_last_move < 0.0) return 0; *time_for_last_move = timer->time_for_last_move; if (timer->stones == 0) { /* Main time running. */ *time_left = timer->time_left + byoyomi_time; if (byoyomi_time > 0) *stones_left = byoyomi_stones; else { /* Absolute time. Here we aim to be able to play at least X more * moves or a total of Y moves. We choose Y as a third of the * number of vertices and X as 40% of Y. For 19x19 this means * that we aim to play at least a total of 120 moves * (corresponding to a 240 move game) or another 24 moves. * * FIXME: Maybe we should use the game_status of * influence_evaluate_position() here to guess how many moves * are remaining. */ int nominal_moves = board_size * board_size / 3; *stones_left = gg_max(nominal_moves - movenum / 2, 2 * nominal_moves / 5); } } else { *time_left = timer->time_left; *stones_left = timer->stones; } return 1; } /* Adjust the level offset given information of current playing speed * and remaining time and stones. */ void adjust_level_offset(int color) { double time_for_last_move; double time_left; int stones_left; if (!analyze_time_data(color, &time_for_last_move, &time_left, &stones_left)) return; /* These rules are both crude and ad hoc. * * FIXME: Use rules with at least some theoretical basis. */ if (time_left < time_for_last_move * (stones_left + 3)) level_offset--; if (time_left < time_for_last_move * stones_left) level_offset--; if (3 * time_left < 2 * time_for_last_move * stones_left) level_offset--; if (2 * time_left < time_for_last_move * stones_left) level_offset--; if (3 * time_left < time_for_last_move * stones_left) level_offset--; if (time_for_last_move == 0) time_for_last_move = 1; if (time_left > time_for_last_move * (stones_left + 6)) level_offset++; if (time_left > 2 * time_for_last_move * (stones_left + 6)) level_offset++; if (level + level_offset < min_level) level_offset = min_level - level; if (level + level_offset > max_level) level_offset = max_level - level; DEBUG(DEBUG_TIME, "New level %d (%d %C %f %f %d)\n", level + level_offset, movenum / 2, color, time_for_last_move, time_left, stones_left); } /********************************/ /* Interface to level settings. */ /********************************/ int get_level() { return level + level_offset; } void set_level(int new_level) { level = new_level; level_offset = 0; if (level > max_level) max_level = level; if (level < min_level) min_level = level; } void set_max_level(int new_max) { max_level = new_max; } void set_min_level(int new_min) { min_level = new_min; } /* * Local Variables: * tab-width: 8 * c-basic-offset: 2 * End: */