/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* 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 */
/* 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
xalloc(unsigned int size
)
fprintf(stderr
, "xalloc: Out of memory!\n");
memset(pt
, 0, (unsigned long) size
);
xrealloc(void *pt
, unsigned int size
)
void *ptnew
= realloc(pt
, size
);
fprintf(stderr
, "xrealloc: Out of memory!\n");
/* ================================================================ */
/* ================================================================ */
* Allocate memory for a new SGF node.
newnode
= xalloc(sizeof(SGFNode
));
* Recursively free an sgf node
sgfFreeNode(SGFNode
*node
)
sgfFreeNode(node
->child
);
sgfFreeProperty(node
->props
);
* Add a generic text property to an SGF node.
sgfAddProperty(SGFNode
*node
, const char *name
, const char *value
)
SGFProperty
*prop
= node
->props
;
sgfMkProperty(name
, value
, node
, prop
);
* Add an integer property to an SGF node.
sgfAddPropertyInt(SGFNode
*node
, const char *name
, long val
)
gg_snprintf(buffer
, 10, "%ld", val
);
sgfAddProperty(node
, name
, buffer
);
* Add a float property to an SGF node.
sgfAddPropertyFloat(SGFNode
*node
, const char *name
, float val
)
gg_snprintf(buffer
, 10, "%3.1f", val
);
sgfAddProperty(node
, name
, buffer
);
* Read a property as int from an SGF node.
sgfGetIntProperty(SGFNode
*node
, const char *name
, int *value
)
short nam
= name
[0] | name
[1] << 8;
for (prop
= node
->props
; prop
; prop
= prop
->next
)
*value
= atoi(prop
->value
);
* Read a property as float from an SGF node.
sgfGetFloatProperty(SGFNode
*node
, const char *name
, float *value
)
short nam
= name
[0] | name
[1] << 8;
for (prop
= node
->props
; prop
; prop
= prop
->next
)
*value
= (float) atof(prop
->value
);
/* MS-C warns of loss of data (double to float) */
* Read a property as text from an SGF node.
sgfGetCharProperty(SGFNode
*node
, const char *name
, char **value
)
short nam
= name
[0] | name
[1] << 8;
for (prop
= node
->props
; prop
; prop
= prop
->next
)
* Is there a property of this type in the node?
sgfHasProperty(SGFNode
*node
, const char *name
)
short nam
= name
[0] | name
[1] << 8;
for (prop
= node
->props
; prop
; prop
= prop
->next
)
* Overwrite a property from an SGF node with text or create a new
* one if it does not exist.
sgfOverwriteProperty(SGFNode
*node
, const char *name
, const char *text
)
short nam
= name
[0] | name
[1] << 8;
for (prop
= node
->props
; prop
; prop
= prop
->next
)
prop
->value
= xrealloc(prop
->value
, strlen(text
)+1);
strcpy(prop
->value
, text
);
sgfAddProperty(node
, name
, text
);
* Overwrite an int property in an SGF node with val or create a new
* one if it does not exist.
sgfOverwritePropertyInt(SGFNode
*node
, const char *name
, int val
)
short nam
= name
[0] | name
[1] << 8;
for (prop
= node
->props
; prop
; prop
= prop
->next
)
prop
->value
= xrealloc(prop
->value
, 12);
gg_snprintf(prop
->value
, 12, "%d", val
);
sgfAddPropertyInt(node
, name
, val
);
* Overwrite a float property in the gametree with val or create
* a new one if it does not exist.
sgfOverwritePropertyFloat(SGFNode
*node
, const char *name
, float val
)
short nam
= name
[0] | name
[1] << 8;
for (prop
= node
->props
; prop
; prop
= prop
->next
)
prop
->value
= xrealloc(prop
->value
, 15);
gg_snprintf(prop
->value
, 15, "%3.1f", val
);
sgfAddPropertyFloat(node
, name
, val
);
/* ================================================================ */
/* ================================================================ */
do_sgf_make_property(short sgf_name
, const char *value
,
SGFNode
*node
, SGFProperty
*last
)
prop
= (SGFProperty
*) xalloc(sizeof(SGFProperty
));
prop
->value
= xalloc(strlen(value
) + 1);
strcpy(prop
->value
, value
);
/* Make an SGF property. In case of a property with a range it
* expands it and makes several properties instead.
sgfMkProperty(const char *name
, const char *value
,
SGFNode
*node
, SGFProperty
*last
)
static const short properties_allowing_ranges
[12] = {
/* Board setup properties. */
SGFCR
, SGFMA
, SGFSQ
, SGFTR
, SGFDD
, SGFSL
,
/* Miscellaneous properties. */
/* Go-specific properties. */
sgf_name
= name
[0] | (short) (' ' << 8);
sgf_name
= name
[0] | name
[1] << 8;
for (k
= 0; k
< 12; k
++) {
if (properties_allowing_ranges
[k
] == sgf_name
)
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
);
/* Not a range property. */
return do_sgf_make_property(sgf_name
, value
, node
, last
);
* Recursively free an SGF property.
sgfFreeProperty(SGFProperty
*prop
)
sgfFreeProperty(prop
->next
);
/* ================================================================ */
/* High level functions */
/* ================================================================ */
* Add a stone to the current or the given node.
* Return the node where the stone was added.
sgfAddStone(SGFNode
*node
, int color
, int movex
, int movey
)
sprintf(move
, "%c%c", movey
+ 'a', movex
+ 'a');
sgfAddProperty(node
, (color
== BLACK
) ? "AB" : "AW", move
);
* Add a move to the gametree.
sgfAddPlay(SGFNode
*node
, int who
, int movex
, int movey
)
if (movex
== -1 && movey
== -1)
sprintf(move
, "%c%c", movey
+ 'a', movex
+ 'a');
new = sgfStartVariantFirst(node
->child
);
sgfAddProperty(new, (who
== BLACK
) ? "B" : "W", move
);
* Add a move to the gametree. New variations are added after the old
* ones rather than before.
sgfAddPlayLast(SGFNode
*node
, int who
, int movex
, int movey
)
if (movex
== -1 && movey
== -1)
sprintf(move
, "%c%c", movey
+ 'a', movex
+ 'a');
sgfAddProperty(new, (who
== BLACK
) ? "B" : "W", move
);
sgfCreateHeaderNode(int boardsize
, float komi
, int handicap
)
SGFNode
*root
= sgfNewNode();
sgfAddPropertyInt(root
, "SZ", boardsize
);
sgfAddPropertyFloat(root
, "KM", komi
);
sgfAddPropertyInt(root
, "HA", handicap
);
* Add a comment to an SGF node.
sgfAddComment(SGFNode
*node
, const char *comment
)
sgfAddProperty(node
, "C ", comment
);
* Place text on the board at position (i, j).
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
);
* Place a character on the board at position (i, j).
sgfBoardChar(SGFNode
*node
, int i
, int j
, char c
)
return sgfBoardText(node
, i
, j
, text
);
* Place a number on the board at position (i, j).
sgfBoardNumber(SGFNode
*node
, int i
, int j
, int number
)
gg_snprintf(text
, 10, "%c%c:%i", j
+'a', i
+'a', number
);
sgfAddProperty(node
, "LB", text
);
* Place a triangle mark on the board at position (i, j).
sgfTriangle(SGFNode
*node
, int i
, int j
)
gg_snprintf(text
, 3, "%c%c", j
+'a', i
+'a');
sgfAddProperty(node
, "TR", text
);
* Place a label on the board at position (i, j).
sgfLabel(SGFNode
*node
, const char *label
, int i
, int j
)
/* allows 12 chars labels - more than enough */
gg_snprintf(text
, 16, "%c%c:%s", j
+'a', i
+'a', label
);
sgfAddProperty(node
, "LB", text
);
* Place a numeric label on the board at position (i, j).
sgfLabelInt(SGFNode
*node
, int num
, int i
, int j
)
gg_snprintf(text
, 16, "%c%c:%d", j
+'a', i
+'a', num
);
sgfAddProperty(node
, "LB", text
);
* Place a circle mark on the board at position (i, j).
sgfCircle(SGFNode
*node
, int i
, int j
)
gg_snprintf(text
, 3, "%c%c", j
+'a', i
+'a');
sgfAddProperty(node
, "CR", text
);
* Place a square mark on the board at position (i, j).
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).
sgfMark(SGFNode
*node
, int i
, int j
)
gg_snprintf(text
, 3, "%c%c", j
+'a', i
+'a');
sgfAddProperty(node
, "MA", text
);
* Start a new variant. Returns a pointer to the new node.
sgfStartVariant(SGFNode
*node
)
node
->next
= sgfNewNode();
node
->next
->parent
= node
->parent
;
* Start a new variant as first child. Returns a pointer to the new node.
sgfStartVariantFirst(SGFNode
*node
)
SGFNode
*old_first_child
= node
;
SGFNode
*new_first_child
= sgfNewNode();
new_first_child
->next
= old_first_child
;
new_first_child
->parent
= old_first_child
->parent
;
new_first_child
->parent
->child
= new_first_child
;
* If no child exists, add one. Otherwise add a sibling to the
* existing children. Returns a pointer to the new node.
sgfAddChild(SGFNode
*node
)
SGFNode
*new_node
= sgfNewNode();
* Write result of the game to the game tree.
sgfWriteResult(SGFNode
*node
, float score
, int overwrite
)
/* If not writing to the SGF file, skip everything and return now. */
/* If not overwriting and there already is a result property, return. */
if (sgfGetIntProperty(node
, "RE", &dummy
))
gg_snprintf(text
, 8, "0");
else if (score
< 1000.0 && score
> -1000.0)
gg_snprintf(text
, 8, "%c+%3.1f", winner
, s
);
gg_snprintf(text
, 8, "%c+%c", winner
, 'R');
sgfOverwriteProperty(node
, "RE", text
);
sgf_write_header_reduced(SGFNode
*root
, int overwrite
)
time_t curtime
= time(NULL
);
struct tm
*loctime
= localtime(&curtime
);
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");
sgf_write_header(SGFNode
*root
, int overwrite
, int seed
, float komi
,
int handicap
, int level
, int rules
)
gg_snprintf(str
, 128, "GNU Go %s Random Seed %d level %d",
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
);
/* ================================================================ */
/* ================================================================ */
#define MAX_FILE_BUFFER 200000 /* buffer for reading SGF file. */
* 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
static void parse_error(const char *msg
, int arg
);
static void nexttoken(void);
static void match(int expected
);
#define sgf_getch() (getc(sgffile))
/* ---------------------------------------------------------------- */
/* ---------------------------------------------------------------- */
parse_error(const char *msg
, int arg
)
fprintf(stderr
, msg
, arg
);
while (isspace(lookahead
));
if (lookahead
!= expected
)
parse_error("expected: %c", expected
);
/* ---------------------------------------------------------------- */
/* ---------------------------------------------------------------- */
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) {
propvalue(char *buffer
, int size
)
while (lookahead
!= ']' && lookahead
!= EOF
) {
/* Follow the FF4 definition of backslash */
else if (lookahead
== '\n') {
/* 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.
while (p
> buffer
&& isspace((int) (unsigned char) *p
))
property(SGFNode
*n
, SGFProperty
*last
)
propident(name
, sizeof(name
));
propvalue(buffer
, sizeof(buffer
));
last
= sgfMkProperty(name
, buffer
, n
, last
);
} while (lookahead
== '[');
SGFProperty
*last
= NULL
;
while (lookahead
!= EOF
&& isupper(lookahead
))
last
= property(n
, last
);
while (lookahead
== ';') {
SGFNode
*new = sgfNewNode();
gametree(SGFNode
**p
, SGFNode
*parent
, int mode
)
parse_error("Empty file?", 0);
SGFNode
*head
= sgfNewNode();
while (lookahead
== '(') {
gametree(p
, last
, STRICT_SGF
);
* Reads an SGF file for extract_fuseki in a compact way
gametreefuseki(SGFNode
**p
, SGFNode
*parent
, int mode
,
int moves_per_game
, int i
)
parse_error("Empty file?", 0);
SGFNode
*head
= sgfNewNode();
while (lookahead
== '(') {
&& (last
->props
->name
== SGFB
|| last
->props
->name
== SGFW
))
/* break after number_of_moves moves in SGF file */
if (i
>= moves_per_game
) {
gametreefuseki(p
, last
, mode
, moves_per_game
, i
);
readsgffilefuseki(const char *filename
, int moves_per_game
)
if (strcmp(filename
, "-") == 0)
sgffile
= fopen(filename
, "r");
gametreefuseki(&root
, NULL
, LAX_SGF
, moves_per_game
, 0);
fprintf(stderr
, "Parse error: %s at position %d\n", sgferr
, sgferrpos
);
/* perform some simple checks on the file */
if (!sgfGetIntProperty(root
, "GM", &tmpi
)) {
fprintf(stderr
, "Couldn't find the game type (GM) attribute!\n");
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
)) {
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
);
* 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.
readsgffile(const char *filename
)
if (strcmp(filename
, "-") == 0)
sgffile
= fopen(filename
, "r");
gametree(&root
, NULL
, LAX_SGF
);
fprintf(stderr
, "Parse error: %s at position %d\n", sgferr
, sgferrpos
);
/* perform some simple checks on the file */
if (!sgfGetIntProperty(root
, "GM", &tmpi
)) {
fprintf(stderr
, "Couldn't find the game type (GM) attribute!\n");
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
)) {
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
);
/* ================================================================ */
/* ================================================================ */
#define OPTION_STRICT_FF4 0
static int sgf_column
= 0;
sgf_putc(int c
, FILE *file
)
if (c
== '\n' && sgf_column
== 0)
if (c
== ']' && sgf_column
> 60) {
sgf_puts(const char *s
, FILE *file
)
if (*s
== '[' || *s
== ']' || *s
== '\\') {
/* Print all properties with the given name in a node to file and mark
* If is_comment is 1, multiple properties are concatenated with a
* Most other property types should be written in the latter style.
sgf_print_name(FILE *file
, short name
)
sgf_putc(name
& 0xff, file
);
sgf_putc(name
>> 8, file
);
sgf_print_property(FILE *file
, SGFNode
*node
, short name
, int is_comment
)
for (prop
= node
->props
; prop
; prop
= prop
->next
) {
if (prop
->name
== name
) {
prop
->name
|= 0x20; /* Indicate already printed. */
sgf_print_name(file
, name
);
sgf_puts(prop
->value
, file
);
/* Add a newline after certain properties. */
if (name
== SGFAB
|| name
== SGFAW
|| name
== SGFAE
|| (is_comment
&& n
> 1))
* Print all remaining unprinted property values at node N to file.
sgfPrintRemainingProperties(FILE *file
, SGFNode
*node
)
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.
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
sgfPrintCommentProperty(FILE *file
, SGFNode
*node
, const char *name
)
short nam
= name
[0] | name
[1] << 8;
sgf_print_property(file
, node
, nam
, 1);
unparse_node(FILE *file
, SGFNode
*node
)
sgfPrintCharProperty(file
, node
, "B ");
sgfPrintCharProperty(file
, node
, "W ");
sgfPrintCommentProperty(file
, node
, "N ");
sgfPrintCommentProperty(file
, node
, "C ");
sgfPrintRemainingProperties(file
, node
);
unparse_root(FILE *file
, SGFNode
*node
)
if (sgfHasProperty(node
, "GM"))
sgfPrintCharProperty(file
, node
, "GM");
sgfPrintCharProperty(file
, node
, "FF");
sgfPrintCharProperty(file
, node
, "SZ");
sgfPrintCharProperty(file
, node
, "GN");
sgfPrintCharProperty(file
, node
, "DT");
sgfPrintCommentProperty(file
, node
, "PB");
sgfPrintCommentProperty(file
, node
, "BR");
sgfPrintCommentProperty(file
, node
, "PW");
sgfPrintCommentProperty(file
, node
, "WR");
sgfPrintCommentProperty(file
, node
, "N ");
sgfPrintCommentProperty(file
, node
, "C ");
sgfPrintRemainingProperties(file
, node
);
* p->child is the next move.
* p->next is the next variation
unparse_game(FILE *file
, SGFNode
*node
, int root
)
unparse_root(file
, node
);
unparse_node(file
, node
);
while (node
!= NULL
&& node
->next
== NULL
) {
unparse_node(file
, node
);
unparse_game(file
, node
, 0);
/* 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.
restore_property(SGFProperty
*prop
)
restore_property(prop
->next
);
/* When called with the tree root, recurses to all properties in the
* tree and removes all print marks.
restore_node(SGFNode
*node
)
restore_property(node
->props
);
restore_node(node
->child
);
restore_node(node
->next
);
* Opens filename and writes the game stored in the sgf structure.
writesgf(SGFNode
*root
, const char *filename
)
if (strcmp(filename
, "-") == 0)
outfile
= fopen(filename
, "w");
fprintf(stderr
, "Can not open %s\n", filename
);
sgf_write_header_reduced(root
, 0);
unparse_game(outfile
, root
, 1);
/* Remove "printed" marks so that the tree can be written multiple
static char buffer
[25000];
static char output
[25000];
gametree(&game
, LAX_SGF
);
fprintf(stderr
, "Parse error:");
fprintf(stderr
, sgferr
, sgferrarg
);
fprintf(stderr
, " at position %d\n", sgferrpos
);
unparse_game(stdin
, game
, 1);
write(1, output
, outputp
- output
);