540 lines
12 KiB
C
540 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. *
|
|
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
#include "board.h"
|
|
#include "hash.h"
|
|
#include "gg_utils.h"
|
|
#include "sgftree.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <ctype.h>
|
|
|
|
/*
|
|
* This function underpins all the TRACE and DEBUG stuff.
|
|
* It accepts %c, %d, %f, %s, and %x as usual. But it
|
|
* also accepts %m, which takes TWO integers and writes a move.
|
|
* Other accepted formats are
|
|
* %H: Print a hashvalue.
|
|
* %C: Print a color as a string.
|
|
* Nasty bodge: %o at the start means outdent, i.e. cancel indent.
|
|
*/
|
|
|
|
void
|
|
vgprintf(FILE *outputfile, const char *fmt, va_list ap)
|
|
{
|
|
if (fmt[0] == '%' && fmt[1] == 'o')
|
|
fmt += 2; /* cancel indent */
|
|
else if (stackp > 0)
|
|
fprintf(outputfile, "%.*s", stackp*2, " ");
|
|
|
|
for (; *fmt; ++fmt) {
|
|
if (*fmt == '%') {
|
|
switch (*++fmt) {
|
|
case 'c':
|
|
{
|
|
/* rules of promotion => passed as int, not char */
|
|
int c = va_arg(ap, int);
|
|
putc(c, outputfile);
|
|
break;
|
|
}
|
|
case 'd':
|
|
{
|
|
int d = va_arg(ap, int);
|
|
fprintf(outputfile, "%d", d);
|
|
break;
|
|
}
|
|
case 'x':
|
|
{
|
|
unsigned int d = va_arg(ap, unsigned int);
|
|
fprintf(outputfile, "%x", d);
|
|
break;
|
|
}
|
|
case 'f':
|
|
{
|
|
double f = va_arg(ap, double); /* passed as double, not float */
|
|
fprintf(outputfile, "%.2f", f);
|
|
break;
|
|
}
|
|
case 's':
|
|
{
|
|
char *s = va_arg(ap, char *);
|
|
fputs(s, outputfile);
|
|
break;
|
|
}
|
|
case '2':
|
|
fmt++;
|
|
if (*fmt != 'm' && *fmt != 'M') {
|
|
fprintf(outputfile, "\n\nUnknown format string '2%c'\n", *fmt);
|
|
break;
|
|
}
|
|
/* else fall through - 2 modifier on %m is default. */
|
|
case 'm':
|
|
case 'M':
|
|
{
|
|
char movename[4];
|
|
int m = va_arg(ap, int);
|
|
int n = va_arg(ap, int);
|
|
if (m == -1 && n == -1)
|
|
fputs("PASS", outputfile);
|
|
else if (!ON_BOARD2(m, n))
|
|
fprintf(outputfile, "[%d,%d]", m, n);
|
|
else {
|
|
/* Generate the move name. */
|
|
if (n < 8)
|
|
movename[0] = n + 65;
|
|
else
|
|
movename[0] = n + 66;
|
|
if (*fmt == 'm')
|
|
sprintf(movename+1, "%d", board_size - m);
|
|
else
|
|
sprintf(movename+1, "%-2d", board_size - m);
|
|
fputs(movename, outputfile);
|
|
}
|
|
break;
|
|
}
|
|
case '1':
|
|
fmt++;
|
|
if (*fmt != 'm' && *fmt != 'M') {
|
|
fprintf(outputfile, "\n\nUnknown format string '1%c'\n", *fmt);
|
|
break;
|
|
}
|
|
else {
|
|
char movename[4];
|
|
int pos = va_arg(ap, int);
|
|
int m = I(pos);
|
|
int n = J(pos);
|
|
if (pos == NO_MOVE)
|
|
fputs("PASS", outputfile);
|
|
else if (!ON_BOARD1(pos))
|
|
fprintf(outputfile, "[%d]", pos);
|
|
else {
|
|
/* Generate the move name. */
|
|
if (n < 8)
|
|
movename[0] = n + 65;
|
|
else
|
|
movename[0] = n + 66;
|
|
if (*fmt == 'm')
|
|
sprintf(movename + 1, "%d", board_size - m);
|
|
else
|
|
sprintf(movename + 1, "%-2d", board_size - m);
|
|
fputs(movename, outputfile);
|
|
}
|
|
break;
|
|
}
|
|
case 'H':
|
|
{
|
|
unsigned long h = va_arg(ap, unsigned long);
|
|
fprintf(outputfile, "%lx", h);
|
|
break;
|
|
}
|
|
case 'C':
|
|
{
|
|
int color = va_arg(ap, int);
|
|
fputs(color_to_string(color), outputfile);
|
|
break;
|
|
}
|
|
default:
|
|
fprintf(outputfile, "\n\nUnknown format character '%c'\n", *fmt);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
putc(*fmt, outputfile);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* required wrapper around vgprintf, writes to outfile.
|
|
*/
|
|
|
|
void
|
|
gfprintf(FILE *outfile, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
vgprintf(outfile, fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
|
|
/*
|
|
* required wrapper around vgprintf, writes to stderr.
|
|
* Always returns 1 to allow use in short-circuit logical expressions.
|
|
*/
|
|
|
|
int
|
|
gprintf(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
vgprintf(stderr, fmt, ap);
|
|
va_end(ap);
|
|
return 1;
|
|
}
|
|
|
|
|
|
/*
|
|
* required wrapper around vgprintf, in contrast to gprintf this one
|
|
* writes to stdout.
|
|
*/
|
|
|
|
void
|
|
mprintf(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
vgprintf(stdout, fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
/* This writes the move history information in sgf format to stderr.
|
|
* This is only intended as a stand-alone debug tool for use in
|
|
* abortgo(). Anywhere else you should use the normal sgf library.
|
|
*/
|
|
static void
|
|
dump_board_sgf(void)
|
|
{
|
|
int pos;
|
|
int initial_colors_found = EMPTY;
|
|
int color;
|
|
int k;
|
|
|
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
|
|
if (ON_BOARD(pos))
|
|
initial_colors_found |= initial_board[pos];
|
|
|
|
fprintf(stderr, "(;GM[1]FF[4]SZ[%d]KM[%.1f]HA[%d]GN[GNU Go %s stepped on a bug]\n",
|
|
board_size, komi, handicap, gg_version());
|
|
|
|
for (color = WHITE; color <= BLACK; color++) {
|
|
if (initial_colors_found & color) {
|
|
fprintf(stderr, "A%s", color == WHITE ? "W" : "B");
|
|
for (k = 0, pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
|
if (ON_BOARD(pos) && initial_board[pos] == color) {
|
|
fprintf(stderr, "[%c%c]", 'a' + J(pos), 'a' + I(pos));
|
|
k++;
|
|
if (k % 16 == 0)
|
|
fprintf(stderr, "\n");
|
|
}
|
|
}
|
|
if (k % 16 != 0)
|
|
fprintf(stderr, "\n");
|
|
}
|
|
}
|
|
|
|
if (move_history_pointer > 0) {
|
|
for (k = 0; k < move_history_pointer; k++) {
|
|
fprintf(stderr, ";%s", move_history_color[k] == WHITE ? "W" : "B");
|
|
if (move_history_pos[k] == PASS_MOVE)
|
|
fprintf(stderr, "[]");
|
|
else
|
|
fprintf(stderr, "[%c%c]", 'a' + J(move_history_pos[k]),
|
|
'a' + I(move_history_pos[k]));
|
|
|
|
if (k % 12 == 11)
|
|
fprintf(stderr, "\n");
|
|
}
|
|
if (k % 12 != 0)
|
|
fprintf(stderr, "\n");
|
|
}
|
|
fprintf(stderr, ")\n");
|
|
}
|
|
|
|
/*
|
|
* A wrapper around abort() which shows the state variables at the time
|
|
* of the problem. (pos) is typically a related move, or NO_MOVE.
|
|
*/
|
|
|
|
void
|
|
abortgo(const char *file, int line, const char *msg, int pos)
|
|
{
|
|
gprintf("%o\n\n***assertion failure:\n%s:%d - %s near %1m***\n\n",
|
|
file, line, msg, pos);
|
|
dump_stack();
|
|
|
|
/* Print the board at the top of the stack. */
|
|
simple_showboard(stderr);
|
|
fprintf(stderr, "\n");
|
|
|
|
dump_board_sgf();
|
|
|
|
fprintf(stderr, "gnugo %s (seed %d): You stepped on a bug.\n",
|
|
gg_version(), get_random_seed());
|
|
if (board_size >= 9 && board_size <= 19) {
|
|
fprintf(stderr, "\
|
|
Please mail this message, including the debug output above, \
|
|
to gnugo@gnu.org\n");
|
|
}
|
|
fprintf(stderr, "\n");
|
|
|
|
fflush(stderr);
|
|
fflush(stdout);
|
|
|
|
abort(); /* cause core dump */
|
|
}
|
|
|
|
static const char *color_names[] = {
|
|
COLOR_NAMES
|
|
};
|
|
|
|
/* Convert a color value to a string. */
|
|
const char *
|
|
color_to_string(int color)
|
|
{
|
|
gg_assert(color < NUM_KOMASTER_STATES);
|
|
return color_names[color];
|
|
}
|
|
|
|
/* Convert a location to a string. */
|
|
const char *
|
|
location_to_string(int pos)
|
|
{
|
|
static int init = 0;
|
|
static char buf[BOARDSIZE][5];
|
|
if (!init) {
|
|
int pos;
|
|
for (pos = 0; pos < BOARDSIZE; pos++)
|
|
location_to_buffer(pos, buf[pos]);
|
|
init = 1;
|
|
}
|
|
ASSERT1(pos >= 0 && pos < BOARDSIZE, pos);
|
|
return buf[pos];
|
|
}
|
|
|
|
/* Convert a location to a string, writing to a buffer. */
|
|
|
|
void
|
|
location_to_buffer(int pos, char *buf)
|
|
{
|
|
char *bufp = buf;
|
|
int i = I(pos);
|
|
int j = J(pos);
|
|
|
|
if (pos == NO_MOVE) {
|
|
strcpy(buf, "Pass");
|
|
return;
|
|
}
|
|
|
|
*bufp = 'A'+j;
|
|
if (*bufp >= 'I')
|
|
(*bufp)++;
|
|
bufp++;
|
|
|
|
i = board_size - i;
|
|
if (i > 9)
|
|
*bufp++ = '0' + i/10;
|
|
*bufp++ = '0' + i%10;
|
|
|
|
*bufp = 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Convert the string str to a 1D coordinate. Return NO_MOVE if invalid
|
|
* string.
|
|
*/
|
|
|
|
int
|
|
string_to_location(int boardsize, const char *str)
|
|
{
|
|
int m, n;
|
|
|
|
if (*str == '\0')
|
|
return NO_MOVE;
|
|
|
|
if (!isalpha((int) *str))
|
|
return NO_MOVE;
|
|
|
|
n = tolower((int) *str) - 'a';
|
|
if (tolower((int) *str) >= 'i')
|
|
--n;
|
|
if (n < 0 || n > boardsize - 1)
|
|
return NO_MOVE;
|
|
|
|
if (!isdigit((int) *(str + 1)))
|
|
return NO_MOVE;
|
|
|
|
m = boardsize - atoi(str + 1);
|
|
if (m < 0 || m > boardsize - 1)
|
|
return NO_MOVE;
|
|
|
|
return POS(m, n);
|
|
}
|
|
|
|
|
|
/* Some simple functions to draw an ASCII board. */
|
|
|
|
/* True if the coordinate is a hoshi point. */
|
|
int
|
|
is_hoshi_point(int m, int n)
|
|
{
|
|
int hoshi;
|
|
int middle;
|
|
|
|
/* No hoshi points on these boards. */
|
|
if (board_size == 2 || board_size == 4)
|
|
return 0;
|
|
|
|
/* In the middle of a 3x3 board. */
|
|
if (board_size == 3) {
|
|
if (m == 1 && n == 1)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (board_size == 5) {
|
|
if (m == 1 && (n == 1 || n == 3))
|
|
return 1;
|
|
if (m == 2 && n == 2)
|
|
return 1;
|
|
if (m == 3 && (n == 1 || n == 3))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* 3-3 points are hoshi on sizes 7--11, 4-4 on larger. */
|
|
if (board_size <= 11)
|
|
hoshi = 2;
|
|
else
|
|
hoshi = 3;
|
|
|
|
/* Coordinate for midpoint. */
|
|
middle = board_size/2;
|
|
|
|
/* Normalize the coordinates by mirroring to the lower numbers. */
|
|
if (m >= middle)
|
|
m = board_size - 1 - m;
|
|
if (n >= middle)
|
|
n = board_size - 1 - n;
|
|
|
|
/* Is this a corner hoshi? */
|
|
if (m == hoshi && n == hoshi)
|
|
return 1;
|
|
|
|
/* If even sized board, only hoshi points in the corner. */
|
|
if (board_size%2 == 0)
|
|
return 0;
|
|
|
|
/* Less than 12 in board size only middle point. */
|
|
if (board_size < 12) {
|
|
if (m == middle && n == middle)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Is this a midpoint hoshi? */
|
|
if ((m == hoshi || m == middle)
|
|
&& (n == hoshi || n == middle))
|
|
return 1;
|
|
|
|
/* No more chances. */
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Print a line with coordinate letters above the board. */
|
|
void
|
|
draw_letter_coordinates(FILE *outfile)
|
|
{
|
|
int i;
|
|
int ch;
|
|
|
|
fprintf(outfile, " ");
|
|
for (i = 0, ch = 'A'; i < board_size; i++, ch++) {
|
|
if (ch == 'I')
|
|
ch++;
|
|
fprintf(outfile, " %c", ch);
|
|
}
|
|
}
|
|
|
|
|
|
/* Bare bones version of showboard(0). No fancy options, no hint of
|
|
* color, and you can choose where to write it.
|
|
*/
|
|
void
|
|
simple_showboard(FILE *outfile)
|
|
{
|
|
int i, j;
|
|
|
|
draw_letter_coordinates(outfile);
|
|
|
|
for (i = 0; i < board_size; i++) {
|
|
fprintf(outfile, "\n%2d", board_size - i);
|
|
|
|
for (j = 0; j < board_size; j++) {
|
|
if (BOARD(i, j) == EMPTY)
|
|
fprintf(outfile, " %c", is_hoshi_point(i, j) ? '+' : '.');
|
|
else
|
|
fprintf(outfile, " %c", BOARD(i, j) == BLACK ? 'X' : 'O');
|
|
}
|
|
|
|
fprintf(outfile, " %d", board_size - i);
|
|
|
|
if ((board_size < 10 && i == board_size-2)
|
|
|| (board_size >= 10 && i == 8))
|
|
fprintf(outfile, " WHITE (O) has captured %d stones", black_captured);
|
|
|
|
if ((board_size < 10 && i == board_size-1)
|
|
|| (board_size >= 10 && i == 9))
|
|
fprintf(outfile, " BLACK (X) has captured %d stones", white_captured);
|
|
}
|
|
|
|
fprintf(outfile, "\n");
|
|
draw_letter_coordinates(outfile);
|
|
}
|
|
|
|
|
|
/* Adds square marks for each goal intersecion in the current sgf_dumptree.
|
|
* This function cannot be in sgf/ as it has to understand the 1-D board.
|
|
*/
|
|
void
|
|
mark_goal_in_sgf(signed char goal[BOARDMAX])
|
|
{
|
|
int pos;
|
|
SGFNode *node;
|
|
|
|
if (!sgf_dumptree)
|
|
return;
|
|
node = sgftreeNodeCheck(sgf_dumptree);
|
|
|
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
|
|
if (ON_BOARD(pos) && goal[pos])
|
|
sgfSquare(node, I(pos), J(pos));
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Local Variables:
|
|
* tab-width: 8
|
|
* c-basic-offset: 2
|
|
* End:
|
|
*/
|