226 lines
9.4 KiB
Plaintext
226 lines
9.4 KiB
Plaintext
In the tactical reading code in @file{reading.c}, the
|
|
code generating the moves which are tried are all hand
|
|
coded in C, for efficiency. There is much to be said for
|
|
another type of reading, in which the moves to be tried are
|
|
generated from a pattern database.
|
|
|
|
GNU Go does three main types of pattern based reading. First,
|
|
there is the OWL code (Optics with Limit Negotiation) which
|
|
attempts to read out to a point where the code in
|
|
@file{engine/optics.c} (@pxref{Eyes}) can be used to evaluate
|
|
it. Like the tactical reading code, a persistent cache is
|
|
employed to maintain some of the owl data from move to
|
|
move. This is an essential speedup without which GNU Go would
|
|
play too slowly.
|
|
|
|
Secondly, there is the @file{engine/combination.c} which
|
|
attempts to find combinations---situations where a series
|
|
of threats eventually culminates in one that cannot be
|
|
parried.
|
|
|
|
Finally there is the semeai module. A @strong{semeai} is
|
|
a capturing race between two adjacent DEAD or CRITICAL
|
|
dragons of opposite colors. The principal function,
|
|
@code{owl_analyze_semeai()} is contained in @file{owl.c}.
|
|
Due to the complex nature of semeais, the results of
|
|
this function are more frequently wrong than the usual
|
|
owl code.
|
|
|
|
@menu
|
|
* The Owl Code:: Life and death reading
|
|
* Combinations:: Combinations
|
|
@end menu
|
|
|
|
@node The Owl Code
|
|
@section The Owl Code
|
|
|
|
The life and death code in @file{optics.c}, described elsewhere
|
|
(@pxref{Eyes}), works reasonably well as long as the position is in a
|
|
@dfn{terminal position}, which we define to be one where there are no
|
|
moves left which can expand the eye space, or limit it. In situations
|
|
where the dragon is surrounded, yet has room to thrash around a bit
|
|
making eyes, a simple application of the graph-based analysis will not
|
|
work. Instead, a bit of reading is needed to reach a terminal position.
|
|
|
|
The defender tries to expand his eyespace, the attacker to limit
|
|
it, and when neither finds an effective move, the position is
|
|
evaluated. We call this type of life and death reading
|
|
@dfn{Optics With Limit-negotiation} (OWL). The module which
|
|
implements it is in @file{engine/owl.c}.
|
|
|
|
There are two reasonably small databases
|
|
@file{patterns/owl_defendpats.db} and @file{patterns/owl_attackpats.db}
|
|
of expanding and limiting moves. The code in @file{owl.c} generates a
|
|
small move tree, allowing the attacker only moves from
|
|
@file{owl_attackpats.db}, and the defender only moves from
|
|
@file{owl_defendpats.db}. In addition to the moves suggested by
|
|
patterns, vital moves from the eye space analysis are also tested.
|
|
|
|
A third database, @file{owl_vital_apats.db} includes patterns which
|
|
override the eyespace analysis done by the optics code. Since the
|
|
eyeshape graphs ignore the complications of shortage of liberties and
|
|
cutting points in the surrounding chains, the static analysis of
|
|
eyespace is sometimes wrong. The problem is when the optics code says
|
|
that a dragon definitely has 2 eyes, but it isn't true due to
|
|
shortage of liberties, so the ordinary owl patterns never get into play.
|
|
In such situations @file{owl_vital_apats.db} is the only available measure
|
|
to correct mistakes by the optics. Currently the patterns in
|
|
@file{owl_vital_apats.db} are only matched when the level is 9 or
|
|
greater.
|
|
|
|
The owl code is tuned by editing these three pattern databases,
|
|
principally the first two.
|
|
|
|
@findex owl_attack
|
|
@findex owl_defend
|
|
@findex compute_eyes_pessimistic
|
|
A node of the move tree is considered @code{terminal} if no further moves
|
|
are found from @file{owl_attackpats.db} or @file{owl_defendpats.db}, or if
|
|
the function @code{compute_eyes_pessimistic()} reports that the group is
|
|
definitely alive. At this point, the status of the group is evaluated.
|
|
The functions @code{owl_attack()} and @code{owl_defend()}, with
|
|
usage similar to @code{attack()} and @code{find_defense()}, make
|
|
use of the owl pattern databases to generate the move tree and decide
|
|
the status of the group.
|
|
|
|
The function @code{compute_eyes_pessimistic()} used by the owl
|
|
code is very conservative and only feels certain about eyes if the
|
|
eyespace is completely closed (i.e. no marginal vertices).
|
|
|
|
The maximum number of moves tried at each node is limited by
|
|
the parameter @code{MAX_MOVES} defined at the beginning of
|
|
@file{engine/owl.c}. The most most valuable moves are
|
|
tried first, with the following restrictions:
|
|
|
|
@itemize @bullet
|
|
@item
|
|
If @code{stackp > owl_branch_depth} then only one move is tried per
|
|
variation.
|
|
@item
|
|
If @code{stackp > owl_reading_depth} then the reading terminates,
|
|
and the situation is declared a win for the defender (since
|
|
deep reading may be a sign of escape).
|
|
@item
|
|
If the node count exceeds @code{owl_node_limit}, the reading also
|
|
terminates with a win for the defender.
|
|
@item
|
|
Any pattern with value 99 is considered a forced move: no
|
|
other move is tried, and if two such moves are found, the function
|
|
returns false. This is only relevant for the attacker.
|
|
@item
|
|
Any pattern in @file{patterns/owl_attackpats.db} and
|
|
@file{patterns/owl_defendpats.db} with value 100 is considered a win: if
|
|
such a pattern is found by @code{owl_attack} or @code{owl_defend}, the
|
|
function returns true. This feature must be used most carefully.
|
|
@end itemize
|
|
|
|
The functions @code{owl_attack()} and @code{owl_defend()} may, like
|
|
@code{attack()} and @code{find_defense()}, return an attacking or
|
|
defending move through their pointer arguments. If the position is
|
|
already won, @code{owl_attack()} may or may not return an attacking
|
|
move. If it finds no move of interest, it will return @code{PASS}, that
|
|
is, @code{0}. The same goes for @code{owl_defend()}.
|
|
|
|
When @code{owl_attack()} or @code{owl_defend()} is called,
|
|
the dragon under attack is marked in the array @code{goal}.
|
|
The stones of the dragon originally on the board are marked
|
|
with goal=1; those added by @code{owl_defend()} are marked
|
|
with goal=2. If all the original strings of the original dragon
|
|
are captured, @code{owl_attack()} considers the dragon to be defeated,
|
|
even if some stones added later can make a live group.
|
|
|
|
Only dragons with small escape route are studied when the
|
|
functions are called from @code{make_dragons()}.
|
|
|
|
The owl code can be conveniently tested using the
|
|
@option{--decide-owl @var{location}} option. This should be used with
|
|
@option{-t} to produce a useful trace, @option{-o} to produce
|
|
an SGF file of variations produced when the life and death of
|
|
the dragon at @var{location} is checked, or both.
|
|
@option{--decide-position} performs the same analysis for all
|
|
dragons with small escape route.
|
|
|
|
@node Combinations
|
|
@section Combination reading
|
|
|
|
It may happen that no single one of a set of worms can be killed,
|
|
yet there is a move that guarantees that at least one can be captured.
|
|
The simplest example is a double atari. The purpose of the code in
|
|
@file{combination.c} is to find such moves.
|
|
|
|
For example, consider the following situation:
|
|
|
|
@example
|
|
|
|
+---------
|
|
|....OOOOX
|
|
|....OOXXX
|
|
|..O.OXX..
|
|
|.OXO.OX..
|
|
|.OX..OO..
|
|
|.XXOOOXO.
|
|
|..*XXOX..
|
|
|....XOX..
|
|
|.XX..X...
|
|
|X........
|
|
|
|
@end example
|
|
|
|
Every @samp{X} stone in this position is alive. However the move
|
|
at @samp{*} produces a position in which at least one of four
|
|
strings will get captured. This is a @emph{combination}.
|
|
|
|
The driving function is called @code{atari_atari} because typically
|
|
a combination involves a sequence of ataris culminating in a capture,
|
|
though sometimes the moves involved are not ataris. For example in
|
|
the above example, the first move at @samp{*} is @emph{not} an
|
|
atari, though after @samp{O} defends the four stones above, a
|
|
sequence of ataris ensues resulting in the capture of some
|
|
string.
|
|
|
|
Like the owl functions @code{atari_atari} does pattern-based
|
|
reading. The database generating the attacking moves is
|
|
@file{aa_attackpats.db}. One danger with this function is
|
|
that the first atari tried might be irrelevant to the actual
|
|
combination. To detect this possibility, once we've found a
|
|
combination, we mark that first move as forbidden, then try
|
|
again. If no combination of the same size or larger turns
|
|
up, then the first move was indeed essential.
|
|
|
|
@itemize @bullet
|
|
@item @code{void combinations(int color)}
|
|
@findex combinations
|
|
@quotation
|
|
Generate move reasons for combination attacks and defenses against
|
|
them. This is one of the move generators called from genmove().
|
|
@end quotation
|
|
@item @code{int atari_atari(int color, int *attack_move, char defense_moves[BOARDMAX], int save_verbose)}
|
|
@findex atari_atari
|
|
@quotation
|
|
Look for a combination for @code{color}. For the purpose of
|
|
the move generation, returns the size of the smallest of the
|
|
worms under attack.
|
|
@end quotation
|
|
@item @code{int atari_atari_confirm_safety(int color, int move, int *defense, int minsize, const char saved_dragons[BOARDMAX], const char saved_worms[BOARDMAX])}
|
|
@findex atari_atari_confirm_safety
|
|
@quotation
|
|
Tries to determine whether a move is a blunder. Wrapper
|
|
around atari_atari_blunder_size. Check whether a combination
|
|
attack of size at least @code{minsize} appears after move at
|
|
@code{move} has been made. The arrays @code{saved_dragons[]} and
|
|
@code{saved_worms[]} should be one for stones belonging to dragons
|
|
or worms respectively, which are supposedly saved by @code{move}.
|
|
@end quotation
|
|
@item @code{int atari_atari_blunder_size(int color, int move, int *defense, const char safe_stones[BOARDMAX])}
|
|
@findex atari_atari_blunder_size
|
|
@quotation
|
|
This function checks whether any new combination attack appears after
|
|
move at (move) has been made, and returns its size (in points).
|
|
@code{safe_stones} marks which of our stones are supposedly safe
|
|
after this move.
|
|
@end quotation
|
|
@end itemize
|
|
|
|
|
|
|