Initial commit of GNU Go v3.8.
[sgk-go] / engine / shapes.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 "liberty.h"
#include "patterns.h"
/* Maximum number of dragons considered by a, B, C, and d class patterns. */
#define MAX_DRAGONS_PER_PATTERN 5
#define MAX_STRINGS_PER_PATTERN 5
/* Values of joseki patterns. */
#define U_VALUE 40.0
#define J_VALUE 35.0
#define j_VALUE 24.0
#define t_VALUE 16.0
/* Take care of joseki patterns. */
static void
handle_joseki_patterns(struct pattern_attribute *attributes,
unsigned int class, int move,
int my_dragons[MAX_DRAGONS_PER_PATTERN],
int my_ndragons,
int your_dragons[MAX_DRAGONS_PER_PATTERN],
int your_ndragons)
{
struct pattern_attribute *attribute;
/* Pattern class J, joseki standard move. Add expand territory and
* moyo, and require the value at least J_value.
*/
if (class & CLASS_J) {
TRACE("...joseki standard move\n");
add_expand_territory_move(move);
TRACE("...expands territory\n");
add_expand_moyo_move(move);
TRACE("...expands moyo\n");
set_minimum_move_value(move, J_VALUE);
TRACE("... minimum move value %f\n", J_VALUE);
}
/* Class `j' and `t' patterns are treated similarly. */
if (class & (CLASS_j | CLASS_t)) {
float min_value;
float shape_value = 0.0;
if (class & CLASS_j) {
min_value = j_VALUE;
TRACE("...less urgent joseki move\n");
add_expand_territory_move(move);
TRACE("...expands territory\n");
add_expand_moyo_move(move);
TRACE("...expands moyo\n");
}
else {
min_value = t_VALUE;
TRACE("...minor joseki move\n");
}
/* Board size modification. */
min_value *= board_size / 19.0;
for (attribute = attributes; attribute->type != LAST_ATTRIBUTE;
attribute++) {
if (attribute->type == SHAPE) {
shape_value = attribute->value;
min_value *= (1 + 0.01 * shape_value);
break;
}
}
if ((board_size >= 17) && (class & CLASS_F)) {
/* Otherwise, `j' and `t' patterns not of CLASS_F would get
* preferred in value_move_reasons().
*/
min_value *= 1.005;
set_maximum_move_value(move, min_value);
scale_randomness(move, 5.0);
TRACE("...move value %f (shape %f)\n", min_value, shape_value);
}
else
TRACE("...minimum move value %f (shape %f)\n", min_value, shape_value);
set_minimum_move_value(move, min_value);
}
/* Pattern class U, very urgent joseki move. Add strategical defense
* and attack, plus a shape bonus of 15 and a minimum value of 40.
*/
if (class & CLASS_U) {
int k;
TRACE("...joseki urgent move\n");
for (k = 0; k < my_ndragons; k++) {
add_strategical_defense_move(move, my_dragons[k]);
TRACE("...strategical defense of %1m\n", my_dragons[k]);
}
for (k = 0; k < your_ndragons; k++) {
add_strategical_attack_move(move, your_dragons[k]);
TRACE("...strategical attack on %1m\n", your_dragons[k]);
}
add_shape_value(move, 15);
TRACE("...shape value 15\n");
set_minimum_move_value(move, U_VALUE);
TRACE("...(min) move value %f\n", U_VALUE);
}
/* Pattern class T, joseki trick move. For the moment we never play
* these.
*/
if (class & CLASS_T) {
TRACE("...joseki trick move\n");
add_antisuji_move(move);
TRACE("...antisuji\n");
}
for (attribute = attributes; attribute->type != LAST_ATTRIBUTE;
attribute++) {
switch (attribute->type) {
case MIN_VALUE:
set_minimum_move_value(move, attribute->value);
TRACE("...(min) move value %f\n", attribute->value);
break;
case MAX_VALUE:
set_maximum_move_value(move, attribute->value);
TRACE("...max move value %f\n", attribute->value);
break;
case MIN_TERRITORY:
set_minimum_territorial_value(move, attribute->value);
TRACE("...(min) territorial value %f\n", attribute->value);
break;
case MAX_TERRITORY:
set_maximum_territorial_value(move, attribute->value);
TRACE("...max territorial value %f\n", attribute->value);
break;
case SHAPE:
/* For class `j' and `t' patterns shape value has been counted
* already.
*/
if (!(class & (CLASS_j | CLASS_t))) {
add_shape_value(move, attribute->value);
TRACE("...shape value %f\n", attribute->value);
}
break;
case FOLLOWUP:
add_followup_value(move, attribute->value);
TRACE("...followup value %f\n", attribute->value);
break;
case REVERSE_FOLLOWUP:
add_reverse_followup_value(move, attribute->value);
TRACE("...reverse followup value %f\n", attribute->value);
break;
default:
/* Must not happen. */
gg_assert(0);
}
}
}
/*
* This callback is invoked for each matched pattern.
*/
static void
shapes_callback(int anchor, int color, struct pattern *pattern, int ll,
void *data)
{
int other = OTHER_COLOR(color);
int k, l;
int move;
/* For restricted search, the pattern must intersect the search area */
/* Dragons of our color. */
int my_dragons[MAX_DRAGONS_PER_PATTERN];
int my_ndragons = 0;
/* Dragons of other color. */
int your_dragons[MAX_DRAGONS_PER_PATTERN];
int your_ndragons = 0;
/* Strings of our color. */
int my_strings[MAX_STRINGS_PER_PATTERN];
int my_nstrings = 0;
/* Strings of other color. */
int your_strings[MAX_STRINGS_PER_PATTERN];
int your_nstrings = 0;
/* Make a local copy of the classification that we may modify. */
unsigned int class = pattern->class;
/* Don't accept fuseki marked patterns while scoring. */
if (doing_scoring && (class & CLASS_F))
return;
/* Don't need auxiliary data in this callback. */
UNUSED(data);
/* Pick up the location of the move */
move = AFFINE_TRANSFORM(pattern->move_offset, ll, anchor);
/* For some classes of patterns we need to find all dragons present
* in the pattern.
*/
if ((class & (CLASS_B | CLASS_C | CLASS_c | CLASS_a | CLASS_d | CLASS_O
| CLASS_J | CLASS_j | CLASS_U | CLASS_T | CLASS_t)) != 0) {
/* Match each point. */
for (k = 0; k < pattern->patlen; ++k) {
int pos; /* absolute (board) co-ord of (transformed) pattern element */
int origin; /* dragon origin */
/* all the following stuff (currently) applies only at occupied cells */
if (pattern->patn[k].att == ATT_dot)
continue;
/* transform pattern real coordinate */
pos = AFFINE_TRANSFORM(pattern->patn[k].offset, ll, anchor);
/* Already, matchpat rejects O patterns containing a friendly stone with
* DEAD or CRITICAL matcher_status. If the stone is tactically
* CRITICAL it still could have matcher_status ALIVE since it might
* be amalgamated into a live dragon. In this case we want to reject the
* pattern if (move) does not rescue it. This is most easily tested
* here within shapes_callback(), since the value of (move) is not
* known by matchpat().
*/
if ((class & CLASS_O)
&& board[pos] == color
&& worm[pos].attack_points[0] != 0
&& !does_defend(move, pos))
return;
origin = dragon[pos].origin;
if (board[pos] == color && my_ndragons < MAX_DRAGONS_PER_PATTERN) {
for (l = 0; l < my_ndragons; l++) {
if (my_dragons[l] == origin)
break;
}
if (l == my_ndragons) {
/* We found another dragon of our color. Check that it (or
* rather the underlying worm) cannot be tactically
* captured before adding it to the list of my_dragons.
*/
if (worm[pos].attack_codes[0] == 0
|| does_defend(move, pos)) {
/* Ok, add the dragon to the list. */
my_dragons[l] = origin;
my_ndragons++;
}
}
}
if (board[pos] == other && your_ndragons < MAX_DRAGONS_PER_PATTERN) {
for (l = 0; l < your_ndragons; l++) {
if (your_dragons[l] == origin)
break;
}
if (l == your_ndragons) {
/* We found another opponent dragon, add it to the list. */
your_dragons[l] = origin;
your_ndragons++;
}
}
if (pattern->patn[k].att == ATT_O || pattern->patn[k].att == ATT_X) {
origin = find_origin(pos);
if (board[pos] == color && my_nstrings < MAX_STRINGS_PER_PATTERN) {
for (l = 0; l < my_nstrings; l++) {
if (my_strings[l] == origin)
break;
}
if (l == my_nstrings) {
/* We found another string of our color. Check that it
* cannot be tactically captured before adding it to the
* list of my_strings.
*/
if (worm[pos].attack_codes[0] == 0
|| does_defend(move, pos)) {
/* Ok, add the string to the list. */
my_strings[l] = origin;
my_nstrings++;
}
}
}
if (board[pos] == other && your_nstrings < MAX_STRINGS_PER_PATTERN) {
for (l = 0; l < your_nstrings; l++) {
if (your_strings[l] == origin)
break;
}
if (l == your_nstrings) {
/* We found another opponent string, add it to the list. */
your_strings[l] = origin;
your_nstrings++;
}
}
}
} /* loop over elements */
} /* if we need to loop over the elements */
/* Nothing to connect. Remove C class bit. */
if (my_nstrings < 2)
class &= ~CLASS_C;
/* Nothing to cut. Remove B class bit. */
if (your_nstrings < 2)
class &= ~CLASS_B;
/*
* If this pattern can't produce any effect (e.g. if it was a B or C
* pattern with only one dragon of the appropriate color), don't
* do any expensive checking but return immediately.
* If it only has some move_values, these will be ignored.
*/
if (!pattern->helper
&& !allpats
&& !(pattern->autohelper_flag & HAVE_ACTION)
&& !(class & CLASS_MOVE_REASONS)
&& pattern->attributes->type == LAST_ATTRIBUTE)
return;
/* For sacrifice patterns, the survival of the stone to be played is
* not checked (but it still needs to be legal). Otherwise we
* discard moves which can be captured.
*/
if (!(class & CLASS_s)) {
/* Don't allow ko unsafety. */
if (safe_move(move, color) != WIN) {
if (0)
TRACE(" move at %1m wasn't safe, discarded\n", move);
return;
}
}
else {
/* Allow illegal ko captures at this stage. */
if (!is_ko(move, color, NULL) && !is_legal(move, color)) {
if (0)
TRACE(" move at %1m wasn't legal, discarded\n", move);
return;
}
}
/* For class n patterns, the pattern is contingent on an opponent
* move at * not being captured.
*/
if (class & CLASS_n) {
/* Allow ko unsafety. */
if (safe_move(move, other) == 0) {
if (0)
TRACE(" opponent can't play safely at %1m, move discarded\n", move);
return;
}
}
/* If the pattern has a constraint, call the autohelper to see
* if the pattern must be rejected.
*/
if (pattern->autohelper_flag & HAVE_CONSTRAINT) {
if (!pattern->autohelper(ll, move, color, 0))
return;
}
/* Ask helper for acceptance of pattern. */
if (pattern->helper) {
/* ask helper function to consider the move */
int accepted;
DEBUG(DEBUG_HELPER, " asking helper to consider '%s'+%d at %1m\n",
pattern->name, ll, move);
accepted = pattern->helper(pattern, ll, move, color);
if (accepted) {
DEBUG(DEBUG_HELPER, "helper likes pattern '%s' at %1m\n",
pattern->name, move);
}
else {
DEBUG(DEBUG_HELPER, " helper does not like pattern '%s' at %1m\n",
pattern->name, move);
return; /* pattern matcher does not like it */
}
}
/* If using -a, want to see all matches even if not -v */
if (allpats || verbose) {
TRACE("pattern '%s'+%d matched at %1m\n", pattern->name, ll, move);
}
/* does the pattern have an action? */
if (pattern->autohelper_flag & HAVE_ACTION)
pattern->autohelper(ll, move, color, 1);
/* Pattern class B, try to cut all combinations of opponent strings. */
if (class & CLASS_B) {
for (k = 0; k < your_nstrings; k++)
for (l = k+1; l < your_nstrings; l++) {
if (string_connect(your_strings[k], your_strings[l], NULL)
&& !play_connect_n(color, 1, 1, move,
your_strings[k], your_strings[l])) {
add_cut_move(move, your_strings[k], your_strings[l]);
TRACE("...cuts strings %1m, %1m\n",
your_strings[k], your_strings[l]);
}
}
}
/* Pattern class C, try to connect all combinations of our strings. */
if (class & CLASS_C) {
for (k = 0; k < my_nstrings; k++)
for (l = k+1; l < my_nstrings; l++) {
if (disconnect(my_strings[k], my_strings[l], NULL)
&& !play_connect_n(color, 0, 1, move,
my_strings[k], my_strings[l])) {
add_connection_move(move, my_strings[k], my_strings[l]);
TRACE("...connects strings %1m, %1m\n",
my_strings[k], my_strings[l]);
}
}
}
/* Pattern class c, add strategical defense move reason for all our
* dragons and a small shape bonus.
*
* This is a preliminary effect of "weak connection" and may need to
* be revised.
*/
if (class & CLASS_c) {
for (k = 0; k < my_ndragons; k++) {
add_strategical_defense_move(move, my_dragons[k]);
TRACE("...strategical defense (weak connection) of %1m\n",
my_dragons[k]);
}
add_shape_value(move, 1);
TRACE("...shape value 1\n");
}
/* Pattern class b is obsolete in the pattern databases handled here. */
gg_assert(!(class & CLASS_b));
/* Pattern class e, expand to make territory. */
if (class & CLASS_e) {
add_expand_territory_move(move);
TRACE("...expands territory\n");
}
/* Pattern class E, expand to make moyo. */
if (class & CLASS_E) {
add_expand_moyo_move(move);
TRACE("...expands moyo\n");
}
/* Pattern class i, an invasion. */
if (class & CLASS_I) {
add_invasion_move(move);
TRACE("...is an invasion\n");
}
/* Pattern class a, strategical level attack on all opponent dragons. */
if (class & CLASS_a) {
for (k = 0; k < your_ndragons; k++) {
add_strategical_attack_move(move, your_dragons[k]);
TRACE("...strategical attack on %1m\n", your_dragons[k]);
}
}
/* Pattern class d, strategical level defense of all own dragons. */
if (class & CLASS_d) {
for (k = 0; k < my_ndragons; k++) {
add_strategical_defense_move(move, my_dragons[k]);
TRACE("...strategical defense of %1m\n", my_dragons[k]);
}
}
/* Pattern class W, worthwhile threat move. */
if (class & CLASS_W) {
TRACE("...worthwhile threat move\n");
add_worthwhile_threat_move(move);
}
handle_joseki_patterns(pattern->attributes, class, move,
my_dragons, my_ndragons, your_dragons, your_ndragons);
}
/* This callback is invoked for each matched pattern from joseki
* database. This function is just a copy of relevant parts of
* shapes_callback(). However, most of the common code resides in
* handle_joseki_patterns().
*/
static void
joseki_callback(int move, int color, struct corner_pattern *pattern,
int trans, int *stones, int num_stones)
{
int k, l;
int class = pattern->class;
/* Dragons of our color. */
int my_dragons[MAX_DRAGONS_PER_PATTERN];
int my_ndragons = 0;
/* Dragons of other color. */
int your_dragons[MAX_DRAGONS_PER_PATTERN];
int your_ndragons = 0;
/* For urgent joseki patterns we need to find all dragons present in the
* pattern since such patterns are assumed to have strategical effect on
* them.
*/
if (class & CLASS_U) {
/* Loop over all stones in the pattern. */
for (k = 0; k < num_stones; k++) {
int pos = stones[k];
int origin = dragon[pos].origin;
if (board[pos] == color && my_ndragons < MAX_DRAGONS_PER_PATTERN) {
for (l = 0; l < my_ndragons; l++) {
if (my_dragons[l] == origin)
break;
}
if (l == my_ndragons) {
/* We found another dragon of our color. Check that it (or
* rather the underlying worm) cannot be tactically
* captured before adding it to the list of my_dragons.
*/
if (worm[pos].attack_codes[0] == 0 || does_defend(move, pos)) {
/* Ok, add the dragon to the list. */
my_dragons[l] = origin;
my_ndragons++;
}
}
}
if (board[pos] != color && your_ndragons < MAX_DRAGONS_PER_PATTERN) {
for (l = 0; l < your_ndragons; l++) {
if (your_dragons[l] == origin)
break;
}
if (l == your_ndragons) {
/* We found another opponent dragon, add it to the list. */
your_dragons[l] = origin;
your_ndragons++;
}
}
}
}
/* For joseki patterns we don't check if the proposed move is safe or legal.
*/
/* If the pattern has a constraint, call the autohelper to see
* if the pattern must be rejected.
*/
if (pattern->autohelper_flag & HAVE_CONSTRAINT) {
if (!pattern->autohelper(trans, move, color, 0))
return;
}
/* If using -a, want to see all matches even if not -v. */
if (allpats || verbose)
TRACE("pattern '%s'+%d matched at %1m\n", pattern->name, trans, move);
/* Does the pattern have an action? */
if (pattern->autohelper_flag & HAVE_ACTION)
pattern->autohelper(trans, move, color, 1);
/* Pattern class N, antisuji move. */
if (class & CLASS_N) {
TRACE("...antisuji move\n");
add_antisuji_move(move);
}
handle_joseki_patterns(pattern->attributes, class, move,
my_dragons, my_ndragons, your_dragons, your_ndragons);
}
/*
* Match all patterns in patterns.db and patterns2.db on all positions.
*
* This function is one of the basic generators of move reasons, called
* by genmove().
*/
void
shapes(int color)
{
TRACE("\nPattern matcher is looking for move reasons for %s!\n",
color_to_string(color));
matchpat(shapes_callback, color, &pat_db, NULL, NULL);
/* Don't match joseki patterns while scoring. */
if (josekidb && !doing_scoring)
#if 1
corner_matchpat(joseki_callback, color, &joseki_db);
#else
matchpat(shapes_callback, color, &joseki_db, NULL, NULL);
#endif
if (!disable_fuseki && !doing_scoring)
matchpat(shapes_callback, color, &fusekipat_db, NULL, NULL);
}
/*
* Match all patterns in endgame.db on all positions.
*/
void
endgame_shapes(int color)
{
TRACE("\nEndgame pattern matcher is looking for move reasons for %s!\n",
color_to_string(color));
matchpat(shapes_callback, color, &endpat_db, NULL, NULL);
}
/*
* Local Variables:
* tab-width: 8
* c-basic-offset: 2
* End:
*/