558 lines
14 KiB
C
558 lines
14 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. *
|
|
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <assert.h>
|
|
|
|
#include "gg_utils.h"
|
|
#include "random.h"
|
|
|
|
#ifdef HAVE_GLIB_H
|
|
#include <glib.h>
|
|
#endif
|
|
|
|
/* Avoid compiler warnings with unused parameters */
|
|
#define UNUSED(x) (void)x
|
|
|
|
/* Define TERMINFO or ANSI_COLOR to enable coloring of pieces.
|
|
* This is normally done in config.h.
|
|
*/
|
|
|
|
/* enabling color */
|
|
|
|
/* linux console :
|
|
* 0=black
|
|
* 1=red
|
|
* 2=green
|
|
* 3=yellow/brown
|
|
* 4=blue
|
|
* 5=magenta
|
|
* 6=cyan
|
|
* 7=white
|
|
*/
|
|
|
|
#ifdef TERMINFO
|
|
|
|
#ifdef _AIX
|
|
#define _TPARM_COMPAT
|
|
#endif
|
|
|
|
#if HAVE_CURSES_H
|
|
#include <curses.h>
|
|
#elif HAVE_NCURSES_CURSES_H
|
|
#include <ncurses/curses.h>
|
|
#else
|
|
#endif
|
|
|
|
#if HAVE_TERM_H
|
|
#include <term.h>
|
|
#elif HAVE_NCURSES_TERM_H
|
|
#include <ncurses/term.h>
|
|
#else
|
|
#endif
|
|
|
|
|
|
/* terminfo attributes */
|
|
static char *setaf; /* terminfo string to set color */
|
|
static char *op; /* terminfo string to reset colors */
|
|
|
|
static int init = 0;
|
|
|
|
#endif /* TERMINFO */
|
|
|
|
/* for gg_cputime */
|
|
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_TIMES_H
|
|
#include <sys/times.h>
|
|
#elif defined(WIN32)
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
void
|
|
gg_init_color()
|
|
{
|
|
#ifdef TERMINFO
|
|
|
|
/* compiler is set to make string literals const char *
|
|
* But system header files dont prototype things correctly.
|
|
* These are equivalent to a non-const string literals
|
|
*/
|
|
|
|
static char setaf_literal[] = "setaf";
|
|
static char op_literal[] = "op";
|
|
static char empty_literal[] = "";
|
|
|
|
if (init)
|
|
return;
|
|
|
|
init = 1;
|
|
|
|
setupterm(NULL, 2, NULL);
|
|
setaf = tigetstr(setaf_literal);
|
|
if (!setaf)
|
|
setaf = empty_literal;
|
|
op = tigetstr(op_literal);
|
|
if (!op)
|
|
op = empty_literal;
|
|
|
|
#endif /* TERMINFO */
|
|
}
|
|
|
|
|
|
|
|
#ifdef WIN32
|
|
#ifdef VC
|
|
#include <crtdbg.h>
|
|
|
|
verifyW32(BOOL b)
|
|
{
|
|
if (!b) {
|
|
_ASSERTE(0 && "Win32 Error");
|
|
fprintf(stderr, "Win32 Err: %ld\n", GetLastError());
|
|
}
|
|
}
|
|
|
|
#else
|
|
/* mingw32 lacks crtdbg.h and _ASSERTE */
|
|
verifyW32(BOOL b)
|
|
{
|
|
if (!b) {
|
|
fprintf(stderr, "Win32 Err: %ld\n", GetLastError());
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
void
|
|
write_color_char_no_space(int c, int x)
|
|
{
|
|
#ifdef TERMINFO
|
|
|
|
fprintf(stderr, "%s%c", tparm(setaf, c, 0, 0, 0, 0, 0, 0, 0, 0), x);
|
|
fputs(tparm(op, 0, 0, 0, 0, 0, 0, 0, 0, 0), stderr);
|
|
|
|
#elif defined(ANSI_COLOR)
|
|
|
|
fprintf(stderr, "\033[%dm%c\033[0m", 30+c, x);
|
|
|
|
#elif defined(WIN32)
|
|
|
|
static HANDLE hStdErr = 0;
|
|
DWORD iCharsWritten;
|
|
BOOL succeed32;
|
|
CONSOLE_SCREEN_BUFFER_INFO bufInfo;
|
|
if (!hStdErr) {
|
|
hStdErr = GetStdHandle(STD_ERROR_HANDLE);
|
|
if (hStdErr == INVALID_HANDLE_VALUE) {
|
|
fprintf(stderr, "Unable to open stderr.\n");
|
|
}
|
|
}
|
|
|
|
/* Red & Blue are switched from what MS-Windows wants:
|
|
* FOREGROUND_BLUE 0x0001 // text color contains blue.
|
|
* FOREGROUND_GREEN 0x0002 // text color contains green.
|
|
* FOREGROUND_RED 0x0004 // text color contains red
|
|
* This magic switches the bits back:
|
|
*/
|
|
c = (c & 1) * 4 + (c & 2) + (c & 4) / 4;
|
|
c += FOREGROUND_INTENSITY;
|
|
succeed32 = GetConsoleScreenBufferInfo(hStdErr, &bufInfo);
|
|
if (!succeed32) { /* Probably redirecting output, just give plain text. */
|
|
fprintf(stderr, "%c", x);
|
|
return;
|
|
}
|
|
verifyW32(SetConsoleTextAttribute(hStdErr, (WORD) c));
|
|
verifyW32(WriteConsole(hStdErr, &x, 1, &iCharsWritten, 0));
|
|
verifyW32(SetConsoleTextAttribute(hStdErr, bufInfo.wAttributes));
|
|
|
|
#else
|
|
|
|
fprintf(stderr, "%c", x);
|
|
|
|
#endif
|
|
}
|
|
|
|
void
|
|
write_color_string(int c, const char *str)
|
|
{
|
|
while (*str)
|
|
write_color_char_no_space(c, *str++);
|
|
}
|
|
|
|
void
|
|
write_color_char(int c, int x)
|
|
{
|
|
fprintf(stderr, " ");
|
|
write_color_char_no_space(c, x);
|
|
}
|
|
|
|
/*
|
|
* A wrapper around vsnprintf.
|
|
*/
|
|
|
|
void
|
|
gg_vsnprintf(char *dest, unsigned long len, const char *fmt, va_list args)
|
|
{
|
|
|
|
#ifdef HAVE_VSNPRINTF
|
|
vsnprintf(dest, len, fmt, args);
|
|
#elif HAVE_G_VSNPRINTF
|
|
g_vsnprintf(dest, len, fmt, args);
|
|
#elif HAVE__VSNPRINTF
|
|
_vsnprintf(dest, len, fmt, args);
|
|
#else
|
|
UNUSED(len);
|
|
vsprintf(dest, fmt, args);
|
|
#endif
|
|
|
|
}
|
|
|
|
void
|
|
gg_snprintf(char *dest, unsigned long len, const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
gg_vsnprintf(dest, len, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
/* Get the time of day, calling gettimeofday from sys/time.h
|
|
* if available, otherwise substituting a workaround for portability.
|
|
*/
|
|
|
|
double
|
|
gg_gettimeofday(void)
|
|
{
|
|
struct timeval tv;
|
|
#ifdef HAVE_GETTIMEOFDAY
|
|
gettimeofday(&tv, NULL);
|
|
#else
|
|
tv.tv_sec = time(NULL);
|
|
tv.tv_usec = 0;
|
|
#endif
|
|
return tv.tv_sec + 1.e-6 * tv.tv_usec;
|
|
}
|
|
|
|
const char *
|
|
gg_version(void)
|
|
{
|
|
return VERSION;
|
|
}
|
|
|
|
/* return cputime used in secs */
|
|
|
|
double
|
|
gg_cputime(void)
|
|
{
|
|
#if HAVE_SYS_TIMES_H && HAVE_TIMES && HAVE_UNISTD_H
|
|
struct tms t;
|
|
times(&t);
|
|
return (t.tms_utime + t.tms_stime + t.tms_cutime + t.tms_cstime)
|
|
/ ((double) sysconf(_SC_CLK_TCK));
|
|
#elif defined(WIN32)
|
|
FILETIME creationTime, exitTime, kernelTime, userTime;
|
|
ULARGE_INTEGER uKernelTime, uUserTime, uElapsedTime;
|
|
GetProcessTimes(GetCurrentProcess(), &creationTime, &exitTime,
|
|
&kernelTime, &userTime);
|
|
uKernelTime.LowPart = kernelTime.dwLowDateTime;
|
|
uKernelTime.HighPart = kernelTime.dwHighDateTime;
|
|
uUserTime.LowPart = userTime.dwLowDateTime;
|
|
uUserTime.HighPart = userTime.dwHighDateTime;
|
|
uElapsedTime.QuadPart = uKernelTime.QuadPart + uUserTime.QuadPart;
|
|
/*_ASSERTE(0 && "Debug Times");*/
|
|
/* convert from multiples of 100nanosecs to seconds: */
|
|
return (signed __int64)(uElapsedTime.QuadPart) * 1.e-7;
|
|
#else
|
|
static int warned = 0;
|
|
if (!warned) {
|
|
fprintf(stderr, "CPU timing unavailable - returning wall time.");
|
|
warned = 1;
|
|
}
|
|
/* return wall clock seconds */
|
|
return gg_gettimeofday();
|
|
#endif
|
|
}
|
|
|
|
/* Before we sort floating point values (or just compare them) we
|
|
* may need to normalize them. This may sound cryptic but is
|
|
* required to avoid an obscure platform dependency.
|
|
*
|
|
* The underlying problem is that most fractional decimal numbers
|
|
* can't be represented exactly in a floating point number with base
|
|
* two. The error may be small but it is there. When such numbers
|
|
* are added or subtracted, the errors accumulate and even if the
|
|
* result (counting exactly) should be a number which can be
|
|
* represented exactly, this cannot be assumed to be the case.
|
|
*
|
|
* To give an example of this, the computation 0.3 + 0.05 - 0.35 may
|
|
* sum to 0, a small negative value, or a small positive value.
|
|
* Moreover, which case we encounter depends on the number of
|
|
* mantissa bits in the floating point type used and the exact
|
|
* details of the floating point arithmetic on the platform.
|
|
*
|
|
* In the context of sorting, assume that two values both should be
|
|
* 0.35, but one has been computed as 0.3 + 0.05 and the other
|
|
* directly assigned 0.35. Then it depends on the platform whether
|
|
* they compare as equal or one of them is larger than the other.
|
|
*
|
|
* This code normalizes the values to avoid this problem. It is
|
|
* assumed that all values encountered are integer multiples of a.
|
|
*/
|
|
float
|
|
gg_normalize_float(float x, float a)
|
|
{
|
|
return a * ((int) (0.5 + x / a));
|
|
}
|
|
|
|
int
|
|
gg_normalize_float2int(float x, float a)
|
|
{
|
|
return ((int) (0.5 + x / a));
|
|
}
|
|
|
|
/* A sorting algorithm, call-compatible with the libc qsort() function.
|
|
*
|
|
* The reason to prefer this to standard qsort() is that quicksort is
|
|
* an unstable sorting algorithm, i.e. the relative ordering of
|
|
* elements with the same comparison value may change. Exactly how the
|
|
* ordering changes depends on implementation specific details like
|
|
* the strategy for choosing the pivot element. Thus a list with
|
|
* "equal" values may be sorted differently between platforms, which
|
|
* potentially can lead to significant differences in the move
|
|
* generation.
|
|
*
|
|
* This is an implementation of the combsort algorithm.
|
|
*
|
|
* Testing shows that it is faster than the GNU libc qsort() function
|
|
* on small data sets and within a factor of two slower for large
|
|
* random data sets. Its performance does not degenerate for common
|
|
* special cases (i.e. sorted or reversed data) but it seems to be
|
|
* susceptible to O(N^2) behavior for repetitive data with specific
|
|
* cycle lengths.
|
|
*
|
|
* Like qsort() this algorithm is unstable, but since the same
|
|
* implementation (this one) is used on all platforms, the reordering
|
|
* of equal elements will be consistent.
|
|
*/
|
|
void
|
|
gg_sort(void *base, size_t nel, size_t width,
|
|
int (*cmp)(const void *, const void *))
|
|
{
|
|
int gap = nel;
|
|
int swap_made;
|
|
char *end = (char *) base + width * (nel - 1);
|
|
do {
|
|
char *a, *b;
|
|
swap_made = 0;
|
|
gap = (10 * gap + 3) / 13;
|
|
for (a = base, b = a + gap * width; b <= end; a += width, b += width) {
|
|
if (cmp((void *) a, (void *) b) > 0) {
|
|
char *c = a;
|
|
char *d = b;
|
|
size_t size = width;
|
|
while (size-- > 0) {
|
|
char tmp = *c;
|
|
*c++ = *d;
|
|
*d++ = tmp;
|
|
}
|
|
swap_made = 1;
|
|
}
|
|
}
|
|
} while (gap > 1 || swap_made);
|
|
}
|
|
|
|
|
|
/* Linearly interpolate f(x) from the data given in interpolation_data. */
|
|
float
|
|
gg_interpolate(struct interpolation_data *f, float x)
|
|
{
|
|
int i;
|
|
float ratio;
|
|
float diff;
|
|
if (x < f->range_lowerbound)
|
|
return f->values[0];
|
|
else if (x > f->range_upperbound)
|
|
return f->values[f->sections];
|
|
else {
|
|
ratio = ((float) f->sections) * (x - f->range_lowerbound)
|
|
/ (f->range_upperbound - f->range_lowerbound);
|
|
i = (int) ratio;
|
|
diff = ratio - ((float) i);
|
|
if (0)
|
|
fprintf(stderr, "Floating point Ratio: %f, integer: %d, diff %f",
|
|
ratio, i, diff);
|
|
return ((1 - diff) * f->values[i] + diff * f->values[i+1]);
|
|
}
|
|
}
|
|
|
|
|
|
/* This is the simplest function that returns appr. a when a is small,
|
|
* and approximately b when a is large.
|
|
*/
|
|
float
|
|
soft_cap(float a, float b)
|
|
{
|
|
return ((a * b) / (a + b));
|
|
}
|
|
|
|
|
|
/* Reorientation of point (i, j) into (*ri, *rj) */
|
|
void
|
|
rotate(int i, int j, int *ri, int *rj, int bs, int rot)
|
|
{
|
|
int bs1;
|
|
assert(bs > 0);
|
|
assert(ri != NULL && rj != NULL);
|
|
assert(rot >= 0 && rot < 8);
|
|
/* PASS case */
|
|
if (i == -1 && j == -1) {
|
|
*ri = i;
|
|
*rj = j;
|
|
return;
|
|
}
|
|
|
|
assert(i >= 0 && i < bs);
|
|
assert(j >= 0 && j < bs);
|
|
|
|
bs1 = bs - 1;
|
|
if (rot == 0) {
|
|
/* identity map */
|
|
*ri = i;
|
|
*rj = j;
|
|
}
|
|
else if (rot == 1) {
|
|
/* rotation over 90 degrees */
|
|
*ri = bs1 - j;
|
|
*rj = i;
|
|
}
|
|
else if (rot == 2) {
|
|
/* rotation over 180 degrees */
|
|
*ri = bs1 - i;
|
|
*rj = bs1 - j;
|
|
}
|
|
else if (rot == 3) {
|
|
/* rotation over 270 degrees */
|
|
*ri = j;
|
|
*rj = bs1 - i;
|
|
}
|
|
else if (rot == 4) {
|
|
/* flip along diagonal */
|
|
*ri = j;
|
|
*rj = i;
|
|
}
|
|
else if (rot == 5) {
|
|
/* flip */
|
|
*ri = bs1 - i;
|
|
*rj = j;
|
|
}
|
|
else if (rot == 6) {
|
|
/* flip along diagonal */
|
|
*ri = bs1 - j;
|
|
*rj = bs1 - i;
|
|
}
|
|
else if (rot == 7) {
|
|
/* flip */
|
|
*ri = i;
|
|
*rj = bs1 - j;
|
|
}
|
|
}
|
|
|
|
/* inverse reorientation of reorientation rot */
|
|
void
|
|
inv_rotate(int i, int j, int *ri, int *rj, int bs, int rot)
|
|
{
|
|
/* every reorientation is it's own inverse except rotations
|
|
over 90 and 270 degrees */
|
|
if (rot == 1)
|
|
rotate(i, j, ri, rj, bs, 3);
|
|
else if (rot == 3)
|
|
rotate(i, j, ri, rj, bs, 1);
|
|
else
|
|
rotate(i, j, ri, rj, bs, rot);
|
|
}
|
|
|
|
|
|
/* Intermediate layer to random.c. gg_srand() should only be called via the
|
|
* functions below.
|
|
*/
|
|
|
|
/* Private variable remembering the random seed. */
|
|
static unsigned int random_seed;
|
|
|
|
unsigned int
|
|
get_random_seed()
|
|
{
|
|
return random_seed;
|
|
}
|
|
|
|
void
|
|
set_random_seed(unsigned int seed)
|
|
{
|
|
random_seed = seed;
|
|
gg_srand(seed);
|
|
}
|
|
|
|
/* Update the random seed. This should be called at the start of each
|
|
* new game.
|
|
* We reset the random seed before obtaining a new one, to make the
|
|
* next random seed depend deterministically on the old one.
|
|
*/
|
|
void
|
|
update_random_seed(void)
|
|
{
|
|
gg_srand(random_seed);
|
|
random_seed = gg_rand();
|
|
/* Since random seed 0 has a special interpretation when given as
|
|
* command line argument with the -r option, we make sure to avoid
|
|
* it.
|
|
*/
|
|
if (random_seed == 0)
|
|
random_seed = 1;
|
|
gg_srand(random_seed);
|
|
}
|
|
|
|
|
|
/* Restart the pseudo-random sequence with the initialization given
|
|
* by the random seed. Should be called at each move.
|
|
*/
|
|
void
|
|
reuse_random_seed()
|
|
{
|
|
gg_srand(random_seed);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Local Variables:
|
|
* tab-width: 8
|
|
* c-basic-offset: 2
|
|
* End:
|
|
*/
|