From abbf2ac52a86bdd670494cb652c818e1c913e5cc Mon Sep 17 00:00:00 2001 From: lemonsqueeze Date: Tue, 3 May 2016 18:39:54 +0200 Subject: [PATCH] dragon.c: deal with virtually connected groups --- board.c | 32 +++ board.h | 2 + tactics/Makefile | 2 +- tactics/dragon.c | 664 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ tactics/dragon.h | 52 +++++ 5 files changed, 751 insertions(+), 1 deletion(-) create mode 100644 tactics/dragon.c create mode 100644 tactics/dragon.h diff --git a/board.c b/board.c index a8de74b..8fc16d8 100644 --- a/board.c +++ b/board.c @@ -480,6 +480,38 @@ board_print_custom(struct board *board, FILE *f, board_cprint cprint, void *data } static char * +board_hprint_row(struct board *board, int y, char *s, char *end, board_print_handler handler, void *data) +{ + s += snprintf(s, end - s, " %2d | ", y); + for (int x = 1; x < board_size(board) - 1; x++) { + char *stone_str = handler(board, coord_xy(board, x, y), data); + if (coord_x(board->last_move.coord, board) == x && coord_y(board->last_move.coord, board) == y) + s += snprintf(s, end - s, "%s)", stone_str); + else + s += snprintf(s, end - s, "%s ", stone_str); + } + s += snprintf(s, end - s, "|"); + s += snprintf(s, end - s, "\n"); + return s; +} + +void +board_hprint(struct board *board, FILE *f, board_print_handler handler, void *data) +{ + char buf[10240]; + char *s = buf; + char *end = buf + sizeof(buf); + s += snprintf(s, end - s, "Move: % 3d Komi: %2.1f Handicap: %d Captures B: %d W: %d\n", + board->moves, board->komi, board->handicap, + board->captures[S_BLACK], board->captures[S_WHITE]); + s = board_print_top(board, s, end, 1); + for (int y = board_size(board) - 2; y >= 1; y--) + s = board_hprint_row(board, y, s, end, handler, data); + board_print_bottom(board, s, end, 1); + fprintf(f, "%s\n", buf); +} + +static char * cprint_group(struct board *board, coord_t c, char *s, char *end, void *data) { s += snprintf(s, end - s, "%d ", group_base(group_at(board, c))); diff --git a/board.h b/board.h index 2af93b3..738e0b6 100644 --- a/board.h +++ b/board.h @@ -381,8 +381,10 @@ void board_resize(struct board *board, int size); void board_clear(struct board *board); typedef char *(*board_cprint)(struct board *b, coord_t c, char *s, char *end, void *data); +typedef char *(*board_print_handler)(struct board *b, coord_t c, void *data); void board_print(struct board *board, FILE *f); void board_print_custom(struct board *board, FILE *f, board_cprint cprint, void *data); +void board_hprint(struct board *board, FILE *f, board_print_handler handler, void *data); /* Debugging: Compare 2 boards byte by byte. Don't use that for sorting =) */ int board_cmp(struct board *b1, struct board *b2); diff --git a/tactics/Makefile b/tactics/Makefile index 344dde3..74e47be 100644 --- a/tactics/Makefile +++ b/tactics/Makefile @@ -1,5 +1,5 @@ INCLUDES=-I.. -OBJS=1lib.o 2lib.o nlib.o ladder.o nakade.o selfatari.o util.o +OBJS=1lib.o 2lib.o nlib.o ladder.o nakade.o selfatari.o util.o dragon.o all: tactics.a tactics.a: $(OBJS) diff --git a/tactics/dragon.c b/tactics/dragon.c new file mode 100644 index 0000000..8921fe1 --- /dev/null +++ b/tactics/dragon.c @@ -0,0 +1,664 @@ +#include +#include +#include + +#define QUICK_BOARD_CODE + +#define DEBUG +#include "board.h" +#include "debug.h" +#include "tactics/dragon.h" + +static char* +print_handler(struct board *board, coord_t c, void *data) +{ + static char buf[64]; + group_t dragon = *(group_t*)data; + char *before = "", *after = ""; + if (dragon_at(board, c) == dragon) { + before = "\x1b[40;33;1m"; + after = "\x1b[0m"; + } + sprintf(buf, "%s%c%s", before, stone2char(board_at(board, c)), after); + return buf; +} + +void +dragon_print(struct board *board, FILE *f, group_t dragon) +{ + board_hprint(board, f, print_handler, &dragon); +} + +static char *bold_colors[] = { + "\x1b[40;33;1m", // gold + "\x1b[40;32;1m", // green + "\x1b[40;31;1m", // red + "\x1b[40;34;1m", // blue + "\x1b[40;35;1m", // purple + "\x1b[40;36;1m", // lblue + "\x1b[40;37;1m", // white +}; + +static char *normal_colors[] = { + "\x1b[40;33m", // gold + "\x1b[40;32m", // green + "\x1b[40;31m", // red + "\x1b[40;34m", // blue + "\x1b[40;35m", // purple + "\x1b[40;36m", // lblue + "\x1b[40;37m", // white, must be last +}; + +static char *ansi_color_end = "\x1b[0m"; + +char * +pick_dragon_color(int i, bool bold, bool white_ok) +{ + int ncolors = sizeof(normal_colors) / sizeof(char *); + if (!white_ok) + ncolors--; + if (bold) + return bold_colors[i % ncolors]; + return normal_colors[i % ncolors]; +} + +static char* +print_dragons(struct board *board, coord_t c, void *data) +{ + static char buf[64]; + group_t *dragons = data; + group_t d = dragon_at(board, c); + char *before = "", *after = ""; + + if (d) { + int i; // Dragon index + for (i = 0; dragons[i] && dragons[i] != d; i++) + ; + if (!dragons[i]) { dragons[i] = d; } /* Add new */ + + before = pick_dragon_color(i, (c == d), true); // Dragon base: bold + after = ansi_color_end; + } + + sprintf(buf, "%s%c%s", before, stone2char(board_at(board, c)), after); + return buf; +} + +void +board_print_dragons(struct board *board, FILE *f) +{ + group_t dragons[BOARD_MAX_GROUPS] = { 0, }; + board_hprint(board, f, print_dragons, dragons); +} + +#define no_stone_at(c) (board_at(b, (c)) == S_NONE) +#define own_stone_at(c) (board_at(b, (c)) == color) +#define enemy_stone_at(c) (board_at(b, (c)) == stone_other(color)) +#define no_stone_atxy(x, y) (board_atxy(b, (x), (y)) == S_NONE) +#define own_stone_atxy(x, y) (board_atxy(b, (x), (y)) == color) +#define enemy_stone_atxy(x, y) (board_atxy(b, (x), (y)) == stone_other(color)) + +static bool +is_controlled_eye_point(struct board *b, coord_t to, enum stone color); + +/* Check if g and g2 are virtually connected through lib. + * c2 is a stone of g2 next to lib */ +static bool +virtual_connection_at(struct board *b, enum stone color, coord_t lib, coord_t c2, group_t g1, group_t g2) +{ + assert(board_at(b, lib) == S_NONE); + assert(board_at(b, c2) == color); + assert(group_at(b, c2) == g2); + + /* Eye / Hanging connection ? */ + if (is_controlled_eye_point(b, lib, color)) + return true; + + /* Diagonal connection ? */ + int x2 = coord_x(c2, b), y2 = coord_y(c2, b); + foreach_diag_neighbor(b, c2) { + if (board_at(b, c) != color || group_at(b, c) != g1) + continue; + int x = coord_x(c, b); coord_t d1 = coord_xy(b, x, y2); + int y = coord_y(c, b); coord_t d2 = coord_xy(b, x2, y); + if (no_stone_at(d1) && no_stone_at(d2)) + return true; + } foreach_diag_neighbor_end; + + int x = coord_x(lib, b); int dx = coord_dx(lib, c2, b); + int y = coord_y(lib, b); int dy = coord_dy(lib, c2, b); + int x1 = x + dx; + int y1 = y + dy; + coord_t c1 = coord_xy(b, x1, y1); // other side of lib wrt c2 + + /* Bamboo joint or stronger ? */ + if ( own_stone_at(c1) && group_at(b, c1) == g1 && + ( (!dx && own_stone_atxy(x1-1, y1) && own_stone_atxy(x2-1, y2) && !enemy_stone_atxy(x-1, y)) || + (!dx && own_stone_atxy(x1+1, y1) && own_stone_atxy(x2+1, y2) && !enemy_stone_atxy(x+1, y)) || + (!dy && own_stone_atxy(x1, y1-1) && own_stone_atxy(x2, y2-1) && !enemy_stone_atxy(x, y-1)) || + (!dy && own_stone_atxy(x1, y1+1) && own_stone_atxy(x2, y2+1) && !enemy_stone_atxy(x, y+1)) )) + return true; + + /* TODO more fancy stuff ... */ + + return false; +} + + + +/* Handler should return -1 to stop iterating */ +typedef int (*foreach_in_connected_groups_t)(struct board *b, enum stone color, coord_t c, void *data); + +static int +foreach_in_connected_groups_(struct board *b, enum stone color, group_t g, + foreach_in_connected_groups_t f, void *data, int *visited) +{ + if (visited[group_base(g)]) + return 0; + visited[group_base(g)] = 1; + + foreach_in_group(b, g) { + if (f(b, color, c, data) == -1) + return -1; + } foreach_in_group_end; + + // Look for virtually connected groups + for (int i = 0; i < board_group_info(b, g).libs; i++) { + coord_t lib = board_group_info(b, g).lib[i]; + // TODO could mark liberties visited, more efficient ? + foreach_neighbor(b, lib, { + if (board_at(b, c) != color) + continue; + group_t g2 = group_at(b, c); + if (visited[g2] || !virtual_connection_at(b, color, lib, c, g, g2)) + continue; + if (foreach_in_connected_groups_(b, color, g2, f, data, visited) == -1) + return -1; + }); + } + return 0; +} + +/* Call f() for each stone in dragon at @to. */ +static void +foreach_in_connected_groups(struct board *b, enum stone color, coord_t to, + foreach_in_connected_groups_t f, void *data) +{ + int visited[BOARD_MAX_COORDS] = {0, }; + assert(board_at(b, to) == color); + group_t g = group_at(b, to); + foreach_in_connected_groups_(b, color, g, f, data, visited); +} + + +/* Handler should return -1 to stop iterating. */ +typedef int (*foreach_connected_group_t)(struct board *b, enum stone color, group_t g, void *data); + +static int +foreach_connected_group_(struct board *b, enum stone color, group_t g, + foreach_connected_group_t f, void *data, int *visited) +{ + if (visited[group_base(g)]) + return 0; + + visited[group_base(g)] = 1; + if (f(b, color, g, data) == -1) + return -1; + + // Look for virtually connected groups + for (int i = 0; i < board_group_info(b, g).libs; i++) { + coord_t lib = board_group_info(b, g).lib[i]; + // TODO could mark liberties visited, more efficient ? + foreach_neighbor(b, lib, { + if (board_at(b, c) != color) + continue; + group_t g2 = group_at(b, c); + if (visited[g2] || !virtual_connection_at(b, color, lib, c, g, g2)) + continue; + if (foreach_connected_group_(b, color, g2, f, data, visited) == -1) + return -1; + }); + } + return 0; +} + +/* Call f() for each group in dragon at @to. */ +static void +foreach_connected_group(struct board *b, enum stone color, coord_t to, + foreach_connected_group_t f, void *data) +{ + int visited[BOARD_MAX_COORDS] = {0, }; + assert(board_at(b, to) == color); + group_t g = group_at(b, to); + foreach_connected_group_(b, color, g, f, data, visited); +} + +struct foreach_lib_data { + int *visited; + foreach_in_connected_groups_t f; + void *data; +}; + +static int +foreach_lib_handler(struct board *b, enum stone color, group_t g, void *data) +{ + struct foreach_lib_data *d = data; + for (int i = 0; i < board_group_info(b, g).libs; i++) { + coord_t lib = board_group_info(b, g).lib[i]; + if (d->visited[lib]) + continue; + d->visited[lib] = 1; + if (d->f(b, color, lib, d->data) == -1) + return -1; + } + return 0; +} + +/* Call f() for each liberty of dragon at @to. */ +static void +foreach_lib_in_connected_groups(struct board *b, enum stone color, coord_t to, + foreach_in_connected_groups_t f, void *data) +{ + int visited[BOARD_MAX_COORDS] = {0, }; + struct foreach_lib_data d = { .visited = visited, .f = f, .data = data }; + foreach_connected_group(b, color, to, foreach_lib_handler, &d); +} + + +static int +stones_all_connected_handler(struct board *b, enum stone color, coord_t c, void *data) +{ + int *connected = data; + connected[c] = 1; return 0; +} + +static bool +stones_all_connected(struct board *b, enum stone color, coord_t *stones, int n) +{ + // TODO optimize: check if all same group first ... + int connected[BOARD_MAX_COORDS] = {0, }; + + foreach_in_connected_groups(b, color, stones[0], stones_all_connected_handler, connected); + + for (int i = 0; i < n; i++) + if (!connected[stones[i]]) + return false; + return true; +} + +/* Try to detect big eye area, ie: + * - completely enclosed area, not too big, + * - surrounding stones all connected to each other + * - size >= 2 (so no false eye issues) + * Returns size of the area, or 0 if doesn't match. */ +int +big_eye_area(struct board *b, enum stone color, coord_t around, int *visited) +{ + int NAKADE_MAX = 8; // min area size for living group (corner) + // could increase to 10 (side) and 12 (middle) + // and/or check prisoners + coord_t area[NAKADE_MAX]; + int area_n = 0; + int STONES_MAX = 50; + int stones[STONES_MAX]; + int stones_n = 0; + area[area_n++] = around; + + assert(!visited[around]); + for (int i = 0; i < area_n; i++) { + foreach_neighbor(b, area[i], { + if (board_at(b, c) == S_OFFBOARD) + continue; + + if (board_at(b, c) == color) { // Found border, save it and continue + bool dup = false; + for (int j = 0; j < stones_n; j++) + if (c == stones[j]) { + dup = true; + break; + } + if (dup) continue; + + assert(stones_n < STONES_MAX); + stones[stones_n++] = c; + continue; + } + + // Empty spot or prisoner, add it to area + bool dup = false; + for (int j = 0; j < area_n; j++) + if (c == area[j]) { + dup = true; + break; + } + if (dup) continue; + + if (area_n >= NAKADE_MAX) + return 0; + + area[area_n++] = c; + }); + } + + if (area_n < 3 || !stones_all_connected(b, color, stones, stones_n)) + return 0; + + // Ok good, mark area visited + // TODO if (area_n < 7) ... + for (int i = 0; i < area_n; i++) + visited[area[i]] = 1; + + return area_n; +} + + +/* Point we control: + * Opponent can't play there or we can capture if he does. + * TODO - could make tiger mouth check smarter (check selfatari) + * - handle more exotic cases (ladders ?) */ +static bool +is_controlled_eye_point(struct board *b, coord_t to, enum stone color) +{ + assert(no_stone_at(to)); + + /* Eye-like ? */ + if (!board_is_valid_play_no_suicide(b, stone_other(color), to)) + return true; + + /* Tiger mouth ? + * Check no opponent stone nearby and we can't be captured. + * also works for side connection */ + if (immediate_liberty_count(b, to) == 1) { + int good = 0; + foreach_neighbor(b, to, { + if (enemy_stone_at(c)) + return false; + if ((own_stone_at(c) && board_group_info(b, group_at(b, c)).libs > 1) || + board_at(b, c) == S_OFFBOARD) + good++; + }); + return (good == 3); + } + + return false; +} + + +static bool +real_eye_endpoint(struct board *board, coord_t to, enum stone color) +{ + enum stone color_diag_libs[S_MAX] = {0, 0, 0, 0}; + + foreach_diag_neighbor(board, to) { + color_diag_libs[(enum stone) board_at(board, c)]++; + } foreach_diag_neighbor_end; + /* We need to control 3 corners of the eye in the middle of the board, + * 2 on the side, and 1 in the corner. */ + if (color_diag_libs[S_OFFBOARD]) { + color_diag_libs[color] += color_diag_libs[S_OFFBOARD] - 1; + color_diag_libs[stone_other(color)]++; + } + + /* Corners could be eye-like too ... */ + foreach_diag_neighbor(board, to) { + if (color_diag_libs[color] >= 3) return true; + if (color_diag_libs[stone_other(color)] >= 2) return false; + + if (board_at(board, c) != S_NONE) + continue; + if (is_controlled_eye_point(board, c, color)) /* No need to recurse, thank goodness */ + color_diag_libs[color]++; + else + color_diag_libs[stone_other(color)]++; + } foreach_diag_neighbor_end; + + return (color_diag_libs[color] >= 3); +} + +/* Point is finished one point eye. + * (board_is_one_point_eye() ones can become false later ...) */ +static bool +is_real_one_point_eye(struct board *b, coord_t to, enum stone color) +{ + return (board_is_eyelike(b, to, color) && + real_eye_endpoint(b, to, color)); +} + +static bool +is_real_two_point_eye(struct board *b, coord_t to, enum stone color, coord_t *pother) +{ + if ((neighbor_count_at(b, to, color) + + neighbor_count_at(b, to, S_OFFBOARD)) != 3) + return false; + coord_t other = pass; + foreach_neighbor(b, to, { /* Find the other point ... */ + if ((board_at(b, c) == S_NONE || + board_at(b, c) == stone_other(color)) && + (neighbor_count_at(b, c, color) + + neighbor_count_at(b, c, S_OFFBOARD)) == 3) { + other = c; + break; + } + }); + *pother = other; + + return (!is_pass(other) && + real_eye_endpoint(b, to, color) && + real_eye_endpoint(b, other, color)); +} + +struct safe_data { + int *visited; + int *eyes; +}; + +static int +count_eyes(struct board *b, enum stone color, coord_t lib, void *data) +{ + struct safe_data *d = data; + if (d->visited[lib]) /* Don't visit big eyes multiple times */ + return 0; + + if (is_real_one_point_eye(b, lib, color)) { + // fprintf(stderr, "real eye: %s\n", coord2sstr(lib, b)); + if (++(*d->eyes) >= 2) + return -1; + return 0; + } + + coord_t other = pass; + if (is_real_two_point_eye(b, lib, color, &other)) { + // fprintf(stderr, "two-point eye: %s\n", coord2sstr(lib, b)); + d->visited[other] = 1; + if (++(*d->eyes) >= 2) + return -1; + return 0; + } + + /* TODO check shape ... */ + int area_size = big_eye_area(b, color, lib, d->visited); + if (area_size) { + *d->eyes += (area_size > 7 ? 2 : 1); + if (*d->eyes >= 2) + return -1; + return 0; + } + + return 0; +} + +bool +dragon_is_safe_full(struct board *b, group_t g, enum stone color, int *visited, int *eyes) +{ + struct safe_data d = { .visited = visited, .eyes = eyes }; + foreach_lib_in_connected_groups(b, color, g, count_eyes, &d); + return (*eyes >= 2); +} + +bool +dragon_is_safe(struct board *b, group_t g, enum stone color) +{ + int visited[BOARD_MAX_COORDS] = {0, }; + int eyes = 0; + return dragon_is_safe_full(b, g, color, visited, &eyes); +} + +static int +count_libs(struct board *b, enum stone color, coord_t c, void *data) +{ + int *libs = data; + (*libs)++; return 0; +} + +int +dragon_liberties(struct board *b, enum stone color, coord_t to) +{ + int libs = 0; + foreach_lib_in_connected_groups(b, color, to, count_libs, &libs); + return libs; +} + + +static int +dragon_at_handler(struct board *b, enum stone color, group_t g, void *data) +{ + group_t *d = data; + *d = (*d > g ? *d : g); return 0; +} + +group_t +dragon_at(struct board *b, coord_t to) +{ + group_t g = group_at(b, to); + group_t d = 0; + enum stone color = board_at(b, to); + if (!g) + return 0; + + foreach_connected_group(b, color, to, dragon_at_handler, &d); + return d; +} + + +#define GAP_LENGTH 4 + +/* Vertical gap ? */ +static inline bool +is_vert_gap(struct board *b, enum stone color, int *connected, int lx, int ly, int x, int dy) +{ + assert(dy); + for (int i = 0; i < GAP_LENGTH; i++) { + int y = ly + dy * i; + coord_t d = coord_xy(b, x, y); + if (board_at(b, d) == S_NONE) + continue; + if (board_at(b, d) == color && !connected[d]) + return false; // reach other group, could still be cut though ... + if (board_at(b, d) == color && connected[d]) + return false; // wrong direction + return false; + } + //fprintf(stderr, "vert gap %s %s\n", (dy > 0 ? "above" : "below"), coord2sstr(coord_xy(b, x, ly), b)); + return true; +} + +/* Horizontal gap ? */ +static inline bool +is_horiz_gap(struct board *b, enum stone color, int *connected, int lx, int ly, int y, int dx) +{ + assert(dx); + for (int i = 0; i < GAP_LENGTH; i++) { + int x = lx + dx * i; + coord_t d = coord_xy(b, x, y); + if (board_at(b, d) == S_NONE) + continue; + if (board_at(b, d) == color && !connected[d]) + return false; // reach other group, could still be cut though ... + if (board_at(b, d) == color && connected[d]) + return false; // wrong direction + return false; + } + return true; +} + +#define vert_gap(x, dy) is_vert_gap(b, color, connected, lx, ly, x, dy) +#define horiz_gap(y, dx) is_horiz_gap(b, color, connected, lx, ly, y, dx) + +/* Looking for 2-stones horizontal/vertical gap of length GAP_LENGTH extending + * outwards from lib. For example, something like this: + * + * . X X . . . X X X X X . . . + * . O . X . . X O O O * * * * + * . O * * * * X O . O * * * * + * . O * * * * X . O . . . . . + * . O . X . . + */ +static bool +two_stones_gap(struct board *b, enum stone color, coord_t lib, int *connected) +{ + int lx = coord_x(lib, b); + int ly = coord_y(lib, b); + + for (int dx = -1; dx <= 1; dx++) + for (int dy = -1; dy <= 1; dy++) { + if (dy && !dx) + if ( vert_gap(lx, dy) && // center gap + 1 on either side + (vert_gap(lx - 1, dy) || vert_gap(lx + 1, dy)) ) + return true; + if (dx && !dy) + if ( horiz_gap(ly, dx) && // center gap + 1 on either side + (horiz_gap(ly - 1, dx) || horiz_gap(ly + 1, dx)) ) + return true; + } + return false; +} + +static int +mark_connected(struct board *b, enum stone color, coord_t c, void *data) +{ + int *connected = data; + connected[c] = 1; return 0; +} + +struct surrounded_data { + int *connected; + bool surrounded; +}; + +static int +surrounded_check(struct board *b, enum stone color, coord_t lib, void *data) +{ + struct surrounded_data *d = data; + if (two_stones_gap(b, color, lib, d->connected)) { + d->surrounded = 0; return -1; + } + /* Other group we could connect to ? */ + foreach_neighbor(b, lib, { + if (board_at(b, c) == color && !d->connected[c]) { + with_move(b, lib, color, { + if (!group_at(b, lib)) + break; + d->surrounded = dragon_is_surrounded(b, lib); + with_move_return(-1); + }); + } + }); + return 0; +} + +bool +dragon_is_surrounded(struct board *b, coord_t to) +{ + enum stone color = board_at(b, to); + assert(color == S_BLACK || color == S_WHITE); + int connected[BOARD_MAX_COORDS] = {0, }; + + /* Mark connected stones */ + foreach_in_connected_groups(b, color, to, mark_connected, connected); + + struct surrounded_data d = { .connected = connected, .surrounded = 1 }; + foreach_lib_in_connected_groups(b, color, to, surrounded_check, &d); + return d.surrounded; +} + + diff --git a/tactics/dragon.h b/tactics/dragon.h new file mode 100644 index 0000000..140b217 --- /dev/null +++ b/tactics/dragon.h @@ -0,0 +1,52 @@ +#ifndef PACHI_TACTICS_DRAGON_H +#define PACHI_TACTICS_DRAGON_H + +/* Functions for dealing with dragons, ie virtually connected groups of stones. + * Used for some high-level tactics decisions, like trying to detect useful lost + * ladders or whether breaking a 3-stones seki is safe. + * Currently these are fairly expensive (dragon data is not cached) so shouldn't be + * called by low-level / perf-critical code. */ + + +/* Like group_at() but returns unique id for all stones in a dragon. + * Depending on the situation what is considered to be a dragon here may or + * may not match what we'd intuitively call a dragon: there are connections + * it doesn't understand (dead cutting stones for instance) so it'll usually + * be smaller. Doesn't need to be perfect though. */ +group_t dragon_at(struct board *b, coord_t to); + +/* Returns total number of liberties of dragon at @to. */ +int dragon_liberties(struct board *b, enum stone color, coord_t to); + +/* Print board highlighting given dragon */ +void dragon_print(struct board *board, FILE *f, group_t dragon); + +/* Like board_print() but use a different color for each dragon */ +void board_print_dragons(struct board *board, FILE *f); + +/* Pick a color for dragon with index @i. Returns ansi color code. + * Useful for writing custom board_print_dragons()-like functions. */ +char *pick_dragon_color(int i, bool bold, bool white_ok); + +/* Try to find out if dragon has 2 eyes. Pretty conservative: + * big eye areas are counted as one eye, must be completely enclosed and + * have all surrounded stones connected. Doesn't need to be perfect though. */ +bool dragon_is_safe(struct board *b, group_t g, enum stone color); + +/* Like group_is_safe() but passing already visited stones / eyes. */ +bool dragon_is_safe_full(struct board *b, group_t g, enum stone color, int *visited, int *eyes); + +/* Try to detect big eye area, ie: + * - completely enclosed area, not too big, + * - surrounding stones all connected to each other + * - size >= 2 (so no false eye issues) + * Returns size of the area, or 0 if doesn't match. */ +int big_eye_area(struct board *b, enum stone color, coord_t around, int *visited); + +/* Try to find out if dragon is completely surrounded: + * Look for outwards 2-stones gap from our external liberties. + * (hack, but works pretty well in practice) */ +bool dragon_is_surrounded(struct board *b, coord_t to); + + +#endif /* PACHI_TACTICS_DRAGON_H */ -- 2.11.4.GIT