1#!/usr/bin/env pike
2
3/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
4 * This distributed with GNU Go, a go program.			     *
5 *                                                                   *
6 * Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005 and 2006       *
7 * by the Free Software Foundation.                                  *
8 *                                                                   *
9 * This program is free software; you can redistribute it and/or     *
10 * modify it under the terms of the GNU General Public License as    *
11 * published by the Free Software Foundation - version 3             *
12 * or (at your option) any later version.                            *
13 *                                                                   *
14 * This program is distributed in the hope that it will be useful,   *
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of    *
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the     *
17 * GNU General Public License in file COPYING for more details.      *
18 *                                                                   *
19 * You should have received a copy of the GNU General Public         *
20 * License along with this program; if not, write to the Free        *
21 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor,       *
22 * Boston, MA 02111, USA.                                            *
23\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
24
25// Defaults:
26// linux
27    string sgf_viewer_command = "quarry %s";
28// windows (for example)
29//  string sgf_viewer_command = "c:\\programs\\winmgt\\winmgt %s";
30
31class GtpResponse
32{
33    string status;
34    string text;
35
36    void create(string|void _status, string|void _text)
37    {
38	status = _status;
39	text = _text;
40    }
41
42    int success()
43    {
44	return status == "=";
45    }
46
47    int failure()
48    {
49	return status == "?";
50    }
51}
52
53class SimpleGtp
54{
55    static object engine;
56    static function crash_callback = 0;
57    function trace_callback = 0;
58
59    Stdio.File engine_in;
60    Stdio.FILE engine_out;
61    Stdio.FILE engine_err;
62
63    string command_line;
64
65    int id_number = 1;
66
67    // Send a command to the go engine.
68    GtpResponse send_command(string s)
69    {
70	s = id_number + " " + s + "\n";
71	id_number++;
72	engine_in->write(s);
73	int first_line = 1;
74	GtpResponse response = GtpResponse();
75	while (1)
76	{
77	    string s = engine_out->gets();
78	    if (!s)
79	    {
80		// FIXME: This is probably not adequate.
81		if (crash_callback)
82		    crash_callback();
83		engine_in->close();
84		engine_out->close();
85		break;
86	    }
87
88	    s -= "\r";
89
90	    if (first_line)
91	    {
92		if (s == "")
93		    continue;
94		response->status = s[0..0];
95		first_line = 0;
96		sscanf(s[1..], "%*d %s", response->text);
97	    }
98	    else
99	    {
100		if (s == "")
101		    break;
102		response->text += "\n" + s;
103	    }
104	}
105	return response;
106    }
107
108    // Tell the program to stop playing.
109    void quit()
110    {
111	crash_callback = 0;
112	send_command("quit");
113    }
114
115    static void program_trace_reader()
116    {
117	while (1)
118	{
119	    string s = engine_err->gets();
120	    if (!s)
121		break;
122	    s -= "\r";
123	    if (trace_callback)
124		trace_callback(s);
125	}
126
127	engine_err->close();
128    }
129
130    void create(array(string) program_start_array,
131		function|void crash_callback_)
132    {
133	command_line = program_start_array * " ";
134	crash_callback = crash_callback_;
135	engine_in = Stdio.File();
136	engine_out = Stdio.FILE();
137	engine_err = Stdio.FILE();
138	engine = Process.create_process(program_start_array,
139					(["stdin":engine_in->pipe(),
140					  "stdout":engine_out->pipe(),
141					  "stderr":engine_err->pipe()]));
142	thread_create(program_trace_reader);
143    }
144}
145
146class Goban
147{
148    constant letters = "ABCDEFGHJKLMNOPQRSTUVWXYZ" / "";
149    int boardsize;
150    int gobansize;
151    int spacing;
152    int offset;
153    Image.Fonts.Font font;
154    Image.Fonts.Font small_font;
155    array(string) white_stones;
156    array(string) black_stones;
157
158    class Markup(string vertex, string symbol, string color)
159    {
160	void draw(Image.Image image)
161	{
162	    vertex = upper_case(vertex);
163	    if (vertex == "PASS")
164		return;
165	    [int x, int y] = vertex_to_pixel_coord(vertex);
166	    image->setcolor(@Image.Color(color)->rgb());
167	    if (sscanf(symbol, "text:%s", string text) == 1)
168	    {
169		Image.Image text_image = font->write(text);
170		if (text_image->xsize() >= 0.9*spacing)
171		    text_image = small_font->write(text);
172		int width = text_image->xsize();
173		int height = text_image->ysize();
174		image->paste(text_image * 0 + ({220, 150, 50}), x - width / 2,
175			     y - height / 2);
176		image->paste_alpha_color(text_image, x - width / 2,
177					 y - height / 2);
178	    }
179	    else
180	    {
181		switch (symbol)
182		{
183		case "circle":
184		    image->circle(x, y, spacing / 3, spacing / 3);
185		    break;
186		case "square":
187		    int delta = spacing / 4;
188		    image->line(x - delta, y - delta, x - delta, y + delta);
189		    image->line(x - delta, y + delta, x + delta, y + delta);
190		    image->line(x + delta, y + delta, x + delta, y - delta);
191		    image->line(x + delta, y - delta, x - delta, y - delta);
192		    break;
193		case "big_square":
194		    delta = spacing / 2 - 1;
195		    image->line(x - delta, y - delta, x - delta, y + delta);
196		    image->line(x - delta, y + delta, x + delta, y + delta);
197		    image->line(x + delta, y + delta, x + delta, y - delta);
198		    image->line(x + delta, y - delta, x - delta, y - delta);
199		    break;
200		case "triangle":
201		    delta = spacing / 2 - 1;
202		    image->line(x - delta, y + delta, x + delta, y + delta);
203		    image->line(x + delta, y + delta, x, y - delta);
204		    image->line(x, y - delta, x - delta, y + delta);
205		    break;
206		case "dot":
207		    draw_disc(image, x, y, spacing / 6);
208		    break;
209		case "small_dot":
210		    draw_disc(image, x, y, spacing / 9);
211		    break;
212		case "big_dot":
213		    draw_disc(image, x, y, spacing / 4);
214		    break;
215		case "stone":
216		    draw_disc(image, x, y, spacing / 2, 0.5);
217		    break;
218		default:
219		    werror("Unknown symbol: " + symbol + "\n");
220		    break;
221		}
222	    }
223	}
224    }
225
226    array(Markup) markups;
227
228    static void create(int boardsize_, int gobansize_)
229    {
230	boardsize = boardsize_;
231	gobansize = gobansize_;
232	spacing = (int) (gobansize / (boardsize + 1.5));
233	offset = (gobansize - spacing * (boardsize - 1)) / 2;
234	font = Image.Fonts.Font(font_filename, spacing / 2);
235	small_font = Image.Fonts.Font(font_filename, spacing / 3);
236	white_stones = ({});
237	black_stones = ({});
238
239	markups = ({});
240    }
241
242    void add_stones(string color, string|array(string) stones)
243    {
244	if (color == "WHITE")
245	    white_stones |= Array.arrayify(stones);
246	else if (color == "BLACK")
247	    black_stones |= Array.arrayify(stones);
248    }
249
250    int occupied(string vertex)
251    {
252	return (has_value(white_stones, vertex)
253		|| has_value(black_stones, vertex));
254    }
255
256    void add_symbol(string vertex, string name, string color)
257    {
258	markups += ({Markup(upper_case(vertex), name, color)});
259    }
260
261    void add_text(string vertex, string text, string color)
262    {
263	markups += ({Markup(upper_case(vertex), "text:" + text, color)});
264    }
265
266    void clear_markup()
267    {
268	markups = ({});
269    }
270
271    Image.Image draw_board()
272    {
273	Image.Image board = Image.Image(gobansize, gobansize);
274	board = board->clear(220, 150, 50);
275
276	draw_grid(board);
277
278	draw_hoshi_marks(board);
279
280	draw_letters_and_numbers(board);
281
282	foreach (black_stones, string stone)
283	    draw_stone(board, "BLACK", stone);
284	foreach (white_stones, string stone)
285	    draw_stone(board, "WHITE", stone);
286
287	markups->draw(board);
288
289	return board;
290    }
291
292    static void draw_grid(Image.Image board)
293    {
294	int start = offset;
295	int end = start + (boardsize - 1) * spacing;
296	for (int k = 0; k < boardsize; k++)
297	{
298	    int kth = start + k * spacing;
299	    board->setcolor(0, 0, 0);
300	    board->line(start, kth, end, kth);
301	    board->line(kth, start, kth, end);
302	}
303    }
304
305    static void draw_hoshi_marks(Image.Image board)
306    {
307	int a = 2 + (boardsize >= 12);
308	int b = boardsize - a - 1;
309	int c = boardsize / 2;
310
311	if ((boardsize % 2 == 0 && boardsize >= 8)
312	    || (boardsize % 2 == 1 && boardsize >= 9))
313	{
314	    draw_disc(board, offset + a * spacing,
315		      offset + a * spacing, 0.1 * spacing);
316	    draw_disc(board, offset + a * spacing,
317		      offset + b * spacing, 0.1 * spacing);
318	    draw_disc(board, offset + b * spacing,
319		      offset + a * spacing, 0.1 * spacing);
320	    draw_disc(board, offset + b * spacing,
321		      offset + b * spacing, 0.1 * spacing);
322	}
323
324	if (boardsize % 2 == 1 && boardsize >= 5)
325	    draw_disc(board, offset + c * spacing,
326		      offset + c * spacing, 0.1 * spacing);
327
328	if (boardsize % 2 == 1 && boardsize >= 13)
329	{
330	    draw_disc(board, offset + a * spacing,
331		      offset + c * spacing, 0.1 * spacing);
332	    draw_disc(board, offset + b * spacing,
333		      offset + c * spacing, 0.1 * spacing);
334	    draw_disc(board, offset + c * spacing,
335		      offset + a * spacing, 0.1 * spacing);
336	    draw_disc(board, offset + c * spacing,
337		      offset + b * spacing, 0.1 * spacing);
338	}
339
340    }
341
342    static void draw_letters_and_numbers(Image.Image board)
343    {
344	int start = offset;
345	int end = start + (boardsize - 1) * spacing;
346	for (int k = 0; k < boardsize; k++)
347	{
348	    int kth = start + k * spacing;
349	    Image.Image number = font->write((string) (boardsize - k));
350	    int width = number->xsize();
351	    int height = number->ysize();
352	    board->paste_alpha_color(number,
353				     (start - spacing / 2 - width) / 2 - 1,
354				     kth - height / 2 + 1);
355	    board->paste_alpha_color(number,
356				     end + (start + spacing / 2 - width) / 2 + 1,
357				     kth - height / 2 + 1);
358	    Image.Image letter = font->write(letters[k]);
359	    width = letter->xsize();
360	    height = letter->ysize();
361	    board->paste_alpha_color(letter,
362				     kth - width / 2,
363				     (start - spacing / 2 - height) / 2 - 1);
364	    board->paste_alpha_color(letter,
365				     kth - width / 2,
366				     end + (start + spacing / 2 - height) / 2 + 1);
367	}
368    }
369
370    static void draw_stone(Image.Image board, string color, string vertex)
371    {
372	int start = offset;
373	int x, y;
374	[x, y] = vertex_to_xy(upper_case(vertex));
375
376	if (color == "BLACK")
377	    board->setcolor(0, 0, 0);
378	else if (color == "WHITE")
379	    board->setcolor(255, 255, 255);
380	else
381	    board->setcolor(128, 128, 128);
382
383	float radius = (spacing + 1) / 2.0;
384	draw_disc(board, start + x * spacing, start + y * spacing, radius);
385
386    }
387
388    array(int) vertex_to_xy(string vertex)
389    {
390	int x = search(letters, vertex[0..0]);
391	int y = boardsize - (int) vertex[1..];
392	return ({x, y});
393    }
394
395    string xy_to_vertex(int x, int y)
396    {
397	return letters[x] + (boardsize - y);
398    }
399
400    static array(int) vertex_to_pixel_coord(string vertex)
401    {
402	[int x, int y] = vertex_to_xy(vertex);
403	return ({offset + x * spacing, offset + y * spacing});
404    }
405
406    string pixel_coord_to_vertex(int|float pixel_x, int|float pixel_y)
407    {
408	int x = (int) floor((pixel_x - offset + spacing / 2.0) / spacing);
409	int y = (int) floor((pixel_y - offset + spacing / 2.0) / spacing);
410	if (x < 0 || x >= boardsize || y < 0 || y >= boardsize)
411	    return "";
412	return sprintf("%s%d", letters[x], boardsize - y);
413    }
414
415    static void draw_disc(Image.Image image, int|float x, int|float y,
416			  int|float r, float|void alpha)
417    {
418	int N = 20;
419	x = (float) x;
420	y = (float) y;
421	r = (float) r;
422	if (!alpha)
423	    alpha = 1.0;
424	array(float) coords = ({});
425	for (int k = 0; k < N; k++)
426	    coords += ({r + 0.5 + r * cos(2 * 3.14159265 * k / N),
427			r + 0.5 + r * sin(2 * 3.14159265 * k / N)});
428	Image.Image disc = Image.Image((int) ceil(2*r), (int) ceil(2*r));
429	disc->setcolor(255, 255, 255);
430	disc->polyfill(coords);
431	image->paste_alpha_color(disc*alpha, (int) (x - 0.5 * disc->xsize()),
432				 (int) (y - 0.5 * disc->ysize()));
433    }
434}
435
436string font_filename = "";
437
438int main(int argc, array(string) argv)
439{
440    if (argc < 2) {
441	werror("Usage: %s TEST-FILE:TEST-NUMBER\n", basename(argv[0]));
442	return 1;
443    }
444
445    if (!find_font())
446	return 1;
447
448    SimpleGtp engine = SimpleGtp("../interface/gnugo --quiet --mode gtp -w -t -d0x101840" / " ");
449    if (!engine)
450    {
451	werror("Failed to start engine.");
452	return 1;
453    }
454
455    GTK.setup_gtk(argv);
456    Controller controller = Controller(engine, argv[1..]);
457
458    GTK.main();
459
460    return 0;
461}
462
463
464array(string) recursive_find_files(string dir, string suffix)
465{
466    array(string) found_files = ({});
467    if (!get_dir(dir))
468	return ({});
469    foreach (get_dir(dir), string filename)
470    {
471	string full_name = dir + "/" + filename;
472	if (Stdio.is_dir(full_name))
473	    found_files += recursive_find_files(full_name, suffix);
474	else if (has_suffix(filename, suffix))
475	    found_files += ({full_name});
476    }
477    return found_files;
478}
479
480int find_font()
481{
482    if (getenv("GNUGO_FONT"))
483    {
484	font_filename = getenv("GNUGO_FONT");
485	return 1;
486    }
487
488    // Search for fonts below /usr/share/fonts.
489    array(string) font_files = recursive_find_files("/usr/share/fonts",
490						    ".ttf");
491    if (sizeof(font_files) == 0)
492	font_files = recursive_find_files("/usr/share/fonts", ".pfb");
493
494    if (sizeof(font_files) == 0)
495    {
496	werror("No font found while searching below /usr/share/fonts.\n");
497	werror("Locate a font file with suffix .ttf (truetype) or .pfb (type1)\n");
498	werror("and point to it from the environment variable GNUGO_FONT.\n");
499	return 0;
500    }
501
502    // Compute the length of the filename proper, i.e. without the
503    // path to the file.
504    int fontlength(string s) {
505	return sizeof((s / "/")[-1]);
506    };
507
508    // Choose the one with shortest name (arbitrary but may avoid e.g.
509    // italic fonts).
510    font_filename = font_files[0];
511    foreach (font_files[1..], string font_file)
512    {
513	if (fontlength(font_filename) > fontlength(font_file))
514	    font_filename = font_file;
515	else if (fontlength(font_filename) == fontlength(font_file)
516		 && has_value(lower_case(font_filename), "mono"))
517	    font_filename = font_file;
518    }
519
520    return 1;
521}
522
523class RegressionViewer
524{
525    Goban goban;
526    SimpleGtp engine;
527    array(string) traces;
528
529    GTK.Widget goban_widget;
530    GTK.Widget data_widget;
531
532    GTK.ScrolledWindow scrolled_data_window;
533    GTK.Image gtk_image;
534    GDK.Image gdk_image;
535    GTK.Clist clist;
536
537    Controller parent; //Evil. Used for callbacks.
538
539    mapping(string:array(string)) worms = ([]);
540    mapping(string:array(string)) dragons = ([]);
541    int worms_initialized = 0;
542    int dragons_initialized = 0;
543
544    string name;
545    string result;
546    string testcase_command;
547    array(string) complete_test;
548
549    function on_board_click_callback;
550
551    static void create(SimpleGtp engine_,
552		       array(string) complete_test_, string testcase_command_,
553		       function callback, string name_,
554		       Controller parent_)
555    {
556	engine = engine_;
557	parent = parent_;
558	complete_test = complete_test_;
559        testcase_command = testcase_command_;
560	name = name_;
561
562	load_testcase();
563	werror("%s\n", send_command("showboard"));
564	int boardsize = (int) send_command("query_boardsize");
565	on_board_click_callback = callback;
566
567	setup_board(boardsize);
568
569	scrolled_data_window = GTK.ScrolledWindow();
570	scrolled_data_window->set_policy(GTK.POLICY_AUTOMATIC,
571					 GTK.POLICY_AUTOMATIC);
572
573	clist = GTK.Clist(3);
574	scrolled_data_window->add(clist);
575	handle_testcase();
576    }
577
578    static void setup_board(int boardsize)
579    {
580	goban = Goban(boardsize, 600);
581	goban->add_stones("WHITE", send_command("list_stones white") / " ");
582	goban->add_stones("BLACK", send_command("list_stones black") / " ");
583	Image.Image im = goban->draw_board();
584
585	gdk_image = GDK.Image(0)->set(im);
586	gtk_image = GTK.Image(gdk_image);
587	goban_widget = GTK.EventBox()->add(gtk_image);
588	goban_widget->add_events(GDK.ButtonPressMask);
589	goban_widget->add_events(GDK.KeyPressMask);
590	goban_widget->signal_connect_new("button_press_event",
591					 button_pressed_on_board);
592	goban_widget->signal_connect_new("key_press_event",
593					 key_pressed_on_board);
594    }
595
596
597    void new_testcase(array(string) complete_test_, string testcase_command_)
598    {
599	werror("Loading new testcase.\n");
600        worms_initialized = 0;
601        dragons_initialized = 0;
602        result = "";
603        worms = ([]);
604        dragons = ([]);
605
606        complete_test = complete_test_;
607	testcase_command = testcase_command_;
608
609	load_testcase();
610	werror("%s\n", send_command("showboard"));
611	int boardsize = (int) send_command("query_boardsize");
612
613	werror("Loaded new testcase.\n");
614	goban = Goban(boardsize, 600);
615	goban->add_stones("WHITE", send_command("list_stones white") / " ");
616	goban->add_stones("BLACK", send_command("list_stones black") / " ");
617	redraw_board();
618    }
619
620
621    static void load_testcase()
622    {
623	foreach(complete_test, string testline) {
624	    werror(testline + "\n");
625	    if (!has_value("0123456789 #", testline[0..0]))
626		send_command(testline);
627        }
628    }
629
630    void handle_testcase()
631    {
632	traces = ({});
633	engine->trace_callback = collect_traces;
634        result = send_command(testcase_command);
635	redraw_board();
636	engine->trace_callback = 0;
637    }
638
639    static void collect_traces(string s)
640    {
641	traces += ({s});
642    }
643
644    void get_dragons()
645    {
646        foreach (send_command("dragon_stones") / "\n", string dragon)
647            dragons[(dragon / " ")[0]] = dragon / " " - ({""});
648        dragons_initialized = 1;
649    }
650
651    static void get_worms()
652    {
653	foreach (send_command("worm_stones") / "\n", string worm)
654	    worms[(worm / " ")[0]] = worm / " " - ({""});
655	worms_initialized = 1;
656    }
657
658
659    void add_markup(int mode)
660    {
661	goban->clear_markup();
662	if (mode <= 4) {
663	    function add_suitable_markup = ({add_worms_and_dragons_markup,
664					     add_move_generation_markup,
665					     add_eyes_markup,
666					     add_influence_markup,
667					     add_reading_markup})[mode];
668	    add_suitable_markup();
669	    redraw_board();
670	}
671    }
672
673    static void add_worms_and_dragons_markup()
674    {
675	mapping status_colors = (["alive":"green",
676				  "critical":"yellow",
677				  "dead":"red",
678				  "unknown":"blue"]);
679	mapping safety_colors = (["alive":"green",
680				  "critical":"yellow",
681				  "dead":"red",
682				  "tactically dead":"brown",
683				  "alive in seki":"cyan",
684				  "strongly alive":"blue",
685				  "invincible":"purple",
686				  "inessential":"orange"]);
687
688	if (parent->dragon_status_button->get_active())
689	{
690            if (!dragons_initialized)
691                get_dragons();
692	    foreach (dragons; string dragon; array(string) stones)
693	    {
694		string status = get_worm_or_dragon_data("dragon", "status",
695							dragon);
696		foreach (stones, string stone)
697		    goban->add_symbol(stone, "dot",
698				      status_colors[status]);
699	    }
700	}
701	else if (parent->dragon_safety_button->get_active())
702	{
703            if (!dragons_initialized)
704                get_dragons();
705	    foreach (dragons; string dragon; array(string) stones)
706	    {
707		string safety = get_worm_or_dragon_data("dragon", "safety",
708							dragon);
709		foreach (stones, string stone)
710		    goban->add_symbol(stone, "dot",
711				      safety_colors[safety]);
712	    }
713	}
714	else if (parent->worm_status_button->get_active())
715	{
716            if (!worms_initialized)
717                get_worms();
718	    foreach (worms; string worm; array(string) stones)
719	    {
720		string attack = get_worm_or_dragon_data("worm", "attack_code",
721							worm);
722		string defense = get_worm_or_dragon_data("worm",
723							 "defense_code", worm);
724		string status = "alive";
725		if (attack != "0")
726		{
727		    if (defense == "0")
728			status = "dead";
729		    else
730			status = "critical";
731		}
732
733		foreach (stones, string stone)
734		    goban->add_symbol(stone, "dot",
735				      status_colors[status]);
736	    }
737	}
738    }
739
740    static void add_move_generation_markup()
741    {
742	if ((parent->top_moves_button->get_active()
743	     || parent->all_moves_button->get_active())
744	    && (has_prefix(testcase_command, "reg_genmove")
745		|| has_prefix(testcase_command, "restricted_genmove")))
746	{
747	    string color = "green";
748	    string answers = parent->expected_result;
749	    if (answers[0..0] == "!") {
750		answers = answers[1..];
751		color = "red";
752	    }
753	    if (has_prefix(testcase_command, "restricted_genmove"))
754	    {
755		foreach ((testcase_command / " ")[2..], string allowed_move)
756		{
757		    if (color == "green" ^ has_value(answers, allowed_move))
758			goban->add_symbol(allowed_move, "triangle", "red");
759		    else
760			goban->add_symbol(allowed_move, "triangle", "green");
761		}
762	    }
763	    else
764		foreach (answers / "|" - ({""}), string answer)
765		    goban->add_symbol(answer, "big_square", color);
766
767
768	    color = (testcase_command / " ")[1];
769	    goban->add_symbol(result, "stone", color);
770	}
771
772	if (parent->top_moves_button->get_active())
773	{
774	    array(string) top_moves = send_command("top_moves") / " ";
775	    for (int k = 0; k < sizeof(top_moves) / 2; k++)
776		goban->add_text(top_moves[2 * k],
777				top_moves[2 * k + 1], "blue");
778	}
779	else if (parent->all_moves_button->get_active())
780	{
781	    array(string) all_moves = send_command("all_move_values") / "\n";
782	    foreach (all_moves, string move_value)
783	    {
784		sscanf(move_value, "%s%*[ ]%s", string vertex, string value);
785		goban->add_text(vertex, value, "blue");
786	    }
787	}
788	else if (parent->delta_territory_button->get_active()
789		 && parent->delta_territory_move != "PASS")
790	{
791	    goban->add_symbol(parent->delta_territory_move, "stone", "gray");
792	    parent->delta_territory_button_text
793	    		->set_text("delta territory for "
794				   + parent->delta_territory_move);
795	    clist->clear();
796
797	    int k;
798	    for (k = sizeof(traces) - 1; k >= 0; k--)
799	    {
800		if (sscanf(traces[k], "  " + parent->delta_territory_move
801			   + ": %*f - change in territory%*s") == 2
802		    && !has_value(traces[k], "cached"))
803		    break;
804	    }
805	    if (k >= 0)
806	    {
807		clist->append(({traces[k], "", ""}));
808		for (k--; k >= 0; k--)
809		{
810		    if (sscanf(traces[k], "    %*s:   - %*s") < 2)
811			break;
812
813		    clist->prepend(({traces[k], "", ""}));
814		    if (sscanf(traces[k],
815			       "    %*s:   - %s territory change %s ",
816			       string vertex, string value) == 3)
817		    {
818			goban->add_text(vertex, value,
819					(float) value < 0.0 ? "red" : "blue");
820		    }
821		}
822	    }
823	    clist->columns_autosize();
824	    parent->set_title(this_object(),
825			      ("Delta territory for "
826			       + parent->delta_territory_move));
827	}
828    }
829
830    static mapping(string:mapping(string:string)) eye_data = 0;
831    static mapping(string:string) half_eye_data = ([]);
832    static mapping(string:mapping(string:string)) eye_types = 0;
833
834    static void add_eyes_markup()
835    {
836	if (!eye_data)
837	{
838	    eye_data = (["white":([]), "black":([])]);
839	    eye_types = (["white":([]), "black":([])]);
840	    for (int y = 0; y < goban->boardsize; y++)
841	    {
842		for (int x = 0; x < goban->boardsize; x++)
843		{
844		    string vertex = goban->xy_to_vertex(x, y);
845		    multiset(string) is_marginal = (<>);
846		    foreach (({"white", "black"}), string color)
847		    {
848			string this_eye = send_command("eye_data " + color +
849						       " " + vertex);
850			if (!Regexp("origin +PASS")->match(this_eye))
851			{
852			    eye_data[color][vertex] = this_eye;
853			    if (Regexp("marginal +1")->match(this_eye))
854				is_marginal[color] = 1;
855			}
856		    }
857
858		    if (!eye_data["white"][vertex]
859			&& !eye_data["black"][vertex])
860			continue;
861
862		    string half_eye = send_command("half_eye_data " + vertex);
863		    if (!Regexp("type +0")->match(half_eye))
864			half_eye_data[vertex] = half_eye;
865
866		    foreach (({"white", "black"}), string color)
867		    {
868			if (!eye_data[color][vertex])
869			    continue;
870			if (is_marginal[color])
871			    eye_types[color][vertex] = "marginal";
872			else if (Regexp("type +HALF_EYE")->match(half_eye))
873			    eye_types[color][vertex] = "half";
874			else
875			    eye_types[color][vertex] = "proper";
876		    }
877		}
878	    }
879	}
880
881	string color;
882	if (parent->white_eyes_button->get_active())
883	    color = "white";
884	else
885	    color = "black";
886
887	foreach (eye_types[color]; string vertex; string eye_type)
888	{
889	    mapping (string:string) grayish = (["white" : "#c0c0c0",
890						"black" : "#404040"]);
891	    if (eye_type == "proper")
892		goban->add_symbol(vertex, "big_dot", color);
893	    else if (eye_type == "half")
894		goban->add_symbol(vertex, "big_dot", grayish[color]);
895	    else
896		goban->add_symbol(vertex, "dot", grayish[color]);
897	}
898    }
899
900    static void add_influence_markup()
901    {
902	string command;
903	string what_data;
904	if (parent->initial_w_influence_dragons_known_button->get_active())
905	    command = "initial_influence white";
906	else if (parent->initial_b_influence_dragons_known_button->get_active())
907	    command = "initial_influence black";
908	else if (parent->after_move_influence_button->get_active())
909	{
910	    if (parent->move_influence_move == "PASS")
911		return;
912	    command = sprintf("move_influence %s %s",
913	    		      parent->current_move_color,
914			      parent->move_influence_move);
915	    goban->add_symbol(parent->move_influence_move, "stone", "gray");
916	}
917	else if (parent->followup_influence_button->get_active())
918	{
919	    if (parent->move_influence_move == "PASS")
920		return;
921	    command = sprintf("followup_influence %s %s",
922	    		      parent->current_move_color,
923			      parent->move_influence_move);
924	    goban->add_symbol(parent->move_influence_move, "stone", "gray");
925	}
926
927	if (parent->influence_regions_button->get_active())
928	    what_data = "influence_regions";
929	if (parent->territory_value_button->get_active())
930	    what_data = "territory_value";
931	else if (parent->white_influence_button->get_active())
932	    what_data = "white_influence";
933	else if (parent->black_influence_button->get_active())
934	    what_data = "black_influence";
935	else if (parent->white_influence_button->get_active())
936	    what_data = "white_influence";
937	else if (parent->black_influence_button->get_active())
938	    what_data = "black_influence";
939	else if (parent->white_strength_button->get_active())
940	    what_data = "white_strength";
941	else if (parent->black_strength_button->get_active())
942	    what_data = "black_strength";
943	else if (parent->white_permeability_button->get_active())
944	    what_data = "white_permeability";
945	else if (parent->black_permeability_button->get_active())
946	    what_data = "black_permeability";
947	else if (parent->white_attenuation_button->get_active())
948	    what_data = "white_attenuation";
949	else if (parent->black_attenuation_button->get_active())
950	    what_data = "black_attenuation";
951	else if (parent->non_territory_button->get_active())
952	    what_data = "non_territory";
953
954	string result = send_command(command + " " + what_data);
955	array(string) values = (replace(result, "\n", " ") / " ") - ({""});
956	int k = 0;
957	for (int y = 0; y < goban->boardsize; y++)
958	{
959	    for (int x = 0; x < goban->boardsize; x++)
960	    {
961		string vertex = goban->xy_to_vertex(x, y);
962		if (what_data == "influence_regions")
963		{
964		    int value = (int) values[k];
965		    mapping(int:string) sizes = ([3:"big_dot",
966						  2:"big_dot",
967						  1:"dot",
968						  -1:"dot",
969						  -2:"big_dot",
970						  -3:"big_dot"]);
971		    mapping(int:string) colors = ([3:"white",
972						   2:"#c0c0c0",
973						   1:"#c0c0c0",
974						   -1:"#404040",
975						   -2:"#404040",
976						   -3:"black"]);
977		    if (sizes[value])
978			goban->add_symbol(vertex, sizes[value], colors[value]);
979		}
980		else if (has_value(what_data, "permeability"))
981		{
982		    if ((float) values[k] != 1.0)
983			goban->add_text(vertex, values[k], "blue");
984		}
985		else if (what_data == "non_territory")
986		{
987		    if (!goban->occupied(vertex))
988		    {
989			int value = (int) values[k];
990			if (value == 1)
991			    goban->add_symbol(vertex, "dot", "black");
992			else if (value == 2)
993			    goban->add_symbol(vertex, "dot", "white");
994			else if (value == 0)
995			    goban->add_symbol(vertex, "dot", "gray");
996		    }
997		}
998		else if ((float) values[k] > 0.0)
999		    goban->add_text(vertex, values[k], "blue");
1000		else if ((float) values[k] < 0.0)
1001		    goban->add_text(vertex, values[k], "red");
1002
1003		k++;
1004	    }
1005	}
1006    }
1007
1008    static void add_reading_markup()
1009    {
1010	if ((parent->connection_reading_button->get_active()
1011	     || parent->semeai_reading_button->get_active())
1012	    && parent->first_vertex != "")
1013	    goban->add_symbol(parent->first_vertex,
1014	    		      "big_dot", "green");
1015    }
1016
1017    void redraw_board()
1018    {
1019	gdk_image->set(goban->draw_board());
1020	gtk_image->queue_draw();
1021    }
1022
1023    void show_worm_data(string vertex)
1024    {
1025	string worm_data = send_command("worm_data " + vertex);
1026	clist->clear();
1027	foreach ((worm_data / "\n")[1..], string data_item)
1028	{
1029	    sscanf(data_item, "%s%*[ ]%s", string field, string value);
1030	    clist->append(({field, value, ""}));
1031	}
1032	clist->columns_autosize();
1033	parent->set_title(this_object(), "Worm data for " + vertex);
1034    }
1035
1036    void show_dragon_data(string vertex, int part)
1037    {
1038	string dragon_data = send_command("dragon_data " + vertex);
1039	clist->clear();
1040
1041	array(string) selected_data;
1042	if (part == 1)
1043	    selected_data = (dragon_data / "\n")[1..20];
1044	else
1045	    selected_data = (dragon_data / "\n")[21..];
1046
1047	foreach (selected_data, string data_item)
1048	{
1049	    sscanf(data_item, "%s%*[ ]%s", string field, string value);
1050	    clist->append(({field, value, ""}));
1051	}
1052	clist->columns_autosize();
1053	parent->set_title(this_object(), "Dragon data for " + vertex);
1054    }
1055
1056    void show_move_reasons(string vertex)
1057    {
1058	string move_reasons = send_command("move_reasons " + vertex);
1059	clist->clear();
1060
1061	foreach ((move_reasons / "\n"), string move_reason)
1062	    clist->append(({move_reason, "", ""}));
1063
1064	int k;
1065	for (k = sizeof(traces) - 1; k >= 0; k--)
1066	{
1067	    if (sscanf(traces[k], "Move generation values "
1068		       + vertex + " to %*s") == 1)
1069		break;
1070	}
1071	if (k >= 0)
1072	{
1073	    array(string) interesting_lines = ({traces[k]});
1074	    for (k--; k >= 0; k--)
1075	    {
1076		if (sscanf(traces[k], "  " + vertex + ": %*s") == 1)
1077		    interesting_lines += ({traces[k]});
1078		else if (sscanf(traces[k], "    " + vertex + ": %*s") != 1)
1079		    break;
1080	    }
1081	    clist->append(({"", "", ""}));
1082	    foreach (reverse(interesting_lines), string line)
1083		clist->append(({line, "", ""}));
1084	}
1085
1086	int first_pattern = 1;
1087	int add_continuation_lines = 0;
1088	foreach (traces, string line)
1089	{
1090	    if (sscanf(line, "pattern %*s matched at " + vertex + "%s",
1091		       string s) == 2
1092		&& s == "")
1093	    {
1094		if (first_pattern)
1095		{
1096		    clist->append(({"", "", ""}));
1097		    first_pattern = 0;
1098		}
1099		add_continuation_lines = 1;
1100		clist->append(({line, "", ""}));
1101	    }
1102	    else if (has_prefix(line, "...") && add_continuation_lines)
1103		clist->append(({line, "", ""}));
1104	    else
1105		add_continuation_lines = 0;
1106	}
1107
1108	/* Look for blunder devaluation */
1109	foreach (traces, string line)
1110	    if (has_prefix(line, "Move at " + vertex + " is"))
1111		clist->append(({line, "", ""}));
1112
1113	clist->columns_autosize();
1114	parent->set_title(this_object(), "Move reasons for " + vertex);
1115    }
1116
1117    void show_eye_data(string vertex)
1118    {
1119	clist->clear();
1120
1121	string color;
1122	if (parent->white_eyes_button->get_active())
1123	    color = "white";
1124	else
1125	    color = "black";
1126
1127	if (eye_data[color][vertex])
1128	{
1129	    foreach (eye_data[color][vertex] / "\n", string data_item)
1130	    {
1131		sscanf(data_item, "%s%*[ ]%s", string field, string value);
1132		clist->append(({field, value, ""}));
1133	    }
1134	    if (half_eye_data[vertex])
1135	    {
1136		clist->append(({"", "", ""}));
1137		foreach (half_eye_data[vertex] / "\n", string data_item)
1138		{
1139		    sscanf(data_item, "%s%*[ ]%s", string field, string value);
1140		    clist->append(({field, value, ""}));
1141		}
1142	    }
1143	}
1144
1145	clist->columns_autosize();
1146	parent->set_title(this_object(), color + " eye data for " + vertex);
1147    }
1148
1149
1150    static void button_pressed_on_board(GDK.Event event)
1151    {
1152//	werror("Button: %O\n", (mapping) event);
1153	string vertex = goban->pixel_coord_to_vertex(event->x, event->y);
1154        on_board_click_callback(vertex);
1155    }
1156
1157    static void key_pressed_on_board(GDK.Event event)
1158    {
1159//	werror("Key: %O\n", (mapping) event);
1160	// First thing to find out is what the event object contains and
1161	// how we can get the mouse position when the key was pressed.
1162    }
1163
1164    string send_command(string command)
1165    {
1166	string result;
1167	result = engine->send_command(command)->text;
1168	return result;
1169    }
1170
1171    mapping(string:string) worm_and_dragon_cache = ([]);
1172
1173    static string get_worm_or_dragon_data(string worm_or_dragon, string field,
1174					  string vertex)
1175    {
1176	string command = worm_or_dragon + "_data " + vertex;
1177	string data = worm_and_dragon_cache[command];
1178	if (!data)
1179	{
1180	    data = send_command(command);
1181	    worm_and_dragon_cache[command] = data;
1182	}
1183
1184	foreach (data / "\n", string row)
1185	    if (has_prefix(row, field))
1186	    {
1187		sscanf(row, "%*s%*[ ]%s", string value);
1188		return value;
1189	    }
1190
1191	return "";
1192    }
1193
1194    void do_reading(string reset_counter, string get_counter,
1195	    	    string sgffilename, string sgf_viewer_cmd,
1196    		    string first_command, string second_command)
1197    {
1198	string result;
1199	send_command("clear_cache");
1200        if (sizeof(parent->viewers) > 1)
1201            sgffilename += "." + replace(name, " ", "_");
1202	if (sgffilename != "")
1203	    send_command("start_sgftrace");
1204
1205	send_command(reset_counter);
1206	result = send_command(first_command);
1207	clist->append(({first_command, result,
1208			"(" + send_command(get_counter) + " nodes)"}));
1209
1210	if (result[0..0] != "0" && second_command != "")
1211	{
1212	    send_command(reset_counter);
1213	    string result = send_command(second_command);
1214	    clist->append(({second_command, result,
1215			    "(" + send_command(get_counter) + " nodes)"}));
1216	}
1217	if (sgffilename != "")
1218	    send_command("finish_sgftrace " + sgffilename);
1219        if (sgf_viewer_cmd != "")
1220            Process.create_process(sprintf(sgf_viewer_cmd, sgffilename)
1221                                   / " ");
1222	clist->columns_autosize();
1223	parent->set_title(this_object(), "Reading result");
1224     }
1225}
1226
1227
1228class Controller
1229{
1230    array(RegressionViewer) viewers = ({});
1231    array(GTK.Widget) viewer_title_widgets = ({});
1232
1233    string current_move_color = "";
1234
1235    GTK.Window main_window;
1236    GTK.Notebook controller_notebook;
1237    GTK.Notebook gobans_notebook;
1238    GTK.Notebook data_notebook;
1239    GTK.Notebook selector_notebook;
1240
1241    GTK.ScrolledWindow scrolled_testcase_text;
1242    GTK.Label testcase_text;
1243
1244    GTK.RadioButton worm_data_button;
1245    GTK.RadioButton dragon_data1_button;
1246    GTK.RadioButton dragon_data2_button;
1247    GTK.RadioButton worm_status_button;
1248    GTK.RadioButton dragon_status_button;
1249    GTK.RadioButton dragon_safety_button;
1250
1251    GTK.RadioButton top_moves_button;
1252    GTK.RadioButton all_moves_button;
1253    GTK.RadioButton delta_territory_button;
1254    GTK.Label delta_territory_button_text;
1255
1256//    GTK.RadioButton initial_w_influence_dragons_unknown_button;
1257//    GTK.RadioButton initial_b_influence_dragons_unknown_button;
1258    GTK.RadioButton initial_w_influence_dragons_known_button;
1259    GTK.RadioButton initial_b_influence_dragons_known_button;
1260    GTK.RadioButton after_move_influence_button;
1261    GTK.Label after_move_influence_button_text;
1262    GTK.RadioButton followup_influence_button;
1263    GTK.Label followup_influence_button_text;
1264    GTK.RadioButton influence_regions_button;
1265    GTK.RadioButton territory_value_button;
1266    GTK.RadioButton white_influence_button;
1267    GTK.RadioButton black_influence_button;
1268    GTK.RadioButton white_strength_button;
1269    GTK.RadioButton black_strength_button;
1270    GTK.RadioButton white_permeability_button;
1271    GTK.RadioButton black_permeability_button;
1272    GTK.RadioButton white_attenuation_button;
1273    GTK.RadioButton black_attenuation_button;
1274    GTK.RadioButton non_territory_button;
1275
1276    GTK.RadioButton white_eyes_button;
1277    GTK.RadioButton black_eyes_button;
1278
1279    GTK.RadioButton tactical_reading_button, owl_reading_button,
1280	owl_does_attack_button, owl_does_defend_button,
1281	connection_reading_button, semeai_reading_button;
1282    GTK.CheckButton sgf_traces_button, sgf_viewer_button;
1283    GTK.Entry sgf_filename_entry, sgf_viewer_entry;
1284    GTK.Table sgf_stuff;
1285    GTK.Button new_testcase_button, new_engine_button;
1286    GTK.Button next_testcase_button, prev_testcase_button;
1287    GTK.Entry new_testcase_entry, engine_path_entry, engine_name_entry;
1288
1289    string delta_territory_move = "PASS";
1290    string move_influence_move = "PASS";
1291    string first_vertex = "";
1292
1293    int testcase_index = 0;
1294
1295    array(string) testcases;
1296    string testcase_name;
1297    string testcase_command;
1298    string result;
1299    string expected_result;
1300
1301    // All lines from the test file shown at the top of the control window.
1302    array(string) full_testcase;
1303
1304    // All lines from the test file which may be needed to load the
1305    // testcase correctly.
1306    array(string) complete_testcase;
1307
1308    static mixed nasty_signal_id;
1309
1310    static void create(SimpleGtp engine_, array(string) testcases_)
1311    {
1312	testcases = testcases_;
1313	if (!excerpt_testcase(testcases[0], engine_))
1314	{
1315	    werror("Failed to load testcase.\n");
1316	    exit(1);
1317	}
1318	testcase_name = testcases[0];
1319
1320	scrolled_testcase_text
1321	     = GTK.ScrolledWindow(GTK.Adjustment(), GTK.Adjustment())
1322		->set_policy(GTK.POLICY_AUTOMATIC, GTK.POLICY_AUTOMATIC)
1323                ->set_usize(450, 100);
1324        testcase_text = GTK.Label(full_testcase * "\n")
1325                	->set_justify(GTK.JUSTIFY_LEFT)
1326			->set_alignment(0.0, 0.0);
1327 	scrolled_testcase_text->add(testcase_text);
1328
1329	main_window = GTK.Window(GTK.WindowToplevel);
1330	controller_notebook = GTK.Notebook();
1331	controller_notebook->set_tab_pos(GTK.POS_LEFT);
1332
1333	gobans_notebook   = (GTK.Notebook()
1334			     ->set_show_tabs(0));
1335	data_notebook     = (GTK.Notebook()
1336			     ->set_show_tabs(0)
1337			     ->set_show_border(0));
1338	selector_notebook = (GTK.Notebook()
1339			     ->set_tab_pos(GTK.POS_TOP));
1340	selector_notebook->signal_connect_new("switch_page", change_engine);
1341
1342	GTK.Widget main_window_contents
1343	    = (GTK.Vbox(0, 0)
1344	       ->pack_start(GTK.Hbox(0, 24)
1345			    ->pack_start(scrolled_testcase_text, 0, 0, 24)
1346			    ->pack_start(GTK.Alignment(0.0, 0.5, 0.0, 0.0)
1347					 ->add(selector_notebook),
1348					 0, 0, 0),
1349			    0, 0, 0)
1350	       ->add(GTK.Hbox(0, 2)
1351		     ->add(GTK.Vbox(0, 6)
1352			   ->pack_start(controller_notebook, 0, 0, 0)
1353			   ->add(data_notebook))
1354		     ->pack_start(GTK.Alignment(0.0, 0.0, 0.0, 0.0)
1355				  ->add(gobans_notebook),
1356				  0, 0, 0)));
1357	main_window->add(main_window_contents);
1358
1359	main_window->set_title(testcases[0]);
1360	main_window->signal_connect_new("destroy", quit);
1361
1362	if (has_prefix(testcase_command, "reg_genmove")
1363	    || has_prefix(testcase_command, "restricted_genmove"))
1364	{
1365	    sscanf(testcase_command, "%*s %s", string color);
1366	    if (lower_case(color[0..0]) == "w")
1367		current_move_color = "white";
1368	    else
1369		current_move_color = "black";
1370	}
1371
1372	worm_data_button = GTK.RadioButton("worm data");
1373	dragon_data1_button = GTK.RadioButton("dragon data, part 1",
1374					      worm_data_button);
1375	dragon_data2_button = GTK.RadioButton("dragon data, part 2",
1376					      worm_data_button);
1377
1378	worm_status_button = GTK.RadioButton("worm status");
1379	dragon_status_button = GTK.RadioButton("dragon status",
1380					       worm_status_button);
1381	dragon_safety_button = GTK.RadioButton("dragon safety",
1382					       worm_status_button);
1383	worm_status_button->signal_connect_new("clicked",
1384					       markup_button_pressed);
1385	dragon_status_button->signal_connect_new("clicked",
1386						 markup_button_pressed);
1387	dragon_safety_button->signal_connect_new("clicked",
1388						 markup_button_pressed);
1389
1390	top_moves_button = GTK.RadioButton("top moves");
1391	all_moves_button = GTK.RadioButton("all moves", top_moves_button);
1392	delta_territory_button_text = GTK.Label("delta territory for PASS");
1393	delta_territory_button_text->set_alignment(0.0, 0.0);
1394	delta_territory_button = GTK.RadioButton(0, top_moves_button);
1395	delta_territory_button->add(delta_territory_button_text);
1396	top_moves_button->signal_connect_new("clicked", markup_button_pressed);
1397	all_moves_button->signal_connect_new("clicked", markup_button_pressed);
1398	delta_territory_button->signal_connect_new("clicked",
1399						   markup_button_pressed);
1400
1401	white_eyes_button = GTK.RadioButton("white eyes");
1402	black_eyes_button = GTK.RadioButton("black eyes", white_eyes_button);
1403	white_eyes_button->signal_connect_new("clicked",
1404					      markup_button_pressed);
1405	black_eyes_button->signal_connect_new("clicked",
1406					      markup_button_pressed);
1407
1408//	  initial_w_influence_dragons_unknown_button =
1409//	      GTK.RadioButton("white influence, dragons unknown");
1410//	  initial_b_influence_dragons_unknown_button =
1411//	      GTK.RadioButton("black influence, dragons unknown",
1412//			      initial_w_influence_dragons_unknown_button);
1413	initial_w_influence_dragons_known_button =
1414	    GTK.RadioButton("white influence, dragons known");
1415	initial_b_influence_dragons_known_button =
1416	    GTK.RadioButton("black influence, dragons known",
1417			    initial_w_influence_dragons_known_button);
1418	after_move_influence_button_text =
1419	    GTK.Label("after move influence for PASS");
1420	after_move_influence_button_text->set_alignment(0.0, 0.0);
1421	after_move_influence_button =
1422	    GTK.RadioButton(0, initial_w_influence_dragons_known_button);
1423	after_move_influence_button->add(after_move_influence_button_text);
1424	followup_influence_button_text =
1425	    GTK.Label("followup influence for PASS");
1426	followup_influence_button_text->set_alignment(0.0, 0.0);
1427	followup_influence_button =
1428	    GTK.RadioButton(0, initial_w_influence_dragons_known_button);
1429	followup_influence_button->add(followup_influence_button_text);
1430	influence_regions_button = GTK.RadioButton("influence regions");
1431	territory_value_button = GTK.RadioButton("territory value",
1432						 influence_regions_button);
1433	white_influence_button = GTK.RadioButton("white influence",
1434						 influence_regions_button);
1435	black_influence_button = GTK.RadioButton("black influence",
1436						 influence_regions_button);
1437	white_strength_button = GTK.RadioButton("white strength",
1438						influence_regions_button);
1439	black_strength_button = GTK.RadioButton("black strength",
1440						influence_regions_button);
1441	white_permeability_button = GTK.RadioButton("white permeability",
1442						    influence_regions_button);
1443	black_permeability_button = GTK.RadioButton("black permeability",
1444						    influence_regions_button);
1445	white_attenuation_button = GTK.RadioButton("white attenuation",
1446						   influence_regions_button);
1447	black_attenuation_button = GTK.RadioButton("black attenuation",
1448						   influence_regions_button);
1449	non_territory_button = GTK.RadioButton("non-territory",
1450					       influence_regions_button);
1451	({initial_w_influence_dragons_known_button,
1452	  initial_b_influence_dragons_known_button,
1453	  after_move_influence_button,
1454	  followup_influence_button,
1455	  influence_regions_button,
1456	  territory_value_button,
1457	  white_influence_button,
1458	  black_influence_button,
1459	  white_strength_button,
1460	  black_strength_button,
1461	  white_permeability_button,
1462	  black_permeability_button,
1463	  white_attenuation_button,
1464	  black_attenuation_button,
1465	  non_territory_button})->signal_connect_new("clicked",
1466						     markup_button_pressed);
1467
1468
1469	tactical_reading_button = GTK.RadioButton("tactical reading");
1470	owl_reading_button = GTK.RadioButton("owl reading",
1471					     tactical_reading_button);
1472        owl_does_attack_button = GTK.RadioButton("owl_does_attack",
1473                                                 tactical_reading_button);
1474        owl_does_defend_button = GTK.RadioButton("owl_does_defend",
1475                                                 tactical_reading_button);
1476	connection_reading_button = GTK.RadioButton("connection reading",
1477						    tactical_reading_button);
1478	semeai_reading_button = GTK.RadioButton("semeai reading",
1479						tactical_reading_button);
1480	sgf_traces_button = (GTK.CheckButton("save sgf traces to")
1481			     ->set_active(1));
1482	sgf_filename_entry = GTK.Entry();
1483	sgf_filename_entry->set_text("vars.sgf");
1484	sgf_filename_entry->set_editable(1);
1485
1486	sgf_viewer_button = GTK.CheckButton("start sgf viewer as");
1487        sgf_viewer_entry = GTK.Entry()->set_text(sgf_viewer_command)
1488                           ->set_editable(1);
1489        sgf_viewer_button->signal_connect("toggled", sgf_viewer_button_toggled);
1490        sgf_traces_button->signal_connect("toggled", sgf_traces_button_toggled);
1491        sgf_stuff = GTK.Table(2, 2, 0)
1492                    ->attach_defaults(sgf_traces_button, 0, 1, 0, 1)
1493                    ->attach_defaults(sgf_filename_entry, 1, 2, 0, 1)
1494                    ->attach_defaults(sgf_viewer_button, 0, 1, 1, 2)
1495                    ->attach_defaults(sgf_viewer_entry, 1, 2, 1, 2);
1496
1497	new_testcase_entry = GTK.Entry();
1498        new_testcase_entry->set_text(testcases[0]);
1499        new_testcase_entry->set_editable(1);
1500        new_testcase_button = GTK.Button("Load new testcase");
1501        new_testcase_button->signal_connect_new("clicked", new_testcase);
1502	new_testcase_entry->signal_connect_new("activate", new_testcase);
1503	if (sizeof(testcases)) {
1504	    prev_testcase_button = GTK.Button("Previous testcase");
1505	    prev_testcase_button->signal_connect_new("clicked", prev_testcase);
1506	    prev_testcase_button->set_sensitive(0);
1507	    next_testcase_button = GTK.Button("Next testcase");
1508	    next_testcase_button->signal_connect_new("clicked", next_testcase);
1509	}
1510	engine_path_entry = GTK.Entry();
1511	engine_path_entry->set_text("../interface/gnugo");
1512	engine_path_entry->set_editable(1);
1513
1514	engine_name_entry = GTK.Entry();
1515	engine_name_entry->set_text("Engine 2");
1516	engine_name_entry->set_editable(1);
1517
1518	engine_path_entry->signal_connect_new("activate", select_new_engine);
1519	new_engine_button = GTK.Button("Start new engine");
1520	new_engine_button->signal_connect_new("clicked", select_new_engine);
1521
1522	GTK.Widget worms_and_dragons_page
1523	    = (GTK.Vbox(0, 0)
1524	       ->pack_start(worm_data_button, 0, 0, 0)
1525	       ->pack_start(dragon_data1_button, 0, 0, 0)
1526	       ->pack_start(dragon_data2_button, 0, 0, 0)
1527	       ->pack_start(GTK.Label(""), 0, 0, 0)
1528	       ->pack_start(worm_status_button, 0, 0, 0)
1529	       ->pack_start(dragon_status_button, 0, 0, 0)
1530	       ->pack_start(dragon_safety_button, 0, 0, 0));
1531	controller_notebook->append_page(worms_and_dragons_page,
1532					 GTK.Label("worms and dragons"));
1533
1534	GTK.Widget move_generation_page
1535	    = (GTK.Vbox(0, 0)
1536	       ->pack_start(top_moves_button, 0, 0, 0)
1537	       ->pack_start(all_moves_button, 0, 0, 0)
1538	       ->pack_start(delta_territory_button, 0, 0, 0));
1539	controller_notebook->append_page(move_generation_page,
1540					 GTK.Label("move generation"));
1541
1542	GTK.Widget eyes_page = (GTK.Vbox(0, 0)
1543				->pack_start(white_eyes_button, 0, 0, 0)
1544				->pack_start(black_eyes_button, 0, 0, 0));
1545	controller_notebook->append_page(eyes_page, GTK.Label("eyes"));
1546
1547	GTK.Widget influence_page
1548	    = (GTK.Vbox(0, 0)
1549	       ->pack_start(GTK.Vbox(0,0)
1550// 			    ->add(initial_w_influence_dragons_unknown_button)
1551// 			    ->add(initial_b_influence_dragons_unknown_button)
1552			    ->add(initial_w_influence_dragons_known_button)
1553			    ->add(initial_b_influence_dragons_known_button)
1554			    ->add(after_move_influence_button)
1555			    ->add(followup_influence_button), 0, 0, 0)
1556	       ->pack_start(GTK.Label(""), 0, 0, 0)
1557	       ->pack_start(GTK.Hbox(0,12)
1558			    ->add(GTK.Vbox(0,0)
1559				  ->pack_start(influence_regions_button, 0, 0, 0)
1560				  ->pack_start(territory_value_button, 0, 0, 0)
1561				  ->pack_start(non_territory_button, 0, 0, 0)
1562				  ->pack_start(white_influence_button, 0, 0, 0)
1563				  ->pack_start(black_influence_button, 0, 0, 0))
1564			    ->add(GTK.Vbox(0,0)
1565				  ->pack_start(white_strength_button, 0, 0, 0)
1566				  ->pack_start(black_strength_button, 0, 0, 0)
1567				  ->pack_start(white_permeability_button, 0, 0, 0)
1568				  ->pack_start(black_permeability_button, 0, 0, 0)
1569				  ->pack_start(white_attenuation_button, 0, 0, 0)
1570				  ->pack_start(black_attenuation_button, 0, 0, 0)),
1571			    0, 0, 0));
1572	controller_notebook->append_page(influence_page,
1573					 GTK.Label("influence"));
1574
1575	GTK.Widget reading_page
1576	    = (GTK.Vbox(0, 0)
1577	       ->pack_start(tactical_reading_button, 0, 0, 0)
1578	       ->pack_start(owl_reading_button, 0, 0, 0)
1579	       ->pack_start(owl_does_attack_button, 0, 0, 0)
1580               ->pack_start(owl_does_defend_button, 0, 0, 0)
1581	       ->pack_start(connection_reading_button, 0, 0, 0)
1582	       ->pack_start(semeai_reading_button, 0, 0, 0)
1583	       ->pack_start(GTK.Label(""), 0, 0, 0)
1584	       ->pack_start(sgf_stuff, 0, 0, 0));
1585	controller_notebook->append_page(reading_page, GTK.Label("reading"));
1586
1587	GTK.Widget engines_page
1588	    = (GTK.Vbox(0, 12)
1589	       ->pack_start(engine_path_entry, 0, 0, 0));
1590	engines_page->pack_start(engine_name_entry, 0, 0, 0);
1591	engines_page->pack_start(GTK.Alignment(1.0, 0.0, 0.0, 0.0)
1592				 ->add(new_engine_button), 0, 0, 0)
1593		     ->pack_start(new_testcase_entry, 0, 0, 0)
1594		     ->pack_start(new_testcase_button, 0, 0, 0);
1595	if  (sizeof(testcases) > 1) {
1596	    GTK.Widget next_prev
1597	    	= (GTK.Hbox(0, 12)->pack_start(prev_testcase_button, 0, 0, 0)
1598				  ->pack_start(next_testcase_button, 0, 0, 0));
1599	    engines_page->pack_start(next_prev, 0, 0, 0);
1600	}
1601	controller_notebook->append_page(engines_page->set_border_width(12),
1602					 GTK.Label("engines"));
1603
1604	nasty_signal_id = controller_notebook->signal_connect_new("switch_page",
1605								  add_markup);
1606
1607	if (has_prefix(testcase_command, "reg_genmove")
1608	    || has_prefix(testcase_command, "restricted_genmove")) {
1609	    controller_notebook->show_all();
1610	    controller_notebook->set_page(1);
1611	}
1612
1613	main_window->show_all();
1614
1615        add_regression_viewer(RegressionViewer(engine_,
1616					       complete_testcase,
1617					       testcase_command,
1618					       button_pressed_on_a_board,
1619					       "Default engine",
1620					       this_object()));
1621	add_markup(controller_notebook->get_current_page());
1622    }
1623
1624    static void select_new_engine()
1625    {
1626	string new_engine_path = engine_path_entry->get_text();
1627	if (!Stdio.is_file(new_engine_path)) {
1628	    viewers->clist->clear();
1629	    viewers->clist->append(({"The engine path does not point to a file.\n", "", ""}));
1630	    return;
1631	}
1632
1633	SimpleGtp new_engine = SimpleGtp((new_engine_path + " --quiet --mode gtp -w -t -d0x101840") / " ");
1634    	if (!new_engine)
1635	    werror("Failed to start new engine.\n");
1636	else {
1637	    add_regression_viewer(
1638		RegressionViewer(new_engine, complete_testcase,
1639				 testcase_command, button_pressed_on_a_board,
1640				 engine_name_entry->get_text(),
1641				 this_object()));
1642	}
1643
1644	engine_name_entry->set_text(sprintf("Engine %d", sizeof(viewers) + 1));
1645    }
1646
1647    static void add_regression_viewer(RegressionViewer viewer)
1648    {
1649	viewers += ({ viewer });
1650	viewer->goban_widget->show_all();
1651	gobans_notebook->append_page(viewer->goban_widget, 0);
1652
1653	GTK.Widget title_label = GTK.Label("");
1654	viewer_title_widgets += ({ title_label });
1655
1656	GTK.Widget data_page = (GTK.Vbox(0, 2)
1657				->pack_start(title_label, 0, 0, 0)
1658				->add(viewer->scrolled_data_window)
1659				->show_all());
1660	data_notebook->append_page(data_page, 0);
1661
1662	selector_notebook
1663	    ->append_page((GTK.Alignment(0.0, 0.5, 0.0, 0.0)
1664			   ->set_border_width(4)
1665			   ->add(GTK.Label(viewer->engine->command_line))),
1666			  GTK.Label(viewer->name));
1667	selector_notebook->show_all();
1668	selector_notebook->set_page(sizeof(viewers) - 1);
1669    }
1670
1671    void set_title(RegressionViewer viewer, string title)
1672    {
1673	for (int k = 0; k < sizeof(viewers); k++) {
1674	    if (viewers[k] == viewer)
1675		viewer_title_widgets[k]->set_text(title);
1676	}
1677    }
1678
1679    static void markup_button_pressed(mixed ... foo)
1680    {
1681	add_markup(controller_notebook->get_current_page());
1682    }
1683
1684    static void add_markup(int mode)
1685    {
1686	viewers->add_markup(mode);
1687    }
1688
1689    static void change_engine(int engine_index)
1690    {
1691	gobans_notebook->set_page(engine_index);
1692	data_notebook->set_page(engine_index);
1693    }
1694
1695    void button_pressed_on_a_board(string vertex)
1696    {
1697	if (vertex == "")
1698	    return;
1699
1700	switch (controller_notebook->get_current_page()) {
1701	case 0:
1702	    // Worms and dragons.
1703	    if (worm_data_button->get_active())
1704		viewers->show_worm_data(vertex);
1705	    else if (dragon_data1_button->get_active())
1706		viewers->show_dragon_data(vertex, 1);
1707	    else
1708		viewers->show_dragon_data(vertex, 2);
1709	    break;
1710
1711	case 1:
1712	    // Move generation.
1713	    if (!delta_territory_button->get_active())
1714		viewers->show_move_reasons(vertex);
1715	    else
1716	    {
1717		delta_territory_move = vertex;
1718		markup_button_pressed();
1719	    }
1720	    break;
1721
1722	case 2:
1723	    // Eyes.
1724	    viewers->show_eye_data(vertex);
1725	    break;
1726
1727	case 3:
1728	    // Influence.
1729	    if (after_move_influence_button->get_active()
1730		|| followup_influence_button->get_active())
1731	    {
1732		move_influence_move = vertex;
1733		after_move_influence_button_text
1734		    ->set_text("after move influence for " + vertex);
1735		followup_influence_button_text
1736		    ->set_text("followup influence for " + vertex);
1737		markup_button_pressed();
1738	    }
1739	    break;
1740
1741	case 4:
1742	    // Reading.
1743	    string sgffilename;
1744	    string sgf_viewer_cmd;
1745	    string reset_counter, get_counter;
1746
1747            if (sgf_viewer_button->get_active()) {
1748                sgffilename = sgf_filename_entry->get_text();
1749                sgf_viewer_cmd = sgf_viewer_entry->get_text();
1750            }
1751	    else if (sgf_traces_button->get_active()) {
1752	    	sgffilename = sgf_filename_entry->get_text();
1753                sgf_viewer_cmd = "";
1754	    }
1755	    else {
1756	    	sgffilename = "";
1757                sgf_viewer_cmd = "";
1758	    }
1759
1760	    if (first_vertex == ""
1761		|| tactical_reading_button->get_active()
1762		|| owl_reading_button->get_active())
1763		viewers->goban->clear_markup();
1764
1765	    viewers->goban->add_symbol(vertex, "big_dot", "green");
1766	    viewers->redraw_board();
1767
1768	    viewers->clist->clear();
1769
1770	    if (tactical_reading_button->get_active()
1771		|| owl_reading_button->get_active())
1772	    {
1773		string prefix = "";
1774		reset_counter = "reset_reading_node_counter";
1775		get_counter = "get_reading_node_counter";
1776		if (owl_reading_button->get_active())
1777		{
1778		    prefix = "owl_";
1779		    reset_counter = "reset_owl_node_counter";
1780		    get_counter = "get_owl_node_counter";
1781		}
1782
1783		viewers->do_reading(reset_counter, get_counter,
1784				    sgffilename, sgf_viewer_cmd,
1785				    prefix + "attack " + vertex,
1786				    prefix + "defend " + vertex);
1787	    }
1788	    else if (owl_does_attack_button->get_active()
1789                     || owl_does_defend_button->get_active()
1790		     || connection_reading_button->get_active()
1791		     || semeai_reading_button->get_active())
1792	    {
1793		if (first_vertex == "")
1794		{
1795		    first_vertex = vertex;
1796		}
1797		else if (first_vertex != vertex)
1798		{
1799		    string c1, c2;
1800
1801                    if (owl_does_attack_button->get_active()) {
1802                        c1 = sprintf("owl_does_attack %s %s\n",
1803                                     first_vertex, vertex);
1804                        viewers->do_reading("reset_owl_node_counter",
1805                                            "get_owl_node_counter",
1806                                            sgffilename, sgf_viewer_cmd,
1807                                            c1, "");
1808                    }
1809                    else if (owl_does_defend_button->get_active()) {
1810                        c1 = sprintf("owl_does_defend %s %s\n",
1811                                     first_vertex, vertex);
1812                        viewers->do_reading("reset_owl_node_counter",
1813                                            "get_owl_node_counter",
1814                                            sgffilename, sgf_viewer_cmd,
1815                                            c1, "");
1816                    }
1817		    else if (connection_reading_button->get_active())
1818		    {
1819			c1 = sprintf("connect %s %s\n",
1820				    first_vertex,
1821				    vertex);
1822			c2 = "dis" + c1;
1823			viewers->do_reading("reset_connection_node_counter",
1824					    "get_connection_node_counter",
1825					    sgffilename, sgf_viewer_cmd,
1826					    c1, c2);
1827		    }
1828		    else
1829		    {
1830			c1 = sprintf("analyze_semeai %s %s",
1831				     first_vertex, vertex);
1832			c2 = sprintf("analyze_semeai %s %s", vertex,
1833				     first_vertex);
1834			// FIXME: We should use a semeai node counter rather
1835			// than the owl node counter, except that it doesn't
1836			// exist yet.
1837			viewers->do_reading("reset_owl_node_counter",
1838					    "get_owl_node_counter",
1839					    sgffilename, sgf_viewer_cmd,
1840					    c1, c2);
1841		    }
1842		    first_vertex = "";
1843		}
1844	    }
1845	    break;
1846	}
1847    }
1848
1849    static void this_new_testcase(string new_testcase)
1850    {
1851        werror("Trying to load new testcase %s.", new_testcase);
1852        if (!excerpt_testcase(new_testcase, viewers[0]->engine))
1853        {
1854            werror("Failed to load testcase.\n");
1855            return;
1856        }
1857        testcase_name = new_testcase;
1858	main_window->set_title(testcase_name);
1859	testcase_text->set_text(full_testcase * "\n");
1860        viewers->new_testcase(complete_testcase, testcase_command);
1861        viewers->handle_testcase();
1862
1863	if (has_prefix(testcase_command, "reg_genmove")
1864	    || has_prefix(testcase_command, "restricted_genmove")) {
1865	    controller_notebook->show_all();
1866	    controller_notebook->set_page(1);
1867	}
1868    }
1869
1870    static void new_testcase()
1871    {
1872        this_new_testcase(new_testcase_entry->get_text());
1873    }
1874
1875    static void next_testcase()
1876    {
1877    	if (testcase_index >= sizeof(testcases) - 1)
1878	    return;
1879    	testcase_index += 1;
1880	prev_testcase_button->set_sensitive(1);
1881	if (testcase_index == sizeof(testcases) - 1)
1882	    next_testcase_button->set_sensitive(0);
1883	this_new_testcase(testcases[testcase_index]);
1884    }
1885
1886    static void prev_testcase()
1887    {
1888    	if (testcase_index == 0)
1889	    return;
1890    	testcase_index -= 1;
1891	if (!testcases)
1892	    werror("Error handling list of test cases!\n");
1893	next_testcase_button->set_sensitive(1);
1894	if (testcase_index == 0)
1895	    prev_testcase_button->set_sensitive(0);
1896	this_new_testcase(testcases[testcase_index]);
1897    }
1898
1899
1900    // The engine parameter is only needed to find out the color to
1901    // move when an sgf file is given. Since there can be multiple
1902    // viewers we shouldn't use this engine object for anything else
1903    // though. Notice also that no viewer has been set up yet at the
1904    // time of this call.
1905    static int excerpt_testcase(string testcase, SimpleGtp engine)
1906    {
1907	string filename;
1908	int number;
1909	if (sscanf(testcase, "%s:%d", filename, number) < 2)
1910	    return 0;
1911
1912	if (!has_suffix(filename, ".tst")
1913	    && !has_suffix(filename, ".sgf"))
1914	    filename += ".tst";
1915
1916	string testfile = Stdio.read_file(filename);
1917	if (!testfile)
1918	    return 0;
1919
1920	if (has_suffix(filename, ".sgf"))
1921	{
1922	    // Only sgf file provided. Fake a testcase.
1923	    string s = "loadsgf " + filename + " " + number;
1924	    string color = engine->send_command(s)->text;
1925	    testcase_command = "reg_genmove " + color;
1926	    full_testcase = ({s, testcase_command});
1927	    complete_testcase = ({s, testcase_command});
1928	    expected_result = "";
1929	    return 1;
1930	}
1931
1932	full_testcase = ({});
1933	complete_testcase = ({});
1934	array(string) testlines = testfile / "\n";
1935	for (int k = 0; k < sizeof(testlines); k++)
1936	{
1937	    int this_number;
1938	    string testline = testlines[k];
1939	    if (testline[0..0] >= "a" && testline[0..0] <= "z") {
1940	        if (testline[0..6] != "loadsgf")
1941		    complete_testcase += ({ testline });
1942		else
1943		    complete_testcase = ({ testline });
1944	    }
1945	    else if (sscanf(testline, "%d %s", this_number, testline) == 2
1946		&& this_number == number)
1947	    {
1948		testcase_command = testline;
1949		sscanf(testlines[k + 1], "#? [%s]", expected_result);
1950		full_testcase += ({testlines[k]});
1951		full_testcase += ({testlines[k + 1]});
1952		return 1;
1953	    }
1954
1955	    if (has_value("0123456789 ", testline[0..0]))
1956		full_testcase = ({});
1957	    else
1958		full_testcase += ({testline});
1959	}
1960
1961	return 0;
1962    }
1963
1964    void sgf_traces_button_toggled()
1965    {
1966        if (!sgf_traces_button->get_active())
1967            sgf_viewer_button->set_active(0);
1968    }
1969
1970    void sgf_viewer_button_toggled()
1971    {
1972        if (sgf_viewer_button->get_active())
1973            sgf_traces_button->set_active(1);
1974    }
1975
1976    void debug_callback(mixed ... args)
1977    {
1978	write("Debug callback:%O\n", args);
1979    }
1980
1981    static void quit()
1982    {
1983	// Otherwise Pike errors occur.
1984	controller_notebook->signal_disconnect(nasty_signal_id);
1985	GTK.main_quit();
1986    }
1987}
1988
1989
1990/*
1991 * Local Variables:
1992 * tab-width: 8
1993 * c-basic-offset: 4
1994 * End:
1995 */
1996