CleanSgfFromKgs: Sgf.pmod

File Sgf.pmod, 19.0 kB (added by gunnar, 3 years ago)

sgf library

Line 
1/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
2 * This is gnugoclient, a program to connect go engines to go        *
3 * servers. See http://www.lysator.liu.se/~gunnar/gnugoclient        *
4 * for information.                                                  *
5 *                                                                   *
6 * Copyright 1997-2004 by Gunnar Farnebäck.                          *
7 * E-mail: gunnar@lysator.liu.se                                     *
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 2             *
12 *                                                                   *
13 * This program is distributed in the hope that it will be useful,   *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of    *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the     *
16 * GNU General Public License in file COPYING for more details.      *
17 *                                                                   *
18 * You should have received a copy of the GNU General Public         *
19 * License along with this program; if not, write to the Free        *
20 * Software Foundation, Inc., 59 Temple Place - Suite 330,           *
21 * Boston, MA 02111, USA.                                            *
22\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
23
24constant sgf_letters =
25([
26     1:"a",   2:"b",   3:"c",   4:"d",   5:"e",   6:"f",   7:"g",
27     8:"h",   9:"i",  10:"j",  11:"k",  12:"l",  13:"m",  14:"n",
28    15:"o",  16:"p",  17:"q",  18:"r",  19:"s",  20:"t",  21:"u",
29    22:"v",  23:"w",  24:"x",  25:"y",  26:"z",
30    27:"A",  28:"B",  29:"C",  30:"D",  31:"E",  32:"F",  33:"G",
31    34:"H",  35:"I",  36:"J",  37:"K",  38:"L",  39:"M",  40:"N",
32    41:"O",  42:"P",  43:"Q",  44:"R",  45:"S",  46:"T",  47:"U",
33    48:"V",  49:"W",  50:"X",  51:"Y",  52:"Z"
34]);
35
36string decode_move(string sgfmove, int size)
37{
38    if (sgfmove == "" || (sgfmove == "tt" && size <= 19))
39        return "PASS";
40   
41    int row = 1 + size - search(sgf_letters, sgfmove[1..1]);
42    int col = search(sgf_letters, sgfmove[0..0]);
43   
44    return sprintf("%c%d", 'A' + (col - 1) + (col > 8), row);
45}
46
47int sgf_linewidth = 70;
48
49void set_linewidth(int n)
50{
51    sgf_linewidth = n;
52}
53
54// Building block for the sgf tree structure
55class SgfNode
56{
57    constant SGF_INDENTSTEP = 2;
58
59    mapping(string:string|array(string)) properties = ([]);
60    object parent;                 // Pointer to parent node.
61    array(SgfNode) children = ({}); // Pointers to child nodes.
62   
63    constant root_properties_order =
64    ({
65        "GM", "FF",
66        "SZ", "HA", "KM",
67        "PW", "WR",
68        "PB", "BR",
69        "PC", "CP",
70        "DT", "AP",
71        "EV", "GN",
72        "RE",
73        "TM", "OT", "OP", "OM", "RU"
74    });
75
76    constant node_properties_order = ({"B", "W", "BL", "OB", "WL", "OW",
77                                       "AB", "AW", "TB", "TW", "C"});
78
79    constant needs_quoting = (<"EV", "GN", "C">);
80   
81    constant property_requires_newline = (<"FF", "CP", "KM", "WR", "BR",
82                                           "GN", "OT", "OM", "AP", "RE",
83                                           "RU", "AB", "AW">);
84
85    // Methods to add properties.
86
87    static string encode_move(int size, int col, int row)
88    {
89        return sgf_letters[(int)col] + sgf_letters[1 + size - (int)row];
90    }
91
92    // Notice that in the case of composed text, ":" must also be
93    // quoted. That is assumed to have been done previously.
94    string quote_text(string s)
95    {
96        return replace(s, ({"]", "\\"}), ({"\\]", "\\\\"}));
97    }
98
99    void add_property(string identity, string|array(string) value)
100    {
101        if (needs_quoting[identity])
102            value = quote_text(value);
103        switch (identity)
104        {
105        case "C":
106        case "AB":
107        case "AW":
108        case "TB":
109        case "TW":
110            if (properties[identity])
111                if (stringp(value))
112                    properties[identity] += ({value});
113                else
114                    properties[identity] += value;
115            else
116                if (stringp(value))
117                    properties[identity] = ({value});
118                else
119                    properties[identity] = value;
120            break;
121        default:
122            properties[identity] = value;
123        }
124    }
125
126    void remove_property(string identity)
127    {
128        m_delete(properties, identity);
129    }
130
131    static void add_stone_or_move(string prefix, string color, string move,
132                               int size)
133    {
134        color = lower_case(color);
135        if (color == "b" || color == "B")
136            color = "B";
137        else
138            color = "W";
139
140        move = lower_case(move);
141        if (move == "pass")
142            add_property(prefix + color, "");
143        else
144        {
145            int col = 1 + move[0] - 'a' - (move[0] > 'i');
146            int row = (int) move[1..];
147            add_property(prefix + color, encode_move(size, col, row));
148        }
149    }
150
151    void add_move(string color, string move, int size)
152    {
153        add_stone_or_move("", color, move, size);
154    }
155
156    void add_stone(string color, string move, int size)
157    {
158        add_stone_or_move("A", color, move, size);
159    }
160
161    // Methods to retrieve information.
162
163    SgfNode get_parent()
164    {
165        return parent;
166    }
167
168    SgfNode get_child(int n)
169    {
170        if (n >= sizeof(children))
171            return 0;
172        else
173            return children[n];
174    }
175
176    array(SgfNode) get_children()
177    {
178        return children;
179    }
180
181    string|array(string) get_property(string s)
182    {
183        return properties[s];
184    }
185
186    int get_size()
187    {
188        if (parent)
189            return parent->get_size();
190       
191        return ((int) properties->SZ) || 19;
192    }
193
194    array(string) get_move(int|void size)
195    {
196        string color = "";
197        string move = "";
198
199        if (!size)
200            size = get_size();
201
202        if (properties->B)
203        {
204            color = "B";
205            move = decode_move(properties->B, size);
206        }
207        else if (properties->W)
208        {
209            color = "W";
210            move = decode_move(properties->W, size);
211        }
212
213        return ({color, move});
214    }
215   
216    array(string) get_handicap_stones()
217    {
218        int size = get_size();
219        array(string) stones = ({});
220
221        if (properties->AB)
222            foreach (properties->AB, string stone)
223                stones += ({decode_move(stone, size)});
224       
225        return stones;
226    }
227
228
229    // Methods to write to string.
230 
231    static string indentation(int indentation_level)
232    {
233        return " " * (indentation_level * SGF_INDENTSTEP);
234    }
235   
236    static array(string) add_text(array(string) sgf, string s,
237                                  int indentation_level)
238    {
239        if (sizeof(sgf[-1]) + sizeof((s / "\n")[0]) >= sgf_linewidth)
240            sgf = force_newline(sgf, indentation_level);
241        sgf[-1] += s;
242        return sgf;
243    }
244
245    array(string) force_newline(array(string) sgf, int indentation_level)
246    {
247        if (sizeof(sgf[-1]) != 0 && sgf[-1][-1] != ' ')
248            sgf += ({indentation(indentation_level)});
249        return sgf;
250    }
251
252    array(string) write_property(array(string) sgf, string name,
253                                 int indentation_level, string|void prefix)
254    {
255        string s = "";
256        if (prefix)
257            s = prefix;
258        if (stringp(properties[name]))
259            s += name + "[" + properties[name] + "]";
260        else if (name == "C")
261            s += name + "[" + properties[name] * "\n" + "]";
262        else // AB, AW, TB, TW
263        {
264            if (sizeof(properties[name]) > 0)
265            {
266                s += name;
267                foreach (properties[name], string propvalue)
268                {
269                    s += "[" + propvalue + "]";
270                    sgf = add_text(sgf, s, indentation_level);
271                    s = "";
272                }
273            }
274        }
275        if (sizeof(s) > 0)
276            sgf = add_text(sgf, s, indentation_level);
277        if (property_requires_newline[name])
278            sgf = force_newline(sgf, indentation_level);
279        return sgf;
280    }
281   
282    array(string)|string write(array(string)|void sgf,
283                               int|void indentation_level)
284    {
285        int root = 0;
286        if (!sgf) // Root node
287        {
288            indentation_level = 0;
289            root = 1;
290            sgf = ({"(;"});
291            foreach (root_properties_order + node_properties_order,
292                     string name)
293                if (properties[name])
294                    sgf = write_property(sgf, name, indentation_level);
295
296            // Is there some property we missed above?
297            foreach (indices(properties), string name)
298                if (!has_value(root_properties_order + node_properties_order,
299                               name))
300                    sgf = write_property(sgf, name, indentation_level);
301
302            // Always newline at the end of the root node.
303            sgf = force_newline(sgf, indentation_level);
304        }
305        else
306        {
307            string prefix = ";";
308            foreach (node_properties_order, string name)
309                if (properties[name])
310                {
311                    sgf = write_property(sgf, name, indentation_level, prefix);
312                    prefix = "";
313                }
314
315            // Is there some property we missed above?
316            foreach (indices(properties), string name)
317                if (!has_value(node_properties_order, name))
318                    sgf = write_property(sgf, name, indentation_level);
319        }
320       
321        if (sizeof(children) == 1)
322        {
323            SgfNode child = children[0];
324            sgf = child->write(sgf, indentation_level);
325        }
326        else if (sizeof(children) > 1)
327        {
328            foreach (children, SgfNode child)
329            {
330                sgf = force_newline(sgf, indentation_level);
331                sgf = add_text(sgf, "(", indentation_level);
332                sgf = child->write(sgf, indentation_level + 1);
333                sgf = add_text(sgf, ")", indentation_level);
334            }
335        }
336        if (root)
337        {
338            sgf = add_text(sgf, ")", indentation_level);
339            return sgf * "\n";
340        }
341        else
342            return sgf;
343    }
344
345    string read_property(string name, string sgfdata)
346    {
347        string value = "";
348        int quoted = 0;
349        while (sgfdata != "")
350        {
351            string c = sgfdata[0..0];
352            sgfdata = sgfdata[1..];
353            if (quoted)
354            {
355                if (value != "\n")
356                    value += c;
357                quoted = 0;
358            }
359            else if (c == "\\")
360                quoted = 1;
361            else if (c == "]")
362            {
363                add_property(name, value);
364                return sgfdata;
365            }
366            else
367                value += c;
368        }
369        werror("Unexpected end of sgf data.\n");
370        return sgfdata;
371    }
372
373    string read(string sgfdata)
374    {
375        string property_name = "";
376        int variation = 0;
377        while (sgfdata != "")
378        {
379            string c = sgfdata[0..0];
380            if (c >= "A" && c <= "Z")
381                sscanf(sgfdata, "%[A-Z]%s", property_name, sgfdata);
382            else if (c == "[")
383                sgfdata = read_property(property_name, sgfdata[1..]);
384            else if (c == "(")
385            {
386                variation = 1;
387                sgfdata = sgfdata[1..];
388            }
389            else if (c == ")")
390                return sgfdata[1..];
391            else if (c == ";")
392            {
393                SgfNode child = SgfNode(this_object());
394                if (!variation)
395                    return child->read(sgfdata[1..]);
396                else
397                    sgfdata = child->read(sgfdata[1..]);
398            }
399            else if (c == " " || c == "\t" || c == "\v" || c == "\n")
400                sgfdata = sgfdata[1..];
401            else
402            {
403                werror("Warning: Skipping unexpected character %s.\n", c);
404                sgfdata = sgfdata[1..];
405            }
406        }
407        werror("Unexpected end of sgf data.\n");
408        return sgfdata;
409    }
410   
411    void add_child(SgfNode node)
412    {
413        children += ({node});
414    }
415
416    SgfNode new_node()
417    {
418        return SgfNode(this_object());
419    }
420
421    void create(SgfNode|void _parent)
422    {
423        parent = _parent;
424        if (parent)
425            parent->add_child(this_object());
426    }
427}
428
429
430SgfNode parse_sgf(string sgfdata)
431{
432    SgfNode sgf = SgfNode();
433    sgfdata = normalize_linebreaks(sgfdata);
434    while (1)
435    {
436        if (sscanf(sgfdata, "%*s(%s", sgfdata) == 0)
437            return 0;
438        if (sscanf(sgfdata, "%*[ \t\v\n];%s", sgfdata) == 2)
439            break;
440    }
441    sgf->read(sgfdata);
442    return sgf;
443}
444
445
446string normalize_linebreaks(string s)
447{
448    if (search(s, "\r\n") >= 0 || search(s, "\n\r") >= 0)
449        return s - "\r";
450
451    return replace(s, "\r", "\n");
452}
453
454void simplify_dates(array(string) dates)
455{
456    array(string) last_date = "--"/"-";
457    for (int i=0; i<sizeof(dates); i++)
458    {
459        array(string) date = dates[i] / "-";
460        if (date[0] != last_date[0])
461            ;
462        else if (date[1] != last_date[1])
463            dates[i] = date[1..] * "-";
464        else if (date[2] != last_date[2])
465            dates[i] = date[2];
466        last_date = date;
467    }
468}
469
470class Territory
471{
472    class Run
473    {
474        int start_col;
475        int start_row;
476        string direction;
477        int length;
478
479        array(int) start()
480        {
481            return ({start_col, start_row});
482        }
483
484        array(int) end()
485        {
486            if (direction == "vertical")
487                return ({start_col, start_row + length - 1});
488            else
489                return ({start_col + length - 1, start_row});
490        }
491       
492        void enlarge()
493        {
494            length++;
495        }
496       
497        void create(int col, int row, string dir)
498        {
499            start_col = col;
500            start_row = row;
501            direction = dir;
502            length = 0;
503        }
504    }
505   
506    int size;
507    // Use a zero padded board to get rid of boundary checking.
508    array(int) board;
509    array(int) vertical_runs;
510    array(int) horizontal_runs;
511    mapping(int:object) runs = ([]);
512    int max_run_length = 0;
513    int next_run = 1;
514
515    int index(int col, int row)
516    {
517        return col + row * (size + 2);
518    }
519   
520    void find_runs(string direction)
521    {
522        int col, row;
523        for (int x=1; x<=size; x++)
524        {
525            int run = 0;
526            for (int y=1; y<=size+1; y++)
527            {
528                if (direction == "vertical")
529                {
530                    col = x;
531                    row = y;
532                }
533                else
534                {
535                    col = y;
536                    row = x;
537                }
538                if (board[index(col, row)])
539                {
540                    if (!run)
541                    {
542                        run = next_run++;
543                        runs[run] = Run(col, row, direction);
544                    }
545                    runs[run]->enlarge();
546                    if (direction == "vertical")
547                        vertical_runs[index(col, row)] = run;
548                    else
549                        horizontal_runs[index(col, row)] = run;
550                }
551                else
552                    run = 0;
553            }
554        }
555    }
556
557    string other_direction(string direction)
558    {
559        if (direction == "vertical")
560            return "horizontal";
561        else
562            return "vertical";
563    }
564       
565    object get_run(int col, int row, string direction)
566    {
567        if (direction == "vertical")
568            return runs[vertical_runs[index(col, row)]];
569        else
570            return runs[horizontal_runs[index(col, row)]];
571    }
572
573    object get_run_length(int col, int row, string direction)
574    {
575        return get_run(col, row, direction)->length;
576    }
577
578    string encode_rectangle(array(int) rectangle, function encode_move)
579    {
580        int col1, row1, col2, row2;
581        [col1, row1, col2, row2] = rectangle;
582        if (col1 == col2 && row1 == row2)
583            return encode_move(size, col1, row1);
584        else
585            return (encode_move(size, col2, row1) + ":"
586                    + encode_move(size, col1, row2));
587    }
588
589    void rebuild_run(object old_run)
590    {
591        int col1, row1, col2, row2;
592        [col1, row1] = old_run->start();
593        [col2, row2] = old_run->end();
594        if (old_run->direction == "vertical")
595        {
596            int run = 0;
597            for (int row=row1; row<=row2+1; row++)
598            {
599                if (board[index(col1, row)])
600                {
601                    if (!run)
602                    {
603                        run = next_run++;
604                        runs[run] = Run(col1, row, old_run->direction);
605                    }
606                    runs[run]->enlarge();
607                    vertical_runs[index(col1, row)] = run;
608                }
609                else
610                    run = 0;
611            }
612        }
613        else
614        {
615            int run = 0;
616            for (int col=col1; col<=col2+1; col++)
617            {
618                if (board[index(col, row1)])
619                {
620                    if (!run)
621                    {
622                        run = next_run++;
623                        runs[run] = Run(col, row1, old_run->direction);
624                    }
625                    runs[run]->enlarge();
626                    horizontal_runs[index(col, row1)] = run;
627                }
628                else
629                    run = 0;
630            }
631        }
632    }
633
634    // This is a ridiculously inefficient implementation.
635    array(string) noncompressed_encode(function encode_move)
636    {
637        array(string) s = ({});
638        for (int col=1; col<=size; col++)
639            for (int row=1; row<=size; row++)
640                if (board[index(col, row)])
641                    s += ({encode_move(size, col, row)});
642        return s;
643    }
644   
645    array(string) compressed_encode(function encode_move)
646    {
647        vertical_runs = allocate((size+2) * (size+2));
648        horizontal_runs = allocate((size+2) * (size+2));
649        find_runs("vertical");
650        find_runs("horizontal");
651//      debug();
652        int max_run_length = 0;
653        mapping(object:int) run_lengths = ([]);
654        foreach (values(runs), object run)
655        {
656            run_lengths[run] = run->length;
657            if (run->length > max_run_length)
658                max_run_length = run->length;
659        }
660        array(string) territory_strings = ({});
661        int col1, row1, col2, row2;
662        while (max_run_length>0)
663        {
664            object run = search(run_lengths, max_run_length);
665            if (!run)
666            {
667                max_run_length--;
668                continue;
669            }
670            [col1, row1] = run->start();
671            [col2, row2] = run->end();
672
673            array(int) rectangle;
674            if (run->direction == "vertical")
675            {
676                if (run->length > 2)
677                {
678                    if (get_run_length(col1, row1, "horizontal") == 1)
679                        row1++;
680                    if (get_run_length(col2, row2, "horizontal") == 1)
681                        row2--;
682                }
683                while (get_run(col2+1, row1, "vertical")
684                       && (get_run(col2+1, row1, "vertical")
685                           == get_run(col2+1, row2, "vertical")))
686                    col2++;
687                while (get_run(col1-1, row1, "vertical")
688                       && (get_run(col1-1, row1, "vertical")
689                           == get_run(col1-1, row2, "vertical")))
690                    col1--;
691                if (col1 == col2)
692                    rectangle = run->start() + run->end();
693                else
694                {
695                    array(object) splinters = ({});
696                    for (int col=col1; col<=col2; col++)
697                        splinters != ({get_run(col, row1-1, "horizontal"),
698                                       get_run(col, row2+1, "horizontal")});
699                    int cost = 7;
700                    foreach (splinters, object splinter)
701                    {
702                        if (!splinter)
703                            continue;
704                        if (splinter->length == 1)
705                            cost += 4;
706                        else
707                            cost += 7;
708                    }
709                    // This is indeed completely heuristic.
710                    if (cost > (col2 - col1 + 1) * 7)
711                        rectangle = run->start() + run->end();
712                    else
713                        rectangle = ({col1, row1, col2, row2});
714                }
715            }
716            else
717            {
718                if (run->length > 2)
719                {
720                    if (get_run_length(col1, row1, "vertical") == 1)
721                        col1++;
722                    if (get_run_length(col2, row2, "vertical") == 1)
723                        col2--;
724                }
725                while (get_run(col1, row2+1, "horizontal")
726                       && (get_run(col1, row2+1, "horizontal")
727                           == get_run(col2, row2+1, "horizontal")))
728                    row2++;
729                while (get_run(col1, row1-1, "horizontal")
730                       && (get_run(col1, row1-1, "horizontal")
731                           == get_run(col2, row1-1, "horizontal")))
732                    row1--;
733                if (row1 == row2)
734                    rectangle = run->start()+run->end();
735                else
736                {
737                    array(object) splinters = ({});
738                    for (int row=row1; row<=row2; row++)
739                        splinters |= ({get_run(col1-1, row, "vertical"),
740                                       get_run(col2+1, row, "vertical")});
741                    int cost = 7;
742                    foreach (splinters, object splinter)
743                    {
744                        if (!splinter)
745                            continue;
746                        if (splinter->length == 1)
747                            cost += 4;
748                        else
749                            cost += 7;
750                    }
751                    // This is indeed completely heuristic.
752                    if (cost > (col2 - col1 + 1) * 7)
753                        rectangle = run->start() + run->end();
754                    else
755                        rectangle = ({col1, row1, col2, row2});
756                }
757            }
758            territory_strings += ({encode_rectangle(rectangle, encode_move)});
759            // Update the set of runs.
760            [col1, row1, col2, row2] = rectangle;
761            array(int) destroyed_runs = ({});
762            for (int col=col1; col<=col2; col++)
763                for (int row=row1; row<=row2; row++)
764                {
765                    int i = index(col, row);
766                    destroyed_runs |= ({vertical_runs[i],
767                                        horizontal_runs[i]});
768                    board[i] = 0;
769                    vertical_runs[i] = 0;
770                    horizontal_runs[i] = 0;
771                }
772            foreach (destroyed_runs, int run)
773            {
774                rebuild_run(runs[run]);
775                m_delete(runs, run);
776            }
777            run_lengths = ([]);
778            foreach (values(runs), object run)
779                run_lengths[run] = run->length;
780//          debug();
781        }
782        // Remove outer pair of brackets.
783        return territory_strings;
784    }
785
786#if 0   
787    void debug()
788    {
789        string s1 = "";
790        string s2 = "";
791        string s3 = "";
792        for (int row=1; row<=size; row++)
793        {
794            for (int col=1; col<=size; col++)
795            {
796                int i = index(col, row);
797                s1 += sprintf(" %d", board[i]);
798                s2 += sprintf(" %2d", vertical_runs[i]);
799                s3 += sprintf(" %2d", horizontal_runs[i]);
800            }
801            s1 += "\n";
802            s2 += "\n";
803            s3 += "\n";
804        }
805        werror(s1 + "\n" + s2 + "\n" + s3 + "\n");
806    }
807#endif
808
809    void create(int board_size, array(array(int)) territory)
810    {
811        size = board_size;
812        board = allocate((size+2) * (size+2));
813        foreach (territory, array(int) point)
814        {
815            int col = 1 + point[0];
816            int row = 1 + point[1];
817            board[index(col, row)] = 1;
818        }
819    }
820}