Initial commit of GNU Go v3.8.
[sgk-go] / doc / board.texi
@menu
* Board Data Structures:: Board Data Structures
* The Board Array:: One-dimensional board array
* Incremental Board:: Incremental board data structures
* Some Board Functions:: Explanation of some board functions
@end menu
The foundation of the GNU Go engine is a library of very efficient
routines for handling go boards. This board library, called
@file{libboard}, can be used for those programs that only need a
basic go board but no AI capability. One such program is
@file{patterns/joseki.c}, which compiles joseki pattern
databases from SGF files.
If you want to use the board library in your own program, you need all
the .c-files listed under libboard_SOURCES in engine/Makefile.am, and
the files in the directories sgf/ and utils/. Then you should include
engine/board.h in your code.
The library consists of the following files:
@itemize
@item @file{board.h}
@quotation
The public interface to the board library.
@end quotation
@item @file{board.c}
@quotation
The basic board code. It uses incremental algorithms for keeping track
of strings and liberties on the go board.
@end quotation
@item @file{boardlib.c}
@quotation
This contains all global variable of the board library.
@end quotation
@item @file{hash.c}
@quotation
Code for hashing go positions.
@end quotation
@item @file{sgffile.c}
@quotation
Implementation of output file in SGF format.
@end quotation
@item @file{printutils.c}
@quotation
Utilities for printing go boards and other things.
@end quotation
@end itemize
To use the board library, you must include @file{liberty.h} just like
when you use the whole engine, but of course you cannot use all the
functions declared in it, i.e. the functions that are part of the
engine, but not part of the board library. You must link your
application with @code{libboard.a}.
@node Board Data Structures
@section Board Data structures
The basic data structures of the board correspond tightly to the
@code{board_state} struct described in @xref{The Board State}. They are all
stored in global variables for efficiency reasons, the most important of which
are:
@example
@group
int board_size;
Intersection board[MAXSIZE];
int board_ko_pos;
float komi;
int white_captured;
int black_captured;
Hash_data hashdata;
@end group
@end example
The description of the @code{Position} struct is applicable to these
variables also, so we won't duplicate it here. All these variables are
globals for performance reasons. Behind these variables, there are a
number of other private data structures. These implement incremental
handling of strings, liberties and other properties
(@pxref{Incremental Board}). The variable @code{hashdata} contains information
about the hash value for the current position (@pxref{Hashing}).
These variables should never be manipulated directly, since they are
only the front end for the incremental machinery. They can be read, but
should only be written by using the functions described in the next
section. If you write directly to them, the incremental data structures
will become out of sync with each other, and a crash is the likely
result.
@node The Board Array
@section The Board Array
GNU Go represents the board in a one-dimensional array called
@code{board}. For some purposes a two dimensional indexing of the
board by parameters @code{(i,j)} might be used.
The @code{board} array includes out-of-board markers around the
board. To make the relation to the old two-dimensional board
representation clear, this figure shows how the 1D indices correspond
to the 2D indices when MAX_BOARD is 7.
@example
@group
j -1 0 1 2 3 4 5 6
i +----------------------------------
-1| 0 1 2 3 4 5 6 7
0| 8 9 10 11 12 13 14 15
1| 16 17 18 19 20 21 22 23
2| 24 25 26 27 28 29 30 31
3| 32 33 34 35 36 37 38 39
4| 40 41 42 43 44 45 46 47
5| 48 49 50 51 52 53 54 55
6| 56 57 58 59 60 61 62 63
7| 64 65 66 67 68 69 70 71 72
@end group
@end example
To convert between a 1D index @code{pos} and a 2D index @code{(i,j)},
the macros @code{POS}, @code{I}, and @code{J} are provided, defined as
below:
@example
#define POS(i, j) ((MAX_BOARD + 2) + (i) * (MAX_BOARD + 1) + (j))
#define I(pos) ((pos) / (MAX_BOARD + 1) - 1)
#define J(pos) ((pos) % (MAX_BOARD + 1) - 1)
@end example
All 1D indices not corresponding to points on the board have the out
of board marker value @code{GRAY}. Thus if @code{board_size} and
@code{MAX_BOARD} both are 7, this looks like
@example
@group
j -1 0 1 2 3 4 5 6
i +----------------------------------
-1| # # # # # # # #
0| # . . . . . . .
1| # . . . . . . .
2| # . . . . . . .
3| # . . . . . . .
4| # . . . . . . .
5| # . . . . . . .
6| # . . . . . . .
7| # # # # # # # # #
@end group
@end example
The indices marked @samp{#} have value @code{GRAY}.
If @code{MAX_BOARD} is 7 and @code{board_size} is only 5:
@example
@group
j -1 0 1 2 3 4 5 6
i +----------------------------------
-1| # # # # # # # #
0| # . . . . . # #
1| # . . . . . # #
2| # . . . . . # #
3| # . . . . . # #
4| # . . . . . # #
5| # # # # # # # #
6| # # # # # # # #
7| # # # # # # # # #
@end group
@end example
Navigation on the board is done by the @code{SOUTH}, @code{WEST},
@code{NORTH}, and @code{EAST} macros,
@example
#define NS (MAX_BOARD + 1)
#define WE 1
#define SOUTH(pos) ((pos) + NS)
#define WEST(pos) ((pos) - 1)
#define NORTH(pos) ((pos) - NS)
#define EAST(pos) ((pos) + 1)
@end example
There are also shorthand macros @code{SW}, @code{NW}, @code{NE},
@code{SE}, @code{SS}, @code{WW}, @code{NN}, @code{EE} for two step
movements.
Any movement from a point on the board to an adjacent or diagonal
vertex is guaranteed to produce a valid index into the board array, and
the color found is GRAY if it is not on the board. To do explicit tests
for out of board there are two macros
@example
#define ON_BOARD(pos) (board[pos] != GRAY)
#define ON_BOARD1(pos) (((unsigned) (pos) < BOARDSIZE) && board[pos] != GRAY)
@end example
where the first one should be used in the algorithms and the second
one is useful for assertion tests.
The advantage of a one-dimensional board array is that it gives a
significant performance advantage. We need only one variable to determine
a board position, which means that many functions need less arguments. Also,
often one computation is sufficient for 1D-coordinate where we would need
two with two 2D-coordinates: If we, for example, want to have the
coordinate of the upper right of @code{pos}, we can do this with
@code{NORTH(EAST(pos))} instead of @code{(i+1, j-1)}.
@strong{Important}: The 2D coordinate @code{(-1,-1)}, which is used for
pass and sometimes to indicate no point, maps to the 1D coordinate
@code{0}, not to @code{-1}. Instead of a plain @code{0}, use one of the
macros @code{NO_MOVE} or @code{PASS_MOVE}.
A loop over multiple directions is straightforwardly written:
@example
for (k = 0; k < 4; k++) @{
int d = delta[k];
do_something(pos + d);
@}
@end example
The following constants are useful for loops over the entire board and
allocation of arrays with a 1-1 mapping to the board.
@example
#define BOARDSIZE ((MAX_BOARD + 2) * (MAX_BOARD + 1) + 1)
#define BOARDMIN (MAX_BOARD + 2)
#define BOARDMAX (MAX_BOARD + 1) * (MAX_BOARD + 1)
@end example
@code{BOARDSIZE} is the actual size of the 1D board array,
@code{BOARDMIN} is the first index corresponding to a point on the
board, and @code{BOARDMAX} is one larger than the last index corresponding to
a point on the board.
Often one wants to traverse the board, carrying out some function
at every vertex. Here are two possible ways of doing this:
@example
int m, n;
for (m = 0; m < board_size; m++)
for (n = 0; n < board_size; n++) @{
do_something(POS(m, n));
@}
@end example
Or:
@example
int pos;
for (pos = BOARDMIN; pos < BOARDMAX; pos++) @{
if (ON_BOARD(pos))
do_something(pos);
@}
@end example
@node Incremental Board
@section Incremental Board data structures
In addition to the global board state, the algorithms in @file{board.c}
implement a method of incremental updates that keeps track of the
following information for each string:
@itemize @bullet
@item The color of the string.
@item Number of stones in the string.
@item Origin of the string, i.e. a canonical reference point, defined
to be the stone with smallest 1D board coordinate.
@item A list of the stones in the string.
@item Number of liberties.
@item A list of the liberties. If there are too many liberties the list is
truncated.
@item The number of neighbor strings.
@item A list of the neighbor strings.
@end itemize
The basic data structure is
@example
struct string_data @{
int color; /* Color of string, BLACK or WHITE */
int size; /* Number of stones in string. */
int origin; /* Coordinates of "origin", i.e. */
/* "upper left" stone. */
int liberties; /* Number of liberties. */
int libs[MAX_LIBERTIES]; /* Coordinates of liberties. */
int neighbors; /* Number of neighbor strings */
int neighborlist[MAXCHAIN]; /* List of neighbor string numbers. */
int mark; /* General purpose mark. */
@};
struct string_data string[MAX_STRINGS];
@end example
It should be clear that almost all information is stored in the
@code{string} array. To get a mapping from the board coordinates to the
@code{string} array we have
@example
static int string_number[BOARDMAX];
@end example
@noindent
which contains indices into the @code{string} array. This information is only
valid at nonempty vertices, however, so it is necessary to first
verify that @code{board[pos] != EMPTY}.
The @code{string_data} structure does not include an array of the stone
coordinates. This information is stored in a separate array:
@example
static int next_stone[BOARDMAX];
@end example
This array implements cyclic linked lists of stones. Each vertex
contains a pointer to another (possibly the same) vertex. Starting at
an arbitrary stone on the board, following these pointers should
traverse the entire string in an arbitrary order before coming back to
the starting point. As for the 'string_number' array, this information
is invalid at empty points on the board. This data structure has the
good properties of requiring fixed space (regardless of the number of
strings) and making it easy to add a new stone or join two strings.
Additionally the code makes use of some work variables:
@example
static int ml[BOARDMAX];
static int liberty_mark;
static int string_mark;
static int next_string;
static int strings_initialized = 0;
@end example
The @code{ml} array and @code{liberty_mark} are used to "mark" liberties on
the board, e.g. to avoid counting the same liberty twice. The convention is
that if @code{ml[pos]} has the same value as @code{liberty_mark}, then
@code{pos} is marked. To clear all marks it suffices to increase the value
of @code{liberty_mark}, since it is never allowed to decrease.
The same relation holds between the @code{mark} field of the @code{string_data}
structure and @code{string_mark}. Of course these are used for marking
individual strings.
@code{next_string} gives the number of the next available entry in the
@code{string} array. Then @code{strings_initialized} is set to one when
all data structures are known to be up to date. Given an arbitrary board
position in the @samp{board} array, this is done by calling
@code{incremental_board_init()}. It is not necessary to call this
function explicitly since any other function that needs the information
does this if it has not been done.
The interesting part of the code is the incremental update of the data
structures when a stone is played and subsequently removed. To
understand the strategies involved in adding a stone it is necessary
to first know how undoing a move works. The idea is that as soon as
some piece of information is about to be changed, the old value is
pushed onto a stack which stores the value and its address. The stack
is built from the following structures:
@example
struct change_stack_entry @{
int *address;
int value;
@};
struct change_stack_entry change_stack[STACK_SIZE];
int change_stack_index;
@end example
@noindent
and manipulated with the macros
@example
BEGIN_CHANGE_RECORD()
PUSH_VALUE(v)
POP_MOVE()
@end example
Calling @code{BEGIN_CHANGE_RECORD()} stores a null pointer in the address
field to indicate the start of changes for a new move. As mentioned
earlier @code{PUSH_VALUE()} stores a value and its corresponding address.
Assuming that all changed information has been duly pushed onto the
stack, undoing the move is only a matter of calling @code{POP_MOVE()},
which simply assigns the values to the addresses in the reverse order
until the null pointer is reached. This description is slightly
simplified because this stack can only store 'int' values and we need
to also store changes to the board. Thus we have two parallel stacks
where one stores @code{int} values and the other one stores
@code{Intersection} values.
When a new stone is played on the board, first captured opponent
strings, if any, are removed. In this step we have to push the board
values and the @code{next_stone} pointers for the removed stones, and
update the liberties and neighbor lists for the neighbors of the
removed strings. We do not have to push all information in the
'string' entries of the removed strings however. As we do not reuse
the entries they will remain intact until the move is pushed and they
are back in use.
After this we put down the new stone and get three distinct cases:
@enumerate
@item The new stone is isolated, i.e. it has no friendly neighbor.
@item The new stone has exactly one friendly neighbor.
@item The new stone has at least two friendly neighbors.
@end enumerate
The first case is easiest. Then we create a new string by using the
number given by @code{next_string} and increasing this variable. The string
will have size one, @code{next_stone} points directly back on itself, the
liberties can be found by looking for empty points in the four
directions, possible neighbor strings are found in the same way, and
those need also to remove one liberty and add one neighbor.
In the second case we do not create a new string but extend the
neighbor with the new stone. This involves linking the new stone into
the cyclic chain, if needed moving the origin, and updating liberties
and neighbors. Liberty and neighbor information also needs updating
for the neighbors of the new stone.
In the third case finally, we need to join already existing strings.
In order not to have to store excessive amounts of information, we
create a new string for the new stone and let it assimilate the
neighbor strings. Thus all information about those can simply be left
around in the 'string' array, exactly as for removed strings. Here it
becomes a little more complex to keep track of liberties and neighbors
since those may have been shared by more than one of the joined
strings. Making good use of marks it all becomes rather
straightforward anyway.
The often used construction
@example
pos = FIRST_STONE(s);
do @{
...
pos = NEXT_STONE(pos);
@} while (!BACK_TO_FIRST_STONE(s, pos));
@end example
@noindent
traverses the stones of the string with number @samp{s} exactly once,
with @code{pos} holding the coordinates. In general @code{pos} is
used as board coordinate and @samp{s} as an index into the
@code{string} array or sometimes a pointer to an entry in the
@code{string} array.
@node Some Board Functions
@section Some Board Functions
@strong{Reading}, often called @strong{search} in computer game
theory, is a fundamental process in GNU Go. This is the process
of generating hypothetical future boards in order to determine
the answer to some question, for example "can these stones live."
Since these are hypothetical future positions, it is important
to be able to undo them, ultimately returning to the present
board. Thus a move stack is maintained during reading. When
a move is tried, by the function @code{trymove}, or its
variant @code{tryko}. This function pushes the current board
on the stack and plays a move. The stack pointer @code{stackp},
which keeps track of the position, is incremented. The function
@code{popgo()} pops the move stack, decrementing @code{stackp} and
undoing the last move made.
Every successful @code{trymove()} must be matched with a @code{popgo()}.
Thus the correct way of using this function is:
@example
@group
if (trymove(pos, color, ... )) @{
... [potentially lots of code here]
popgo();
@}
@end group
@end example
@noindent
In case the move is a ko capture, the legality of the capture is subject to
the komaster scheme (@pxref{Ko}).
@itemize @bullet
@item @code{int trymove(int pos, int color, const char *message)}
@findex trymove
@quotation
Returns true if @code{(pos)} is a legal move for @code{color}. In that
case, it pushes the board on the stack and makes the move, incrementing
@code{stackp}. If the reading code is recording reading variations (as
with @option{--decide-string} or with @option{-o}), the string
@code{*message} will be inserted in the SGF file as a comment. The
comment will also refer to the string at @code{str} if this is not
@code{0}. The value of @code{str} can be NO_MOVE if it is not needed but
otherwise the location of @code{str} is included in the comment.
@end quotation
@item @code{int tryko(int pos, int color, const char *message)}
@findex tryko
@quotation
@code{tryko()} pushes the position onto the stack, and makes a move
@code{pos} of @code{color}. The move is allowed even if it is an
illegal ko capture. It is to be imagined that @code{color} has made an
intervening ko threat which was answered and now the continuation is to
be explored. Return 1 if the move is legal with the above
caveat. Returns zero if it is not legal because of suicide.
@end quotation
@item @code{void popgo()}
@findex popgo
@quotation
Pops the move stack. This function must (eventually) be called after a
succesful @code{trymove} or @code{tryko} to restore the board
position. It undoes all the changes done by the call to
@code{trymove/tryko} and leaves the board in the same state as it was
before the call.
@strong{NOTE}: If @code{trymove/tryko} returns @code{0}, i.e. the tried
move was not legal, you must @strong{not} call @code{popgo}.
@end quotation
@item @code{int komaster_trymove(int pos, int color, const char *message, int str, int *is_conditional_ko, int consider_conditional_ko)}
@findex komaster_trymove
@quotation
Variation of @code{trymove}/@code{tryko} where ko captures (both
conditional and unconditional) must follow a komaster scheme
(@pxref{Ko}).
@end quotation
@end itemize
As you see, @code{trymove()} plays a move which can be easily
retracted (with @code{popgo()}) and it is call thousands of
times per actual game move as GNU Go analyzes the board position.
By contrast the function @code{play_move()} plays a move which
is intended to be permanent, though it is still possible to
undo it if, for example, the opponent retracts a move.
@itemize @bullet
@item @code{void play_move(int pos, int color)}
@findex play_move
@quotation
Play a move. If you want to test for legality you should first call
@code{is_legal()}. This function strictly follows the algorithm:
@enumerate
@item Place a stone of given color on the board.
@item If there are any adjacent opponent strings without liberties,
remove them and increase the prisoner count.
@item If the newly placed stone is part of a string without liberties,
remove it and increase the prisoner count.
@end enumerate
In spite of the name ``permanent move'', this move can (usually) be
unplayed by @code{undo_move()}, but it is significantly more costly than
unplaying a temporary move. There are limitations on the available
move history, so under certain circumstances the move may not be
possible to unplay at a later time.
@end quotation
@item @code{int undo_move(int n)}
@findex undo_move
@quotation
Undo @samp{n} permanent moves. Returns 1 if successful and 0 if it fails.
If @samp{n} moves cannot be undone, no move is undone.
@end quotation
@end itemize
Other board functions are documented in @xref{Board Utilities}.