www.delorie.com/gnu/docs/gnugo/gnugo_205.html search
GNU Go Documentation

 [ < ] [ > ] [ << ] [ Up ] [ >> ] [Top] [Contents] [Index] [ ? ]

# 19. Incremental Algorithms in Reading

The algorithms in `board.c' implement a method of incremental board updates that keeps track of the following information for each string:

• The color of the string.
• Number of stones in the string.
• Origin of the string, i.e. a canonical reference point, defined to be the stone with smallest 1D board coordinate.
• A list of the stones in the string.
• Number of liberties.
• A list of the liberties. If there are too many liberties the list is truncated.
• The number of neighbor strings.
• A list of the neighbor strings.

The basic data structure is

 ```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]; ```

It should be clear that almost all information is stored in the `string` array. To get a mapping from the board coordinates to the `string` array we have

 ```static int string_number[BOARDMAX]; ```

which contains indices into the `string` array. This information is only valid at nonempty vertices, however, so it is necessary to first verify that `board[pos] != EMPTY`.

The `string_data` structure does not include an array of the stone coordinates. This information is stored in a separate array:

 ```static int next_stone[BOARDMAX]; ```

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:

 ```static int ml[BOARDMAX]; static int liberty_mark; static int string_mark; static int next_string; static int strings_initialized = 0; ```

The `ml` array and `liberty_mark` are used to "mark" liberties on the board, e.g. to avoid counting the same liberty twice. The convention is that if `ml[pos]` has the same value as `liberty_mark`, then `pos` is marked. To clear all marks it suffices to increase the value of `liberty_mark`, since it is never allowed to decrease.

The same relation holds between the `mark` field of the `string_data` structure and `string_mark`. Of course these are used for marking individual strings.

`next_string` gives the number of the next available entry in the `string` array. Then `strings_initialized` is set to one when all data structures are known to be up to date. Given an arbitrary board position in the `board' array, this is done by calling `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:

 ```struct change_stack_entry { int *address; int value; }; struct change_stack_entry change_stack[STACK_SIZE]; int change_stack_index; ```

and manipulated with the macros

 ```BEGIN_CHANGE_RECORD() PUSH_VALUE(v) POP_MOVE() ```

Calling `BEGIN_CHANGE_RECORD()` stores a null pointer in the address field to indicate the start of changes for a new move. As mentioned earlier `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 `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 `int` values and the other one stores `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 `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:

1. The new stone is isolated, i.e. it has no friendly neighbor.
2. The new stone has exactly one friendly neighbor.
3. The new stone has at least two friendly neighbors.

The first case is easiest. Then we create a new string by using the number given by `next_string` and increasing this variable. The string will have size one, `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

 ``` pos = FIRST_STONE(s); do { ... pos = NEXT_STONE(pos); } while (!BACK_TO_FIRST_STONE(s, pos)); ```

traverses the stones of the string with number `s' exactly once, with `pos` holding the coordinates. In general `pos` is used as board coordinate and `s' as an index into the `string` array or sometimes a pointer to an entry in the `string` array.

 [ < ] [ > ] [ << ] [ Up ] [ >> ] [Top] [Contents] [Index] [ ? ]

 webmaster delorie software   privacy Copyright © 2003   by The Free Software Foundation Updated Jun 2003