Initial commit of GNU Go v3.8.
[sgk-go] / engine / clock.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:
*/