1138 lines
35 KiB
C
1138 lines
35 KiB
C
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
|
|
* This is GNU Go, a Go program. Contact gnugo@gnu.org, or see *
|
|
* http://www.gnu.org/software/gnugo/ for more information. *
|
|
* *
|
|
* Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, *
|
|
* 2008 and 2009 by the Free Software Foundation. *
|
|
* *
|
|
* This program is free software; you can redistribute it and/or *
|
|
* modify it under the terms of the GNU General Public License as *
|
|
* published by the Free Software Foundation - version 3 or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU General Public License in file COPYING for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public *
|
|
* License along with this program; if not, write to the Free *
|
|
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, *
|
|
* Boston, MA 02111, USA. *
|
|
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
#include "gnugo.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "liberty.h"
|
|
#include "gg_utils.h"
|
|
#include "patterns.h"
|
|
#include "dfa.h"
|
|
|
|
|
|
/**************************************************************************/
|
|
/* Pattern profiling functions: */
|
|
/**************************************************************************/
|
|
|
|
|
|
#if PROFILE_PATTERNS
|
|
/* Initialize pattern profiling fields in one pattern struct array. */
|
|
static void
|
|
clear_profile(struct pattern *pattern)
|
|
{
|
|
for (; pattern->patn; ++pattern) {
|
|
pattern->hits = 0;
|
|
pattern->reading_nodes = 0;
|
|
pattern->dfa_hits = 0;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if PROFILE_PATTERNS
|
|
/* Print profiling information for one pattern struct array. */
|
|
static void
|
|
print_profile(struct pattern *pattern, int *total_hits,
|
|
int *total_nodes, int *total_dfa_hits)
|
|
{
|
|
for (; pattern->patn; ++pattern)
|
|
if (pattern->hits > 0) {
|
|
*total_hits += pattern->hits;
|
|
*total_nodes += pattern->reading_nodes;
|
|
*total_dfa_hits += pattern->dfa_hits;
|
|
fprintf(stderr, "%6d %6d %9d %8.1f %s\n",
|
|
pattern->dfa_hits,
|
|
pattern->hits,
|
|
pattern->reading_nodes,
|
|
pattern->reading_nodes / (float) pattern->hits,
|
|
pattern->name);
|
|
}
|
|
}
|
|
#endif /* PROFILE_PATTERNS */
|
|
|
|
|
|
/* Initialize pattern profiling fields in pattern struct arrays. */
|
|
void
|
|
prepare_pattern_profiling()
|
|
{
|
|
#if PROFILE_PATTERNS
|
|
clear_profile(pat_db.patterns);
|
|
clear_profile(attpat_db.patterns);
|
|
clear_profile(defpat_db.patterns);
|
|
clear_profile(endpat_db.patterns);
|
|
clear_profile(conn_db.patterns);
|
|
clear_profile(influencepat_db.patterns);
|
|
clear_profile(barrierspat_db.patterns);
|
|
clear_profile(aa_attackpat_db.patterns);
|
|
clear_profile(owl_attackpat_db.patterns);
|
|
clear_profile(owl_vital_apat_db.patterns);
|
|
clear_profile(owl_defendpat_db.patterns);
|
|
clear_profile(fusekipat_db.patterns);
|
|
clear_profile(oracle_db.patterns);
|
|
#else
|
|
fprintf(stderr,
|
|
"Warning, no support for pattern profiling in this binary.\n");
|
|
#endif
|
|
}
|
|
|
|
|
|
/* Report result of pattern profiling. Only patterns with at least one
|
|
* match are listed.
|
|
*/
|
|
void
|
|
report_pattern_profiling()
|
|
{
|
|
#if PROFILE_PATTERNS
|
|
int hits = 0;
|
|
int dfa_hits = 0;
|
|
int nodes = 0;
|
|
|
|
print_profile(pat_db.patterns, &hits, &nodes, &dfa_hits);
|
|
print_profile(attpat_db.patterns, &hits, &nodes, &dfa_hits);
|
|
print_profile(defpat_db.patterns, &hits, &nodes, &dfa_hits);
|
|
print_profile(endpat_db.patterns, &hits, &nodes, &dfa_hits);
|
|
print_profile(conn_db.patterns, &hits, &nodes, &dfa_hits);
|
|
print_profile(influencepat_db.patterns, &hits, &nodes, &dfa_hits);
|
|
print_profile(barrierspat_db.patterns, &hits, &nodes, &dfa_hits);
|
|
print_profile(aa_attackpat_db.patterns, &hits, &nodes, &dfa_hits);
|
|
print_profile(owl_attackpat_db.patterns, &hits, &nodes, &dfa_hits);
|
|
print_profile(owl_vital_apat_db.patterns, &hits, &nodes, &dfa_hits);
|
|
print_profile(owl_defendpat_db.patterns, &hits, &nodes, &dfa_hits);
|
|
print_profile(fusekipat_db.patterns, &hits, &nodes, &dfa_hits);
|
|
print_profile(oracle_db.patterns, &hits, &nodes, &dfa_hits);
|
|
fprintf(stderr, "------ ---------\n");
|
|
fprintf(stderr, "%6d, %6d %9d\n", dfa_hits, hits, nodes);
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
/**************************************************************************/
|
|
/* Standard matcher: */
|
|
/**************************************************************************/
|
|
|
|
|
|
/* Forward declarations. */
|
|
|
|
static void fixup_patterns_for_board_size(struct pattern *pattern);
|
|
static void prepare_for_match(int color);
|
|
static void do_matchpat(int anchor, matchpat_callback_fn_ptr callback,
|
|
int color, struct pattern *database,
|
|
void *callback_data, signed char goal[BOARDMAX]);
|
|
static void matchpat_loop(matchpat_callback_fn_ptr callback,
|
|
int color, int anchor,
|
|
struct pattern_db *pdb, void *callback_data,
|
|
signed char goal[BOARDMAX], int anchor_in_goal);
|
|
|
|
/* Precomputed tables to allow rapid checks on the piece at
|
|
* the board. This table relies on the fact that color is
|
|
* 1 or 2.
|
|
*
|
|
* For pattern element i, require (board[pos] & andmask[i]) == valmask[i]
|
|
*
|
|
* .XO) For i=0,1,2, board[pos] & 3 is a no-op, so we check board[pos]
|
|
* == valmask
|
|
* x) For i=3, we are checking that board[pos] is not color, so AND
|
|
* color and we get 0 for either empty or OTHER_COLOR, but color
|
|
* if it contains color
|
|
* o) Works the other way round for checking it is not X.
|
|
*
|
|
*
|
|
* gcc allows the entries to be computed at run-time, but that is not ANSI.
|
|
*/
|
|
|
|
static const int and_mask[2][8] = {
|
|
/* . X O x o , a ! color */
|
|
{ 3, 3, 3, WHITE, BLACK, 3, 3, 3 }, /* BLACK */
|
|
{ 3, 3, 3, BLACK, WHITE, 3, 3, 3 } /* WHITE */
|
|
};
|
|
|
|
static const int val_mask[2][8] = {
|
|
{ EMPTY, BLACK, WHITE, 0, 0, EMPTY, EMPTY, EMPTY}, /* BLACK */
|
|
{ EMPTY, WHITE, BLACK, 0, 0, EMPTY, EMPTY, EMPTY} /* WHITE */
|
|
};
|
|
|
|
|
|
/* and a table for checking classes quickly
|
|
* class_mask[status][color] contains the mask to look for in class.
|
|
* ie. if pat[r].class & class_mask[dragon[pos].status][board[pos]]
|
|
* is not zero then we reject it
|
|
* Most elements if class_mask[] are zero - it is a sparse
|
|
* matrix containing
|
|
* CLASS_O in [DEAD][color]
|
|
* CLASS_O in [CRITICAL][color]
|
|
* CLASS_o in [ALIVE][color]
|
|
* CLASS_X in [DEAD][other]
|
|
* CLASS_x in [ALIVE][other]
|
|
*
|
|
* so eg. if we have a dead white dragon, and we
|
|
* are checking a pattern for black, then
|
|
* class_mask[DEAD][other] will contain CLASS_X
|
|
* Then we reject any patterns which have CLASS_X
|
|
* set in the class bits.
|
|
*
|
|
* Making it static guarantees that all fields are
|
|
* initially set to 0, and we overwrite the ones
|
|
* we care about each time.
|
|
*/
|
|
|
|
static unsigned int class_mask[NUM_DRAGON_STATUS][3];
|
|
|
|
|
|
/* In the current implementation, the edge constraints depend on
|
|
* the board size, because we pad width or height out to the
|
|
* board size. (This is because it is easy to find the corners
|
|
* of the rotated pattern, but it is harder to transform the
|
|
* bitmask of edge constraints.)
|
|
*
|
|
* But since version 1.103, board size is variable. Thus we
|
|
* make a first pass through the table once we know the board
|
|
* size.
|
|
*
|
|
* This should be called once for each pattern database.
|
|
*/
|
|
|
|
static void
|
|
fixup_patterns_for_board_size(struct pattern *pattern)
|
|
{
|
|
for (; pattern->patn; ++pattern)
|
|
if (pattern->edge_constraints != 0) {
|
|
|
|
/* If the patterns have been fixed up for a different board size
|
|
* earlier, we need to undo the modifications that were done
|
|
* below before we do them anew. The first time this function is
|
|
* called, this step is effectively a no-op.
|
|
*/
|
|
|
|
if (pattern->edge_constraints & NORTH_EDGE)
|
|
pattern->maxi = pattern->mini + pattern->height;
|
|
|
|
if (pattern->edge_constraints & SOUTH_EDGE)
|
|
pattern->mini = pattern->maxi - pattern->height;
|
|
|
|
if (pattern->edge_constraints & WEST_EDGE)
|
|
pattern->maxj = pattern->minj + pattern->width;
|
|
|
|
if (pattern->edge_constraints & EAST_EDGE)
|
|
pattern->minj = pattern->maxj - pattern->width;
|
|
|
|
/* we extend the pattern in the direction opposite the constraint,
|
|
* such that maxi (+ve) - mini (-ve) = board_size-1
|
|
* Note : the pattern may be wider than the board, so
|
|
* we need to be a bit careful !
|
|
*/
|
|
|
|
if (pattern->edge_constraints & NORTH_EDGE)
|
|
if (pattern->maxi < (board_size-1) + pattern->mini)
|
|
pattern->maxi = (board_size-1) + pattern->mini;
|
|
|
|
if (pattern->edge_constraints & SOUTH_EDGE)
|
|
if (pattern->mini > pattern->maxi - (board_size-1))
|
|
pattern->mini = pattern->maxi - (board_size-1);
|
|
|
|
if (pattern->edge_constraints & WEST_EDGE)
|
|
if (pattern->maxj < (board_size-1) + pattern->minj)
|
|
pattern->maxj = (board_size-1) + pattern->minj;
|
|
|
|
if (pattern->edge_constraints & EAST_EDGE)
|
|
if (pattern->minj > pattern->maxj - (board_size-1))
|
|
pattern->minj = pattern->maxj - (board_size-1);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* prepare a pattern matching for color point of view
|
|
*/
|
|
static void
|
|
prepare_for_match(int color)
|
|
{
|
|
int other = OTHER_COLOR(color);
|
|
|
|
/* Basic sanity checks. */
|
|
gg_assert(color != EMPTY);
|
|
|
|
/* If we set one of class_mask[XXX][color] and
|
|
* class_mask[XXX][other], we have to explicitly set or reset the
|
|
* other as well, since 'color' may change between calls.
|
|
*/
|
|
class_mask[DEAD][color] = CLASS_O;
|
|
class_mask[DEAD][other] = CLASS_X;
|
|
class_mask[CRITICAL][color] = CLASS_O;
|
|
class_mask[CRITICAL][other] = 0; /* Need to reset this. */
|
|
class_mask[ALIVE][color] = CLASS_o;
|
|
class_mask[ALIVE][other] = CLASS_x;
|
|
}
|
|
|
|
|
|
/*
|
|
* Try all the patterns in the given array at (anchor). Invoke the
|
|
* callback for any that matches. Classes X,O,x,o are checked here. It
|
|
* is up to the callback to process the other classes, and any helper
|
|
* or autohelper functions.
|
|
*
|
|
* If the support of goal[BOARDMAX] is a subset of the board, patterns
|
|
* are rejected which do not involve this dragon. If goal is a null
|
|
* pointer, this parameter is ignored.
|
|
*/
|
|
|
|
static void
|
|
do_matchpat(int anchor, matchpat_callback_fn_ptr callback, int color,
|
|
struct pattern *pattern, void *callback_data,
|
|
signed char goal[BOARDMAX])
|
|
{
|
|
const int anchor_test = board[anchor] ^ color; /* see below */
|
|
int m = I(anchor);
|
|
int n = J(anchor);
|
|
int merged_val;
|
|
|
|
/* Basic sanity checks. */
|
|
ASSERT_ON_BOARD1(anchor);
|
|
|
|
/* calculate the merged value around [m][n] for the grid opt */
|
|
{
|
|
/* FIXME: Convert this to 2D (using delta[]) but be aware that you'll
|
|
* also need to make corresponding changes in mkpat.c!
|
|
*/
|
|
int i, j;
|
|
int shift = 30;
|
|
|
|
merged_val = 0;
|
|
for (i = m-1; i <= m+2; ++i)
|
|
for (j = n-1; j <= n+2; shift -= 2, ++j) {
|
|
unsigned int this;
|
|
if (!ON_BOARD2(i, j))
|
|
this = 3;
|
|
else if ((this = BOARD(i, j)) == 0)
|
|
continue;
|
|
else if (color == 2)
|
|
this = OTHER_COLOR(this);
|
|
merged_val |= (this << shift);
|
|
}
|
|
}
|
|
|
|
/* Try each pattern - NULL pattern marks end of list. Assume at least 1 */
|
|
gg_assert(pattern->patn);
|
|
|
|
do {
|
|
/*
|
|
* These days we always match all patterns.
|
|
*/
|
|
{
|
|
int end_transformation;
|
|
int ll; /* Iterate over transformations (rotations or reflections) */
|
|
int k; /* Iterate over elements of pattern */
|
|
int found_goal;
|
|
|
|
/* We can check the color of the anchor stone now.
|
|
* Roughly half the patterns are anchored at each
|
|
* color, and since the anchor stone is invariant under
|
|
* rotation, we can reject all rotations of a wrongly-anchored
|
|
* pattern in one go.
|
|
*
|
|
* Patterns are always drawn from O perspective in .db,
|
|
* so board[pos] is 'color' if the pattern is anchored
|
|
* at O, or 'other' for X.
|
|
* Since we require that this flag contains 3 for
|
|
* anchored_at_X, we can check that
|
|
* board[pos] == (color ^ anchored_at_X)
|
|
* which is equivalent to
|
|
* (board[pos] ^ color) == anchored_at_X)
|
|
* and the LHS is something we precomputed.
|
|
*/
|
|
|
|
if (anchor_test != pattern->anchored_at_X)
|
|
continue; /* does not match the anchor */
|
|
|
|
ll = 0; /* first transformation number */
|
|
end_transformation = pattern->trfno;
|
|
|
|
/* Ugly trick for dealing with 'O' symmetry. */
|
|
if (pattern->trfno == 5) {
|
|
ll = 2;
|
|
end_transformation = 6;
|
|
}
|
|
|
|
/* try each orientation transformation. Assume at least 1 */
|
|
|
|
do {
|
|
|
|
#if PROFILE_PATTERNS
|
|
int nodes_before;
|
|
#endif
|
|
|
|
#if GRID_OPT == 1
|
|
|
|
/* We first perform the grid check : this checks up to 16
|
|
* elements in one go, and allows us to rapidly reject
|
|
* patterns which do not match. While this check invokes a
|
|
* necessary condition, it is not a sufficient test, so more
|
|
* careful checks are still required, but this allows rapid
|
|
* rejection. merged_val should contain a combination of
|
|
* 16 board positions around m, n. The colours have been fixed
|
|
* up so that stones which are 'O' in the pattern are
|
|
* bit-pattern %01.
|
|
*/
|
|
if ((merged_val & pattern->and_mask[ll]) != pattern->val_mask[ll])
|
|
continue; /* large-scale match failed */
|
|
|
|
#endif /* GRID_OPT == 1 */
|
|
|
|
/* Next, we do the range check. This applies the edge
|
|
* constraints implicitly.
|
|
*/
|
|
{
|
|
int mi, mj, xi, xj;
|
|
|
|
TRANSFORM2(pattern->mini, pattern->minj, &mi, &mj, ll);
|
|
TRANSFORM2(pattern->maxi, pattern->maxj, &xi, &xj, ll);
|
|
|
|
/* {min,max}{i,j} are the appropriate corners of the original
|
|
* pattern, Once we transform, {m,x}{i,j} are still corners,
|
|
* but we don't know *which* corners.
|
|
* We could sort them, but it turns out to be cheaper
|
|
* to just test enough cases to be safe.
|
|
*/
|
|
|
|
DEBUG(DEBUG_MATCHER,
|
|
"---\nconsidering pattern '%s', rotation %d at %1m. Range %d,%d -> %d,%d\n",
|
|
pattern->name, ll, anchor, mi, mj, xi, xj);
|
|
|
|
/* now do the range-check */
|
|
if (!ON_BOARD2(m + mi, n + mj) || !ON_BOARD2(m + xi, n + xj))
|
|
continue; /* out of range */
|
|
}
|
|
|
|
/* Now iterate over the elements of the pattern. */
|
|
found_goal = 0;
|
|
for (k = 0; k < pattern->patlen; ++k) { /* match each point */
|
|
int pos; /* absolute coords of (transformed) pattern element */
|
|
int att = pattern->patn[k].att; /* what we are looking for */
|
|
|
|
/* Work out the position on the board of this pattern element. */
|
|
|
|
/* transform pattern real coordinate... */
|
|
pos = AFFINE_TRANSFORM(pattern->patn[k].offset, ll, anchor);
|
|
|
|
ASSERT_ON_BOARD1(pos);
|
|
|
|
/* ...and check that board[pos] matches (see above). */
|
|
if ((board[pos] & and_mask[color-1][att]) != val_mask[color-1][att])
|
|
goto match_failed;
|
|
|
|
if (goal != NULL && board[pos] != EMPTY && goal[pos])
|
|
found_goal = 1;
|
|
|
|
/* Check out the class_X, class_O, class_x, class_o
|
|
* attributes - see patterns.db and above.
|
|
*/
|
|
if ((pattern->class
|
|
& class_mask[dragon[pos].status][board[pos]]) != 0)
|
|
goto match_failed;
|
|
|
|
} /* loop over elements */
|
|
|
|
|
|
#if GRID_OPT == 2
|
|
/* Make sure the grid optimisation wouldn't have
|
|
rejected this pattern */
|
|
ASSERT2((merged_val & pattern->and_mask[ll])
|
|
== pattern->val_mask[ll], m, n);
|
|
#endif /* we don't trust the grid optimisation */
|
|
|
|
|
|
/* Make it here ==> We have matched all the elements to the board. */
|
|
if ((goal != NULL) && !found_goal)
|
|
goto match_failed;
|
|
|
|
#if PROFILE_PATTERNS
|
|
pattern->hits++;
|
|
nodes_before = stats.nodes;
|
|
#endif
|
|
|
|
/* A match! - Call back to the invoker to let it know. */
|
|
callback(anchor, color, pattern, ll, callback_data);
|
|
|
|
#if PROFILE_PATTERNS
|
|
pattern->reading_nodes += stats.nodes - nodes_before;
|
|
#endif
|
|
|
|
/* We jump to here as soon as we discover a pattern has failed. */
|
|
match_failed:
|
|
DEBUG(DEBUG_MATCHER,
|
|
"end of pattern '%s', rotation %d at %1m\n---\n",
|
|
pattern->name, ll, anchor);
|
|
|
|
} while (++ll < end_transformation); /* ll loop over symmetries */
|
|
} /* if not rejected by maxwt */
|
|
} while ((++pattern)->patn); /* loop over patterns */
|
|
}
|
|
|
|
|
|
/*
|
|
* Scan the board to get patterns anchored by anchor from color
|
|
* point of view.
|
|
* the board must be prepared by dfa_prepare_for_match(color) !
|
|
*/
|
|
static void
|
|
matchpat_loop(matchpat_callback_fn_ptr callback, int color, int anchor,
|
|
struct pattern_db *pdb, void *callback_data,
|
|
signed char goal[BOARDMAX], int anchor_in_goal)
|
|
{
|
|
int pos;
|
|
|
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
|
if (board[pos] == anchor && (!anchor_in_goal || goal[pos] != 0))
|
|
do_matchpat(pos, callback, color, pdb->patterns,
|
|
callback_data, goal);
|
|
}
|
|
}
|
|
|
|
|
|
/**************************************************************************/
|
|
/* DFA matcher: */
|
|
/**************************************************************************/
|
|
|
|
/* Set this to show the dfa board in action */
|
|
/* #define DFA_TRACE 1 */
|
|
|
|
/* Data. */
|
|
static int dfa_board_size = -1;
|
|
static int dfa_p[DFA_BASE * DFA_BASE];
|
|
|
|
/* This is used by the EXPECTED_COLOR macro. */
|
|
static const int convert[3][4] = {
|
|
{-1, -1, -1, -1}, /* not used */
|
|
{EMPTY, WHITE, BLACK, OUT_BOARD}, /* WHITE */
|
|
{EMPTY, BLACK, WHITE, OUT_BOARD} /* BLACK */
|
|
};
|
|
#define EXPECTED_COLOR(player_c, position_c) \
|
|
(convert[player_c][position_c])
|
|
|
|
/* Forward declarations. */
|
|
static void dfa_prepare_for_match(int color);
|
|
static int scan_for_patterns(dfa_rt_t *pdfa, int l, int *dfa_pos,
|
|
int *pat_list);
|
|
static void do_dfa_matchpat(dfa_rt_t *pdfa,
|
|
int anchor, matchpat_callback_fn_ptr callback,
|
|
int color, struct pattern *database,
|
|
void *callback_data, signed char goal[BOARDMAX],
|
|
int anchor_in_goal);
|
|
static void check_pattern_light(int anchor,
|
|
matchpat_callback_fn_ptr callback,
|
|
int color, struct pattern *pattern, int ll,
|
|
void *callback_data,
|
|
signed char goal[BOARDMAX],
|
|
int anchor_in_goal);
|
|
static void dfa_matchpat_loop(matchpat_callback_fn_ptr callback,
|
|
int color, int anchor,
|
|
struct pattern_db *pdb, void *callback_data,
|
|
signed char goal[BOARDMAX], int anchor_in_goal);
|
|
|
|
|
|
/***********************************************************************/
|
|
|
|
|
|
|
|
/* prepare the dfa board (gnugo initialization) */
|
|
void
|
|
dfa_match_init(void)
|
|
{
|
|
build_spiral_order();
|
|
|
|
if (owl_vital_apat_db.pdfa != NULL)
|
|
DEBUG(DEBUG_MATCHER, "owl_vital_apat --> using dfa\n");
|
|
if (owl_attackpat_db.pdfa != NULL)
|
|
DEBUG(DEBUG_MATCHER, "owl_attackpat --> using dfa\n");
|
|
if (owl_defendpat_db.pdfa != NULL)
|
|
DEBUG(DEBUG_MATCHER, "owl_defendpat --> using dfa\n");
|
|
if (pat_db.pdfa != NULL)
|
|
DEBUG(DEBUG_MATCHER, "pat --> using dfa\n");
|
|
if (attpat_db.pdfa != NULL)
|
|
DEBUG(DEBUG_MATCHER, "attpat --> using dfa\n");
|
|
if (defpat_db.pdfa != NULL)
|
|
DEBUG(DEBUG_MATCHER, "defpat --> using dfa\n");
|
|
if (endpat_db.pdfa != NULL)
|
|
DEBUG(DEBUG_MATCHER, "endpat --> using dfa\n");
|
|
if (conn_db.pdfa != NULL)
|
|
DEBUG(DEBUG_MATCHER, "conn --> using dfa\n");
|
|
if (influencepat_db.pdfa != NULL)
|
|
DEBUG(DEBUG_MATCHER, "influencepat --> using dfa\n");
|
|
if (barrierspat_db.pdfa != NULL)
|
|
DEBUG(DEBUG_MATCHER, "barrierspat --> using dfa\n");
|
|
if (fusekipat_db.pdfa != NULL)
|
|
DEBUG(DEBUG_MATCHER, "barrierspat --> using dfa\n");
|
|
|
|
/* force out_board initialization */
|
|
dfa_board_size = -1;
|
|
}
|
|
|
|
/*
|
|
* copy the board on a private board with adapted colors
|
|
* and adapted size
|
|
*/
|
|
static void
|
|
dfa_prepare_for_match(int color)
|
|
{
|
|
int i, j;
|
|
int pos;
|
|
|
|
if (dfa_board_size != board_size) {
|
|
dfa_board_size = board_size;
|
|
/* clean up the board */
|
|
for (pos = 0; pos < DFA_BASE * DFA_BASE; pos++)
|
|
dfa_p[pos] = OUT_BOARD;
|
|
}
|
|
|
|
/* copy the board */
|
|
for (i = 0; i < dfa_board_size; i++)
|
|
for (j = 0; j < dfa_board_size; j++)
|
|
dfa_p[DFA_POS(i, j)] = EXPECTED_COLOR(color, BOARD(i, j));
|
|
|
|
prepare_for_match(color);
|
|
}
|
|
|
|
#if 0
|
|
/* Debug function. */
|
|
static void
|
|
dump_dfa_board(int m, int n)
|
|
{
|
|
int i, j;
|
|
|
|
for (i = 0; i < dfa_board_size; i++) {
|
|
for (j = 0; j < dfa_board_size; j++) {
|
|
if (i != m || j != n)
|
|
fprintf(stderr, "%1d", dfa_p[DFA_POS(i, j)]);
|
|
else
|
|
fprintf(stderr, "*");
|
|
}
|
|
|
|
fprintf(stderr, "\n");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
* Scan the board with a DFA to get all patterns matching at
|
|
* `dfa_pos' with transformation l. Store patterns indexes
|
|
* `pat_list'. Return the number of patterns found.
|
|
*/
|
|
static int
|
|
scan_for_patterns(dfa_rt_t *pdfa, int l, int *dfa_pos, int *pat_list)
|
|
{
|
|
int delta;
|
|
int state = 1; /* initial state */
|
|
int row = 0; /* initial row */
|
|
int id = 0; /* position in id_list */
|
|
|
|
do {
|
|
/* collect patterns indexes */
|
|
int att = pdfa->states[state].att;
|
|
while (att != 0) {
|
|
pat_list[id] = pdfa->indexes[att].val;
|
|
id++;
|
|
att = pdfa->indexes[att].next;
|
|
}
|
|
|
|
/* go to next state */
|
|
delta = pdfa->states[state].next[dfa_pos[spiral[row][l]]];
|
|
state += delta;
|
|
row++;
|
|
} while (delta != 0); /* while not on error state */
|
|
|
|
return id;
|
|
}
|
|
|
|
|
|
/* Perform pattern matching with DFA filtering. */
|
|
static void
|
|
do_dfa_matchpat(dfa_rt_t *pdfa,
|
|
int anchor, matchpat_callback_fn_ptr callback,
|
|
int color, struct pattern *database,
|
|
void *callback_data, signed char goal[BOARDMAX],
|
|
int anchor_in_goal)
|
|
{
|
|
int k;
|
|
int ll; /* Iterate over transformations (rotations or reflections) */
|
|
int patterns[DFA_MAX_MATCHED + 8];
|
|
int num_matched = 0;
|
|
int *dfa_pos = dfa_p + DFA_POS(I(anchor), J(anchor));
|
|
|
|
/* Basic sanity checks. */
|
|
ASSERT_ON_BOARD1(anchor);
|
|
|
|
/* One scan by transformation */
|
|
for (ll = 0; ll < 8; ll++) {
|
|
num_matched += scan_for_patterns(pdfa, ll, dfa_pos,
|
|
patterns + num_matched);
|
|
patterns[num_matched++] = -1;
|
|
}
|
|
|
|
ASSERT1(num_matched <= DFA_MAX_MATCHED + 8, anchor);
|
|
|
|
/* Constraints and other tests. */
|
|
for (ll = 0, k = 0; ll < 8; k++) {
|
|
int matched;
|
|
|
|
if (patterns[k] == -1) {
|
|
ll++;
|
|
continue;
|
|
}
|
|
|
|
matched = patterns[k];
|
|
|
|
#if PROFILE_PATTERNS
|
|
database[matched].dfa_hits++;
|
|
#endif
|
|
|
|
check_pattern_light(anchor, callback, color, database + matched,
|
|
ll, callback_data, goal, anchor_in_goal);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Do the pattern matching for a given pattern and a given
|
|
* transformation ll.
|
|
* (does not recompute what dfa filtering has already done)
|
|
*/
|
|
|
|
static void
|
|
check_pattern_light(int anchor, matchpat_callback_fn_ptr callback, int color,
|
|
struct pattern *pattern, int ll, void *callback_data,
|
|
signed char goal[BOARDMAX], int anchor_in_goal)
|
|
{
|
|
int k; /* Iterate over elements of pattern */
|
|
int found_goal = 0;
|
|
|
|
#if PROFILE_PATTERNS
|
|
int nodes_before;
|
|
#endif
|
|
|
|
if (0)
|
|
gprintf("check_pattern_light @ %1m rot:%d pattern: %s\n",
|
|
anchor, ll, pattern->name);
|
|
|
|
/* Throw out duplicating orientations of symmetric patterns. */
|
|
if (pattern->trfno == 5) {
|
|
if (ll < 2 || ll >= 6)
|
|
return;
|
|
}
|
|
else {
|
|
if (ll >= pattern->trfno)
|
|
return;
|
|
}
|
|
|
|
|
|
/* Now iterate over the elements of the pattern. */
|
|
for (k = 0; k < pattern->patlen; k++) {
|
|
/* match each point */
|
|
int pos; /* absolute (board) co-ords of
|
|
(transformed) pattern element */
|
|
|
|
/* transform pattern real coordinate... */
|
|
pos = AFFINE_TRANSFORM(pattern->patn[k].offset, ll, anchor);
|
|
ASSERT_ON_BOARD1(pos);
|
|
|
|
if (!anchor_in_goal) {
|
|
/* goal check */
|
|
if (goal != NULL && board[pos] != EMPTY && goal[pos])
|
|
found_goal = 1;
|
|
}
|
|
|
|
/* class check */
|
|
ASSERT1(dragon[pos].status < 4, anchor);
|
|
if ((pattern->class & class_mask[dragon[pos].status][board[pos]]) != 0)
|
|
goto match_failed;
|
|
|
|
} /* loop over elements */
|
|
|
|
/* Make it here ==> We have matched all the elements to the board. */
|
|
if (!anchor_in_goal) {
|
|
if (goal != NULL && !found_goal)
|
|
goto match_failed;
|
|
}
|
|
|
|
#if PROFILE_PATTERNS
|
|
pattern->hits++;
|
|
nodes_before = stats.nodes;
|
|
#endif
|
|
|
|
/* A match! - Call back to the invoker to let it know. */
|
|
callback(anchor, color, pattern, ll, callback_data);
|
|
|
|
#if PROFILE_PATTERNS
|
|
pattern->reading_nodes += stats.nodes - nodes_before;
|
|
#endif
|
|
|
|
/* We jump to here as soon as we discover a pattern has failed. */
|
|
match_failed:
|
|
DEBUG(DEBUG_MATCHER, "end of pattern '%s', rotation %d at %1m\n---\n",
|
|
pattern->name, ll, anchor);
|
|
|
|
} /* check_pattern_light */
|
|
|
|
|
|
/*
|
|
* Scan the board to get patterns anchored by anchor from color
|
|
* point of view.
|
|
* the board must be prepared by dfa_prepare_for_match(color) !
|
|
*/
|
|
static void
|
|
dfa_matchpat_loop(matchpat_callback_fn_ptr callback, int color, int anchor,
|
|
struct pattern_db *pdb, void *callback_data,
|
|
signed char goal[BOARDMAX], int anchor_in_goal)
|
|
{
|
|
int pos;
|
|
|
|
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
|
|
if (board[pos] == anchor && (!anchor_in_goal || goal[pos] != 0))
|
|
do_dfa_matchpat(pdb->pdfa, pos, callback, color, pdb->patterns,
|
|
callback_data, goal, anchor_in_goal);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**************************************************************************/
|
|
/* Main functions: */
|
|
/**************************************************************************/
|
|
|
|
|
|
typedef void (*loop_fn_ptr_t)(matchpat_callback_fn_ptr callback,
|
|
int color, int anchor,
|
|
struct pattern_db *pdb, void *callback_data,
|
|
signed char goal[BOARDMAX], int anchor_in_goal);
|
|
|
|
typedef void (*prepare_fn_ptr_t)(int color);
|
|
|
|
/* same as the old matchpat but for all the board with
|
|
* preparation.
|
|
*
|
|
* 4 possible values for color argument:
|
|
* WHITE or BLACK: matchpat is called from this color point of view.
|
|
* ANCHOR_COLOR : matchpat is called from the anchor's point of view.
|
|
* ANCHOR_OTHER : matchpat is called from the opposite color of the
|
|
* anchor's point of view.
|
|
*/
|
|
|
|
void
|
|
matchpat(matchpat_callback_fn_ptr callback, int color,
|
|
struct pattern_db *pdb, void *callback_data,
|
|
signed char goal[BOARDMAX])
|
|
{
|
|
matchpat_goal_anchor(callback, color, pdb, callback_data, goal,
|
|
pdb->fixed_anchor);
|
|
}
|
|
|
|
void
|
|
matchpat_goal_anchor(matchpat_callback_fn_ptr callback, int color,
|
|
struct pattern_db *pdb, void *callback_data,
|
|
signed char goal[BOARDMAX], int anchor_in_goal)
|
|
{
|
|
loop_fn_ptr_t loop = matchpat_loop;
|
|
prepare_fn_ptr_t prepare = prepare_for_match;
|
|
|
|
/* check board size */
|
|
if (pdb->fixed_for_size != board_size) {
|
|
fixup_patterns_for_board_size(pdb->patterns);
|
|
pdb->fixed_for_size = board_size;
|
|
}
|
|
|
|
/* select pattern matching strategy */
|
|
if (pdb->pdfa != NULL) {
|
|
loop = dfa_matchpat_loop;
|
|
prepare = dfa_prepare_for_match;
|
|
}
|
|
|
|
/* select strategy */
|
|
switch (color) {
|
|
case ANCHOR_COLOR:
|
|
{ /* match pattern for the color of their anchor */
|
|
prepare(WHITE);
|
|
loop(callback, WHITE, WHITE, pdb, callback_data, goal, anchor_in_goal);
|
|
prepare(BLACK);
|
|
loop(callback, BLACK, BLACK, pdb, callback_data, goal, anchor_in_goal);
|
|
}
|
|
break;
|
|
case ANCHOR_OTHER:
|
|
{ /* match pattern for the opposite color of their anchor */
|
|
prepare(WHITE);
|
|
loop(callback, WHITE, BLACK, pdb, callback_data, goal, anchor_in_goal);
|
|
prepare(BLACK);
|
|
loop(callback, BLACK, WHITE, pdb, callback_data, goal, anchor_in_goal);
|
|
}
|
|
break;
|
|
default:
|
|
{ /* match all patterns for color */
|
|
prepare(color);
|
|
loop(callback, color, WHITE, pdb, callback_data, goal, anchor_in_goal);
|
|
loop(callback, color, BLACK, pdb, callback_data, goal, anchor_in_goal);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
fullboard_transform(int pos, int trans)
|
|
{
|
|
int dx = I(pos) - (board_size-1)/2;
|
|
int dy = J(pos) - (board_size-1)/2;
|
|
int x, y;
|
|
gg_assert(POS((board_size-1)/2, (board_size-1)/2) + DELTA(dx, dy) == pos);
|
|
TRANSFORM2(dx, dy, &x, &y, trans);
|
|
return POS(x + (board_size-1)/2, y + (board_size-1)/2);
|
|
}
|
|
|
|
/* A dedicated matcher which can only do fullboard matching on
|
|
* odd-sized boards, optimized for fuseki patterns.
|
|
*/
|
|
void
|
|
fullboard_matchpat(fullboard_matchpat_callback_fn_ptr callback, int color,
|
|
struct fullboard_pattern *pattern)
|
|
{
|
|
int ll; /* Iterate over transformations (rotations or reflections) */
|
|
/* We transform around the center point. */
|
|
int number_of_stones_on_board = stones_on_board(BLACK | WHITE);
|
|
static int color_map[gg_max(WHITE, BLACK) + 1];
|
|
/* One hash value for each rotation/reflection: */
|
|
Hash_data current_board_hash[8];
|
|
|
|
/* Basic sanity check. */
|
|
gg_assert(color != EMPTY);
|
|
gg_assert(board_size % 2 == 1);
|
|
|
|
color_map[EMPTY] = EMPTY;
|
|
if (color == WHITE) {
|
|
color_map[WHITE] = WHITE;
|
|
color_map[BLACK] = BLACK;
|
|
}
|
|
else {
|
|
color_map[WHITE] = BLACK;
|
|
color_map[BLACK] = WHITE;
|
|
}
|
|
|
|
/* Get hash data of all rotations/reflections of current board position. */
|
|
for (ll = 0; ll < 8; ll++) {
|
|
Intersection p[BOARDSIZE];
|
|
int pos;
|
|
for (pos = 0; pos < BOARDSIZE; pos++)
|
|
if (ON_BOARD(pos))
|
|
p[pos] = color_map[board[fullboard_transform(pos, ll)]];
|
|
else
|
|
p[pos] = GRAY;
|
|
|
|
if (ON_BOARD(board_ko_pos))
|
|
hashdata_recalc(¤t_board_hash[ll], p,
|
|
fullboard_transform(board_ko_pos, ll));
|
|
else
|
|
hashdata_recalc(¤t_board_hash[ll], p, NO_MOVE);
|
|
}
|
|
|
|
/* Try each pattern - NULL pattern name marks end of list. */
|
|
for (; pattern->name; pattern++) {
|
|
if (pattern->number_of_stones != number_of_stones_on_board)
|
|
continue;
|
|
/* Try each orientation transformation. */
|
|
for (ll = 0; ll < 8; ll++)
|
|
if (hashdata_is_equal(current_board_hash[ll], pattern->fullboard_hash)) {
|
|
/* A match! - Call back to the invoker to let it know. */
|
|
int pos = AFFINE_TRANSFORM(pattern->move_offset, ll,
|
|
POS((board_size-1)/2, (board_size-1)/2));
|
|
callback(pos, pattern, ll);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**************************************************************************/
|
|
/* Corner matcher */
|
|
/**************************************************************************/
|
|
|
|
/* These arrays specify anchor corner for each transformation. They _must_
|
|
* be in line with transformation2[][] array in patterns/transform.c.
|
|
*/
|
|
static const int corner_x[8] = {0, 0, 1, 1, 1, 1, 0, 0};
|
|
static const int corner_y[8] = {0, 1, 1, 0, 1, 0, 0, 1};
|
|
|
|
/* The number of stones in "corner area" for each board position. For example,
|
|
* corner area for position E3 when anchoring at A1 corner, looks like this:
|
|
*
|
|
* |........ In general, NUM_STONES(pos) is the number of stones
|
|
* |........ which are closer to the corner (stone at pos, if any,
|
|
* 3 |#####... counts too) than pos. Note, that say G2 is not closer
|
|
* |#####... to the corner than E3, the reverse isn't true either.
|
|
* 1 |#####... Their distances are "incomparable" since E < G but
|
|
* +-------- 3 > 2.
|
|
* A E
|
|
*
|
|
* Note that we need these values in at most MAX_BOARD x MAX_BOARD array.
|
|
* However, it may be anchored at any corner of the board, so if the board is
|
|
* small, we may calculate NUM_STONES() at negative coordinates.
|
|
*/
|
|
static int num_stones[2*BOARDMAX];
|
|
#define NUM_STONES(pos) num_stones[(pos) + BOARDMAX]
|
|
|
|
/* Stone locations are stored in this array. They might be needed by callback
|
|
* function.
|
|
*/
|
|
static int pattern_stones[BOARDMAX];
|
|
|
|
|
|
/* Recursively performs corner matching. This function checks whether
|
|
* `num_variation' variations pointed by `variation' parameter match.
|
|
* If any of them does, the function calls itself recursively. If any
|
|
* pattern corresponding to those variations matches, it notifies
|
|
* callback function.
|
|
*/
|
|
static void
|
|
do_corner_matchpat(int num_variations, struct corner_variation *variation,
|
|
int match_color, corner_matchpat_callback_fn_ptr callback,
|
|
int callback_color, int trans, int anchor, int stones)
|
|
{
|
|
for (; --num_variations >= 0; variation++) {
|
|
int move = AFFINE_TRANSFORM(variation->move_offset, trans, anchor);
|
|
int color_check = match_color ^ variation->xor_att;
|
|
struct corner_pattern *pattern = variation->pattern;
|
|
|
|
if (pattern && color_check == callback_color) {
|
|
int second_corner
|
|
= AFFINE_TRANSFORM(pattern->second_corner_offset, trans, anchor);
|
|
|
|
if (NUM_STONES(second_corner) == stones
|
|
&& (!pattern->symmetric || trans < 4)) {
|
|
/* We have found a matching pattern. */
|
|
ASSERT1(board[move] == EMPTY, move);
|
|
|
|
callback(move, callback_color, pattern, trans, pattern_stones, stones);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (variation->num_variations
|
|
&& NUM_STONES(move) == variation->num_stones
|
|
&& board[move] == color_check) {
|
|
/* A matching variation. */
|
|
pattern_stones[stones] = move;
|
|
do_corner_matchpat(variation->num_variations, variation->variations,
|
|
match_color, callback, callback_color,
|
|
trans, anchor, stones + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Perform corner matching at all four corners and both possible
|
|
* transformations at each corner. `callback' is notified if any
|
|
* matching pattern is found.
|
|
*/
|
|
void
|
|
corner_matchpat(corner_matchpat_callback_fn_ptr callback, int color,
|
|
struct corner_db *database)
|
|
{
|
|
int k;
|
|
|
|
for (k = 0; k < 8; k++) {
|
|
int anchor = POS(corner_x[k] * (board_size - 1),
|
|
corner_y[k] * (board_size - 1));
|
|
int i;
|
|
int j;
|
|
int dx = TRANSFORM(OFFSET(1, 0), k);
|
|
int dy = TRANSFORM(OFFSET(0, 1), k);
|
|
int pos;
|
|
struct corner_variation *variation = database->top_variations;
|
|
|
|
/* Fill in the NUM_STONES() array. We use `max_width' and `max_height'
|
|
* fields of database structure to stop working as early as possible.
|
|
*/
|
|
NUM_STONES(anchor) = IS_STONE(board[anchor]);
|
|
|
|
pos = anchor;
|
|
for (i = 1; i < database->max_height; i++) {
|
|
pos += dx;
|
|
if (!ON_BOARD(pos)) {
|
|
do {
|
|
NUM_STONES(pos) = BOARDMAX;
|
|
pos += dx;
|
|
} while (++i < database->max_height);
|
|
|
|
break;
|
|
}
|
|
|
|
NUM_STONES(pos) = NUM_STONES(pos - dx) + IS_STONE(board[pos]);
|
|
}
|
|
|
|
pos = anchor;
|
|
for (j = 1; j < database->max_width; j++) {
|
|
pos += dy;
|
|
if (!ON_BOARD(pos)) {
|
|
do {
|
|
NUM_STONES(pos) = BOARDMAX;
|
|
pos += dy;
|
|
} while (++j < database->max_width);
|
|
|
|
break;
|
|
}
|
|
|
|
NUM_STONES(pos) = NUM_STONES(pos - dy) + IS_STONE(board[pos]);
|
|
}
|
|
|
|
for (i = 1; i < database->max_height; i++) {
|
|
pos = anchor + i * dy;
|
|
for (j = 1; j < database->max_width; j++) {
|
|
pos += dx;
|
|
NUM_STONES(pos) = NUM_STONES(pos - dx) + NUM_STONES(pos - dy)
|
|
- NUM_STONES(pos - dx - dy);
|
|
if (ON_BOARD1(pos) && IS_STONE(board[pos]))
|
|
NUM_STONES(pos)++;
|
|
}
|
|
}
|
|
|
|
/* Try to match top variations. If any of them matches, we call
|
|
* do_corner_matchpat() to recurse that variation's tree.
|
|
*/
|
|
for (i = 0; i < database->num_top_variations; i++) {
|
|
int move = AFFINE_TRANSFORM(variation->move_offset, k, anchor);
|
|
|
|
if (NUM_STONES(move) == 1 && IS_STONE(board[move])) {
|
|
pattern_stones[0] = move;
|
|
do_corner_matchpat(variation->num_variations, variation->variations,
|
|
board[move], callback, color, k, anchor, 1);
|
|
}
|
|
|
|
variation++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Local Variables:
|
|
* tab-width: 8
|
|
* c-basic-offset: 2
|
|
* End:
|
|
*/
|