415 lines
12 KiB
C
415 lines
12 KiB
C
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
||
|
* 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:
|
||
|
*/
|