line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Games::Go::SGF;
|
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
42874
|
use 5.006;
|
|
1
|
|
|
|
|
4
|
|
|
1
|
|
|
|
|
35
|
|
4
|
1
|
|
|
1
|
|
5
|
use strict;
|
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
27
|
|
5
|
1
|
|
|
1
|
|
4
|
use warnings;
|
|
1
|
|
|
|
|
5
|
|
|
1
|
|
|
|
|
31
|
|
6
|
1
|
|
|
1
|
|
4
|
use Carp; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
96
|
|
7
|
1
|
|
|
1
|
|
1579
|
use IO::File; |
|
1
|
|
|
|
|
14713
|
|
|
1
|
|
|
|
|
137
|
|
8
|
1
|
|
|
1
|
|
990
|
use English; |
|
1
|
|
|
|
|
3804
|
|
|
1
|
|
|
|
|
5
|
|
9
|
1
|
|
|
1
|
|
2636
|
use Parse::RecDescent; |
|
1
|
|
|
|
|
50515
|
|
|
1
|
|
|
|
|
8
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
require Exporter;
|
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
our @ISA = qw(Exporter);
|
14
|
|
|
|
|
|
|
our %EXPORT_TAGS = ( 'all' => [ qw() ] );
|
15
|
|
|
|
|
|
|
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
|
16
|
|
|
|
|
|
|
our @EXPORT = qw();
|
17
|
|
|
|
|
|
|
our $VERSION = '0.11';
|
18
|
|
|
|
|
|
|
our $AUTOLOAD;
|
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
{
|
21
|
|
|
|
|
|
|
my %nodehash;
|
22
|
|
|
|
|
|
|
my %onersfound;
|
23
|
|
|
|
|
|
|
my %duplicates;
|
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
# organise a node's property values and tags
|
26
|
|
|
|
|
|
|
# drop leading [, and trailing ]
|
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
sub store {
|
29
|
0
|
|
|
0
|
0
|
|
my ($ident, $values) = @_;
|
30
|
0
|
0
|
|
|
|
|
if (exists($nodehash{tags})) {
|
31
|
0
|
0
|
|
|
|
|
if ($nodehash{tags} !~ /,$ident,/) {
|
32
|
0
|
|
|
|
|
|
$nodehash{tags} .= $ident.',';
|
33
|
|
|
|
|
|
|
}
|
34
|
|
|
|
|
|
|
} else { |
35
|
0
|
|
|
|
|
|
$nodehash{tags} = ','.$ident.',' |
36
|
|
|
|
|
|
|
}
|
37
|
|
|
|
|
|
|
|
38
|
0
|
0
|
|
|
|
|
if (exists($nodehash{$ident})){
|
39
|
0
|
|
|
|
|
|
$nodehash{$ident} = join (',', $nodehash{$ident}, map (substr($_,1,-1), @{$values}));
|
|
0
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
} else {
|
41
|
0
|
|
|
|
|
|
$nodehash{$ident} = join (',', map (substr($_,1,-1), @{$values}));
|
|
0
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
}
|
43
|
|
|
|
|
|
|
}
|
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
# detect duplicate tags and mixed nodes
|
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
sub isDuplicate {
|
48
|
0
|
|
|
0
|
0
|
|
my $ident = shift;
|
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
# bad sgf if more than one of these in a file
|
51
|
0
|
|
|
|
|
|
my $oners = ',SZ,GM,ST,FF,CA,AP,RU,SZ,KM,';
|
52
|
0
|
0
|
0
|
|
|
|
if (exists($onersfound{$ident}) and $oners =~ /,$ident,/) {
|
53
|
0
|
|
|
|
|
|
print 'Duplicated ',$ident, ' property',"\n";
|
54
|
0
|
|
|
|
|
|
return 1;
|
55
|
|
|
|
|
|
|
}
|
56
|
0
|
|
|
|
|
|
$onersfound{$ident} = undef;
|
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
# bad sgf if any of these are duplicated in a node
|
59
|
0
|
|
|
|
|
|
my $singletons = ',B,W,PL,MN,';
|
60
|
0
|
0
|
0
|
|
|
|
if (exists($duplicates{$ident}) and $singletons =~ /,$ident,/) {
|
61
|
0
|
|
|
|
|
|
print 'Duplicated ',$ident, ' property',"\n";
|
62
|
0
|
|
|
|
|
|
return 1;
|
63
|
|
|
|
|
|
|
}
|
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
# bad sgf if both of these are in a node
|
66
|
0
|
|
|
|
|
|
my $alones = ',B,W,';
|
67
|
0
|
0
|
0
|
|
|
|
if ((grep (exists($duplicates{$_}),('B','W')) ) and $alones =~ /,$ident,/) {
|
68
|
0
|
|
|
|
|
|
print $ident, ' property not allowed in this node',"\n";
|
69
|
0
|
|
|
|
|
|
return 1;
|
70
|
|
|
|
|
|
|
}
|
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
# flag mixed nodes - if this is B or W, have we already got AB or AW or AE
|
73
|
0
|
|
|
|
|
|
my $setup = ',AB,AW,AE,';
|
74
|
0
|
0
|
0
|
|
|
|
if ((grep (exists($duplicates{$_}),('B','W')) ) and $setup =~ /,$ident,/) {
|
75
|
0
|
|
|
|
|
|
print 'Setup and move in the same node',"\n";
|
76
|
0
|
|
|
|
|
|
return 1;
|
77
|
|
|
|
|
|
|
}
|
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
# flag mixed nodes - if this is AB or AW or AE, have we already got B or W
|
80
|
0
|
|
|
|
|
|
my $move = ',B,W,';
|
81
|
0
|
0
|
0
|
|
|
|
if ((grep (exists($duplicates{$_}),('AB','AW','AE')) ) and $move =~ /,$ident,/) {
|
82
|
0
|
|
|
|
|
|
print 'Setup and move in the same node',"\n";
|
83
|
0
|
|
|
|
|
|
return 1;
|
84
|
|
|
|
|
|
|
}
|
85
|
|
|
|
|
|
|
|
86
|
0
|
|
|
|
|
|
$duplicates{$ident} = 0;
|
87
|
|
|
|
|
|
|
|
88
|
0
|
|
|
|
|
|
return 0;
|
89
|
|
|
|
|
|
|
}
|
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
# return and clear the tags and values for a node
|
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
sub unload {
|
94
|
0
|
|
|
0
|
0
|
|
my %hash = %nodehash;
|
95
|
0
|
|
|
|
|
|
%nodehash = ();
|
96
|
0
|
|
|
|
|
|
%duplicates = ();
|
97
|
0
|
|
|
|
|
|
return %hash
|
98
|
|
|
|
|
|
|
}
|
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
sub refresh {
|
101
|
0
|
|
|
0
|
0
|
|
%onersfound = (); |
102
|
0
|
|
|
|
|
|
%nodehash = ();
|
103
|
0
|
|
|
|
|
|
%duplicates = ();
|
104
|
|
|
|
|
|
|
}
|
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
}
|
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
my $grammar = q{
|
109
|
|
|
|
|
|
|
File : GameTree { $return = $item[1]; Games::Go::SGF::refresh }
|
110
|
|
|
|
|
|
|
GameTree : '(' Node(s) GameTree(s?) ')' {
|
111
|
|
|
|
|
|
|
$return = $item[2];
|
112
|
|
|
|
|
|
|
push @{$return} , bless( $item[3], 'Games::Go::SGF::Variation') if (@{$item[3]})
|
113
|
|
|
|
|
|
|
}
|
114
|
|
|
|
|
|
|
Node : ';' Property(s?) {
|
115
|
|
|
|
|
|
|
$return = bless({Games::Go::SGF::unload()}, 'Games::Go::SGF::Node')
|
116
|
|
|
|
|
|
|
}
|
117
|
|
|
|
|
|
|
Property : ...Validate Tag Value(s) {
|
118
|
|
|
|
|
|
|
Games::Go::SGF::store( $item[2], $item[3] );
|
119
|
|
|
|
|
|
|
}
|
120
|
|
|
|
|
|
|
Validate : (/B\[/|/W\[/) MovePoint
|
121
|
|
|
|
|
|
|
|('AB'|'AW'|'AE'|'CR'|'MA'|'SL'|'SQ'|'TR') Point(s)
|
122
|
|
|
|
|
|
|
|'PL' Colour
|
123
|
|
|
|
|
|
|
|/C\[/ Comment
|
124
|
|
|
|
|
|
|
|'AP'|'CA' Value
|
125
|
|
|
|
|
|
|
|('SZ'|'FF'|'HA'|'OW'|'OB'|'ST'|'GM') Integer
|
126
|
|
|
|
|
|
|
|('BL'|'WL') Real
|
127
|
|
|
|
|
|
|
|'LB' Markup(s)
|
128
|
|
|
|
|
|
|
|/[A-Z]+/ Value(s)
|
129
|
|
|
|
|
|
|
Tag : /[A-Z]+/ { $return = $item[1] }
|
130
|
|
|
|
|
|
|
Value : /\[.*?(?
|
131
|
|
|
|
|
|
|
Comment : /.*?(?
|
132
|
|
|
|
|
|
|
Markup : /\[[a-zA-Z]{2}/ ':' /.*?(?
|
133
|
|
|
|
|
|
|
MovePoint : /[a-zA-Z]{2}\][^\[]/ | /\]/
|
134
|
|
|
|
|
|
|
Point : /\[[a-zA-Z]{2}\]/
|
135
|
|
|
|
|
|
|
Integer : /\[\d+\]/
|
136
|
|
|
|
|
|
|
Real :/\[\d+\.\d+\]|\[\d+\]/
|
137
|
|
|
|
|
|
|
Colour : /\[[WB]\]/
|
138
|
|
|
|
|
|
|
};
|
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
sub new {
|
141
|
0
|
|
|
0
|
0
|
|
my ($class, $file, $grammarflag) = @_; |
142
|
0
|
|
|
|
|
|
my $grammar = _choosegrammar($grammarflag);
|
143
|
0
|
0
|
|
|
|
|
my $parser = new Parse::RecDescent $grammar or croak "Bad grammar!\n"; |
144
|
0
|
0
|
|
|
|
|
my $fh = IO::File->new($file, '<') or croak $ERRNO; |
145
|
0
|
|
|
|
|
|
my $slurpfile = do { local $/; <$fh> }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
146
|
0
|
0
|
|
|
|
|
$fh->close or croak $ERRNO;
|
147
|
0
|
|
|
|
|
|
my $a = $parser->File($slurpfile);
|
148
|
0
|
0
|
|
|
|
|
defined $a or croak "Bad Go sgf\n";
|
149
|
0
|
|
|
|
|
|
bless $a, 'Games::Go::SGF';
|
150
|
0
|
|
|
|
|
|
_sew($a);
|
151
|
0
|
|
|
|
|
|
return $a;
|
152
|
|
|
|
|
|
|
}
|
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
sub _sew {
|
155
|
0
|
|
|
0
|
|
|
my $a = shift;
|
156
|
0
|
|
|
|
|
|
$a->[0]->{moves_to_first_variation} = 0;
|
157
|
0
|
|
|
|
|
|
for (0..@$a) {
|
158
|
0
|
0
|
|
|
|
|
if (ref $a->[$_] eq 'Games::Go::SGF::Variation') {
|
159
|
0
|
|
0
|
|
|
|
$a->[0]->{moves_to_first_variation} ||= $_;
|
160
|
0
|
|
|
|
|
|
_sew($_) for $a->[$_]->variations;
|
161
|
|
|
|
|
|
|
} else {
|
162
|
0
|
|
|
|
|
|
$a->[$_]->{next} = $a->[$_+1];
|
163
|
|
|
|
|
|
|
}
|
164
|
|
|
|
|
|
|
}
|
165
|
|
|
|
|
|
|
}
|
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
sub _choosegrammar {
|
168
|
0
|
|
|
0
|
|
|
my $grammarflag = shift;
|
169
|
0
|
|
|
|
|
|
my $res;
|
170
|
0
|
|
0
|
|
|
|
$grammarflag ||= 'lite';
|
171
|
0
|
|
|
|
|
|
for ($grammarflag) {
|
172
|
0
|
0
|
|
|
|
|
if ($_ eq 'lite') { $res = $grammar;
|
|
0
|
|
|
|
|
|
|
173
|
0
|
|
|
|
|
|
$res =~ s/\.\.\.Validate//;
|
174
|
0
|
|
|
|
|
|
$res =~ s/\[2\], \$item\[3\]/\[1\], \$item\[2\]/;
|
175
|
0
|
|
|
|
|
|
$res =~ s/Validate.*Value\(s\)//s;
|
176
|
0
|
|
|
|
|
|
$res =~ s/Comment.*eofile/eofile/s;
|
177
|
0
|
|
|
|
|
|
last }
|
178
|
0
|
0
|
|
|
|
|
if ($_ eq 'full') { $res = $grammar; last }
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
179
|
0
|
|
|
|
|
|
croak 'Unknown grammar type';
|
180
|
|
|
|
|
|
|
}
|
181
|
0
|
|
|
|
|
|
return $res
|
182
|
|
|
|
|
|
|
}
|
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
# Game info methods
|
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
sub date {
|
187
|
0
|
|
|
0
|
0
|
|
my ($self, $value) = @_;
|
188
|
0
|
0
|
|
|
|
|
_setvalue($self, 'DT', $value) if ($value);
|
189
|
0
|
|
|
|
|
|
return $self->[0]->{DT};
|
190
|
|
|
|
|
|
|
}
|
191
|
|
|
|
|
|
|
|
192
|
0
|
|
|
0
|
0
|
|
sub time { date(@_) }
|
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
sub white {
|
195
|
0
|
|
|
0
|
0
|
|
my ($self, $value) = @_;
|
196
|
0
|
0
|
|
|
|
|
_setvalue($self, 'PW', $value) if ($value);
|
197
|
0
|
|
|
|
|
|
return $self->[0]->{PW};
|
198
|
|
|
|
|
|
|
}
|
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
sub black {
|
201
|
0
|
|
|
0
|
0
|
|
my ($self, $value) = @_;
|
202
|
0
|
0
|
|
|
|
|
_setvalue($self, 'PB', $value) if ($value);
|
203
|
0
|
|
|
|
|
|
return $self->[0]->{PB};
|
204
|
|
|
|
|
|
|
}
|
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
sub size {
|
207
|
0
|
|
|
0
|
0
|
|
my ($self, $value) = @_;
|
208
|
0
|
0
|
|
|
|
|
_setvalue($self, 'SZ', $value) if ($value);
|
209
|
0
|
|
|
|
|
|
return $self->[0]->{SZ};
|
210
|
|
|
|
|
|
|
}
|
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
sub komi {
|
213
|
0
|
|
|
0
|
0
|
|
my ($self, $value) = @_;
|
214
|
0
|
0
|
|
|
|
|
_setvalue($self, 'KM', $value) if ($value);
|
215
|
0
|
|
|
|
|
|
return $self->[0]->{KM};
|
216
|
|
|
|
|
|
|
}
|
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
sub delete{
|
219
|
0
|
|
|
0
|
1
|
|
my ($self, $tag) = @_;
|
220
|
0
|
0
|
|
|
|
|
if (exists $self->[0]->{$tag}) {
|
221
|
0
|
|
|
|
|
|
delete $self->[0]->{$tag};
|
222
|
0
|
|
|
|
|
|
$self->[0]->{tags} =~ s/$tag,?//;
|
223
|
|
|
|
|
|
|
}
|
224
|
|
|
|
|
|
|
}
|
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
# change the value of a tag
|
227
|
|
|
|
|
|
|
# if a new tag is being created, add it to {tags}
|
228
|
|
|
|
|
|
|
sub _setvalue {
|
229
|
0
|
|
|
0
|
|
|
my ($self, $tag, $value) = @_;
|
230
|
0
|
0
|
|
|
|
|
$self->[0]->{tags} = join(',', $self->[0]->{tags}, $tag) unless (exists $self->[0]->{$tag});
|
231
|
0
|
|
|
|
|
|
$self->[0]->{$tag} = $value;
|
232
|
|
|
|
|
|
|
}
|
233
|
|
|
|
|
|
|
|
234
|
0
|
|
|
0
|
1
|
|
sub move { $_[0]->[$_[1]]; }
|
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
sub getsgf {
|
237
|
0
|
|
|
0
|
1
|
|
my $self = shift;
|
238
|
0
|
|
|
|
|
|
my $move_no = 0;
|
239
|
0
|
|
|
|
|
|
my $startvar = 1; # used for formatting of output
|
240
|
0
|
|
|
|
|
|
my $string = '(';
|
241
|
|
|
|
|
|
|
|
242
|
0
|
|
|
|
|
|
while (my $walker = $self->move($move_no++)) {
|
243
|
0
|
|
|
|
|
|
$string .= _donode($walker, $startvar);
|
244
|
0
|
|
|
|
|
|
$startvar = 0;
|
245
|
|
|
|
|
|
|
}
|
246
|
|
|
|
|
|
|
|
247
|
0
|
|
|
|
|
|
$string .= ')'."\n";
|
248
|
0
|
|
|
|
|
|
return $string
|
249
|
|
|
|
|
|
|
}
|
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
sub _iterate {
|
252
|
0
|
|
|
0
|
|
|
my $startpoint = shift;
|
253
|
0
|
|
|
|
|
|
my $v = 0;
|
254
|
0
|
|
|
|
|
|
my $string;
|
255
|
0
|
|
|
|
|
|
my @vars = $startpoint->variations;
|
256
|
|
|
|
|
|
|
|
257
|
0
|
|
|
|
|
|
while (defined $vars[$v]){
|
258
|
0
|
|
|
|
|
|
$string .= "\n".'(';
|
259
|
0
|
|
|
|
|
|
my $startvar = 1;
|
260
|
0
|
|
|
|
|
|
for (@{$vars[$v++]}){
|
|
0
|
|
|
|
|
|
|
261
|
0
|
|
|
|
|
|
$string .= _donode($_, $startvar);
|
262
|
0
|
|
|
|
|
|
$startvar = 0;
|
263
|
|
|
|
|
|
|
}
|
264
|
0
|
|
|
|
|
|
$string .= ')';
|
265
|
|
|
|
|
|
|
}
|
266
|
|
|
|
|
|
|
|
267
|
0
|
|
|
|
|
|
return $string
|
268
|
|
|
|
|
|
|
}
|
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
sub _donode {
|
271
|
0
|
|
|
0
|
|
|
my ($node, $startvar) = @_;
|
272
|
0
|
|
|
|
|
|
my $string = '';
|
273
|
0
|
0
|
|
|
|
|
if (ref($node) eq 'Games::Go::SGF::Node'){
|
274
|
0
|
0
|
|
|
|
|
$string .= "\n" unless $startvar;
|
275
|
0
|
|
|
|
|
|
$string .= ';';
|
276
|
0
|
0
|
|
|
|
|
if ($node->tags) {
|
277
|
0
|
|
|
|
|
|
for (split (',', $node->tags)) {
|
278
|
0
|
|
|
|
|
|
$string .= $_;
|
279
|
0
|
|
|
|
|
|
my $property = $node->$_;
|
280
|
0
|
0
|
|
|
|
|
if ($property) {
|
281
|
0
|
|
|
|
|
|
for (split (',', $property)) {
|
282
|
0
|
|
|
|
|
|
$string .= '['.$_.']';
|
283
|
|
|
|
|
|
|
}
|
284
|
|
|
|
|
|
|
} else {
|
285
|
0
|
|
|
|
|
|
$string .= '[]';
|
286
|
|
|
|
|
|
|
}
|
287
|
|
|
|
|
|
|
}
|
288
|
|
|
|
|
|
|
}
|
289
|
|
|
|
|
|
|
} else {
|
290
|
0
|
0
|
|
|
|
|
if (ref($node) eq 'Games::Go::SGF::Variation'){
|
291
|
0
|
|
|
|
|
|
$string .= _iterate($node);
|
292
|
|
|
|
|
|
|
}
|
293
|
|
|
|
|
|
|
}
|
294
|
0
|
|
|
|
|
|
return $string
|
295
|
|
|
|
|
|
|
}
|
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
sub AUTOLOAD {
|
298
|
0
|
|
|
0
|
|
|
my ($self, $value) = @_;
|
299
|
0
|
0
|
|
|
|
|
my $type = ref($self) or croak $self.' is not an object';
|
300
|
0
|
|
|
|
|
|
my $name = $AUTOLOAD;
|
301
|
0
|
|
|
|
|
|
$name =~ s/.*://; # strip fully-qualified portion
|
302
|
0
|
0
|
|
|
|
|
_setvalue($self, $name, $value) if ($value);
|
303
|
0
|
|
|
|
|
|
return $self->[0]->{$name};
|
304
|
|
|
|
|
|
|
}
|
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
package Games::Go::SGF::Variation;
|
307
|
|
|
|
|
|
|
our $AUTOLOAD;
|
308
|
0
|
|
|
0
|
|
|
sub mainline { return $_[0]->[0] }
|
309
|
0
|
|
|
0
|
|
|
sub variation { return $_[0]->[$_[1]]}
|
310
|
0
|
|
|
0
|
|
|
sub variations { return @{$_[0]} }
|
|
0
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
# This is - as I shouldn't need to tell you - is a dirty hack.
|
313
|
|
|
|
|
|
|
# But I like it (Simon)
|
314
|
|
|
|
|
|
|
sub AUTOLOAD {
|
315
|
0
|
|
|
0
|
|
|
$AUTOLOAD=~ s/Variation/Node/;
|
316
|
0
|
|
|
|
|
|
&$AUTOLOAD($_[0]->mainline, @_[1..@_]);
|
317
|
|
|
|
|
|
|
}
|
318
|
0
|
|
|
0
|
|
|
sub DESTROY { }
|
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
package Games::Go::SGF::Node;
|
321
|
|
|
|
|
|
|
our $AUTOLOAD;
|
322
|
|
|
|
|
|
|
|
323
|
0
|
0
|
|
0
|
|
|
sub move { my $node = shift; $node->{B} || $node->{W} }
|
|
0
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
|
325
|
0
|
|
|
0
|
|
|
sub color { colour(shift) }
|
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
sub colour {
|
328
|
0
|
|
|
0
|
|
|
my $node = shift;
|
329
|
0
|
0
|
|
|
|
|
if (exists($node->{B})){'B'}
|
|
0
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
else {
|
331
|
0
|
0
|
|
|
|
|
if (exists($node->{W})){'W'}
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
else {'None'}
|
333
|
|
|
|
|
|
|
}
|
334
|
|
|
|
|
|
|
}
|
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
sub nodedump {
|
337
|
0
|
|
|
0
|
|
|
my $node = shift;
|
338
|
0
|
|
|
|
|
|
my $result;
|
339
|
0
|
|
|
|
|
|
for (split(',',$node->{tags})) {$result .= join(' ', $_, $node->{$_}, "\n")}
|
|
0
|
|
|
|
|
|
|
340
|
0
|
|
|
|
|
|
return $result
|
341
|
|
|
|
|
|
|
}
|
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
sub tags {
|
344
|
0
|
|
|
0
|
|
|
my $node = shift;
|
345
|
0
|
|
|
|
|
|
$node->{tags};
|
346
|
|
|
|
|
|
|
}
|
347
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
sub delete{
|
349
|
0
|
|
|
0
|
|
|
my ($node, $tag) = @_;
|
350
|
0
|
0
|
|
|
|
|
if (exists $node->{$tag}) {
|
351
|
0
|
|
|
|
|
|
delete $node->{$tag};
|
352
|
0
|
|
|
|
|
|
$node->{tags} =~ s/$tag,?//;
|
353
|
|
|
|
|
|
|
}
|
354
|
|
|
|
|
|
|
}
|
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
sub AUTOLOAD {
|
357
|
0
|
|
|
0
|
|
|
my ($node, $value) = @_;
|
358
|
0
|
|
|
|
|
|
my $name = $AUTOLOAD;
|
359
|
0
|
|
|
|
|
|
$name =~ s/.*://; # strip fully-qualified portion
|
360
|
0
|
0
|
|
|
|
|
_nodesetvalue($node, $name, $value) if $value;
|
361
|
0
|
|
|
|
|
|
return $node->{$name};
|
362
|
|
|
|
|
|
|
}
|
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
sub _nodesetvalue {
|
365
|
0
|
|
|
0
|
|
|
my ($node, $tag, $value) = @_;
|
366
|
0
|
0
|
|
|
|
|
if (exists $node->{tags}) {
|
367
|
0
|
0
|
|
|
|
|
$node->{tags} = join(',', $node->{tags}, $tag) unless exists $node->{$tag};
|
368
|
|
|
|
|
|
|
} else {
|
369
|
0
|
|
|
|
|
|
$node->{tags} = $tag;
|
370
|
|
|
|
|
|
|
}
|
371
|
0
|
|
|
|
|
|
$node->{$tag} = $value;
|
372
|
|
|
|
|
|
|
}
|
373
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
# Preloaded methods go here.
|
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
1;
|
377
|
|
|
|
|
|
|
__END__
|