Knowledge Representation in The Many Faces of Go
David Fotland
4863 Capistrano Ave
San Jose Ca 95129
fotland@hpihoc.cup.hp.com
February 27, 1993
Abstract:
This paper describes the representations of Go knowledge in The Many
Faces of GO, a strong computer Go program. Knowledge is encoded in the
algorithms that recognize low level features, in the data structures
that describe the current position, and in patterns that are used to
suggest plausible moves.
1. Introduction
The Many Faces of Go is one of the strongest Go programs commercially
available. It won the United States Computer Go competitions in
1988, 1991, and 1992, and is ranked 13 kyu in the USA. It is based on
traditional AI techniques, such as alpha-beta search, rule based expert
systems, and pattern matching. The go playing algorithm is about 30K
lines of ‘C’, plus a pattern database and a joseki database, and was
written by one person, part time, over a 10 year period. As a
commercial program, it has about another 20K lines of code in user
interfaces for the IBM-PC, X windows, and PenPoint.
Go is a much more difficult AI problem than Chess, and must be attacked
with a knowledge intensive approach rather than brute force search.
2. Go is harder than Chess
There are several reasons why Go is a more difficult problem than Chess.
Strong chess programs depend on full width search 7 or more moves deep.
This is not possible in Go since a Go position is much more difficult to
evaluate than a chess position. An accurate evaluation of a Go position must do
life and death analysis and assign a value for relative thickness and territory. Just
Determining the location of secure territory is a difficult problem. A chess
evaluation is dominated by the values of the pieces remaining on the board,
which is easy to get right. If a go program misreads a life and death fight
there would be a large error in the evaluation score. This leads to an
evaluation function for a go program that is orders of magnitude slower than
the chess evaluation function. Where a PC chess program can examine 100,000
positions each move, Many Faces examines less than 100 positions before
deciding on a move.
Second, Go has many more legal plays than Chess at each move. Even if the
evaluation functions were the same speed, the go program could not look as
many moves ahead.
Third, people read deeper in Go than in Chess. A strong chess player might
read 10 moves ahead, but a strong Go player routinely reads much deeper.
This is because the board does not change as much after a move in go as it
does in chess, so it is easier for a person to visualize the board many moves
ahead. Even a beginner can read 60 moves deep in a ladder that crosses the
board.
3. Knowledge in The Many Faces of Go
Go knowledge is incorporated into Many Faces of Go in 5 major areas.
First, there is low level go knowledge built into the evaluation, and
hard coded in ‘C’ ‘if’ statements. This is knowledge used to determine
connectivity, eyes, potential eyes, and territory, as well as the move
generators and move sorters used by the tactician. The general
philosophy here is to classify first, then analyze. This makes the code
easier to debug.
Second, there is the dynamic knowledge stored about the state of the
current position. This data is stored in a global database, and is
built up in successive passes, from low levels to high levels of abstraction.
Third, there is a rule based expert system with about 200 rules that
suggests plausible moves for full board evaluation. I try to only
suggest reasonable moves for evaluation, to reduce the likelihood that
errors in the evaluation function will cause the program to play
terrible moves.
Fourth, there is a Joseki database of standard corner patterns,
currently consisting of over 36,000 moves, including all moves from most
of the joseki books published by Ishi Press.
Fifth, there is a pattern database of 8×8 patterns, each with a move
tree attached, currently with over 700 patterns and over 4,000 moves.
4. Program Components
The dynamic data stored about a position falls into 3 levels. First is
incremental data, used for low level local data, such as
point_empty_neighbors, or string_liberties. These data structures are
modified incrementally and adjusted as each stone is added to or removed
from the board. Second, there is data that is recalculated as needed. After a
move or sequence of moves, the area of the board affected by the move(s)
is recalculated, and areas that are not effected maintain their old
values. This is used for connection strength, eyes, and string tactics.
Third, there is data that is recalculated for the entire board after a move or
sequence of moves. These include group strength and radiated influence.
The dynamic data consists of:
Point environment
which string is this point part of (or None)
lists of adjacent Black and White strings
list of adjacent empty points
number of adjacent Black, White, and empty points
list of connections
which eye is this point part of
White and Black influence
…
String data
color
list of points (stones) in string
number of liberties
list of liberty points of string
list of adjacent enemy strings
tactical status (captured, threatened, OK)
list of connections
which group is this string part of
…
Connection data
two strings
lists of points in connection
number of connections
type of connection
strength of connection
Eye data
list of points in eye
list of vital points
type of eye
number of eyes
Group data
list of strings in group
list of eyes in group
number of eyes in group
group strength
list of potential eyes
lists of running points
list of adjacent enemy groups
Territory
Score
Incremental data structures are modified as moves are made and taken
back. Other data structures are maintained by the evaluation function.
EVALUATION FUNCTION
I have described the evaluation function elsewhere. Its major
components are the tactical analyzer, which reads a single string with
the twin goals of removing it from the board, or making 5 liberties or
two eyes; the connection evaluator; the eye evaluator; the group
strength evaluator; and the territory evaluator. It assigns a score to
the position, using Chinese style counting, where each point on the
board can have a value between +1 and -1, depending on how strongly it
is controlled by white or black. Evaluation is a multiple pass process.
STRATEGY
Before each move, a strategy function looks at the dynamic data and
attempts to focus attention on the important areas of the board. It
decides what phase the game is in. Fuseki lasts as long as there are
corners that still contain unplayed joseki moves. The game is in
Endgame if more that 120 moves have been played and all groups are
stable. Otherwise the game is in the Middle Game. It is possible for
the phase of the game to go back and forth from endgame to middle game.
The phase of the game is used as a qualifier for some of the move
generation moves. For example the fill_dame rule will not fire unless
the phase is the endgame.
Strategy determines the relative score, and classifies it as “way
ahead” (over 40 points), “ahead” (20-40 points), “about even”, “behind”
(over 10 points), and “way behind (over 20 points)”. This is also used
to qualify move generation rules. For example if the program is way
ahead, it will play extra moves to be certain of big captures. If
it is way behind it will try unsound invasions.
The strategy function determines the value of taking sente. This declines
from 7 points at the start of the game to 1 point at move 220 and above. At
the end of a full board lookahead and quiescence search, this value is added
to the side with sente, since presumably it can get that many points with a
big move somewhere. Seven points is used at the start of the game since
internally the program uses Chinese style counting, which increases the value
of a move by one point relative to Japanese counting, and the value of the
first move is known to be about 5 1/2 points with Japanese counting.
It also finds friendly groups that urgently need defending. These are
cutting groups and big groups.
It finds enemy groups that urgently need capturing. These are big
groups, cutting groups, and groups adjacent to groups that urgently need
defending.
Attack and defense of groups marked urgent, and moves marked urgent by
the pattern matcher, are examined first, and if a reasonable urgent move
is discovered, no other moves are considered.
FULL BOARD LOOKAHEAD
Sequences for full board lookahead are provided directly from the Joseki
and Pattern databases. Joseki variations are laid out on the board and
evaluated at the endpoints. This allows the program to pick joseki that
it understands and that work well with adjacent corners. Patterns also
suggest move sequences that are evaluated at the endpoints.
Move generators examine the group types, eye potentials, and running
points to generate move sequences to kill or save groups, fight semeai,
and run out to the center.
A rule based system generates other reasonable moves, such as
extensions, moyo expansion, etc.
QUIESCENCE SEARCH
Since the evaluation function ultimately reduces the position to a
single number, the score in 50ths of points, it is only accurate in a
quiet position. It generates moves to save unsettled friendly groups or
kill unfriendly enemy groups, and calls itself recursively until it
finds a quiet position. For contact fights, a special set of “obvious
local answer” patterns suggests moves that quiet the position.
5. Dynamic Data
CONNECTIONS
Each connection structure records all connectivity between a pair of
strings. A connection point is a liberty of one string that sees a
stone of another string along a straight line, either 1, 2, or 3 points
away. This suffices to discover hane, one point jump, knight moves, 2
point jumps, large knight moves, and 3 points jumps. It does not see
double kosumi or watari played on the 1st line from two stones on the
second line. These are handled by special case patterns. A low level
incremental data structure that records the distance to the nearest
stone in each direction at each point makes it easy to incrementally
maintain the basic connections. The structure looks like:
string s1, s2; // two string numbers
int num_d1; // number of distance one connections
int num_d2; // number of distance 2 connections
int num_d3; // number of distance 3 connections
list_t d1points; // list of distance one connection points
list_t d2points; // list of distance 2 connection points
list_t d3points; // list of distance 3 connections
int type; // type of connection:
// hane
// one point jump
// half knight’s move
// knight’s move
// two point jump
// half large knight’s move
// large knight’s move
// 3 point jump
// multiple
int strength; // strength of the connection
// Already cut
// Cuttable
// Shared connection
// Connected with aji
// Connected solidly
Type and strength are recalculated as needed. The others are incremental.
Each point maintains a list of connection structures that it appears in,
and each string maintains a list of connection structures that it
appears in. The connection analysis code (representing low level go
knowledge coded directly in ‘C’) is about 1500 lines.
EYES
Each eye structure records information about a single block of empty
points completely or partially surrounded by stones of the same color,
or a tactically captured string.
list_t points; // empty points in the eye
list_t vital; // vital points for the eye, destroy it or finish it
int type; // type of the eye
// single point
// two point
// line eye (closed, open one end, open both ends)
// big eye
// dead group eye
int color; // color of the eye
int min_eyes; // number of eyes if opponent moves twice
int typ_eyes; // number of eyes if opponent moves first
int max_eyes; // number of eyes if I move first
The number of eyes is stored using the value 8 for one eye, 4 for 1/2
eye, etc. Each point can be part of only one eye, and which eye is
recorded with the other data about a point. All parts of the eye
structure are recalculated as needed. Eye analysis is about 2400 lines
of ‘C’. There are a lot more patterns to recognize for eyes than for
connections, and nothing is incremental, so if I had to do it over again
I would make it pattern based rather than coded in ‘C’.
POTENTIAL EYES
Each group has a list of potential eye structures, and lists of running
points. These are used to evaluate group strength and to generate
attacking/defending moves. A potential eye contains a value, location,
and type. Types are:
Extend along the edge
value is number of new points of eye space
location is liberty to extend from
Play vital point to make an eye
value is number of new eyes
location is vital point
Connect to another group
value is number of eyes
location is connection number
Capture tactically threatened string
value is number of eyes
location is string number
Defend undercut territory on the edge
value is number of new points for eye space
location is liberty being undercut
Running points are recorded in several lists, by type, depending on
whether running this direction is toward friendly or enemy stones, etc.
Potential eyes and running points are reevaluated on the whole board at
each evaluation, consuming about 7% of the program’s time.
INFLUENCE FUNCTION
An influence function is used that radiates from each stone with an
initial value that is dependent on the group strength, and falls off as
one over the distance. Radiated values are summed independently at each
point for black and white. Influence does not radiate through a stone
or a connection. The influence is used to calculate territory and
thickness, find the best gradient for running moves, and help generate
moyo building or reducing/invading moves. Influence is not used to
determine group connectivity.
A function that falls off as 1/distance will create a constant field inside
any fully enclosed area, no matter how big it is, which is desirable
for estimating territory. Dead stones radiate negative influence.
6. Rule Based System For Move Suggestion
The move suggestion experts are:
Fuseki
Playing in empty corner
Shimari and kakari
Joseki moves
Big moves on edge
Towards enemy
Invasions
Between friendly stones
Playing in the center (reducing moves and junction lines)
Playing safe when ahead
Respond when enemy adds stone to dead group
Add extra stone to kill big group that is probably dead
Squirming around when behind
Make a bogus invasion
Try to make a hopeless group live
Pattern matching
patterns for cutting and connecting
patterns for surrounding and avoiding getting surrounded
patterns for invasion and defending against invasion
endgame patterns
killing/saving groups patterns
Shape moves
Saving a weak group
making eyes
running
fighting semeais
Save threatened string
Capture threatened string
Killing a weak group
surrounding
taking eyes
Cutting and connecting
Contact fights
Blocking
extending for liberties
hane
Ko threats
make a big atari
Filling dame
The move suggestion code is easily extensible by adding more patterns or
firing rules based on other criteria. The program has text associated with
each rule so it can “explain” its reasons for making moves. This makes it
easier to debug.
7. Joseki Database
The Joseki database is stored as a directed acyclic graph of moves, with
the root representing an empty corner. Transpositions are built into the
database manually as data is entered. An uncompressed representation is
maintained in an ASCII file. Each uncompressed node contains:
X and Y relative to corner (1-16 each)
Sibling pointer
First child pointer
Type: Urgent, normal, complex, trick, bad, ignore, …
flags: color, symmetric, color reverse
Urgent moves take priority over any other
Complex and trick moves are avoided by the computer unless it is behind.
Bad moves are never played by the computer, but are in the database so
it can respond correctly if an opponent plays them.
Ignore is used to delete an erroneous entry in the database.
Color indicates whether the current move is the same color or opposite color
as the first move in the corner. Symmetric indicates that this move
makes the corner symmetric again. Color reverse is needed for some
transpositions, for example certain 3-4 joseki are 5-3 joseki transposed with
colors reversed.
A compressed representation is used directly by the program.
(x,y) are reduced to 6 bits via a lookup table. Values 0-62 index the
lookup table of the 62 most common x,y pairs. Value 63 escapes to the
next byte which contains the full x,y pair. Sibling and child pointers
are replaced by two bits, has_sibling, and has_child. Data is stored in
depth first order, except where lines converge due to a transposition,
when a full 2 byte pointer is stored. Most entries are 1 byte long. The
longest possible entry is 5 bytes. Across the entire database, the
average move is stored in 1.2 bytes.
8. Pattern Database
There is a pattern matcher with over 500 patterns in it. Each pattern is
8×8, where each point is specified as black, white, empty, don’t care,
white or empty, black or empty, or black or white. Each pattern has
a set of attributes with it that must also match. For example, “the
stone at (4,2) must be alive”, or “the stone at (2,5) must have at least
2 liberties”. Each pattern has a move tree attached that is used for
full board lookahead.
Patterns are also used for reading obvious local sequences based on shape.
Internally a pattern is stored as three 8 byte arrays, one for black points,
one for white, and one for empty. For example a black point in the
pattern is indicated by setting the appropriate bit in the black array.
Black or Empty is indicated by setting bits in both the black and empty
arrays. Don’t care is indicated by setting corresponding bits in all 3
arrays. Each pattern needs to be matched at each point on the board, in
8 orientations, and with colors reversed. Rather than rotate the
patterns, I rotate the board before comparing. Color reversal is handled
by reversing the black and white arrays. Once I have 3 arrays extracted
from the board for a particular position, the pattern match is simple.
Just bitwise AND the corresponding Black, white, and empty arrays; bitwise
OR the three results, and if the result is all ones, then there is a match.
After I find a matching pattern I check that the attributes specified by
the pattern match.
I plan to have at least 1000 patterns, so performance is an issue. 1000
patterns compared at 361 locations at 16 orientations is over 5
million comparisons. A full comparison requires 24 byte ANDS, 16 byte
ORS, and 8 compares, or 48 primitive operations. That’s almost 300
million operations to determine which patterns match at a single board
position.
I use an 8 bit hash function to reduce the number of patterns examined.
Of course since there are don’t cares in the patterns, a pattern may appear
on more than one hash chain (current average is 3 chains). The hash
reduces the number of patterns examined by about a factor of 20. I
remember which patterns match from move to move and only rematch in the
area near the last move. This reduces the number of points where patterns
need to be matched from 361 to about 50. I do the bitwise operations
32 bits at a time, and most patterns miscompare on the first word, which
reduces the number of logical operations about 6 on average. This reduces
the number of operations to match all patterns down to about 350,000.
This is still slow enough that I only match all the patterns once
each time the computer has to make a move, to find sequences to read. A
small subset of the patterns is used for obvious local lookahead during
the quiescence search. The program currently spends about 1.5% of its time
matching patterns.
9. Knowledge acquisition
A graphical joseki editor allows me to enter about 400 new moves an hour,
and compiles the database to the compressed binary form that the program
uses.
A graphical pattern editor allows easy modification of the patterns
or move trees and can display 36 patterns at a time.
The program can play through a professional game and compare its choices to
the pro’s. When it didn’t even consider the pro’s move, it cuts out
the pattern from the pro’s game and saves it. This turned out to be much
less useful that I expected, since many of these moves involved difficult
fights, where an 8×8 pattern was not big enough, or where the proper move
was selected based on deep reading, not the local stone pattern.
10. Debugging Aids
Many of the data structures are incremental. I can optionally include code
that periodically recalculates the incremental data structures from scratch
and verifies that they are correct. This code also walks all of the
lists and finds any memory leaks. I’m confident that the program contains
no bugs in incremental updates or memory leaks.
Some data values are only recalculated as needed. Sometimes an eye or
a tactical lookahead needs to be recalculated, and it doesn’t get noticed.
To detect bugs here I can include code that evaluates each position twice,
before and after lookahead. A difference indicates that something wasn’t
reevaluated. There are many situations that can cause missed reevaluations.
In test games they come up about once in 1000 moves. These are not critical
bugs since they mimic similar oversights that people make, but I fix them
as I find them.
The debugging version of the program can dump all of the internal data
structures in an easy to read form. When the program makes a bad move I
can quickly determine the cause.
I run two kinds of regression tests. First, I run about 100 self play games
at different levels and handicaps with all the checking code included.
Second, I have about 400 problems from Graded Go Problems For Beginners,
and the program can automatically play thru them to produce a report of
which problems got the wrong answers.
I wrote an interface between Many Faces of Go and the Internet Go Server
so Many Faces can play games there unattended. I collect some of these
test games as samples that I can examine in detail to fix the problems
that lead to bad moves.
11. Summary
Playing the game of Go well is a difficult AI problem. Because of the
large board and difficult evaluation, large searches are impractical,
and a knowledge intensive approach is required. Many Faces encodes
local, low level knowledge in C algorithms for speed, and higher level
pattern knowledge in databases optimized for size.