1563 lines
31 KiB
C
1563 lines
31 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. *
|
||
|
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||
|
|
||
|
/* Parts of this code were given to us by Tommy Thorn */
|
||
|
|
||
|
#ifdef HAVE_CONFIG_H
|
||
|
#include <config.h>
|
||
|
#endif
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <ctype.h>
|
||
|
#include <string.h>
|
||
|
#include <assert.h>
|
||
|
|
||
|
|
||
|
#if TIME_WITH_SYS_TIME
|
||
|
# include <sys/time.h>
|
||
|
# include <time.h>
|
||
|
#else
|
||
|
# if HAVE_SYS_TIME_H
|
||
|
# include <sys/time.h>
|
||
|
# else
|
||
|
# include <time.h>
|
||
|
# endif
|
||
|
#endif
|
||
|
|
||
|
#include "sgftree.h"
|
||
|
#include "gg_utils.h"
|
||
|
|
||
|
#define STRICT_SGF 's'
|
||
|
#define LAX_SGF 'l'
|
||
|
|
||
|
/* Set this to 1 if you want warnings for missing GM and FF properties. */
|
||
|
#define VERBOSE_WARNINGS 0
|
||
|
|
||
|
/* ================================================================ */
|
||
|
/* Some utility functions. */
|
||
|
/* ================================================================ */
|
||
|
|
||
|
/*
|
||
|
* Utility: a checking, initializing malloc
|
||
|
*/
|
||
|
|
||
|
void *
|
||
|
xalloc(unsigned int size)
|
||
|
{
|
||
|
void *pt = malloc(size);
|
||
|
|
||
|
if (!pt) {
|
||
|
fprintf(stderr, "xalloc: Out of memory!\n");
|
||
|
exit(EXIT_FAILURE);
|
||
|
}
|
||
|
|
||
|
memset(pt, 0, (unsigned long) size);
|
||
|
return pt;
|
||
|
}
|
||
|
|
||
|
void *
|
||
|
xrealloc(void *pt, unsigned int size)
|
||
|
{
|
||
|
void *ptnew = realloc(pt, size);
|
||
|
|
||
|
if (!ptnew) {
|
||
|
fprintf(stderr, "xrealloc: Out of memory!\n");
|
||
|
exit(EXIT_FAILURE);
|
||
|
}
|
||
|
return ptnew;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* ================================================================ */
|
||
|
/* SGF Nodes */
|
||
|
/* ================================================================ */
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Allocate memory for a new SGF node.
|
||
|
*/
|
||
|
|
||
|
SGFNode *
|
||
|
sgfNewNode()
|
||
|
{
|
||
|
SGFNode *newnode;
|
||
|
newnode = xalloc(sizeof(SGFNode));
|
||
|
newnode->next = NULL;
|
||
|
newnode->props = NULL;
|
||
|
newnode->parent = NULL;
|
||
|
newnode->child = NULL;
|
||
|
return newnode;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Recursively free an sgf node
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
sgfFreeNode(SGFNode *node)
|
||
|
{
|
||
|
if (node == NULL)
|
||
|
return;
|
||
|
sgfFreeNode(node->next);
|
||
|
sgfFreeNode(node->child);
|
||
|
sgfFreeProperty(node->props);
|
||
|
free(node);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Add a generic text property to an SGF node.
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
sgfAddProperty(SGFNode *node, const char *name, const char *value)
|
||
|
{
|
||
|
SGFProperty *prop = node->props;
|
||
|
|
||
|
if (prop)
|
||
|
while (prop->next)
|
||
|
prop = prop->next;
|
||
|
|
||
|
sgfMkProperty(name, value, node, prop);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Add an integer property to an SGF node.
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
sgfAddPropertyInt(SGFNode *node, const char *name, long val)
|
||
|
{
|
||
|
char buffer[10];
|
||
|
|
||
|
gg_snprintf(buffer, 10, "%ld", val);
|
||
|
sgfAddProperty(node, name, buffer);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Add a float property to an SGF node.
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
sgfAddPropertyFloat(SGFNode *node, const char *name, float val)
|
||
|
{
|
||
|
char buffer[10];
|
||
|
|
||
|
gg_snprintf(buffer, 10, "%3.1f", val);
|
||
|
sgfAddProperty(node, name, buffer);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Read a property as int from an SGF node.
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
sgfGetIntProperty(SGFNode *node, const char *name, int *value)
|
||
|
{
|
||
|
SGFProperty *prop;
|
||
|
short nam = name[0] | name[1] << 8;
|
||
|
|
||
|
for (prop = node->props; prop; prop = prop->next)
|
||
|
if (prop->name == nam) {
|
||
|
*value = atoi(prop->value);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Read a property as float from an SGF node.
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
sgfGetFloatProperty(SGFNode *node, const char *name, float *value)
|
||
|
{
|
||
|
SGFProperty *prop;
|
||
|
short nam = name[0] | name[1] << 8;
|
||
|
|
||
|
for (prop = node->props; prop; prop = prop->next)
|
||
|
if (prop->name == nam) {
|
||
|
*value = (float) atof(prop->value);
|
||
|
/* MS-C warns of loss of data (double to float) */
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Read a property as text from an SGF node.
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
sgfGetCharProperty(SGFNode *node, const char *name, char **value)
|
||
|
{
|
||
|
SGFProperty *prop;
|
||
|
short nam = name[0] | name[1] << 8;
|
||
|
|
||
|
for (prop = node->props; prop; prop = prop->next)
|
||
|
if (prop->name == nam) {
|
||
|
*value = prop->value;
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Is there a property of this type in the node?
|
||
|
*/
|
||
|
|
||
|
static int
|
||
|
sgfHasProperty(SGFNode *node, const char *name)
|
||
|
{
|
||
|
SGFProperty *prop;
|
||
|
short nam = name[0] | name[1] << 8;
|
||
|
|
||
|
for (prop = node->props; prop; prop = prop->next)
|
||
|
if (prop->name == nam)
|
||
|
return 1;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Overwrite a property from an SGF node with text or create a new
|
||
|
* one if it does not exist.
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
sgfOverwriteProperty(SGFNode *node, const char *name, const char *text)
|
||
|
{
|
||
|
SGFProperty *prop;
|
||
|
short nam = name[0] | name[1] << 8;
|
||
|
|
||
|
for (prop = node->props; prop; prop = prop->next)
|
||
|
if (prop->name == nam) {
|
||
|
prop->value = xrealloc(prop->value, strlen(text)+1);
|
||
|
strcpy(prop->value, text);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
sgfAddProperty(node, name, text);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Overwrite an int property in an SGF node with val or create a new
|
||
|
* one if it does not exist.
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
sgfOverwritePropertyInt(SGFNode *node, const char *name, int val)
|
||
|
{
|
||
|
SGFProperty *prop;
|
||
|
short nam = name[0] | name[1] << 8;
|
||
|
|
||
|
for (prop = node->props; prop; prop = prop->next)
|
||
|
if (prop->name == nam) {
|
||
|
prop->value = xrealloc(prop->value, 12);
|
||
|
gg_snprintf(prop->value, 12, "%d", val);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
sgfAddPropertyInt(node, name, val);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Overwrite a float property in the gametree with val or create
|
||
|
* a new one if it does not exist.
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
sgfOverwritePropertyFloat(SGFNode *node, const char *name, float val)
|
||
|
{
|
||
|
SGFProperty *prop;
|
||
|
short nam = name[0] | name[1] << 8;
|
||
|
|
||
|
for (prop = node->props; prop; prop = prop->next)
|
||
|
if (prop->name == nam) {
|
||
|
prop->value = xrealloc(prop->value, 15);
|
||
|
gg_snprintf(prop->value, 15, "%3.1f", val);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
sgfAddPropertyFloat(node, name, val);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Goto previous node.
|
||
|
*/
|
||
|
|
||
|
SGFNode *
|
||
|
sgfPrev(SGFNode *node)
|
||
|
{
|
||
|
SGFNode *q;
|
||
|
SGFNode *prev;
|
||
|
|
||
|
if (!node->parent)
|
||
|
return NULL;
|
||
|
|
||
|
q = node->parent->child;
|
||
|
prev = NULL;
|
||
|
while (q && q != node) {
|
||
|
prev = q;
|
||
|
q = q->next;
|
||
|
}
|
||
|
|
||
|
return prev;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Goto root node.
|
||
|
*/
|
||
|
|
||
|
SGFNode *
|
||
|
sgfRoot(SGFNode *node)
|
||
|
{
|
||
|
while (node->parent)
|
||
|
node = node->parent;
|
||
|
|
||
|
return node;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* ================================================================ */
|
||
|
/* SGF Properties */
|
||
|
/* ================================================================ */
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Make an SGF property.
|
||
|
*/
|
||
|
static SGFProperty *
|
||
|
do_sgf_make_property(short sgf_name, const char *value,
|
||
|
SGFNode *node, SGFProperty *last)
|
||
|
{
|
||
|
SGFProperty *prop;
|
||
|
|
||
|
prop = (SGFProperty *) xalloc(sizeof(SGFProperty));
|
||
|
prop->name = sgf_name;
|
||
|
prop->value = xalloc(strlen(value) + 1);
|
||
|
strcpy(prop->value, value);
|
||
|
prop->next = NULL;
|
||
|
|
||
|
if (last == NULL)
|
||
|
node->props = prop;
|
||
|
else
|
||
|
last->next = prop;
|
||
|
|
||
|
return prop;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Make an SGF property. In case of a property with a range it
|
||
|
* expands it and makes several properties instead.
|
||
|
*/
|
||
|
SGFProperty *
|
||
|
sgfMkProperty(const char *name, const char *value,
|
||
|
SGFNode *node, SGFProperty *last)
|
||
|
{
|
||
|
static const short properties_allowing_ranges[12] = {
|
||
|
/* Board setup properties. */
|
||
|
SGFAB, SGFAW, SGFAE,
|
||
|
|
||
|
/* Markup properties. */
|
||
|
SGFCR, SGFMA, SGFSQ, SGFTR, SGFDD, SGFSL,
|
||
|
|
||
|
/* Miscellaneous properties. */
|
||
|
SGFVW,
|
||
|
|
||
|
/* Go-specific properties. */
|
||
|
SGFTB, SGFTW
|
||
|
};
|
||
|
|
||
|
int k;
|
||
|
short sgf_name;
|
||
|
|
||
|
if (strlen(name) == 1)
|
||
|
sgf_name = name[0] | (short) (' ' << 8);
|
||
|
else
|
||
|
sgf_name = name[0] | name[1] << 8;
|
||
|
|
||
|
for (k = 0; k < 12; k++) {
|
||
|
if (properties_allowing_ranges[k] == sgf_name)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (k < 12
|
||
|
&& strlen(value) == 5
|
||
|
&& value[2] == ':') {
|
||
|
char x1 = value[0];
|
||
|
char y1 = value[1];
|
||
|
char x2 = value[3];
|
||
|
char y2 = value[4];
|
||
|
char new_value[] = "xy";
|
||
|
|
||
|
if (x1 <= x2 && y1 <= y2) {
|
||
|
for (new_value[0] = x1; new_value[0] <= x2; new_value[0]++) {
|
||
|
for (new_value[1] = y1; new_value[1] <= y2; new_value[1]++)
|
||
|
last = do_sgf_make_property(sgf_name, new_value, node, last);
|
||
|
}
|
||
|
|
||
|
return last;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Not a range property. */
|
||
|
return do_sgf_make_property(sgf_name, value, node, last);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Recursively free an SGF property.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
sgfFreeProperty(SGFProperty *prop)
|
||
|
{
|
||
|
if (prop == NULL)
|
||
|
return;
|
||
|
sgfFreeProperty(prop->next);
|
||
|
free(prop->value);
|
||
|
free(prop);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* ================================================================ */
|
||
|
/* High level functions */
|
||
|
/* ================================================================ */
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Add a stone to the current or the given node.
|
||
|
* Return the node where the stone was added.
|
||
|
*/
|
||
|
|
||
|
SGFNode *
|
||
|
sgfAddStone(SGFNode *node, int color, int movex, int movey)
|
||
|
{
|
||
|
char move[3];
|
||
|
|
||
|
sprintf(move, "%c%c", movey + 'a', movex + 'a');
|
||
|
sgfAddProperty(node, (color == BLACK) ? "AB" : "AW", move);
|
||
|
|
||
|
return node;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Add a move to the gametree.
|
||
|
*/
|
||
|
|
||
|
SGFNode *
|
||
|
sgfAddPlay(SGFNode *node, int who, int movex, int movey)
|
||
|
{
|
||
|
char move[3];
|
||
|
SGFNode *new;
|
||
|
|
||
|
/* a pass move? */
|
||
|
if (movex == -1 && movey == -1)
|
||
|
move[0] = 0;
|
||
|
else
|
||
|
sprintf(move, "%c%c", movey + 'a', movex + 'a');
|
||
|
|
||
|
if (node->child)
|
||
|
new = sgfStartVariantFirst(node->child);
|
||
|
else {
|
||
|
new = sgfNewNode();
|
||
|
node->child = new;
|
||
|
new->parent = node;
|
||
|
}
|
||
|
|
||
|
sgfAddProperty(new, (who == BLACK) ? "B" : "W", move);
|
||
|
|
||
|
return new;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Add a move to the gametree. New variations are added after the old
|
||
|
* ones rather than before.
|
||
|
*/
|
||
|
|
||
|
SGFNode *
|
||
|
sgfAddPlayLast(SGFNode *node, int who, int movex, int movey)
|
||
|
{
|
||
|
char move[3];
|
||
|
SGFNode *new;
|
||
|
|
||
|
/* a pass move? */
|
||
|
if (movex == -1 && movey == -1)
|
||
|
move[0] = 0;
|
||
|
else
|
||
|
sprintf(move, "%c%c", movey + 'a', movex + 'a');
|
||
|
|
||
|
new = sgfAddChild(node);
|
||
|
sgfAddProperty(new, (who == BLACK) ? "B" : "W", move);
|
||
|
|
||
|
return new;
|
||
|
}
|
||
|
|
||
|
|
||
|
SGFNode *
|
||
|
sgfCreateHeaderNode(int boardsize, float komi, int handicap)
|
||
|
{
|
||
|
SGFNode *root = sgfNewNode();
|
||
|
|
||
|
sgfAddPropertyInt(root, "SZ", boardsize);
|
||
|
sgfAddPropertyFloat(root, "KM", komi);
|
||
|
sgfAddPropertyInt(root, "HA", handicap);
|
||
|
|
||
|
return root;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Add a comment to an SGF node.
|
||
|
*/
|
||
|
|
||
|
SGFNode *
|
||
|
sgfAddComment(SGFNode *node, const char *comment)
|
||
|
{
|
||
|
sgfAddProperty(node, "C ", comment);
|
||
|
|
||
|
return node;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Place text on the board at position (i, j).
|
||
|
*/
|
||
|
|
||
|
SGFNode *
|
||
|
sgfBoardText(SGFNode *node, int i, int j, const char *text)
|
||
|
{
|
||
|
void *str = xalloc(strlen(text) + 3);
|
||
|
|
||
|
sprintf(str, "%c%c:%s", j+'a', i+'a', text);
|
||
|
sgfAddProperty(node, "LB", str);
|
||
|
free(str);
|
||
|
|
||
|
return node;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Place a character on the board at position (i, j).
|
||
|
*/
|
||
|
|
||
|
SGFNode *
|
||
|
sgfBoardChar(SGFNode *node, int i, int j, char c)
|
||
|
{
|
||
|
char text[2] = "";
|
||
|
|
||
|
text[0] = c;
|
||
|
text[1] = 0;
|
||
|
|
||
|
return sgfBoardText(node, i, j, text);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Place a number on the board at position (i, j).
|
||
|
*/
|
||
|
|
||
|
SGFNode *
|
||
|
sgfBoardNumber(SGFNode *node, int i, int j, int number)
|
||
|
{
|
||
|
char text[10];
|
||
|
|
||
|
gg_snprintf(text, 10, "%c%c:%i", j+'a', i+'a', number);
|
||
|
sgfAddProperty(node, "LB", text);
|
||
|
|
||
|
return node;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Place a triangle mark on the board at position (i, j).
|
||
|
*/
|
||
|
|
||
|
SGFNode *
|
||
|
sgfTriangle(SGFNode *node, int i, int j)
|
||
|
{
|
||
|
char text[3];
|
||
|
|
||
|
gg_snprintf(text, 3, "%c%c", j+'a', i+'a');
|
||
|
sgfAddProperty(node, "TR", text);
|
||
|
|
||
|
return node;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Place a label on the board at position (i, j).
|
||
|
*/
|
||
|
|
||
|
SGFNode *
|
||
|
sgfLabel(SGFNode *node, const char *label, int i, int j)
|
||
|
{
|
||
|
/* allows 12 chars labels - more than enough */
|
||
|
char text[16];
|
||
|
|
||
|
gg_snprintf(text, 16, "%c%c:%s", j+'a', i+'a', label);
|
||
|
sgfAddProperty(node, "LB", text);
|
||
|
|
||
|
return node;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Place a numeric label on the board at position (i, j).
|
||
|
*/
|
||
|
|
||
|
SGFNode *
|
||
|
sgfLabelInt(SGFNode *node, int num, int i, int j)
|
||
|
{
|
||
|
char text[16];
|
||
|
|
||
|
gg_snprintf(text, 16, "%c%c:%d", j+'a', i+'a', num);
|
||
|
sgfAddProperty(node, "LB", text);
|
||
|
|
||
|
return node;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Place a circle mark on the board at position (i, j).
|
||
|
*/
|
||
|
|
||
|
SGFNode *
|
||
|
sgfCircle(SGFNode *node, int i, int j)
|
||
|
{
|
||
|
char text[3];
|
||
|
|
||
|
gg_snprintf(text, 3, "%c%c", j+'a', i+'a');
|
||
|
sgfAddProperty(node, "CR", text);
|
||
|
|
||
|
return node;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Place a square mark on the board at position (i, j).
|
||
|
*/
|
||
|
|
||
|
SGFNode *
|
||
|
sgfSquare(SGFNode *node, int i, int j)
|
||
|
{
|
||
|
return sgfMark(node, i, j); /* cgoban 1.9.5 does not understand SQ */
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Place a (square) mark on the board at position (i, j).
|
||
|
*/
|
||
|
|
||
|
SGFNode *
|
||
|
sgfMark(SGFNode *node, int i, int j)
|
||
|
{
|
||
|
char text[3];
|
||
|
|
||
|
gg_snprintf(text, 3, "%c%c", j+'a', i+'a');
|
||
|
sgfAddProperty(node, "MA", text);
|
||
|
|
||
|
return node;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Start a new variant. Returns a pointer to the new node.
|
||
|
*/
|
||
|
|
||
|
SGFNode *
|
||
|
sgfStartVariant(SGFNode *node)
|
||
|
{
|
||
|
assert(node);
|
||
|
assert(node->parent);
|
||
|
|
||
|
while (node->next)
|
||
|
node = node->next;
|
||
|
node->next = sgfNewNode();
|
||
|
node->next->parent = node->parent;
|
||
|
|
||
|
return node->next;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Start a new variant as first child. Returns a pointer to the new node.
|
||
|
*/
|
||
|
|
||
|
SGFNode *
|
||
|
sgfStartVariantFirst(SGFNode *node)
|
||
|
{
|
||
|
SGFNode *old_first_child = node;
|
||
|
SGFNode *new_first_child = sgfNewNode();
|
||
|
|
||
|
assert(node);
|
||
|
assert(node->parent);
|
||
|
|
||
|
new_first_child->next = old_first_child;
|
||
|
new_first_child->parent = old_first_child->parent;
|
||
|
|
||
|
new_first_child->parent->child = new_first_child;
|
||
|
|
||
|
return new_first_child;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* If no child exists, add one. Otherwise add a sibling to the
|
||
|
* existing children. Returns a pointer to the new node.
|
||
|
*/
|
||
|
|
||
|
SGFNode *
|
||
|
sgfAddChild(SGFNode *node)
|
||
|
{
|
||
|
SGFNode *new_node = sgfNewNode();
|
||
|
assert(node);
|
||
|
|
||
|
new_node->parent = node;
|
||
|
|
||
|
if (!node->child)
|
||
|
node->child = new_node;
|
||
|
else {
|
||
|
node = node->child;
|
||
|
while (node->next)
|
||
|
node = node->next;
|
||
|
node->next = new_node;
|
||
|
}
|
||
|
|
||
|
return new_node;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Write result of the game to the game tree.
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
sgfWriteResult(SGFNode *node, float score, int overwrite)
|
||
|
{
|
||
|
char text[8];
|
||
|
char winner;
|
||
|
float s;
|
||
|
int dummy;
|
||
|
|
||
|
/* If not writing to the SGF file, skip everything and return now. */
|
||
|
if (!node)
|
||
|
return;
|
||
|
|
||
|
/* If not overwriting and there already is a result property, return. */
|
||
|
if (!overwrite)
|
||
|
if (sgfGetIntProperty(node, "RE", &dummy))
|
||
|
return;
|
||
|
|
||
|
if (score > 0.0) {
|
||
|
winner = 'W';
|
||
|
s = score;
|
||
|
}
|
||
|
else if (score < 0.0) {
|
||
|
winner = 'B';
|
||
|
s = -score;
|
||
|
}
|
||
|
else {
|
||
|
winner = '0';
|
||
|
s = 0;
|
||
|
}
|
||
|
|
||
|
if (winner == '0')
|
||
|
gg_snprintf(text, 8, "0");
|
||
|
else if (score < 1000.0 && score > -1000.0)
|
||
|
gg_snprintf(text, 8, "%c+%3.1f", winner, s);
|
||
|
else
|
||
|
gg_snprintf(text, 8, "%c+%c", winner, 'R');
|
||
|
sgfOverwriteProperty(node, "RE", text);
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
sgf_write_header_reduced(SGFNode *root, int overwrite)
|
||
|
{
|
||
|
time_t curtime = time(NULL);
|
||
|
struct tm *loctime = localtime(&curtime);
|
||
|
char str[128];
|
||
|
int dummy;
|
||
|
|
||
|
gg_snprintf(str, 128, "%4.4i-%2.2i-%2.2i",
|
||
|
loctime->tm_year+1900, loctime->tm_mon+1, loctime->tm_mday);
|
||
|
if (overwrite || !sgfGetIntProperty(root, "DT", &dummy))
|
||
|
sgfOverwriteProperty(root, "DT", str);
|
||
|
if (overwrite || !sgfGetIntProperty(root, "AP", &dummy))
|
||
|
sgfOverwriteProperty(root, "AP", "GNU Go:"VERSION);
|
||
|
sgfOverwriteProperty(root, "FF", "4");
|
||
|
}
|
||
|
|
||
|
|
||
|
void
|
||
|
sgf_write_header(SGFNode *root, int overwrite, int seed, float komi,
|
||
|
int handicap, int level, int rules)
|
||
|
{
|
||
|
char str[128];
|
||
|
int dummy;
|
||
|
|
||
|
gg_snprintf(str, 128, "GNU Go %s Random Seed %d level %d",
|
||
|
VERSION, seed, level);
|
||
|
if (overwrite || !sgfGetIntProperty(root, "GN", &dummy))
|
||
|
sgfOverwriteProperty(root, "GN", str);
|
||
|
if (overwrite || !sgfGetIntProperty(root, "RU", &dummy))
|
||
|
sgfOverwriteProperty(root, "RU", rules ? "Chinese" : "Japanese");
|
||
|
sgfOverwritePropertyFloat(root, "KM", komi);
|
||
|
sgfOverwritePropertyInt(root, "HA", handicap);
|
||
|
|
||
|
sgf_write_header_reduced(root, overwrite);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* ================================================================ */
|
||
|
/* Read SGF tree */
|
||
|
/* ================================================================ */
|
||
|
|
||
|
|
||
|
#define MAX_FILE_BUFFER 200000 /* buffer for reading SGF file. */
|
||
|
|
||
|
/*
|
||
|
* SGF grammar:
|
||
|
*
|
||
|
* Collection = GameTree { GameTree }
|
||
|
* GameTree = "(" Sequence { GameTree } ")"
|
||
|
* Sequence = Node { Node }
|
||
|
* Node = ";" { Property }
|
||
|
* Property = PropIdent PropValue { PropValue }
|
||
|
* PropIdent = UcLetter { UcLetter }
|
||
|
* PropValue = "[" CValueType "]"
|
||
|
* CValueType = (ValueType | Compose)
|
||
|
* ValueType = (None | Number | Real | Double | Color | SimpleText |
|
||
|
* Text | Point | Move | Stone)
|
||
|
*
|
||
|
* The above grammar has a number of simple properties which enables us
|
||
|
* to write a simpler parser:
|
||
|
* 1) There is never a need for backtracking
|
||
|
* 2) The only recursion is on gametree.
|
||
|
* 3) Tokens are only one character
|
||
|
*
|
||
|
* We will use a global state to keep track of the remaining input
|
||
|
* and a global char variable, `lookahead' to hold the next token.
|
||
|
* The function `nexttoken' skips whitespace and fills lookahead with
|
||
|
* the new token.
|
||
|
*/
|
||
|
|
||
|
|
||
|
static void parse_error(const char *msg, int arg);
|
||
|
static void nexttoken(void);
|
||
|
static void match(int expected);
|
||
|
|
||
|
|
||
|
static FILE *sgffile;
|
||
|
|
||
|
|
||
|
#define sgf_getch() (getc(sgffile))
|
||
|
|
||
|
|
||
|
static char *sgferr;
|
||
|
#ifdef TEST_SGFPARSER
|
||
|
static int sgferrarg;
|
||
|
#endif
|
||
|
static int sgferrpos;
|
||
|
|
||
|
static int lookahead;
|
||
|
|
||
|
|
||
|
/* ---------------------------------------------------------------- */
|
||
|
/* Parsing primitives */
|
||
|
/* ---------------------------------------------------------------- */
|
||
|
|
||
|
|
||
|
static void
|
||
|
parse_error(const char *msg, int arg)
|
||
|
{
|
||
|
fprintf(stderr, msg, arg);
|
||
|
fprintf(stderr, "\n");
|
||
|
exit(EXIT_FAILURE);
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
nexttoken()
|
||
|
{
|
||
|
do
|
||
|
lookahead = sgf_getch();
|
||
|
while (isspace(lookahead));
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
match(int expected)
|
||
|
{
|
||
|
if (lookahead != expected)
|
||
|
parse_error("expected: %c", expected);
|
||
|
else
|
||
|
nexttoken();
|
||
|
}
|
||
|
|
||
|
/* ---------------------------------------------------------------- */
|
||
|
/* The parser proper */
|
||
|
/* ---------------------------------------------------------------- */
|
||
|
|
||
|
|
||
|
static void
|
||
|
propident(char *buffer, int size)
|
||
|
{
|
||
|
if (lookahead == EOF || !isupper(lookahead))
|
||
|
parse_error("Expected an upper case letter.", 0);
|
||
|
|
||
|
while (lookahead != EOF && isalpha(lookahead)) {
|
||
|
if (isupper(lookahead) && size > 1) {
|
||
|
*buffer++ = lookahead;
|
||
|
size--;
|
||
|
}
|
||
|
nexttoken();
|
||
|
}
|
||
|
*buffer = '\0';
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
propvalue(char *buffer, int size)
|
||
|
{
|
||
|
char *p = buffer;
|
||
|
|
||
|
match('[');
|
||
|
while (lookahead != ']' && lookahead != EOF) {
|
||
|
if (lookahead == '\\') {
|
||
|
lookahead = sgf_getch();
|
||
|
/* Follow the FF4 definition of backslash */
|
||
|
if (lookahead == '\r') {
|
||
|
lookahead = sgf_getch();
|
||
|
if (lookahead == '\n')
|
||
|
lookahead = sgf_getch();
|
||
|
}
|
||
|
else if (lookahead == '\n') {
|
||
|
lookahead = sgf_getch();
|
||
|
if (lookahead == '\r')
|
||
|
lookahead = sgf_getch();
|
||
|
}
|
||
|
}
|
||
|
if (size > 1) {
|
||
|
*p++ = lookahead;
|
||
|
size--;
|
||
|
}
|
||
|
lookahead = sgf_getch();
|
||
|
}
|
||
|
match(']');
|
||
|
|
||
|
/* Remove trailing whitespace. The double cast below is needed
|
||
|
* because "char" may be represented as a signed char, in which case
|
||
|
* characters between 128 and 255 would be negative and a direct
|
||
|
* cast to int would cause a negative value to be passed to isspace,
|
||
|
* possibly causing an assertion failure.
|
||
|
*/
|
||
|
--p;
|
||
|
while (p > buffer && isspace((int) (unsigned char) *p))
|
||
|
--p;
|
||
|
*++p = '\0';
|
||
|
}
|
||
|
|
||
|
|
||
|
static SGFProperty *
|
||
|
property(SGFNode *n, SGFProperty *last)
|
||
|
{
|
||
|
char name[3];
|
||
|
char buffer[4000];
|
||
|
|
||
|
propident(name, sizeof(name));
|
||
|
do {
|
||
|
propvalue(buffer, sizeof(buffer));
|
||
|
last = sgfMkProperty(name, buffer, n, last);
|
||
|
} while (lookahead == '[');
|
||
|
return last;
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
node(SGFNode *n)
|
||
|
{
|
||
|
SGFProperty *last = NULL;
|
||
|
match(';');
|
||
|
while (lookahead != EOF && isupper(lookahead))
|
||
|
last = property(n, last);
|
||
|
}
|
||
|
|
||
|
|
||
|
static SGFNode *
|
||
|
sequence(SGFNode *n)
|
||
|
{
|
||
|
node(n);
|
||
|
while (lookahead == ';') {
|
||
|
SGFNode *new = sgfNewNode();
|
||
|
new->parent = n;
|
||
|
n->child = new;
|
||
|
n = new;
|
||
|
node(n);
|
||
|
}
|
||
|
return n;
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
gametree(SGFNode **p, SGFNode *parent, int mode)
|
||
|
{
|
||
|
if (mode == STRICT_SGF)
|
||
|
match('(');
|
||
|
else
|
||
|
for (;;) {
|
||
|
if (lookahead == EOF) {
|
||
|
parse_error("Empty file?", 0);
|
||
|
break;
|
||
|
}
|
||
|
if (lookahead == '(') {
|
||
|
while (lookahead == '(')
|
||
|
nexttoken();
|
||
|
if (lookahead == ';')
|
||
|
break;
|
||
|
}
|
||
|
nexttoken();
|
||
|
}
|
||
|
|
||
|
/* The head is parsed */
|
||
|
{
|
||
|
SGFNode *head = sgfNewNode();
|
||
|
SGFNode *last;
|
||
|
|
||
|
head->parent = parent;
|
||
|
*p = head;
|
||
|
|
||
|
last = sequence(head);
|
||
|
p = &last->child;
|
||
|
while (lookahead == '(') {
|
||
|
gametree(p, last, STRICT_SGF);
|
||
|
p = &((*p)->next);
|
||
|
}
|
||
|
if (mode == STRICT_SGF)
|
||
|
match(')');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Fuseki readers
|
||
|
* Reads an SGF file for extract_fuseki in a compact way
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
gametreefuseki(SGFNode **p, SGFNode *parent, int mode,
|
||
|
int moves_per_game, int i)
|
||
|
{
|
||
|
if (mode == STRICT_SGF)
|
||
|
match('(');
|
||
|
else
|
||
|
for (;;) {
|
||
|
if (lookahead == EOF) {
|
||
|
parse_error("Empty file?", 0);
|
||
|
break;
|
||
|
}
|
||
|
if (lookahead == '(') {
|
||
|
while (lookahead == '(')
|
||
|
nexttoken();
|
||
|
if (lookahead == ';')
|
||
|
break;
|
||
|
}
|
||
|
nexttoken();
|
||
|
}
|
||
|
|
||
|
/* The head is parsed */
|
||
|
{
|
||
|
|
||
|
SGFNode *head = sgfNewNode();
|
||
|
SGFNode *last;
|
||
|
head->parent = parent;
|
||
|
*p = head;
|
||
|
|
||
|
last = sequence(head);
|
||
|
p = &last->child;
|
||
|
while (lookahead == '(') {
|
||
|
if (last->props
|
||
|
&& (last->props->name == SGFB || last->props->name == SGFW))
|
||
|
i++;
|
||
|
/* break after number_of_moves moves in SGF file */
|
||
|
if (i >= moves_per_game) {
|
||
|
last->child = NULL;
|
||
|
last->next = NULL;
|
||
|
break;
|
||
|
}
|
||
|
else {
|
||
|
gametreefuseki(p, last, mode, moves_per_game, i);
|
||
|
p = &((*p)->next);
|
||
|
}
|
||
|
}
|
||
|
if (mode == STRICT_SGF)
|
||
|
match(')');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
SGFNode *
|
||
|
readsgffilefuseki(const char *filename, int moves_per_game)
|
||
|
{
|
||
|
SGFNode *root;
|
||
|
int tmpi = 0;
|
||
|
|
||
|
if (strcmp(filename, "-") == 0)
|
||
|
sgffile = stdin;
|
||
|
else
|
||
|
sgffile = fopen(filename, "r");
|
||
|
|
||
|
if (!sgffile)
|
||
|
return NULL;
|
||
|
|
||
|
|
||
|
nexttoken();
|
||
|
gametreefuseki(&root, NULL, LAX_SGF, moves_per_game, 0);
|
||
|
|
||
|
fclose(sgffile);
|
||
|
|
||
|
if (sgferr) {
|
||
|
fprintf(stderr, "Parse error: %s at position %d\n", sgferr, sgferrpos);
|
||
|
sgfFreeNode(root);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/* perform some simple checks on the file */
|
||
|
if (!sgfGetIntProperty(root, "GM", &tmpi)) {
|
||
|
if (VERBOSE_WARNINGS)
|
||
|
fprintf(stderr, "Couldn't find the game type (GM) attribute!\n");
|
||
|
}
|
||
|
else if (tmpi != 1) {
|
||
|
fprintf(stderr, "SGF file might be for game other than go: %d\n", tmpi);
|
||
|
fprintf(stderr, "Trying to load anyway.\n");
|
||
|
}
|
||
|
|
||
|
if (!sgfGetIntProperty(root, "FF", &tmpi)) {
|
||
|
if (VERBOSE_WARNINGS)
|
||
|
fprintf(stderr, "Can not determine SGF spec version (FF)!\n");
|
||
|
}
|
||
|
else if ((tmpi < 3 || tmpi > 4) && VERBOSE_WARNINGS)
|
||
|
fprintf(stderr, "Unsupported SGF spec version: %d\n", tmpi);
|
||
|
|
||
|
return root;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Wrapper around readsgf which reads from a file rather than a string.
|
||
|
* Returns NULL if file will not open, or some other parsing error.
|
||
|
* Filename "-" means read from stdin, and leave it open when done.
|
||
|
*/
|
||
|
|
||
|
SGFNode *
|
||
|
readsgffile(const char *filename)
|
||
|
{
|
||
|
SGFNode *root;
|
||
|
int tmpi = 0;
|
||
|
|
||
|
if (strcmp(filename, "-") == 0)
|
||
|
sgffile = stdin;
|
||
|
else
|
||
|
sgffile = fopen(filename, "r");
|
||
|
|
||
|
if (!sgffile)
|
||
|
return NULL;
|
||
|
|
||
|
|
||
|
nexttoken();
|
||
|
gametree(&root, NULL, LAX_SGF);
|
||
|
|
||
|
if (sgffile != stdin)
|
||
|
fclose(sgffile);
|
||
|
|
||
|
if (sgferr) {
|
||
|
fprintf(stderr, "Parse error: %s at position %d\n", sgferr, sgferrpos);
|
||
|
sgfFreeNode(root);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/* perform some simple checks on the file */
|
||
|
if (!sgfGetIntProperty(root, "GM", &tmpi)) {
|
||
|
if (VERBOSE_WARNINGS)
|
||
|
fprintf(stderr, "Couldn't find the game type (GM) attribute!\n");
|
||
|
}
|
||
|
else if (tmpi != 1) {
|
||
|
fprintf(stderr, "SGF file might be for game other than go: %d\n", tmpi);
|
||
|
fprintf(stderr, "Trying to load anyway.\n");
|
||
|
}
|
||
|
|
||
|
if (!sgfGetIntProperty(root, "FF", &tmpi)) {
|
||
|
if (VERBOSE_WARNINGS)
|
||
|
fprintf(stderr, "Can not determine SGF spec version (FF)!\n");
|
||
|
}
|
||
|
else if ((tmpi < 3 || tmpi > 4) && VERBOSE_WARNINGS)
|
||
|
fprintf(stderr, "Unsupported SGF spec version: %d\n", tmpi);
|
||
|
|
||
|
return root;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/* ================================================================ */
|
||
|
/* Write SGF tree */
|
||
|
/* ================================================================ */
|
||
|
|
||
|
|
||
|
#define OPTION_STRICT_FF4 0
|
||
|
|
||
|
static int sgf_column = 0;
|
||
|
|
||
|
static void
|
||
|
sgf_putc(int c, FILE *file)
|
||
|
{
|
||
|
if (c == '\n' && sgf_column == 0)
|
||
|
return;
|
||
|
|
||
|
fputc(c, file);
|
||
|
|
||
|
if (c == '\n')
|
||
|
sgf_column = 0;
|
||
|
else
|
||
|
sgf_column++;
|
||
|
|
||
|
if (c == ']' && sgf_column > 60) {
|
||
|
fputc('\n', file);
|
||
|
sgf_column = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
sgf_puts(const char *s, FILE *file)
|
||
|
{
|
||
|
for (; *s; s++) {
|
||
|
if (*s == '[' || *s == ']' || *s == '\\') {
|
||
|
fputc('\\', file);
|
||
|
sgf_column++;
|
||
|
}
|
||
|
fputc((int) *s, file);
|
||
|
sgf_column++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Print all properties with the given name in a node to file and mark
|
||
|
* them as printed.
|
||
|
*
|
||
|
* If is_comment is 1, multiple properties are concatenated with a
|
||
|
* newline. I.e. we write
|
||
|
*
|
||
|
* C[comment1
|
||
|
* comment2]
|
||
|
*
|
||
|
* instead of
|
||
|
*
|
||
|
* C[comment1][comment2]
|
||
|
*
|
||
|
* Most other property types should be written in the latter style.
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
sgf_print_name(FILE *file, short name)
|
||
|
{
|
||
|
sgf_putc(name & 0xff, file);
|
||
|
if (name >> 8 != ' ')
|
||
|
sgf_putc(name >> 8, file);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
sgf_print_property(FILE *file, SGFNode *node, short name, int is_comment)
|
||
|
{
|
||
|
int n = 0;
|
||
|
SGFProperty *prop;
|
||
|
|
||
|
for (prop = node->props; prop; prop = prop->next) {
|
||
|
if (prop->name == name) {
|
||
|
prop->name |= 0x20; /* Indicate already printed. */
|
||
|
if (n == 0) {
|
||
|
sgf_print_name(file, name);
|
||
|
sgf_putc('[', file);
|
||
|
}
|
||
|
else if (is_comment)
|
||
|
sgf_putc('\n', file);
|
||
|
else {
|
||
|
sgf_putc(']', file);
|
||
|
sgf_putc('[', file);
|
||
|
}
|
||
|
|
||
|
sgf_puts(prop->value, file);
|
||
|
n++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (n > 0)
|
||
|
sgf_putc(']', file);
|
||
|
|
||
|
/* Add a newline after certain properties. */
|
||
|
if (name == SGFAB || name == SGFAW || name == SGFAE || (is_comment && n > 1))
|
||
|
sgf_putc('\n', file);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Print all remaining unprinted property values at node N to file.
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
sgfPrintRemainingProperties(FILE *file, SGFNode *node)
|
||
|
{
|
||
|
SGFProperty *prop;
|
||
|
|
||
|
for (prop = node->props; prop; prop = prop->next)
|
||
|
if (!(prop->name & 0x20))
|
||
|
sgf_print_property(file, node, prop->name, 0);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Print the property values of NAME at node N and mark it as printed.
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
sgfPrintCharProperty(FILE *file, SGFNode *node, const char *name)
|
||
|
{
|
||
|
short nam = name[0] | name[1] << 8;
|
||
|
|
||
|
sgf_print_property(file, node, nam, 0);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Print comments from Node node.
|
||
|
*
|
||
|
* NOTE: cgoban does not print "C[comment1][comment2]" and I don't know
|
||
|
* what the sgfspec says.
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
sgfPrintCommentProperty(FILE *file, SGFNode *node, const char *name)
|
||
|
{
|
||
|
short nam = name[0] | name[1] << 8;
|
||
|
|
||
|
sgf_print_property(file, node, nam, 1);
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
unparse_node(FILE *file, SGFNode *node)
|
||
|
{
|
||
|
sgf_putc(';', file);
|
||
|
sgfPrintCharProperty(file, node, "B ");
|
||
|
sgfPrintCharProperty(file, node, "W ");
|
||
|
sgfPrintCommentProperty(file, node, "N ");
|
||
|
sgfPrintCommentProperty(file, node, "C ");
|
||
|
sgfPrintRemainingProperties(file, node);
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
unparse_root(FILE *file, SGFNode *node)
|
||
|
{
|
||
|
sgf_putc(';', file);
|
||
|
|
||
|
if (sgfHasProperty(node, "GM"))
|
||
|
sgfPrintCharProperty(file, node, "GM");
|
||
|
else {
|
||
|
fputs("GM[1]", file);
|
||
|
sgf_column += 5;
|
||
|
}
|
||
|
|
||
|
sgfPrintCharProperty(file, node, "FF");
|
||
|
sgf_putc('\n', file);
|
||
|
|
||
|
sgfPrintCharProperty(file, node, "SZ");
|
||
|
sgf_putc('\n', file);
|
||
|
|
||
|
sgfPrintCharProperty(file, node, "GN");
|
||
|
sgf_putc('\n', file);
|
||
|
|
||
|
sgfPrintCharProperty(file, node, "DT");
|
||
|
sgf_putc('\n', file);
|
||
|
|
||
|
sgfPrintCommentProperty(file, node, "PB");
|
||
|
sgfPrintCommentProperty(file, node, "BR");
|
||
|
sgf_putc('\n', file);
|
||
|
|
||
|
sgfPrintCommentProperty(file, node, "PW");
|
||
|
sgfPrintCommentProperty(file, node, "WR");
|
||
|
sgf_putc('\n', file);
|
||
|
|
||
|
sgfPrintCommentProperty(file, node, "N ");
|
||
|
sgfPrintCommentProperty(file, node, "C ");
|
||
|
sgfPrintRemainingProperties(file, node);
|
||
|
|
||
|
sgf_putc('\n', file);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* p->child is the next move.
|
||
|
* p->next is the next variation
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
unparse_game(FILE *file, SGFNode *node, int root)
|
||
|
{
|
||
|
if (!root)
|
||
|
sgf_putc('\n', file);
|
||
|
sgf_putc('(', file);
|
||
|
if (root)
|
||
|
unparse_root(file, node);
|
||
|
else
|
||
|
unparse_node(file, node);
|
||
|
|
||
|
node = node->child;
|
||
|
while (node != NULL && node->next == NULL) {
|
||
|
unparse_node(file, node);
|
||
|
node = node->child;
|
||
|
}
|
||
|
|
||
|
while (node != NULL) {
|
||
|
unparse_game(file, node, 0);
|
||
|
node = node->next;
|
||
|
}
|
||
|
sgf_putc(')', file);
|
||
|
if (root)
|
||
|
sgf_putc('\n', file);
|
||
|
}
|
||
|
|
||
|
/* Printed properties are marked by adding the 0x20 bit to the
|
||
|
* property name (changing an upper case letter to lower case). This
|
||
|
* function removes this mark so that we can print the property next
|
||
|
* time too. It recurses to all properties in the linked list.
|
||
|
*/
|
||
|
static void
|
||
|
restore_property(SGFProperty *prop)
|
||
|
{
|
||
|
if (prop) {
|
||
|
restore_property(prop->next);
|
||
|
prop->name &= ~0x20;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* When called with the tree root, recurses to all properties in the
|
||
|
* tree and removes all print marks.
|
||
|
*/
|
||
|
static void
|
||
|
restore_node(SGFNode *node)
|
||
|
{
|
||
|
if (node) {
|
||
|
restore_property(node->props);
|
||
|
restore_node(node->child);
|
||
|
restore_node(node->next);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Opens filename and writes the game stored in the sgf structure.
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
writesgf(SGFNode *root, const char *filename)
|
||
|
{
|
||
|
FILE *outfile;
|
||
|
|
||
|
if (strcmp(filename, "-") == 0)
|
||
|
outfile = stdout;
|
||
|
else
|
||
|
outfile = fopen(filename, "w");
|
||
|
|
||
|
if (!outfile) {
|
||
|
fprintf(stderr, "Can not open %s\n", filename);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
sgf_write_header_reduced(root, 0);
|
||
|
|
||
|
sgf_column = 0;
|
||
|
unparse_game(outfile, root, 1);
|
||
|
if (outfile != stdout)
|
||
|
fclose(outfile);
|
||
|
|
||
|
/* Remove "printed" marks so that the tree can be written multiple
|
||
|
* times.
|
||
|
*/
|
||
|
restore_node(root);
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
#ifdef TEST_SGFPARSER
|
||
|
int
|
||
|
main()
|
||
|
{
|
||
|
static char buffer[25000];
|
||
|
static char output[25000];
|
||
|
SGFNode *game;
|
||
|
|
||
|
sgffile = stdin;
|
||
|
|
||
|
nexttoken();
|
||
|
gametree(&game, LAX_SGF);
|
||
|
if (sgferr) {
|
||
|
fprintf(stderr, "Parse error:");
|
||
|
fprintf(stderr, sgferr, sgferrarg);
|
||
|
fprintf(stderr, " at position %d\n", sgferrpos);
|
||
|
}
|
||
|
else {
|
||
|
unparse_game(stdin, game, 1);
|
||
|
write(1, output, outputp - output);
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Local Variables:
|
||
|
* tab-width: 8
|
||
|
* c-basic-offset: 2
|
||
|
* End:
|
||
|
*/
|