/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* This distributed with GNU Go, a go program. *
* Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005 and 2006 *
* 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. *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
string sgf_viewer_command = "quarry %s";
// string sgf_viewer_command = "c:\\programs\\winmgt\\winmgt %s";
void create(string|void _status, string|void _text)
static function crash_callback = 0;
function trace_callback = 0;
// Send a command to the go engine.
GtpResponse send_command(string s)
s = id_number + " " + s + "\n";
GtpResponse response = GtpResponse();
string s = engine_out->gets();
// FIXME: This is probably not adequate.
response->status = s[0..0];
sscanf(s[1..], "%*d %s", response->text);
response->text += "\n" + s;
// Tell the program to stop playing.
static void program_trace_reader()
string s = engine_err->gets();
void create(array(string) program_start_array,
function|void crash_callback_)
command_line = program_start_array * " ";
crash_callback = crash_callback_;
engine_in = Stdio.File();
engine_out = Stdio.FILE();
engine_err = Stdio.FILE();
engine = Process.create_process(program_start_array,
(["stdin":engine_in->pipe(),
"stdout":engine_out->pipe(),
"stderr":engine_err->pipe()]));
thread_create(program_trace_reader);
constant letters = "ABCDEFGHJKLMNOPQRSTUVWXYZ" / "";
Image.Fonts.Font small_font;
array(string) white_stones;
array(string) black_stones;
class Markup(string vertex, string symbol, string color)
void draw(Image.Image image)
vertex = upper_case(vertex);
[int x, int y] = vertex_to_pixel_coord(vertex);
image->setcolor(@Image.Color(color)->rgb());
if (sscanf(symbol, "text:%s", string text) == 1)
Image.Image text_image = font->write(text);
if (text_image->xsize() >= 0.9*spacing)
text_image = small_font->write(text);
int width = text_image->xsize();
int height = text_image->ysize();
image->paste(text_image * 0 + ({220, 150, 50}), x - width / 2,
image->paste_alpha_color(text_image, x - width / 2,
image->circle(x, y, spacing / 3, spacing / 3);
image->line(x - delta, y - delta, x - delta, y + delta);
image->line(x - delta, y + delta, x + delta, y + delta);
image->line(x + delta, y + delta, x + delta, y - delta);
image->line(x + delta, y - delta, x - delta, y - delta);
image->line(x - delta, y - delta, x - delta, y + delta);
image->line(x - delta, y + delta, x + delta, y + delta);
image->line(x + delta, y + delta, x + delta, y - delta);
image->line(x + delta, y - delta, x - delta, y - delta);
image->line(x - delta, y + delta, x + delta, y + delta);
image->line(x + delta, y + delta, x, y - delta);
image->line(x, y - delta, x - delta, y + delta);
draw_disc(image, x, y, spacing / 6);
draw_disc(image, x, y, spacing / 9);
draw_disc(image, x, y, spacing / 4);
draw_disc(image, x, y, spacing / 2, 0.5);
werror("Unknown symbol: " + symbol + "\n");
static void create(int boardsize_, int gobansize_)
spacing = (int) (gobansize / (boardsize + 1.5));
offset = (gobansize - spacing * (boardsize - 1)) / 2;
font = Image.Fonts.Font(font_filename, spacing / 2);
small_font = Image.Fonts.Font(font_filename, spacing / 3);
void add_stones(string color, string|array(string) stones)
white_stones |= Array.arrayify(stones);
else if (color == "BLACK")
black_stones |= Array.arrayify(stones);
int occupied(string vertex)
return (has_value(white_stones, vertex)
|| has_value(black_stones, vertex));
void add_symbol(string vertex, string name, string color)
markups += ({Markup(upper_case(vertex), name, color)});
void add_text(string vertex, string text, string color)
markups += ({Markup(upper_case(vertex), "text:" + text, color)});
Image.Image board = Image.Image(gobansize, gobansize);
board = board->clear(220, 150, 50);
draw_letters_and_numbers(board);
foreach (black_stones, string stone)
draw_stone(board, "BLACK", stone);
foreach (white_stones, string stone)
draw_stone(board, "WHITE", stone);
static void draw_grid(Image.Image board)
int end = start + (boardsize - 1) * spacing;
for (int k = 0; k < boardsize; k++)
int kth = start + k * spacing;
board->setcolor(0, 0, 0);
board->line(start, kth, end, kth);
board->line(kth, start, kth, end);
static void draw_hoshi_marks(Image.Image board)
int a = 2 + (boardsize >= 12);
int b = boardsize - a - 1;
if ((boardsize % 2 == 0 && boardsize >= 8)
|| (boardsize % 2 == 1 && boardsize >= 9))
draw_disc(board, offset + a * spacing,
offset + a * spacing, 0.1 * spacing);
draw_disc(board, offset + a * spacing,
offset + b * spacing, 0.1 * spacing);
draw_disc(board, offset + b * spacing,
offset + a * spacing, 0.1 * spacing);
draw_disc(board, offset + b * spacing,
offset + b * spacing, 0.1 * spacing);
if (boardsize % 2 == 1 && boardsize >= 5)
draw_disc(board, offset + c * spacing,
offset + c * spacing, 0.1 * spacing);
if (boardsize % 2 == 1 && boardsize >= 13)
draw_disc(board, offset + a * spacing,
offset + c * spacing, 0.1 * spacing);
draw_disc(board, offset + b * spacing,
offset + c * spacing, 0.1 * spacing);
draw_disc(board, offset + c * spacing,
offset + a * spacing, 0.1 * spacing);
draw_disc(board, offset + c * spacing,
offset + b * spacing, 0.1 * spacing);
static void draw_letters_and_numbers(Image.Image board)
int end = start + (boardsize - 1) * spacing;
for (int k = 0; k < boardsize; k++)
int kth = start + k * spacing;
Image.Image number = font->write((string) (boardsize - k));
int width = number->xsize();
int height = number->ysize();
board->paste_alpha_color(number,
(start - spacing / 2 - width) / 2 - 1,
board->paste_alpha_color(number,
end + (start + spacing / 2 - width) / 2 + 1,
Image.Image letter = font->write(letters[k]);
height = letter->ysize();
board->paste_alpha_color(letter,
(start - spacing / 2 - height) / 2 - 1);
board->paste_alpha_color(letter,
end + (start + spacing / 2 - height) / 2 + 1);
static void draw_stone(Image.Image board, string color, string vertex)
[x, y] = vertex_to_xy(upper_case(vertex));
board->setcolor(0, 0, 0);
else if (color == "WHITE")
board->setcolor(255, 255, 255);
board->setcolor(128, 128, 128);
float radius = (spacing + 1) / 2.0;
draw_disc(board, start + x * spacing, start + y * spacing, radius);
array(int) vertex_to_xy(string vertex)
int x = search(letters, vertex[0..0]);
int y = boardsize - (int) vertex[1..];
string xy_to_vertex(int x, int y)
return letters[x] + (boardsize - y);
static array(int) vertex_to_pixel_coord(string vertex)
[int x, int y] = vertex_to_xy(vertex);
return ({offset + x * spacing, offset + y * spacing});
string pixel_coord_to_vertex(int|float pixel_x, int|float pixel_y)
int x = (int) floor((pixel_x - offset + spacing / 2.0) / spacing);
int y = (int) floor((pixel_y - offset + spacing / 2.0) / spacing);
if (x < 0 || x >= boardsize || y < 0 || y >= boardsize)
return sprintf("%s%d", letters[x], boardsize - y);
static void draw_disc(Image.Image image, int|float x, int|float y,
int|float r, float|void alpha)
array(float) coords = ({});
for (int k = 0; k < N; k++)
coords += ({r + 0.5 + r * cos(2 * 3.14159265 * k / N),
r + 0.5 + r * sin(2 * 3.14159265 * k / N)});
Image.Image disc = Image.Image((int) ceil(2*r), (int) ceil(2*r));
disc->setcolor(255, 255, 255);
image->paste_alpha_color(disc*alpha, (int) (x - 0.5 * disc->xsize()),
(int) (y - 0.5 * disc->ysize()));
string font_filename = "";
int main(int argc, array(string) argv)
werror("Usage: %s TEST-FILE:TEST-NUMBER\n", basename(argv[0]));
SimpleGtp engine = SimpleGtp("../interface/gnugo --quiet --mode gtp -w -t -d0x101840" / " ");
werror("Failed to start engine.");
Controller controller = Controller(engine, argv[1..]);
array(string) recursive_find_files(string dir, string suffix)
array(string) found_files = ({});
foreach (get_dir(dir), string filename)
string full_name = dir + "/" + filename;
if (Stdio.is_dir(full_name))
found_files += recursive_find_files(full_name, suffix);
else if (has_suffix(filename, suffix))
found_files += ({full_name});
if (getenv("GNUGO_FONT"))
font_filename = getenv("GNUGO_FONT");
// Search for fonts below /usr/share/fonts.
array(string) font_files = recursive_find_files("/usr/share/fonts",
if (sizeof(font_files) == 0)
font_files = recursive_find_files("/usr/share/fonts", ".pfb");
if (sizeof(font_files) == 0)
werror("No font found while searching below /usr/share/fonts.\n");
werror("Locate a font file with suffix .ttf (truetype) or .pfb (type1)\n");
werror("and point to it from the environment variable GNUGO_FONT.\n");
// Compute the length of the filename proper, i.e. without the
int fontlength(string s) {
return sizeof((s / "/")[-1]);
// Choose the one with shortest name (arbitrary but may avoid e.g.
font_filename = font_files[0];
foreach (font_files[1..], string font_file)
if (fontlength(font_filename) > fontlength(font_file))
font_filename = font_file;
else if (fontlength(font_filename) == fontlength(font_file)
&& has_value(lower_case(font_filename), "mono"))
font_filename = font_file;
GTK.ScrolledWindow scrolled_data_window;
Controller parent; //Evil. Used for callbacks.
mapping(string:array(string)) worms = ([]);
mapping(string:array(string)) dragons = ([]);
int worms_initialized = 0;
int dragons_initialized = 0;
array(string) complete_test;
function on_board_click_callback;
static void create(SimpleGtp engine_,
array(string) complete_test_, string testcase_command_,
function callback, string name_,
complete_test = complete_test_;
testcase_command = testcase_command_;
werror("%s\n", send_command("showboard"));
int boardsize = (int) send_command("query_boardsize");
on_board_click_callback = callback;
scrolled_data_window = GTK.ScrolledWindow();
scrolled_data_window->set_policy(GTK.POLICY_AUTOMATIC,
scrolled_data_window->add(clist);
static void setup_board(int boardsize)
goban = Goban(boardsize, 600);
goban->add_stones("WHITE", send_command("list_stones white") / " ");
goban->add_stones("BLACK", send_command("list_stones black") / " ");
Image.Image im = goban->draw_board();
gdk_image = GDK.Image(0)->set(im);
gtk_image = GTK.Image(gdk_image);
goban_widget = GTK.EventBox()->add(gtk_image);
goban_widget->add_events(GDK.ButtonPressMask);
goban_widget->add_events(GDK.KeyPressMask);
goban_widget->signal_connect_new("button_press_event",
button_pressed_on_board);
goban_widget->signal_connect_new("key_press_event",
void new_testcase(array(string) complete_test_, string testcase_command_)
werror("Loading new testcase.\n");
complete_test = complete_test_;
testcase_command = testcase_command_;
werror("%s\n", send_command("showboard"));
int boardsize = (int) send_command("query_boardsize");
werror("Loaded new testcase.\n");
goban = Goban(boardsize, 600);
goban->add_stones("WHITE", send_command("list_stones white") / " ");
goban->add_stones("BLACK", send_command("list_stones black") / " ");
static void load_testcase()
foreach(complete_test, string testline) {
if (!has_value("0123456789 #", testline[0..0]))
engine->trace_callback = collect_traces;
result = send_command(testcase_command);
engine->trace_callback = 0;
static void collect_traces(string s)
foreach (send_command("dragon_stones") / "\n", string dragon)
dragons[(dragon / " ")[0]] = dragon / " " - ({""});
foreach (send_command("worm_stones") / "\n", string worm)
worms[(worm / " ")[0]] = worm / " " - ({""});
void add_markup(int mode)
function add_suitable_markup = ({add_worms_and_dragons_markup,
add_move_generation_markup,
add_reading_markup})[mode];
static void add_worms_and_dragons_markup()
mapping status_colors = (["alive":"green",
mapping safety_colors = (["alive":"green",
"tactically dead":"brown",
"inessential":"orange"]);
if (parent->dragon_status_button->get_active())
if (!dragons_initialized)
foreach (dragons; string dragon; array(string) stones)
string status = get_worm_or_dragon_data("dragon", "status",
foreach (stones, string stone)
goban->add_symbol(stone, "dot",
else if (parent->dragon_safety_button->get_active())
if (!dragons_initialized)
foreach (dragons; string dragon; array(string) stones)
string safety = get_worm_or_dragon_data("dragon", "safety",
foreach (stones, string stone)
goban->add_symbol(stone, "dot",
else if (parent->worm_status_button->get_active())
foreach (worms; string worm; array(string) stones)
string attack = get_worm_or_dragon_data("worm", "attack_code",
string defense = get_worm_or_dragon_data("worm",
foreach (stones, string stone)
goban->add_symbol(stone, "dot",
static void add_move_generation_markup()
if ((parent->top_moves_button->get_active()
|| parent->all_moves_button->get_active())
&& (has_prefix(testcase_command, "reg_genmove")
|| has_prefix(testcase_command, "restricted_genmove")))
string answers = parent->expected_result;
if (answers[0..0] == "!") {
if (has_prefix(testcase_command, "restricted_genmove"))
foreach ((testcase_command / " ")[2..], string allowed_move)
if (color == "green" ^ has_value(answers, allowed_move))
goban->add_symbol(allowed_move, "triangle", "red");
goban->add_symbol(allowed_move, "triangle", "green");
foreach (answers / "|" - ({""}), string answer)
goban->add_symbol(answer, "big_square", color);
color = (testcase_command / " ")[1];
goban->add_symbol(result, "stone", color);
if (parent->top_moves_button->get_active())
array(string) top_moves = send_command("top_moves") / " ";
for (int k = 0; k < sizeof(top_moves) / 2; k++)
goban->add_text(top_moves[2 * k],
top_moves[2 * k + 1], "blue");
else if (parent->all_moves_button->get_active())
array(string) all_moves = send_command("all_move_values") / "\n";
foreach (all_moves, string move_value)
sscanf(move_value, "%s%*[ ]%s", string vertex, string value);
goban->add_text(vertex, value, "blue");
else if (parent->delta_territory_button->get_active()
&& parent->delta_territory_move != "PASS")
goban->add_symbol(parent->delta_territory_move, "stone", "gray");
parent->delta_territory_button_text
->set_text("delta territory for "
+ parent->delta_territory_move);
for (k = sizeof(traces) - 1; k >= 0; k--)
if (sscanf(traces[k], " " + parent->delta_territory_move
+ ": %*f - change in territory%*s") == 2
&& !has_value(traces[k], "cached"))
clist->append(({traces[k], "", ""}));
if (sscanf(traces[k], " %*s: - %*s") < 2)
clist->prepend(({traces[k], "", ""}));
" %*s: - %s territory change %s ",
string vertex, string value) == 3)
goban->add_text(vertex, value,
(float) value < 0.0 ? "red" : "blue");
clist->columns_autosize();
parent->set_title(this_object(),
+ parent->delta_territory_move));
static mapping(string:mapping(string:string)) eye_data = 0;
static mapping(string:string) half_eye_data = ([]);
static mapping(string:mapping(string:string)) eye_types = 0;
static void add_eyes_markup()
eye_data = (["white":([]), "black":([])]);
eye_types = (["white":([]), "black":([])]);
for (int y = 0; y < goban->boardsize; y++)
for (int x = 0; x < goban->boardsize; x++)
string vertex = goban->xy_to_vertex(x, y);
multiset(string) is_marginal = (<>);
foreach (({"white", "black"}), string color)
string this_eye = send_command("eye_data " + color +
if (!Regexp("origin +PASS")->match(this_eye))
eye_data[color][vertex] = this_eye;
if (Regexp("marginal +1")->match(this_eye))
if (!eye_data["white"][vertex]
&& !eye_data["black"][vertex])
string half_eye = send_command("half_eye_data " + vertex);
if (!Regexp("type +0")->match(half_eye))
half_eye_data[vertex] = half_eye;
foreach (({"white", "black"}), string color)
if (!eye_data[color][vertex])
eye_types[color][vertex] = "marginal";
else if (Regexp("type +HALF_EYE")->match(half_eye))
eye_types[color][vertex] = "half";
eye_types[color][vertex] = "proper";
if (parent->white_eyes_button->get_active())
foreach (eye_types[color]; string vertex; string eye_type)
mapping (string:string) grayish = (["white" : "#c0c0c0",
if (eye_type == "proper")
goban->add_symbol(vertex, "big_dot", color);
else if (eye_type == "half")
goban->add_symbol(vertex, "big_dot", grayish[color]);
goban->add_symbol(vertex, "dot", grayish[color]);
static void add_influence_markup()
if (parent->initial_w_influence_dragons_known_button->get_active())
command = "initial_influence white";
else if (parent->initial_b_influence_dragons_known_button->get_active())
command = "initial_influence black";
else if (parent->after_move_influence_button->get_active())
if (parent->move_influence_move == "PASS")
command = sprintf("move_influence %s %s",
parent->current_move_color,
parent->move_influence_move);
goban->add_symbol(parent->move_influence_move, "stone", "gray");
else if (parent->followup_influence_button->get_active())
if (parent->move_influence_move == "PASS")
command = sprintf("followup_influence %s %s",
parent->current_move_color,
parent->move_influence_move);
goban->add_symbol(parent->move_influence_move, "stone", "gray");
if (parent->influence_regions_button->get_active())
what_data = "influence_regions";
if (parent->territory_value_button->get_active())
what_data = "territory_value";
else if (parent->white_influence_button->get_active())
what_data = "white_influence";
else if (parent->black_influence_button->get_active())
what_data = "black_influence";
else if (parent->white_influence_button->get_active())
what_data = "white_influence";
else if (parent->black_influence_button->get_active())
what_data = "black_influence";
else if (parent->white_strength_button->get_active())
what_data = "white_strength";
else if (parent->black_strength_button->get_active())
what_data = "black_strength";
else if (parent->white_permeability_button->get_active())
what_data = "white_permeability";
else if (parent->black_permeability_button->get_active())
what_data = "black_permeability";
else if (parent->white_attenuation_button->get_active())
what_data = "white_attenuation";
else if (parent->black_attenuation_button->get_active())
what_data = "black_attenuation";
else if (parent->non_territory_button->get_active())
what_data = "non_territory";
string result = send_command(command + " " + what_data);
array(string) values = (replace(result, "\n", " ") / " ") - ({""});
for (int y = 0; y < goban->boardsize; y++)
for (int x = 0; x < goban->boardsize; x++)
string vertex = goban->xy_to_vertex(x, y);
if (what_data == "influence_regions")
int value = (int) values[k];
mapping(int:string) sizes = ([3:"big_dot",
mapping(int:string) colors = ([3:"white",
goban->add_symbol(vertex, sizes[value], colors[value]);
else if (has_value(what_data, "permeability"))
if ((float) values[k] != 1.0)
goban->add_text(vertex, values[k], "blue");
else if (what_data == "non_territory")
if (!goban->occupied(vertex))
int value = (int) values[k];
goban->add_symbol(vertex, "dot", "black");
goban->add_symbol(vertex, "dot", "white");
goban->add_symbol(vertex, "dot", "gray");
else if ((float) values[k] > 0.0)
goban->add_text(vertex, values[k], "blue");
else if ((float) values[k] < 0.0)
goban->add_text(vertex, values[k], "red");
static void add_reading_markup()
if ((parent->connection_reading_button->get_active()
|| parent->semeai_reading_button->get_active())
&& parent->first_vertex != "")
goban->add_symbol(parent->first_vertex,
gdk_image->set(goban->draw_board());
void show_worm_data(string vertex)
string worm_data = send_command("worm_data " + vertex);
foreach ((worm_data / "\n")[1..], string data_item)
sscanf(data_item, "%s%*[ ]%s", string field, string value);
clist->append(({field, value, ""}));
clist->columns_autosize();
parent->set_title(this_object(), "Worm data for " + vertex);
void show_dragon_data(string vertex, int part)
string dragon_data = send_command("dragon_data " + vertex);
array(string) selected_data;
selected_data = (dragon_data / "\n")[1..20];
selected_data = (dragon_data / "\n")[21..];
foreach (selected_data, string data_item)
sscanf(data_item, "%s%*[ ]%s", string field, string value);
clist->append(({field, value, ""}));
clist->columns_autosize();
parent->set_title(this_object(), "Dragon data for " + vertex);
void show_move_reasons(string vertex)
string move_reasons = send_command("move_reasons " + vertex);
foreach ((move_reasons / "\n"), string move_reason)
clist->append(({move_reason, "", ""}));
for (k = sizeof(traces) - 1; k >= 0; k--)
if (sscanf(traces[k], "Move generation values "
+ vertex + " to %*s") == 1)
array(string) interesting_lines = ({traces[k]});
if (sscanf(traces[k], " " + vertex + ": %*s") == 1)
interesting_lines += ({traces[k]});
else if (sscanf(traces[k], " " + vertex + ": %*s") != 1)
clist->append(({"", "", ""}));
foreach (reverse(interesting_lines), string line)
clist->append(({line, "", ""}));
int add_continuation_lines = 0;
foreach (traces, string line)
if (sscanf(line, "pattern %*s matched at " + vertex + "%s",
clist->append(({"", "", ""}));
add_continuation_lines = 1;
clist->append(({line, "", ""}));
else if (has_prefix(line, "...") && add_continuation_lines)
clist->append(({line, "", ""}));
add_continuation_lines = 0;
/* Look for blunder devaluation */
foreach (traces, string line)
if (has_prefix(line, "Move at " + vertex + " is"))
clist->append(({line, "", ""}));
clist->columns_autosize();
parent->set_title(this_object(), "Move reasons for " + vertex);
void show_eye_data(string vertex)
if (parent->white_eyes_button->get_active())
if (eye_data[color][vertex])
foreach (eye_data[color][vertex] / "\n", string data_item)
sscanf(data_item, "%s%*[ ]%s", string field, string value);
clist->append(({field, value, ""}));
if (half_eye_data[vertex])
clist->append(({"", "", ""}));
foreach (half_eye_data[vertex] / "\n", string data_item)
sscanf(data_item, "%s%*[ ]%s", string field, string value);
clist->append(({field, value, ""}));
clist->columns_autosize();
parent->set_title(this_object(), color + " eye data for " + vertex);
static void button_pressed_on_board(GDK.Event event)
// werror("Button: %O\n", (mapping) event);
string vertex = goban->pixel_coord_to_vertex(event->x, event->y);
on_board_click_callback(vertex);
static void key_pressed_on_board(GDK.Event event)
// werror("Key: %O\n", (mapping) event);
// First thing to find out is what the event object contains and
// how we can get the mouse position when the key was pressed.
string send_command(string command)
result = engine->send_command(command)->text;
mapping(string:string) worm_and_dragon_cache = ([]);
static string get_worm_or_dragon_data(string worm_or_dragon, string field,
string command = worm_or_dragon + "_data " + vertex;
string data = worm_and_dragon_cache[command];
data = send_command(command);
worm_and_dragon_cache[command] = data;
foreach (data / "\n", string row)
if (has_prefix(row, field))
sscanf(row, "%*s%*[ ]%s", string value);
void do_reading(string reset_counter, string get_counter,
string sgffilename, string sgf_viewer_cmd,
string first_command, string second_command)
send_command("clear_cache");
if (sizeof(parent->viewers) > 1)
sgffilename += "." + replace(name, " ", "_");
send_command("start_sgftrace");
send_command(reset_counter);
result = send_command(first_command);
clist->append(({first_command, result,
"(" + send_command(get_counter) + " nodes)"}));
if (result[0..0] != "0" && second_command != "")
send_command(reset_counter);
string result = send_command(second_command);
clist->append(({second_command, result,
"(" + send_command(get_counter) + " nodes)"}));
send_command("finish_sgftrace " + sgffilename);
if (sgf_viewer_cmd != "")
Process.create_process(sprintf(sgf_viewer_cmd, sgffilename)
clist->columns_autosize();
parent->set_title(this_object(), "Reading result");
array(RegressionViewer) viewers = ({});
array(GTK.Widget) viewer_title_widgets = ({});
string current_move_color = "";
GTK.Notebook controller_notebook;
GTK.Notebook gobans_notebook;
GTK.Notebook data_notebook;
GTK.Notebook selector_notebook;
GTK.ScrolledWindow scrolled_testcase_text;
GTK.RadioButton worm_data_button;
GTK.RadioButton dragon_data1_button;
GTK.RadioButton dragon_data2_button;
GTK.RadioButton worm_status_button;
GTK.RadioButton dragon_status_button;
GTK.RadioButton dragon_safety_button;
GTK.RadioButton top_moves_button;
GTK.RadioButton all_moves_button;
GTK.RadioButton delta_territory_button;
GTK.Label delta_territory_button_text;
// GTK.RadioButton initial_w_influence_dragons_unknown_button;
// GTK.RadioButton initial_b_influence_dragons_unknown_button;
GTK.RadioButton initial_w_influence_dragons_known_button;
GTK.RadioButton initial_b_influence_dragons_known_button;
GTK.RadioButton after_move_influence_button;
GTK.Label after_move_influence_button_text;
GTK.RadioButton followup_influence_button;
GTK.Label followup_influence_button_text;
GTK.RadioButton influence_regions_button;
GTK.RadioButton territory_value_button;
GTK.RadioButton white_influence_button;
GTK.RadioButton black_influence_button;
GTK.RadioButton white_strength_button;
GTK.RadioButton black_strength_button;
GTK.RadioButton white_permeability_button;
GTK.RadioButton black_permeability_button;
GTK.RadioButton white_attenuation_button;
GTK.RadioButton black_attenuation_button;
GTK.RadioButton non_territory_button;
GTK.RadioButton white_eyes_button;
GTK.RadioButton black_eyes_button;
GTK.RadioButton tactical_reading_button, owl_reading_button,
owl_does_attack_button, owl_does_defend_button,
connection_reading_button, semeai_reading_button;
GTK.CheckButton sgf_traces_button, sgf_viewer_button;
GTK.Entry sgf_filename_entry, sgf_viewer_entry;
GTK.Button new_testcase_button, new_engine_button;
GTK.Button next_testcase_button, prev_testcase_button;
GTK.Entry new_testcase_entry, engine_path_entry, engine_name_entry;
string delta_territory_move = "PASS";
string move_influence_move = "PASS";
string first_vertex = "";
// All lines from the test file shown at the top of the control window.
array(string) full_testcase;
// All lines from the test file which may be needed to load the
array(string) complete_testcase;
static mixed nasty_signal_id;
static void create(SimpleGtp engine_, array(string) testcases_)
if (!excerpt_testcase(testcases[0], engine_))
werror("Failed to load testcase.\n");
testcase_name = testcases[0];
= GTK.ScrolledWindow(GTK.Adjustment(), GTK.Adjustment())
->set_policy(GTK.POLICY_AUTOMATIC, GTK.POLICY_AUTOMATIC)
testcase_text = GTK.Label(full_testcase * "\n")
->set_justify(GTK.JUSTIFY_LEFT)
->set_alignment(0.0, 0.0);
scrolled_testcase_text->add(testcase_text);
main_window = GTK.Window(GTK.WindowToplevel);
controller_notebook = GTK.Notebook();
controller_notebook->set_tab_pos(GTK.POS_LEFT);
gobans_notebook = (GTK.Notebook()
data_notebook = (GTK.Notebook()
selector_notebook = (GTK.Notebook()
->set_tab_pos(GTK.POS_TOP));
selector_notebook->signal_connect_new("switch_page", change_engine);
GTK.Widget main_window_contents
->pack_start(GTK.Hbox(0, 24)
->pack_start(scrolled_testcase_text, 0, 0, 24)
->pack_start(GTK.Alignment(0.0, 0.5, 0.0, 0.0)
->add(selector_notebook),
->pack_start(controller_notebook, 0, 0, 0)
->pack_start(GTK.Alignment(0.0, 0.0, 0.0, 0.0)
main_window->add(main_window_contents);
main_window->set_title(testcases[0]);
main_window->signal_connect_new("destroy", quit);
if (has_prefix(testcase_command, "reg_genmove")
|| has_prefix(testcase_command, "restricted_genmove"))
sscanf(testcase_command, "%*s %s", string color);
if (lower_case(color[0..0]) == "w")
current_move_color = "white";
current_move_color = "black";
worm_data_button = GTK.RadioButton("worm data");
dragon_data1_button = GTK.RadioButton("dragon data, part 1",
dragon_data2_button = GTK.RadioButton("dragon data, part 2",
worm_status_button = GTK.RadioButton("worm status");
dragon_status_button = GTK.RadioButton("dragon status",
dragon_safety_button = GTK.RadioButton("dragon safety",
worm_status_button->signal_connect_new("clicked",
dragon_status_button->signal_connect_new("clicked",
dragon_safety_button->signal_connect_new("clicked",
top_moves_button = GTK.RadioButton("top moves");
all_moves_button = GTK.RadioButton("all moves", top_moves_button);
delta_territory_button_text = GTK.Label("delta territory for PASS");
delta_territory_button_text->set_alignment(0.0, 0.0);
delta_territory_button = GTK.RadioButton(0, top_moves_button);
delta_territory_button->add(delta_territory_button_text);
top_moves_button->signal_connect_new("clicked", markup_button_pressed);
all_moves_button->signal_connect_new("clicked", markup_button_pressed);
delta_territory_button->signal_connect_new("clicked",
white_eyes_button = GTK.RadioButton("white eyes");
black_eyes_button = GTK.RadioButton("black eyes", white_eyes_button);
white_eyes_button->signal_connect_new("clicked",
black_eyes_button->signal_connect_new("clicked",
// initial_w_influence_dragons_unknown_button =
// GTK.RadioButton("white influence, dragons unknown");
// initial_b_influence_dragons_unknown_button =
// GTK.RadioButton("black influence, dragons unknown",
// initial_w_influence_dragons_unknown_button);
initial_w_influence_dragons_known_button =
GTK.RadioButton("white influence, dragons known");
initial_b_influence_dragons_known_button =
GTK.RadioButton("black influence, dragons known",
initial_w_influence_dragons_known_button);
after_move_influence_button_text =
GTK.Label("after move influence for PASS");
after_move_influence_button_text->set_alignment(0.0, 0.0);
after_move_influence_button =
GTK.RadioButton(0, initial_w_influence_dragons_known_button);
after_move_influence_button->add(after_move_influence_button_text);
followup_influence_button_text =
GTK.Label("followup influence for PASS");
followup_influence_button_text->set_alignment(0.0, 0.0);
followup_influence_button =
GTK.RadioButton(0, initial_w_influence_dragons_known_button);
followup_influence_button->add(followup_influence_button_text);
influence_regions_button = GTK.RadioButton("influence regions");
territory_value_button = GTK.RadioButton("territory value",
influence_regions_button);
white_influence_button = GTK.RadioButton("white influence",
influence_regions_button);
black_influence_button = GTK.RadioButton("black influence",
influence_regions_button);
white_strength_button = GTK.RadioButton("white strength",
influence_regions_button);
black_strength_button = GTK.RadioButton("black strength",
influence_regions_button);
white_permeability_button = GTK.RadioButton("white permeability",
influence_regions_button);
black_permeability_button = GTK.RadioButton("black permeability",
influence_regions_button);
white_attenuation_button = GTK.RadioButton("white attenuation",
influence_regions_button);
black_attenuation_button = GTK.RadioButton("black attenuation",
influence_regions_button);
non_territory_button = GTK.RadioButton("non-territory",
influence_regions_button);
({initial_w_influence_dragons_known_button,
initial_b_influence_dragons_known_button,
after_move_influence_button,
followup_influence_button,
influence_regions_button,
white_permeability_button,
black_permeability_button,
white_attenuation_button,
black_attenuation_button,
non_territory_button})->signal_connect_new("clicked",
tactical_reading_button = GTK.RadioButton("tactical reading");
owl_reading_button = GTK.RadioButton("owl reading",
tactical_reading_button);
owl_does_attack_button = GTK.RadioButton("owl_does_attack",
tactical_reading_button);
owl_does_defend_button = GTK.RadioButton("owl_does_defend",
tactical_reading_button);
connection_reading_button = GTK.RadioButton("connection reading",
tactical_reading_button);
semeai_reading_button = GTK.RadioButton("semeai reading",
tactical_reading_button);
sgf_traces_button = (GTK.CheckButton("save sgf traces to")
sgf_filename_entry = GTK.Entry();
sgf_filename_entry->set_text("vars.sgf");
sgf_filename_entry->set_editable(1);
sgf_viewer_button = GTK.CheckButton("start sgf viewer as");
sgf_viewer_entry = GTK.Entry()->set_text(sgf_viewer_command)
sgf_viewer_button->signal_connect("toggled", sgf_viewer_button_toggled);
sgf_traces_button->signal_connect("toggled", sgf_traces_button_toggled);
sgf_stuff = GTK.Table(2, 2, 0)
->attach_defaults(sgf_traces_button, 0, 1, 0, 1)
->attach_defaults(sgf_filename_entry, 1, 2, 0, 1)
->attach_defaults(sgf_viewer_button, 0, 1, 1, 2)
->attach_defaults(sgf_viewer_entry, 1, 2, 1, 2);
new_testcase_entry = GTK.Entry();
new_testcase_entry->set_text(testcases[0]);
new_testcase_entry->set_editable(1);
new_testcase_button = GTK.Button("Load new testcase");
new_testcase_button->signal_connect_new("clicked", new_testcase);
new_testcase_entry->signal_connect_new("activate", new_testcase);
prev_testcase_button = GTK.Button("Previous testcase");
prev_testcase_button->signal_connect_new("clicked", prev_testcase);
prev_testcase_button->set_sensitive(0);
next_testcase_button = GTK.Button("Next testcase");
next_testcase_button->signal_connect_new("clicked", next_testcase);
engine_path_entry = GTK.Entry();
engine_path_entry->set_text("../interface/gnugo");
engine_path_entry->set_editable(1);
engine_name_entry = GTK.Entry();
engine_name_entry->set_text("Engine 2");
engine_name_entry->set_editable(1);
engine_path_entry->signal_connect_new("activate", select_new_engine);
new_engine_button = GTK.Button("Start new engine");
new_engine_button->signal_connect_new("clicked", select_new_engine);
GTK.Widget worms_and_dragons_page
->pack_start(worm_data_button, 0, 0, 0)
->pack_start(dragon_data1_button, 0, 0, 0)
->pack_start(dragon_data2_button, 0, 0, 0)
->pack_start(GTK.Label(""), 0, 0, 0)
->pack_start(worm_status_button, 0, 0, 0)
->pack_start(dragon_status_button, 0, 0, 0)
->pack_start(dragon_safety_button, 0, 0, 0));
controller_notebook->append_page(worms_and_dragons_page,
GTK.Label("worms and dragons"));
GTK.Widget move_generation_page
->pack_start(top_moves_button, 0, 0, 0)
->pack_start(all_moves_button, 0, 0, 0)
->pack_start(delta_territory_button, 0, 0, 0));
controller_notebook->append_page(move_generation_page,
GTK.Label("move generation"));
GTK.Widget eyes_page = (GTK.Vbox(0, 0)
->pack_start(white_eyes_button, 0, 0, 0)
->pack_start(black_eyes_button, 0, 0, 0));
controller_notebook->append_page(eyes_page, GTK.Label("eyes"));
GTK.Widget influence_page
->pack_start(GTK.Vbox(0,0)
// ->add(initial_w_influence_dragons_unknown_button)
// ->add(initial_b_influence_dragons_unknown_button)
->add(initial_w_influence_dragons_known_button)
->add(initial_b_influence_dragons_known_button)
->add(after_move_influence_button)
->add(followup_influence_button), 0, 0, 0)
->pack_start(GTK.Label(""), 0, 0, 0)
->pack_start(GTK.Hbox(0,12)
->pack_start(influence_regions_button, 0, 0, 0)
->pack_start(territory_value_button, 0, 0, 0)
->pack_start(non_territory_button, 0, 0, 0)
->pack_start(white_influence_button, 0, 0, 0)
->pack_start(black_influence_button, 0, 0, 0))
->pack_start(white_strength_button, 0, 0, 0)
->pack_start(black_strength_button, 0, 0, 0)
->pack_start(white_permeability_button, 0, 0, 0)
->pack_start(black_permeability_button, 0, 0, 0)
->pack_start(white_attenuation_button, 0, 0, 0)
->pack_start(black_attenuation_button, 0, 0, 0)),
controller_notebook->append_page(influence_page,
->pack_start(tactical_reading_button, 0, 0, 0)
->pack_start(owl_reading_button, 0, 0, 0)
->pack_start(owl_does_attack_button, 0, 0, 0)
->pack_start(owl_does_defend_button, 0, 0, 0)
->pack_start(connection_reading_button, 0, 0, 0)
->pack_start(semeai_reading_button, 0, 0, 0)
->pack_start(GTK.Label(""), 0, 0, 0)
->pack_start(sgf_stuff, 0, 0, 0));
controller_notebook->append_page(reading_page, GTK.Label("reading"));
->pack_start(engine_path_entry, 0, 0, 0));
engines_page->pack_start(engine_name_entry, 0, 0, 0);
engines_page->pack_start(GTK.Alignment(1.0, 0.0, 0.0, 0.0)
->add(new_engine_button), 0, 0, 0)
->pack_start(new_testcase_entry, 0, 0, 0)
->pack_start(new_testcase_button, 0, 0, 0);
if (sizeof(testcases) > 1) {
= (GTK.Hbox(0, 12)->pack_start(prev_testcase_button, 0, 0, 0)
->pack_start(next_testcase_button, 0, 0, 0));
engines_page->pack_start(next_prev, 0, 0, 0);
controller_notebook->append_page(engines_page->set_border_width(12),
nasty_signal_id = controller_notebook->signal_connect_new("switch_page",
if (has_prefix(testcase_command, "reg_genmove")
|| has_prefix(testcase_command, "restricted_genmove")) {
controller_notebook->show_all();
controller_notebook->set_page(1);
add_regression_viewer(RegressionViewer(engine_,
button_pressed_on_a_board,
add_markup(controller_notebook->get_current_page());
static void select_new_engine()
string new_engine_path = engine_path_entry->get_text();
if (!Stdio.is_file(new_engine_path)) {
viewers->clist->append(({"The engine path does not point to a file.\n", "", ""}));
SimpleGtp new_engine = SimpleGtp((new_engine_path + " --quiet --mode gtp -w -t -d0x101840") / " ");
werror("Failed to start new engine.\n");
RegressionViewer(new_engine, complete_testcase,
testcase_command, button_pressed_on_a_board,
engine_name_entry->get_text(),
engine_name_entry->set_text(sprintf("Engine %d", sizeof(viewers) + 1));
static void add_regression_viewer(RegressionViewer viewer)
viewer->goban_widget->show_all();
gobans_notebook->append_page(viewer->goban_widget, 0);
GTK.Widget title_label = GTK.Label("");
viewer_title_widgets += ({ title_label });
GTK.Widget data_page = (GTK.Vbox(0, 2)
->pack_start(title_label, 0, 0, 0)
->add(viewer->scrolled_data_window)
data_notebook->append_page(data_page, 0);
->append_page((GTK.Alignment(0.0, 0.5, 0.0, 0.0)
->add(GTK.Label(viewer->engine->command_line))),
GTK.Label(viewer->name));
selector_notebook->show_all();
selector_notebook->set_page(sizeof(viewers) - 1);
void set_title(RegressionViewer viewer, string title)
for (int k = 0; k < sizeof(viewers); k++) {
if (viewers[k] == viewer)
viewer_title_widgets[k]->set_text(title);
static void markup_button_pressed(mixed ... foo)
add_markup(controller_notebook->get_current_page());
static void add_markup(int mode)
viewers->add_markup(mode);
static void change_engine(int engine_index)
gobans_notebook->set_page(engine_index);
data_notebook->set_page(engine_index);
void button_pressed_on_a_board(string vertex)
switch (controller_notebook->get_current_page()) {
if (worm_data_button->get_active())
viewers->show_worm_data(vertex);
else if (dragon_data1_button->get_active())
viewers->show_dragon_data(vertex, 1);
viewers->show_dragon_data(vertex, 2);
if (!delta_territory_button->get_active())
viewers->show_move_reasons(vertex);
delta_territory_move = vertex;
viewers->show_eye_data(vertex);
if (after_move_influence_button->get_active()
|| followup_influence_button->get_active())
move_influence_move = vertex;
after_move_influence_button_text
->set_text("after move influence for " + vertex);
followup_influence_button_text
->set_text("followup influence for " + vertex);
string reset_counter, get_counter;
if (sgf_viewer_button->get_active()) {
sgffilename = sgf_filename_entry->get_text();
sgf_viewer_cmd = sgf_viewer_entry->get_text();
else if (sgf_traces_button->get_active()) {
sgffilename = sgf_filename_entry->get_text();
|| tactical_reading_button->get_active()
|| owl_reading_button->get_active())
viewers->goban->clear_markup();
viewers->goban->add_symbol(vertex, "big_dot", "green");
if (tactical_reading_button->get_active()
|| owl_reading_button->get_active())
reset_counter = "reset_reading_node_counter";
get_counter = "get_reading_node_counter";
if (owl_reading_button->get_active())
reset_counter = "reset_owl_node_counter";
get_counter = "get_owl_node_counter";
viewers->do_reading(reset_counter, get_counter,
sgffilename, sgf_viewer_cmd,
prefix + "attack " + vertex,
prefix + "defend " + vertex);
else if (owl_does_attack_button->get_active()
|| owl_does_defend_button->get_active()
|| connection_reading_button->get_active()
|| semeai_reading_button->get_active())
else if (first_vertex != vertex)
if (owl_does_attack_button->get_active()) {
c1 = sprintf("owl_does_attack %s %s\n",
viewers->do_reading("reset_owl_node_counter",
sgffilename, sgf_viewer_cmd,
else if (owl_does_defend_button->get_active()) {
c1 = sprintf("owl_does_defend %s %s\n",
viewers->do_reading("reset_owl_node_counter",
sgffilename, sgf_viewer_cmd,
else if (connection_reading_button->get_active())
c1 = sprintf("connect %s %s\n",
viewers->do_reading("reset_connection_node_counter",
"get_connection_node_counter",
sgffilename, sgf_viewer_cmd,
c1 = sprintf("analyze_semeai %s %s",
c2 = sprintf("analyze_semeai %s %s", vertex,
// FIXME: We should use a semeai node counter rather
// than the owl node counter, except that it doesn't
viewers->do_reading("reset_owl_node_counter",
sgffilename, sgf_viewer_cmd,
static void this_new_testcase(string new_testcase)
werror("Trying to load new testcase %s.", new_testcase);
if (!excerpt_testcase(new_testcase, viewers[0]->engine))
werror("Failed to load testcase.\n");
testcase_name = new_testcase;
main_window->set_title(testcase_name);
testcase_text->set_text(full_testcase * "\n");
viewers->new_testcase(complete_testcase, testcase_command);
viewers->handle_testcase();
if (has_prefix(testcase_command, "reg_genmove")
|| has_prefix(testcase_command, "restricted_genmove")) {
controller_notebook->show_all();
controller_notebook->set_page(1);
static void new_testcase()
this_new_testcase(new_testcase_entry->get_text());
static void next_testcase()
if (testcase_index >= sizeof(testcases) - 1)
prev_testcase_button->set_sensitive(1);
if (testcase_index == sizeof(testcases) - 1)
next_testcase_button->set_sensitive(0);
this_new_testcase(testcases[testcase_index]);
static void prev_testcase()
werror("Error handling list of test cases!\n");
next_testcase_button->set_sensitive(1);
prev_testcase_button->set_sensitive(0);
this_new_testcase(testcases[testcase_index]);
// The engine parameter is only needed to find out the color to
// move when an sgf file is given. Since there can be multiple
// viewers we shouldn't use this engine object for anything else
// though. Notice also that no viewer has been set up yet at the
static int excerpt_testcase(string testcase, SimpleGtp engine)
if (sscanf(testcase, "%s:%d", filename, number) < 2)
if (!has_suffix(filename, ".tst")
&& !has_suffix(filename, ".sgf"))
string testfile = Stdio.read_file(filename);
if (has_suffix(filename, ".sgf"))
// Only sgf file provided. Fake a testcase.
string s = "loadsgf " + filename + " " + number;
string color = engine->send_command(s)->text;
testcase_command = "reg_genmove " + color;
full_testcase = ({s, testcase_command});
complete_testcase = ({s, testcase_command});
complete_testcase = ({});
array(string) testlines = testfile / "\n";
for (int k = 0; k < sizeof(testlines); k++)
string testline = testlines[k];
if (testline[0..0] >= "a" && testline[0..0] <= "z") {
if (testline[0..6] != "loadsgf")
complete_testcase += ({ testline });
complete_testcase = ({ testline });
else if (sscanf(testline, "%d %s", this_number, testline) == 2
&& this_number == number)
testcase_command = testline;
sscanf(testlines[k + 1], "#? [%s]", expected_result);
full_testcase += ({testlines[k]});
full_testcase += ({testlines[k + 1]});
if (has_value("0123456789 ", testline[0..0]))
full_testcase += ({testline});
void sgf_traces_button_toggled()
if (!sgf_traces_button->get_active())
sgf_viewer_button->set_active(0);
void sgf_viewer_button_toggled()
if (sgf_viewer_button->get_active())
sgf_traces_button->set_active(1);
void debug_callback(mixed ... args)
write("Debug callback:%O\n", args);
// Otherwise Pike errors occur.
controller_notebook->signal_disconnect(nasty_signal_id);