|
libsgfc++ 3.0.0
A C++ library that uses SGFC to read and write SGF (Smart Game Format) data.
|
This document contains assorted notes about SGFC, how it operates, and the consequences this has for libsgfc++ and/or the library client.
If you simply want to use libsgfc++ you don't need to read this file, instead SGF notes is the document for you.
If you want to develop for libsgfc++ then these notes can be important to understand the implementation of libsgfc++.
On reading SimpleText/Text property values, and values of Point/Move/Stone properties of game types != Go, SGFC removes all escape characters.
On writing these property values, SGFC adds escape characters back where they are needed to protect the SGF skeleton.
Also see the detailed comments in SgfcPropertyDecoder and SgfcDocumentEncoder.
Note: SgfcPropertyDecoder used to perform some escape processing, but this became unnecessary when SGFC V2.00 started to remove all escape characters. The code that performs the processing has been left in but made optional and disabled by default. Some comments may still mention the old behaviour.
SGFC detects any kind of line breaks when it reads/parses SGF content.
However, the kind of line break SGFC uses when writing SGF content is determined at compile time. By default SGFC uses a single LF character. This can be changed by redefining the pre-processor macro EOLCHAR to something else during compilation. The macro must resolve into a single character, such as a LF character (already the default) or a CR character (used on classic MacOS systems). If you undefine EOLCHAR then SGFC will write two characters, a LF followed by a CR, which is the standard on Windows/MS-DOS systems.
When reading SimpleText or Text property values, SGFC follows the SGF standard rules for soft and hard line breaks.
When writing SimpleText or Text property values, SGFC preserves unescaped line breaks and generates escaped line breaks as it sees fit (cf. -L and -t command line options).
For Go the SGF standard defines that black or white pass moves can have either value "" (an empty string) or "tt". The latter counts as pass move only for board sizes <= 19, for larger boards "tt" is a normal move. The SGF standard also mentions that "tt" is kept only for compatibility with FF3.
The observed behaviour is that SGFC can deal with "tt" both on reading and writing, and it always performs the conversion to an empty string in an attempt to produce FF4 content. Consequently:
SGFC automatically expands compressed point lists during parsing when the game type is Go (GM[1]). See Check_Pos(). It compresses them again during saving unless the -e option is specified. See WriteNode().
It's impossible to preserve the original format in all cases, because SGFC normalizes the input during parsing, but it does not record any traces of what it did.
The original idea was that the SgfcDocumentEncoder class creates data structures using the structs that SGFC defines (e.g. Node, Property, PropValue). In theory it should have been as easy as invoking the appropriate SGFC helper functions, such as NewNode() or NewProperty(), to create the data structures. In practice this scheme turned out at first to be problematic, and then doomed.
The first minor problem is accessibility of the involved functions: NewNode() is declared extern, so it can be used by SgfcDocumentEncoder easily. NewProperty() and other functions, on the other hand, are declared static inside load.c, so it would have been necessary to patch SGFC in order to be able to use those functions.
The next problem, which turned out to be the real bummer, is that each Property structure has a member named buffer, which is a pointer that SGFC expects to point deeply into the SGFInfo file buffer. Although NewProperty() sets buffer up for us, it expects the buffer start as a parameter, i.e. the start within the file buffer from where property parsing should begin. We can't provide NewProperty() with a pointer into an std::string buffer because that goes away when the std::string object is destroyed. This means we would have to create a copy of the std::string buffer on the heap. But then the next problem would be, who frees that memory when the SGFC operation is done? FreeSGFInfo() is not the one, because it assumes that the Property buffer is part of the file buffer, so it merely frees the file buffer. In addition to the memory management issue, there are doubts whether SGFC's parsing functions can handle an abrupt end of the file buffer - which would occur because our copy of the std::string buffer would naturally be bounded with a zero byte.
In the end the best (because simplest and safest) idea seemed to be to just let SgfcDocumentEncoder generate an SGF content stream that simulates an entire file buffer.
This section can be seen as a very high-level approach to an inofficial SGFC API (there is no official API). You may find this interesting if you're new to SGFC and want to learn how you can reuse its code in a software project of your own.
At the highest level the SGFC codebase can be divided into two parts:
A software library must not contain a main() function, so the first thing that needs to be done to enable reusing the "everything else" part is to remove the main() function part. This can be achieved in one of two ways:
libsgfc++ uses the second approach because it allows main.c to be included in IDE projects that are generated by CMake.
The SGFC codebase offers a number of high-level global functions that are of interest to a library. The functions can be placed into four broad categories:
SGFC offers a software library to tap into parts of its data processing by way of hooks/callbacks that can be installed/provided in strategic places.
When writing SGF content, SGFC by default adds the "AP" property to the SGF content, using "SGFC" as the authoring program name, and the version with which it was embedded into libsgfc++. If the SGF content already contains the AP property, SGFC retains the property but overwrites the existing property values with its own values.
A library client that wants to assert its authorship of the SGF content can do so by adding SgfcArgumentType::DoNotAddSgfcApProperty to the arguments of ISgfcDocumentWriter, and by providing an AP property with the client's own program name and version. SGFC in this case will not overwrite the AP property provided by the library client.
According to the SGFC readme document the "KI" property is a private property of the "Smart Game Board" application (SGB). The property name means "integer komi".
SGFC converts "KI" to the Go-specific "KM" property, dividing the original "KI" numeric value by 2 to obtain the new "KM" value. SGFC performs this conversion in all cases, even if the game tree's game type is not Go.
This section covers how to build SGFC (source code and tests) from a fresh clone of the upstream repository.
SGFC uses the check testing framework for its unit tests. Use your package manager of choice to install check, then find out the include and library paths that point to the check header files and the check library.
For Homebrew, execute this command:
brew install check
To find the include and library paths, execute these commands:
echo $(brew --prefix)/include echo $(brew --prefix)/lib
With a modern Homebrew installation, these paths are usually
/opt/homebrew/include /opt/homebrew/lib
Execute these commands to build the vanilla SGFC source code and tests.
Note: make tests both builds and executes the tests.