Initial commit of GNU Go v3.8.
[sgk-go] / engine / genmove.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 "sgftree.h"
#include "gg_utils.h"
/* Return one if x doesn't equal position_number and 0 otherwise.
* After using this macro x will always have the value
* position_number.
*/
#define NEEDS_UPDATE(x) (x != position_number ? (x = position_number, 1) : 0)
/* Mark a limited search area. If limit_search != 1, genmove
* will only return moves within the area marked by the array
* search_mask.
*/
static int limit_search = 0;
static int search_mask[BOARDMAX];
static int do_genmove(int color, float pure_threat_value,
int allowed_moves[BOARDMAX], float *value, int *resign);
/* Position numbers for which various examinations were last made. */
static int worms_examined = -1;
static int initial_influence_examined = -1;
static int dragons_examined_without_owl = -1;
static int dragons_examined = -1;
static int initial_influence2_examined = -1;
static int dragons_refinedly_examined = -1;
static int revise_semeai(int color);
static int revise_thrashing_dragon(int color, float our_score,
float advantage);
static void break_mirror_go(int color);
static int find_mirror_move(int *move, int color);
static int should_resign(int color, float optimistic_score, int move);
static void compute_scores(int use_chinese_rules);
/* Reset some things in the engine.
*
* This prepares the hash table for the reading code for use. It
* should be called when we start examine a new position.
*/
void
reset_engine()
{
/* To improve the reproducability of games, we restart the random
* number generator with the same seed for each move. Thus we don't
* have to know how many previous moves have been played, nor
* actually play through them, in order to get the right random
* numbers.
*/
reuse_random_seed();
/* Initialize things for hashing of positions. */
reading_cache_clear();
hashdata_recalc(&board_hash, board, board_ko_pos);
worms_examined = -1;
initial_influence_examined = -1;
dragons_examined_without_owl = -1;
dragons_examined = -1;
initial_influence2_examined = -1;
dragons_refinedly_examined = -1;
/* Prepare our table of move reasons. */
clear_move_reasons();
clear_break_in_list();
/* Set up depth values (see comments there for details). */
set_depth_values(get_level(), 0);
/* Initialize arrays of moves which are meaningless due to
* static analysis of unconditional status.
*/
clear_unconditionally_meaningless_moves();
}
/*
* Examine the position and try to gather as much information as possible.
* This is used mainly for move generation, but could also be called
* for debugging purposes (decidestring, etc).
*
* The parameter how_much tells us how much of the work we have to do.
* For move generation we have to do it all. For debugging we can
* sometimes stop a little earlier.
*
* aftermath_play indicates we are in aftermath playout phase. It's only
* effect is to always use chinese rules for the score estimates.
*/
void
examine_position(int how_much, int aftermath_play)
{
int save_verbose = verbose;
purge_persistent_caches();
/* Don't print reading traces during make_worms and make_dragons unless
* the user really wants it (verbose == 3).
*/
if (verbose == 1 || verbose == 2)
--verbose;
if (NEEDS_UPDATE(worms_examined)) {
start_timer(0);
make_worms();
time_report(0, " make worms", NO_MOVE, 1.0);
}
if (how_much == EXAMINE_WORMS) {
verbose = save_verbose;
gg_assert(test_gray_border() < 0);
return;
}
if (stones_on_board(BLACK | WHITE) != 0) {
if (NEEDS_UPDATE(initial_influence_examined))
compute_worm_influence();
if (how_much == EXAMINE_INITIAL_INFLUENCE) {
verbose = save_verbose;
gg_assert(test_gray_border() < 0);
return;
}
if (how_much == EXAMINE_DRAGONS_WITHOUT_OWL) {
if (NEEDS_UPDATE(dragons_examined_without_owl))
make_dragons(1);
verbose = save_verbose;
gg_assert(test_gray_border() < 0);
return;
}
if (NEEDS_UPDATE(dragons_examined)) {
make_dragons(0);
compute_scores(chinese_rules || aftermath_play);
/* We have automatically done a partial dragon analysis as well. */
dragons_examined_without_owl = position_number;
}
if (how_much == EXAMINE_DRAGONS) {
verbose = save_verbose;
gg_assert(test_gray_border() < 0);
return;
}
}
else if (how_much == EXAMINE_INITIAL_INFLUENCE
|| how_much == EXAMINE_DRAGONS
|| how_much == EXAMINE_ALL) {
initialize_dragon_data();
compute_scores(chinese_rules || aftermath_play);
verbose = save_verbose;
gg_assert(test_gray_border() < 0);
return;
}
verbose = save_verbose;
if (NEEDS_UPDATE(initial_influence2_examined)) {
compute_dragon_influence();
}
if (how_much == EXAMINE_INITIAL_INFLUENCE2) {
gg_assert(test_gray_border() < 0);
return;
}
if (NEEDS_UPDATE(dragons_refinedly_examined)) {
compute_refined_dragon_weaknesses();
compute_strategic_sizes();
}
if (how_much == FULL_EXAMINE_DRAGONS) {
gg_assert(test_gray_border() < 0);
return;
}
if (printworms)
show_dragons();
}
/* The same as examine_position(), except that all traces, debug
* output, and sgf traces are turned off.
*/
void
silent_examine_position(int how_much)
{
int save_verbose = verbose;
SGFTree *save_sgf_dumptree = sgf_dumptree;
int save_count_variations = count_variations;
int save_debug = debug;
int save_printmoyo = printmoyo;
verbose = 0;
sgf_dumptree = NULL;
count_variations = 0;
debug = 0;
printmoyo = 0;
examine_position(how_much, 0);
verbose = save_verbose;
sgf_dumptree = save_sgf_dumptree;
count_variations = save_count_variations;
debug = save_debug;
printmoyo = save_printmoyo;
}
/*
* Generate computer move for color.
*
* Return the generated move.
*/
int
genmove(int color, float *value, int *resign)
{
int move = PASS_MOVE;
if (resign)
*resign = 0;
#if ORACLE
if (metamachine) {
move = metamachine_genmove(color, value, limit_search);
gg_assert(stackp == 0);
if (move != PASS_MOVE)
return move;
}
#endif
if (limit_search)
move = do_genmove(color, 0.4, search_mask, value, resign);
else
move = do_genmove(color, 0.4, NULL, value, resign);
gg_assert(move == PASS_MOVE || ON_BOARD(move));
return move;
}
/*
* Same as above but doesn't generate pure threat moves. Useful when
* trying to score a game.
*/
int
genmove_conservative(int color, float *value)
{
return do_genmove(color, 0.0, NULL, value, NULL);
}
int
genmove_restricted(int color, int allowed_moves[BOARDMAX])
{
return do_genmove(color, 0.0, allowed_moves, NULL, NULL);
}
/* This function collects move reasons can be generated immediately from
* the data gathered in the examine_position() phase.
*/
void
collect_move_reasons(int color)
{
worm_reasons(color);
semeai_move_reasons(color);
owl_reasons(color);
cut_reasons(color);
break_in_move_reasons(color);
unconditional_move_reasons(color);
}
/* Call Monte Carlo module to generate a move. */
static int
monte_carlo_genmove(int color, int allowed_moves[BOARDMAX],
float *value, int *resign)
{
int pos;
int best_move = PASS_MOVE;
int best_uct_move = PASS_MOVE;
int unconditional_territory_black[BOARDMAX];
int unconditional_territory_white[BOARDMAX];
int forbidden_move[BOARDMAX];
float move_values[BOARDMAX];
int move_frequencies[BOARDMAX];
float best_value;
int frequency_cutoff;
int frequency_cutoff2;
int number_of_simulations;
memset(move_values, 0, sizeof(move_values));
memset(move_frequencies, 0, sizeof(move_frequencies));
if (0) {
simple_showboard(stderr);
gprintf("\n");
}
if (resign)
*resign = 0;
unconditional_life(unconditional_territory_black, BLACK);
unconditional_life(unconditional_territory_white, WHITE);
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
if (!ON_BOARD(pos))
continue;
else if (unconditional_territory_black[pos])
forbidden_move[pos] = BLACK;
else if (unconditional_territory_white[pos])
forbidden_move[pos] = WHITE;
else
forbidden_move[pos] = 0;
number_of_simulations = mc_games_per_level * gg_max(get_level(), 1);
uct_genmove(color, &best_uct_move, forbidden_move, allowed_moves,
number_of_simulations, move_values, move_frequencies);
best_move = best_uct_move;
best_value = 0.0;
frequency_cutoff = move_frequencies[best_uct_move] / 2;
frequency_cutoff2 = move_frequencies[best_uct_move] / 10;
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (ON_BOARD(pos)
&& (move_frequencies[pos] > frequency_cutoff
|| (move_values[pos] > 0.9
&& move_frequencies[pos] > frequency_cutoff2)
|| move_values[best_uct_move] < 0.1)
&& (!allowed_moves || allowed_moves[pos])
&& potential_moves[pos] > best_value) {
best_move = pos;
best_value = potential_moves[pos];
}
}
unconditionally_meaningless_move(best_move, color, &best_move);
*value = 1.0;
return best_move;
}
/*
* Perform the actual move generation.
*
* The array allowed_moves restricts which moves may be considered. If
* NULL any move is allowed. Pass is always allowed and will be chosen
* if the move generation doesn't like any of the allowed moves (or
* overlooks them).
*/
static int
do_genmove(int color, float pure_threat_value,
int allowed_moves[BOARDMAX], float *value, int *resign)
{
float average_score, pessimistic_score, optimistic_score;
int save_verbose;
int save_depth;
int move;
float dummy_value;
int use_thrashing_dragon_heuristics = 0;
if (!value)
value = &dummy_value;
start_timer(0);
clearstats();
/* Usually we would not recommend resignation. */
if (resign)
*resign = 0;
/* Prepare our table of moves considered. */
memset(potential_moves, 0, sizeof(potential_moves));
/* no move is found yet. */
move = PASS_MOVE;
*value = 0.0;
/* Prepare pattern matcher and reading code. */
reset_engine();
/* Store the depth value so we can check that it hasn't changed when
* we leave this function.
*/
save_depth = depth;
/* If in mirror mode, try to find a mirror move. */
if (play_mirror_go
&& (mirror_stones_limit < 0
|| stones_on_board(WHITE | BLACK) <= mirror_stones_limit)
&& find_mirror_move(&move, color)) {
TRACE("genmove() recommends mirror move at %1m\n", move);
*value = 1.0;
return move;
}
/* Find out information about the worms and dragons. */
start_timer(1);
examine_position(EXAMINE_ALL, 0);
time_report(1, "examine position", NO_MOVE, 1.0);
/* The score will be used to determine when we are safely
* ahead. So we want the most conservative score.
*
* We always want to have the score from our point of view. So
* negate it if we are black.
*/
if (color == WHITE) {
pessimistic_score = black_score;
optimistic_score = white_score;
}
else {
pessimistic_score = -white_score;
optimistic_score = -black_score;
}
if (color == WHITE)
average_score = (white_score + black_score)/2.0;
else
average_score = -(white_score + black_score)/2.0;
choose_strategy(color, average_score, game_status(color));
if (printboard) {
if (printboard == 1)
fprintf(stderr, "\n dragon_status display:\n\n");
if (printboard == 2)
fprintf(stderr, "\n eye display:\n\n");
showboard(printboard);
if (printboard == 1) {
fprintf(stderr, "\n owl_status display:\n\n");
showboard(3);
fprintf(stderr, "\n matcher_status display:\n\n");
showboard(4);
}
}
gg_assert(stackp == 0);
/*
* Ok, information gathering is complete. Now start to find some moves!
*/
/* Pick up moves that we know of already. */
save_verbose = verbose;
if (verbose > 0)
verbose--;
collect_move_reasons(color);
verbose = save_verbose;
time_report(1, "generate move reasons", NO_MOVE, 1.0);
/* Try to find empty corner moves. */
fuseki(color);
gg_assert(stackp == 0);
/* Look for moves to break mirror play by the opponent. */
break_mirror_go(color);
/* If we are ahead by 5 points or more, consider a thrashing
* dragon dangerous and change its status from DEAD to
* UNKNOWN. Otherwise, pretend there is no thrashing dragon.
*/
if (!doing_scoring)
use_thrashing_dragon_heuristics
= revise_thrashing_dragon(color, pessimistic_score, 5.0);
/* The general pattern database. */
shapes(color);
time_report(1, "shapes", NO_MOVE, 1.0);
gg_assert(stackp == 0);
/* Look for combination attacks and defenses against them. */
combinations(color);
time_report(1, "combinations", NO_MOVE, 1.0);
gg_assert(stackp == 0);
/* Review the move reasons and estimate move values. */
if (review_move_reasons(&move, value, color,
pure_threat_value, pessimistic_score, allowed_moves,
use_thrashing_dragon_heuristics))
TRACE("Move generation likes %1m with value %f\n", move, *value);
gg_assert(stackp == 0);
time_report(1, "review move reasons", NO_MOVE, 1.0);
/* If the move value is 6 or lower, we look for endgame patterns too. */
if (*value <= 6.0 && !disable_endgame_patterns) {
endgame_shapes(color);
endgame(color);
gg_assert(stackp == 0);
if (review_move_reasons(&move, value, color, pure_threat_value,
pessimistic_score, allowed_moves,
use_thrashing_dragon_heuristics))
TRACE("Move generation likes %1m with value %f\n", move, *value);
gg_assert(stackp == 0);
time_report(1, "endgame", NO_MOVE, 1.0);
}
/* If no move found yet, revisit any semeai and change the
* status of the opponent group from DEAD to UNKNOWN, then
* run shapes and endgame_shapes again. This may turn up a move.
*/
if (move == PASS_MOVE) {
if (revise_semeai(color)) {
shapes(color);
endgame_shapes(color);
if (review_move_reasons(&move, value, color, pure_threat_value,
pessimistic_score, allowed_moves,
use_thrashing_dragon_heuristics)) {
TRACE("Upon reconsideration move generation likes %1m with value %f\n",
move, *value);
}
}
time_report(1, "move reasons with revised semeai status",
NO_MOVE, 1.0);
}
/* If Monte Carlo move generation is enabled, call it now. Do not
* override a fuseki move.
*
* FIXME: Identifying fuseki moves by checking the move value is
* very ugly and fragile.
*/
if (use_monte_carlo_genmove && move != PASS_MOVE
&& (*value < 75.0 || *value > 75.01) && !doing_scoring) {
int allowed_moves2[BOARDMAX];
int num_allowed_moves2 = 0;
int pos;
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
if (ON_BOARD(pos)
&& (!allowed_moves || allowed_moves[pos])
&& is_allowed_move(pos, color)) {
allowed_moves2[pos] = 1;
num_allowed_moves2++;
}
else
allowed_moves2[pos] = 0;
if (num_allowed_moves2 > 1)
move = monte_carlo_genmove(color, allowed_moves2, value, resign);
}
/* If still no move, fill a remaining liberty. This should pick up
* all missing dame points.
*/
if (move == PASS_MOVE
&& fill_liberty(&move, color)) {
if (!allowed_moves || allowed_moves[move]) {
*value = 1.0;
TRACE("Filling a liberty at %1m\n", move);
record_top_move(move, *value);
move_considered(move, *value);
time_report(1, "fill liberty", NO_MOVE, 1.0);
}
else
move = PASS_MOVE;
}
/* If we're instructed to play out the aftermath or capture all dead
* opponent stones, or if the opponent is trying to live inside
* our territory and we are clearly ahead, generate an aftermath move.
*/
if (move == PASS_MOVE) {
if (play_out_aftermath
|| capture_all_dead
|| (!doing_scoring && thrashing_dragon && pessimistic_score > 15.0))
move = aftermath_genmove(color, capture_all_dead, allowed_moves);
if (move != PASS_MOVE) {
ASSERT1(is_legal(move, color), move);
*value = 1.0;
TRACE("Aftermath move at %1m\n", move);
record_top_move(move, *value);
move_considered(move, *value);
time_report(1, "aftermath_genmove", NO_MOVE, 1.0);
}
}
/* If we somehow have managed to generate an illegal move, pass instead. */
if (!is_allowed_move(move, color)) {
TRACE("ILLEGAL MOVE GENERATED. Passing instead.\n");
move = PASS_MOVE;
*value = -1.0;
}
/* If no move is found then pass. */
if (move == PASS_MOVE) {
TRACE("I pass.\n");
}
else {
TRACE("genmove() recommends %1m with value %f\n", move, *value);
}
/* Maybe time to resign...
*/
if (resign && resign_allowed
&& *value < 10.0 && should_resign(color, optimistic_score, move)) {
TRACE("... though, genmove() thinks the position is hopeless\n");
*resign = 1;
}
/* If statistics is turned on, this is the place to show it. */
if (showstatistics)
showstats();
if (showtime) {
double spent = time_report(0, "TIME to generate move at ", move, 1.0);
total_time += spent;
if (spent > slowest_time) {
slowest_time = spent;
slowest_move = move;
slowest_movenum = movenum + 1;
}
}
/* Some consistency checks to verify that things are properly
* restored and/or have not been corrupted.
*/
gg_assert(stackp == 0);
gg_assert(test_gray_border() < 0);
gg_assert(depth == save_depth);
return move;
}
/* This is called for each move which has been considered. For
* debugging purposes, we keep a table of all the moves we
* have considered.
*/
void
move_considered(int move, float value)
{
if (value > potential_moves[move])
potential_moves[move] = value;
}
/* revise_semeai(color) changes the status of any DEAD dragon of
* OPPOSITE_COLOR(color) which occurs in a semeai to UNKNOWN.
* It returns true if such a dragon is found.
*/
static int
revise_semeai(int color)
{
int pos;
int found_one = 0;
int other = OTHER_COLOR(color);
if (stones_on_board(BLACK | WHITE) == 0)
return 0;
if (doing_scoring)
return 0;
gg_assert(dragon2 != NULL);
for (pos = BOARDMIN; pos < BOARDMAX; pos++) {
if (ON_BOARD(pos)
&& dragon[pos].color == other
&& DRAGON2(pos).semeais
&& dragon[pos].status == DEAD) {
found_one = 1;
dragon[pos].status = UNKNOWN;
if (dragon[pos].origin == pos)
TRACE("revise_semeai: changed status of dragon %1m from DEAD to UNKNOWN\n",
pos);
}
}
return found_one;
}
/* If the opponent's last move added a stone to a dead dragon,
* revise it's status to UNKNOWN. This will cause genmove to
* generate moves restraining the dragon. We only do this if
* we are ahead by 'advantage', and no owl threat has been found.
*/
static int
revise_thrashing_dragon(int color, float our_score, float advantage)
{
int pos;
signed char safe_stones[BOARDMAX];
float strength[BOARDMAX];
/* Trust the owl code's opinion if we are behind. */
if (our_score < advantage)
return 0;
if (disable_threat_computation
|| !thrashing_dragon
|| dragon[thrashing_dragon].status != DEAD)
return 0;
for (pos = BOARDMIN; pos < BOARDMAX; pos++)
if (ON_BOARD(pos) && thrashing_stone[pos]
&& worm[pos].unconditional_status != DEAD) {
dragon[pos].status = UNKNOWN;
DRAGON2(pos).safety = ALIVE;
}
set_strength_data(OTHER_COLOR(color), safe_stones, strength);
compute_influence(OTHER_COLOR(color), safe_stones, strength,
OPPOSITE_INFLUENCE(color),
NO_MOVE, "revised thrashing dragon");
compute_refined_dragon_weaknesses();
return 1;
}
/* Look for a mirror move. First try the mirror of the last move. If
* none is available, test all moves to see if one makes the board
* symmetric.
*
* To be able to deal with handicap stones we use a somewhat weak
* definition of symmetry.
*/
static int
find_mirror_move(int *move, int color)
{
int last_move = get_last_move();
int mirror_move;
if (last_move != NO_MOVE) {
mirror_move = MIRROR_MOVE(last_move);
if (test_symmetry_after_move(mirror_move, color, 0)) {
*move = mirror_move;
return 1;
}
}
else {
for (mirror_move = BOARDMIN; mirror_move < BOARDMAX; mirror_move++) {
if (ON_BOARD(mirror_move)
&& test_symmetry_after_move(mirror_move, color, 0)) {
*move = mirror_move;
return 1;
}
}
}
return 0;
}
/* Computer two territory estimates: for *upper, the status of all
* cricital stones gets resolved in White's favor; vice verso for
* black.
*/
static void
compute_scores(int use_chinese_rules)
{
signed char safe_stones[BOARDMAX];
float strength[BOARDMAX];
set_strength_data(WHITE, safe_stones, strength);
compute_influence(EMPTY, safe_stones, strength, &move_influence,
NO_MOVE, "White territory estimate");
white_score = influence_score(&move_influence, use_chinese_rules);
set_strength_data(BLACK, safe_stones, strength);
compute_influence(EMPTY, safe_stones, strength, &move_influence,
NO_MOVE, "White territory estimate");
black_score = influence_score(&move_influence, use_chinese_rules);
if (verbose || showscore) {
if (white_score == black_score)
gprintf("Score estimate: %s %f\n",
black_score > 0 ? "W " : "B ", gg_abs(black_score));
else
gprintf("Score estimate: %s %f to %s %f\n",
black_score > 0 ? "W " : "B ", gg_abs(black_score),
white_score > 0 ? "W " : "B ", gg_abs(white_score));
fflush(stderr);
}
}
/* Detect if a white opponent has played mirror go for at least 10
* moves and if so play on tengen.
*
* Mirror breaking moves in other situations are handled by patterns
* in patterns.db.
*/
static void
break_mirror_go(int color)
{
int tengen = POS((board_size - 1) / 2, (board_size - 1) / 2);
if (board[tengen] == EMPTY
&& color == BLACK
&& stones_on_board(BLACK | WHITE) > 10
&& test_symmetry_after_move(tengen, color, 1)) {
set_minimum_move_value(tengen, 30.0);
TRACE("Play %1m to break mirror go, value 30.\n", tengen);
}
}
/* Helper to decide whether GG should resign a game
*/
static int
should_resign(int color, float optimistic_score, int move)
{
float status;
int d;
/* We resign 19x19 games only, smaller board games are fast enough.
* We resign only if the margin is bigger than 45 pts and if we are
* behind (of course).
*
* As an exception to this rule, we resign on any board size if
* it looks like all our dragons are dead and the generated move
* is a pass.
*/
if (board_size > 2 && move == PASS_MOVE && !lively_dragon_exists(color))
return 1;
if (move == PASS_MOVE
|| board_size < 19
|| optimistic_score > -45.0)
return 0;
/* Check dragon statuses. If a friendly dragon is critical, we are
* possibly not that much behind after we save it. If some hostile
* dragon is at least weak, we possibly have a chance to come back
* if we can kill it.
*/
for (d = 0; d < number_of_dragons; d++) {
/* any friendly critical dragon ? */
if (board[dragon2[d].origin] == color
&& DRAGON(d).status == CRITICAL)
return 0;
/* any weak opponent dragon ? */
if (board[dragon2[d].origin] == OTHER_COLOR(color)
&& DRAGON(d).status != DEAD
&& DRAGON(d).effective_size >= 10
&& dragon_weak(dragon2[d].origin))
return 0;
}
/* Is it already too late to try something ? */
status = game_status(color);
if (status < 0.8)
/* Still "too early".
* Note: the 0.8 constant is very conservative, we actually could give
* up a bit earlier.
*/
return 0;
/* well, it is probably reasonable and polite to give up this game */
return 1;
}
/*********************************************************************\
* Mark a limited search area *
\*********************************************************************/
/* Activate or deactivate search limit. */
void
set_limit_search(int value)
{
limit_search = value;
}
/* The following function marks a diamond of radius 6 with center pos. */
void
set_search_diamond(int pos)
{
int i = I(pos);
int j = J(pos);
int m, n;
for (m = 0; m < board_size; m++)
for (n = 0; n < board_size; n++) {
if (gg_abs(m - i) + gg_abs(n - j) <= 6)
search_mask[POS(m, n)] = 1;
else
search_mask[POS(m, n)] = 0;
}
limit_search = pos;
if (0)
draw_search_area();
}
/* unmarks the entire board */
void
reset_search_mask()
{
memset(search_mask, 0, sizeof(search_mask));
}
/* marks a single vertex */
void
set_search_mask(int pos, int value)
{
search_mask[pos] = value;
}
/* displays the search area */
void
draw_search_area(void)
{
int m, n;
start_draw_board();
for (m = 0; m < board_size; m++)
for (n = 0; n < board_size; n++) {
int col, c;
if (search_mask[POS(m, n)])
col = GG_COLOR_RED;
else
col = GG_COLOR_BLACK;
if (board[POS(m, n)] == BLACK)
c = 'X';
else if (board[POS(m, n)] == WHITE)
c = 'O';
else if (search_mask[POS(m, n)])
c = '*';
else
c = '.';
draw_color_char(m, n, c, col);
}
end_draw_board();
}
/* returns true if the position is within the search area */
int
within_search_area(int pos)
{
if (!limit_search)
return 1;
return search_mask[pos];
}
/*
* Local Variables:
* tab-width: 8
* c-basic-offset: 2
* End:
*/