line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
############################################################ |
2
|
|
|
|
|
|
|
# |
3
|
|
|
|
|
|
|
# Chess:PGN::Parse - a parser for PGN games |
4
|
|
|
|
|
|
|
# |
5
|
|
|
|
|
|
|
# Copyright (c) 2002 by Giuseppe Maxia |
6
|
|
|
|
|
|
|
# Produced under the GPL (Golden Perl Laziness) |
7
|
|
|
|
|
|
|
# Distributed under the GPL (GNU General Public License) |
8
|
|
|
|
|
|
|
# |
9
|
|
|
|
|
|
|
############################################################ |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
# StringHandle |
12
|
|
|
|
|
|
|
# Utility package to read input from string, imitating |
13
|
|
|
|
|
|
|
# a file handle. |
14
|
|
|
|
|
|
|
package StringHandle; |
15
|
1
|
|
|
1
|
|
1035
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
39
|
|
16
|
1
|
|
|
1
|
|
5
|
use warnings; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
61
|
|
17
|
|
|
|
|
|
|
use overload |
18
|
|
|
|
|
|
|
q{<>} => sub { |
19
|
0
|
|
|
0
|
|
0
|
return shift @{$_[0]}; |
|
0
|
|
|
|
|
0
|
|
20
|
1
|
|
|
1
|
|
2240
|
}; |
|
1
|
|
|
|
|
2039
|
|
|
1
|
|
|
|
|
9
|
|
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
sub new { |
23
|
0
|
|
|
0
|
|
0
|
my $class = shift; |
24
|
0
|
|
|
|
|
0
|
return bless [split /^/xm, $_[0]], $class; |
25
|
|
|
|
|
|
|
} |
26
|
0
|
|
|
0
|
|
0
|
sub close { } ## no critic |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
package Chess::PGN::Parse; ## no critic |
29
|
1
|
|
|
1
|
|
1130
|
use English qw( -no_match_vars ) ; |
|
1
|
|
|
|
|
13576
|
|
|
1
|
|
|
|
|
8
|
|
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
require 5.006; |
32
|
1
|
|
|
1
|
|
2209
|
use IO::File; |
|
1
|
|
|
|
|
18202
|
|
|
1
|
|
|
|
|
9623
|
|
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
require Exporter; |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
our @ISA = qw(Exporter); |
37
|
|
|
|
|
|
|
our @EXPORT = qw(shrink_epd expand_epd STR NAG); |
38
|
|
|
|
|
|
|
our @EXPORT_OK = qw(); |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
our $VERSION = '0.19'; # 10-jan-2006 |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
=head1 NAME |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
Chess::PGN::Parse - reads and parses PGN (Portable Game Notation) Chess files |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
=head1 SYNOPSIS |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
use Chess::PGN::Parse; |
49
|
|
|
|
|
|
|
use English qw( -no_match_vars ); |
50
|
|
|
|
|
|
|
my $pgnfile = "kk_2001.pgn"; |
51
|
|
|
|
|
|
|
my $pgn = new Chess::PGN::Parse $pgnfile |
52
|
|
|
|
|
|
|
or die "can't open $pgnfile\n"; |
53
|
|
|
|
|
|
|
while ($pgn->read_game()) { |
54
|
|
|
|
|
|
|
print $pgn->white, ", " , $pgn->black, ", ", |
55
|
|
|
|
|
|
|
$pgn->result, ", ", |
56
|
|
|
|
|
|
|
$pgn->game, "\n"; |
57
|
|
|
|
|
|
|
} |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
use Chess::PGN::Parse; |
61
|
|
|
|
|
|
|
my $text =""; |
62
|
|
|
|
|
|
|
{ |
63
|
|
|
|
|
|
|
local $INPUT_RECORD_SEPARATOR = undef; |
64
|
|
|
|
|
|
|
open PGN "< $pgnfile" or die; |
65
|
|
|
|
|
|
|
$text = ; |
66
|
|
|
|
|
|
|
close $text; |
67
|
|
|
|
|
|
|
} |
68
|
|
|
|
|
|
|
# reads from string instead of a file |
69
|
|
|
|
|
|
|
my $pgn = new Chess::PGN::Parse undef, $text; |
70
|
|
|
|
|
|
|
while ($pgn->read_game()) { |
71
|
|
|
|
|
|
|
print $pgn->white, ", " , $pgn->black, ", ", |
72
|
|
|
|
|
|
|
$pgn->result, ", ", |
73
|
|
|
|
|
|
|
$pgn->game, "\n"; |
74
|
|
|
|
|
|
|
} |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
use Chess::PGN::Parse; |
77
|
|
|
|
|
|
|
my $pgnfile = "kk_2001.pgn"; |
78
|
|
|
|
|
|
|
my $pgn = new Chess::PGN::Parse $pgnfile |
79
|
|
|
|
|
|
|
or die "can't open $pgnfile\n"; |
80
|
|
|
|
|
|
|
my @games = $pgn->smart_read_all(); |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
=head1 DESCRIPTION |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
Chess::PGN::Parse offers a range of methods to read and manipulate Portable Game Notation files. |
86
|
|
|
|
|
|
|
PGN files contain chess games produced by chess programs following a standard format (http://www.schachprobleme.de/chessml/faq/pgn/). It is among the preferred means of chess games distribution. Being a public, well established standard, PGN is understood by many chess archive programs. |
87
|
|
|
|
|
|
|
Parsing simple PGN files is not difficult. However, dealing with some of the intricacies of the Standard is less than trivial. This module offers a clean handle toward reading and parsing complex PGN files. |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
A PGN file has several B, which are key/values pairs at the header of each game, in the format |
90
|
|
|
|
|
|
|
[key "value"] |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
After the header, the game follows. A string of numbered chess moves, optionally interrupted by braced comments and recursive parenthesized variants and comments. While dealing with simple braced comments is straightforward, parsing nested comments can give you more than a headache. |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
Chess::PGN::Parse most immediate methods are: |
95
|
|
|
|
|
|
|
read_game() reads one game, separating the tags and the game text. |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
parse_game() parse the current game, and stores the moves into an |
98
|
|
|
|
|
|
|
array and optionally saves the comments into an array of hashes |
99
|
|
|
|
|
|
|
for furter usage. It can deal with nested comments and recursive |
100
|
|
|
|
|
|
|
variations. |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
quick_parse_game() Same as the above, but doesn't save the comments, |
103
|
|
|
|
|
|
|
which are just stripped from the text. It can't deal with nested |
104
|
|
|
|
|
|
|
comments. Should be the preferred method when we know that we are |
105
|
|
|
|
|
|
|
dealing with simple PGNs. |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
smart_parse_game() Best of the above methods. A preliminary check |
108
|
|
|
|
|
|
|
will call parse_game() or quick_parse_game(), depending on the |
109
|
|
|
|
|
|
|
presence of nested comments in the game. |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
read_all(), quick_read_all(), smart_read_all() will read all the records |
112
|
|
|
|
|
|
|
in the current PGN file and return an array of hashes with all the |
113
|
|
|
|
|
|
|
parsed details from the games. |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
=head2 Parsing games |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
Parsing PGN games is actually two actions: reading and parsing. |
118
|
|
|
|
|
|
|
The reading will only identify the two components of a game, i.e. |
119
|
|
|
|
|
|
|
the tags and the moves text. During this phase, the tags are |
120
|
|
|
|
|
|
|
decomposed and stored into an internal hash for future use, |
121
|
|
|
|
|
|
|
while the game text is left untouched. |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
Reading a game is accomplished through the read_game() method, |
124
|
|
|
|
|
|
|
which will identify not only the standard game format but also |
125
|
|
|
|
|
|
|
some unorthodox cases, such as games with no separating blank line |
126
|
|
|
|
|
|
|
between tags and moves, games with no blank lines at the end of |
127
|
|
|
|
|
|
|
the moves, leading blank lines, tags spanning over several lines |
128
|
|
|
|
|
|
|
and some minor quibbles. |
129
|
|
|
|
|
|
|
If you know that your games don't have any of these problems, |
130
|
|
|
|
|
|
|
you might choose the read_standard_game() method, which is a |
131
|
|
|
|
|
|
|
bit faster. |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
After the reading, you can either use the game text as it is, |
134
|
|
|
|
|
|
|
or you can ask for parsing. What is it? Parsing is the process |
135
|
|
|
|
|
|
|
of identifying and isolating the moves from the rest of the game |
136
|
|
|
|
|
|
|
text, such as comments and recursive variations. This process |
137
|
|
|
|
|
|
|
can be accomplished in two ways: using quick_parse_game(), the |
138
|
|
|
|
|
|
|
non moves elements are just stripped off and discarded, leaving |
139
|
|
|
|
|
|
|
an array of bare moves. If the comments and the recursive |
140
|
|
|
|
|
|
|
variations (RAV) are valuable to you, you can use the parse_game() |
141
|
|
|
|
|
|
|
method, which will strip the excess text, but it can store it |
142
|
|
|
|
|
|
|
into an appropriate data structure. Passing the option |
143
|
|
|
|
|
|
|
{save_comments =>'yes'} to parse_game(), game comments will |
144
|
|
|
|
|
|
|
be stored into a hash, having as key the move number + color. |
145
|
|
|
|
|
|
|
Multiple comments for the same move are appended to the previous |
146
|
|
|
|
|
|
|
one. If this structure doesn't provide enough details, a further |
147
|
|
|
|
|
|
|
option {comments_struct => 'array'} will store an array of |
148
|
|
|
|
|
|
|
comments for each move. Even more details are available using |
149
|
|
|
|
|
|
|
{comments_struct => 'hol'}, which will trigger the creation of |
150
|
|
|
|
|
|
|
a hash of lists (hol), where the key is the comment type (RAV, |
151
|
|
|
|
|
|
|
NAG, brace, semicolon, escaped) and the value is a list of |
152
|
|
|
|
|
|
|
homogeneous comments belonging to the same move. |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
A further option {log_errors => 'yes'} will save the errors |
155
|
|
|
|
|
|
|
into a structure similar to the comments (no options on the |
156
|
|
|
|
|
|
|
format, though. All errors for one given move are just a |
157
|
|
|
|
|
|
|
string). What are errors? Just anything that is not recognized |
158
|
|
|
|
|
|
|
as any of the previous elements. Not a move, or a move number, |
159
|
|
|
|
|
|
|
or a comment, either text or recursive. Anything that the |
160
|
|
|
|
|
|
|
parser cannot actively classify as 'known' will be stored |
161
|
|
|
|
|
|
|
as error. |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
=head2 Getting the parsed values |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
At the end of the exercise, you can access the components |
166
|
|
|
|
|
|
|
through some standard methods. |
167
|
|
|
|
|
|
|
The standard tags have their direct access method (white, |
168
|
|
|
|
|
|
|
black, site, event, date, result, round). More methods give |
169
|
|
|
|
|
|
|
access to some commonly used elements: |
170
|
|
|
|
|
|
|
game() is the unparsed text, moves() returns an array of parsed |
171
|
|
|
|
|
|
|
moves, without move numbers, comments() and errors() return |
172
|
|
|
|
|
|
|
the relative structures after parsing. |
173
|
|
|
|
|
|
|
About game(), it's worth mentioning that, using quick_parse_game(), |
174
|
|
|
|
|
|
|
the game text is stripped of all non moves elements. This is |
175
|
|
|
|
|
|
|
an intended feature, to privilege speed. If you need to preserve |
176
|
|
|
|
|
|
|
the original game text after parsing, either copy it before |
177
|
|
|
|
|
|
|
calling quick_parse_game() or use parse_game() instead. |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
=head2 Recursive Parsing |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
PGN games may include RAV (Recursive Annotated Variations) which |
182
|
|
|
|
|
|
|
is just game text inside parentheses. |
183
|
|
|
|
|
|
|
This module can recognize RAV sequences and store them as comments. |
184
|
|
|
|
|
|
|
One of the things you can do with these sequences is to parse |
185
|
|
|
|
|
|
|
them again and get bare moves that you can feed to a chess engine |
186
|
|
|
|
|
|
|
or a move analyzer (Chess::PGN::EPD by H.S.Myers is one of them). |
187
|
|
|
|
|
|
|
Chess::PGN::Parse does not directly support recursive parsing of |
188
|
|
|
|
|
|
|
games, but it makes it possible. |
189
|
|
|
|
|
|
|
Parse a game, saving the comments as hash of list (see above), |
190
|
|
|
|
|
|
|
and then check for comments that are of 'RAV' type. For each |
191
|
|
|
|
|
|
|
entry in the comments array, strip the surrounding parentheses |
192
|
|
|
|
|
|
|
and create a new Chess::PGN::Parse object with that text. |
193
|
|
|
|
|
|
|
Easier to do than to describe, actually. For an example of this |
194
|
|
|
|
|
|
|
technique, check the file F. |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
=head2 EXPORT |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
new, STR, read_game, tags, event, site, white, black, round, date, result, game , NAG, moves |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
=head2 DEPENDENCIES |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
IO::File |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
=head1 Class methods |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
=over 4 |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
=item new() |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
Create a new Chess::PGN::Parse object (requires file name) |
211
|
|
|
|
|
|
|
my $pgn = Chess::PGN::Parse->new "filename.pgn" |
212
|
|
|
|
|
|
|
or die "no such file \n"; |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
=cut |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
my @seven_tags_roster = qw(Event Site Date Round White Black Result); |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
sub new { |
219
|
2
|
|
|
2
|
1
|
158
|
my $class = shift; |
220
|
2
|
|
|
|
|
4
|
my $filename = shift; |
221
|
2
|
|
|
|
|
4
|
my $fh = undef; |
222
|
2
|
50
|
|
|
|
10
|
if (defined $filename) { |
223
|
2
|
|
50
|
|
|
32
|
$fh = new IO::File "< $filename" |
224
|
|
|
|
|
|
|
|| return ; |
225
|
|
|
|
|
|
|
} |
226
|
|
|
|
|
|
|
else { |
227
|
0
|
|
|
|
|
0
|
my $text = shift; |
228
|
0
|
|
|
|
|
0
|
$fh = new StringHandle $text; |
229
|
|
|
|
|
|
|
} |
230
|
2
|
|
|
|
|
14657
|
my $self = bless { |
231
|
|
|
|
|
|
|
GameMoves =>[], # game moves |
232
|
|
|
|
|
|
|
GameComments =>{}, # comments with reference to the move |
233
|
|
|
|
|
|
|
gamedescr => {}, # will contain the PGN tags |
234
|
|
|
|
|
|
|
GameErrors => {}, # will contain the parsing errors |
235
|
|
|
|
|
|
|
fh => \$fh # filehandle to the PGN file |
236
|
|
|
|
|
|
|
}, $class; |
237
|
2
|
|
|
|
|
11
|
return $self; |
238
|
|
|
|
|
|
|
} |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
=for internal use |
241
|
|
|
|
|
|
|
the object destroyer cleans possible hanging references |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
=cut |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
sub DESTROY { |
246
|
2
|
|
|
2
|
|
79
|
my $self = shift; |
247
|
2
|
|
|
|
|
5
|
undef $self->{GameComments}; |
248
|
2
|
|
|
|
|
4
|
undef $self->{GameErrors}; |
249
|
2
|
|
|
|
|
7
|
undef $self->{gamedescr}; |
250
|
2
|
|
|
|
|
18
|
undef $self->{GameMoves}; |
251
|
2
|
|
|
|
|
4
|
eval { |
252
|
|
|
|
|
|
|
#if (defined ${$self->{fh}}) { |
253
|
2
|
|
|
|
|
3
|
${$self->{fh}}->close(); |
|
2
|
|
|
|
|
22
|
|
254
|
|
|
|
|
|
|
#} |
255
|
|
|
|
|
|
|
}; |
256
|
2
|
|
|
|
|
70
|
undef $self->{fh}; |
257
|
2
|
|
|
|
|
128
|
return; |
258
|
|
|
|
|
|
|
} |
259
|
|
|
|
|
|
|
my %symbolic_annotation_glyph = ( |
260
|
|
|
|
|
|
|
q{$1} => q{!}, |
261
|
|
|
|
|
|
|
q{$2} => q{?}, |
262
|
|
|
|
|
|
|
q{$3} => q{!!}, |
263
|
|
|
|
|
|
|
q{$4} => q{??}, |
264
|
|
|
|
|
|
|
q{$5} => q{!?}, |
265
|
|
|
|
|
|
|
q{$6} => q{?!}, |
266
|
|
|
|
|
|
|
); |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
my %numeric_annotation_glyph = (); |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
=item NAG() |
271
|
|
|
|
|
|
|
returns the corresponding Numeric Annotation Glyph |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
=cut |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
sub NAG { |
276
|
0
|
|
|
0
|
1
|
0
|
my $item = shift; |
277
|
0
|
0
|
|
|
|
0
|
return unless $item =~ /\$?(\d+)/x; |
278
|
0
|
0
|
0
|
|
|
0
|
return if ($1 > 139) or ($1 < 0); |
279
|
0
|
0
|
|
|
|
0
|
unless (scalar keys %numeric_annotation_glyph) { |
280
|
0
|
|
|
|
|
0
|
local $INPUT_RECORD_SEPARATOR = undef; |
281
|
0
|
|
|
|
|
0
|
eval ; ## no critic |
282
|
|
|
|
|
|
|
} |
283
|
0
|
|
|
|
|
0
|
my $nag_ref = \%numeric_annotation_glyph; |
284
|
0
|
0
|
0
|
|
|
0
|
if (($1 > 0) and ($1 <=6)) { |
285
|
0
|
|
|
|
|
0
|
$nag_ref = \%symbolic_annotation_glyph |
286
|
|
|
|
|
|
|
} |
287
|
0
|
0
|
|
|
|
0
|
if ($item =~ /^\$/x) { |
288
|
0
|
|
|
|
|
0
|
return $nag_ref->{$item} |
289
|
|
|
|
|
|
|
} |
290
|
|
|
|
|
|
|
else { |
291
|
0
|
|
|
|
|
0
|
return $nag_ref->{q{$}.$item} |
292
|
|
|
|
|
|
|
} |
293
|
|
|
|
|
|
|
} |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
=item STR() |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
returns the Seven Tags Roster array |
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
@array = $pgn->STR(); |
300
|
|
|
|
|
|
|
@array = PGNParser::STR(); |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
=cut |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
sub STR { |
305
|
0
|
|
|
0
|
1
|
0
|
return @seven_tags_roster; |
306
|
|
|
|
|
|
|
} |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
=item event() |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
returns the Event tag |
311
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
=item site() |
313
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
returns the Site tag |
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
=item date() |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
returns the Date tag |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
=item white() |
321
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
returns the White tag |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
=item black() |
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
returns the Black tag |
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
=item result() |
329
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
returns the result tag |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
=item round() |
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
returns the Round tag |
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
=item game() |
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
returns the unparsed game moves |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
=item time() |
341
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
returns the Time tag |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
=item eco() |
345
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
returns the ECO tag |
347
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
=item eventdate() |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
returns the EventDate tag |
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
=item moves() |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
returns an array reference to the game moves (no numbers) |
355
|
|
|
|
|
|
|
=cut |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
=item comments() |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
returns a hash reference to the game comments (the key is the move number and the value are the comments for such move) |
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
=cut |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
=item errors() |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
returns a hash reference to the game errors (the key is the move number and the value are the errors for such move) |
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
=item set_event() |
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
returns or modifies the Event tag |
370
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
=item set_site() |
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
returns or modifies the Site tag |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
=item set_date() |
376
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
returns or modifies the Date tag |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
=item set_white() |
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
returns or modifies the White tag |
382
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
=item set_black() |
384
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
returns or modifies the Black tag |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
=item set_result() |
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
returns or modifies the result tag |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
=item set_round() |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
returns or modifies the Round tag |
394
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
=item set_game() |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
returns or modifies the unparsed game moves |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
=item set_time() |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
returns or modifies the Time tag |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
=item set_eco() |
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
returns or modifies the ECO tag |
406
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
=item set_eventdate() |
408
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
returns or modifies the EventDate tag |
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
=item set_moves() |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
returns or modifies an array reference to the game moves (no numbers) |
415
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
=cut |
417
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
sub event { |
419
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
420
|
0
|
|
|
|
|
0
|
return $self->{gamedescr}{Event} |
421
|
|
|
|
|
|
|
} |
422
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
sub site { |
424
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
425
|
0
|
|
|
|
|
0
|
return $self->{gamedescr}{Site} |
426
|
|
|
|
|
|
|
} |
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
sub date { |
429
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
430
|
0
|
|
|
|
|
0
|
return $self->{gamedescr}{Date} |
431
|
|
|
|
|
|
|
} |
432
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
sub white { |
434
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
435
|
0
|
|
|
|
|
0
|
return $self->{gamedescr}{White} |
436
|
|
|
|
|
|
|
} |
437
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
sub black { |
439
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
440
|
0
|
|
|
|
|
0
|
return $self->{gamedescr}{Black} |
441
|
|
|
|
|
|
|
} |
442
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
sub result { |
444
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
445
|
0
|
|
|
|
|
0
|
return $self->{gamedescr}{Result} |
446
|
|
|
|
|
|
|
} |
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
sub round { |
449
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
450
|
0
|
|
|
|
|
0
|
return $self->{gamedescr}{Round} |
451
|
|
|
|
|
|
|
} |
452
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
## no critic |
454
|
|
|
|
|
|
|
sub time { |
455
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
456
|
0
|
|
|
|
|
0
|
return $self->{gamedescr}{Time} |
457
|
|
|
|
|
|
|
} |
458
|
|
|
|
|
|
|
## use critic |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
sub eventdate { |
461
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
462
|
0
|
|
|
|
|
0
|
return $self->{gamedescr}{EventDate} |
463
|
|
|
|
|
|
|
} |
464
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
sub eco { |
466
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
467
|
0
|
|
|
|
|
0
|
return $self->{gamedescr}{ECO} |
468
|
|
|
|
|
|
|
} |
469
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
sub game { |
471
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
472
|
0
|
|
|
|
|
0
|
return $self->{gamedescr}{Game} |
473
|
|
|
|
|
|
|
} |
474
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
sub moves { |
476
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
477
|
0
|
|
|
|
|
0
|
return $self->{GameMoves}; |
478
|
|
|
|
|
|
|
} |
479
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
sub set_event { |
482
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
483
|
0
|
0
|
|
|
|
0
|
$self->{gamedescr}{Event} = $_[0] if @_; |
484
|
0
|
|
|
|
|
0
|
return $self->{gamedescr}{Event} |
485
|
|
|
|
|
|
|
} |
486
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
sub set_site { |
488
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
489
|
0
|
0
|
|
|
|
0
|
$self->{gamedescr}{Site} = shift if @_; |
490
|
0
|
|
|
|
|
0
|
return $self->{gamedescr}{Site} |
491
|
|
|
|
|
|
|
} |
492
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
sub set_date { |
494
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
495
|
0
|
0
|
|
|
|
0
|
$self->{gamedescr}{Date} = shift if @_; |
496
|
0
|
|
|
|
|
0
|
return $self->{gamedescr}{Date} |
497
|
|
|
|
|
|
|
} |
498
|
|
|
|
|
|
|
|
499
|
|
|
|
|
|
|
sub set_white { |
500
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
501
|
0
|
0
|
|
|
|
0
|
$self->{gamedescr}{White} = shift if @_; |
502
|
0
|
|
|
|
|
0
|
return $self->{gamedescr}{White} |
503
|
|
|
|
|
|
|
} |
504
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
sub set_black { |
506
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
507
|
0
|
0
|
|
|
|
0
|
$self->{gamedescr}{Black} = shift if @_; |
508
|
0
|
|
|
|
|
0
|
return $self->{gamedescr}{Black} |
509
|
|
|
|
|
|
|
} |
510
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
sub set_result { |
512
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
513
|
0
|
0
|
|
|
|
0
|
$self->{gamedescr}{Result} = shift if @_; |
514
|
0
|
|
|
|
|
0
|
return $self->{gamedescr}{Result} |
515
|
|
|
|
|
|
|
} |
516
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
sub set_round { |
518
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
519
|
0
|
0
|
|
|
|
0
|
$self->{gamedescr}{Round} = shift if @_; |
520
|
0
|
|
|
|
|
0
|
return $self->{gamedescr}{Round} |
521
|
|
|
|
|
|
|
} |
522
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
sub set_time { |
524
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
525
|
0
|
0
|
|
|
|
0
|
$self->{gamedescr}{Time} = shift if @_; |
526
|
0
|
|
|
|
|
0
|
return $self->{gamedescr}{Time} |
527
|
|
|
|
|
|
|
} |
528
|
|
|
|
|
|
|
|
529
|
|
|
|
|
|
|
sub set_eventdate { |
530
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
531
|
0
|
0
|
|
|
|
0
|
$self->{gamedescr}{EventDate} = shift if @_; |
532
|
0
|
|
|
|
|
0
|
return $self->{gamedescr}{EventDate} |
533
|
|
|
|
|
|
|
} |
534
|
|
|
|
|
|
|
|
535
|
|
|
|
|
|
|
sub set_eco { |
536
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
537
|
0
|
0
|
|
|
|
0
|
$self->{gamedescr}{ECO} = shift if @_; |
538
|
0
|
|
|
|
|
0
|
return $self->{gamedescr}{ECO} |
539
|
|
|
|
|
|
|
} |
540
|
|
|
|
|
|
|
|
541
|
|
|
|
|
|
|
sub set_game { |
542
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
543
|
0
|
0
|
|
|
|
0
|
$self->{gamedescr}{Game} = shift if @_; |
544
|
0
|
|
|
|
|
0
|
return $self->{gamedescr}{Game} |
545
|
|
|
|
|
|
|
} |
546
|
|
|
|
|
|
|
|
547
|
|
|
|
|
|
|
sub set_moves { |
548
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
549
|
0
|
0
|
0
|
|
|
0
|
$self->{GameMoves} = shift if (@_ && (ref $_[0] eq 'ARRAY')) ; |
550
|
0
|
|
|
|
|
0
|
return $self->{GameMoves}; |
551
|
|
|
|
|
|
|
} |
552
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
sub errors { |
554
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
555
|
0
|
|
|
|
|
0
|
return $self->{GameErrors}; |
556
|
|
|
|
|
|
|
} |
557
|
|
|
|
|
|
|
|
558
|
|
|
|
|
|
|
sub comments { |
559
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
560
|
0
|
|
|
|
|
0
|
return $self->{GameComments}; |
561
|
|
|
|
|
|
|
} |
562
|
|
|
|
|
|
|
|
563
|
|
|
|
|
|
|
=for internal use |
564
|
|
|
|
|
|
|
initialize the pgn object fields. |
565
|
|
|
|
|
|
|
|
566
|
|
|
|
|
|
|
=cut |
567
|
|
|
|
|
|
|
|
568
|
|
|
|
|
|
|
sub _init { |
569
|
23
|
|
|
23
|
|
25
|
my $self = shift; |
570
|
23
|
|
|
|
|
27
|
for (keys %{$self->{gamedescr}}) { |
|
23
|
|
|
|
|
98
|
|
571
|
273
|
|
|
|
|
524
|
$self->{gamedescr}{$_} = q{}; |
572
|
|
|
|
|
|
|
} |
573
|
23
|
50
|
|
|
|
86
|
delete $self->{gamedescr}{FirstMove} |
574
|
|
|
|
|
|
|
if exists $self->{gamedescr}{FirstMove}; |
575
|
23
|
|
|
|
|
42
|
undef $self->{GameMoves}; |
576
|
23
|
|
|
|
|
31
|
undef $self->{GameComments}; |
577
|
23
|
|
|
|
|
33
|
undef $self->{GameErrors}; # 0.07 |
578
|
23
|
|
|
|
|
41
|
return; |
579
|
|
|
|
|
|
|
} |
580
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
=item tags() |
582
|
|
|
|
|
|
|
|
583
|
|
|
|
|
|
|
returns a hash reference to all the parsed tags |
584
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
$hash_ref = $pgn->tags(); |
586
|
|
|
|
|
|
|
|
587
|
|
|
|
|
|
|
=cut |
588
|
|
|
|
|
|
|
|
589
|
|
|
|
|
|
|
sub tags { |
590
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
591
|
0
|
|
|
|
|
0
|
return \%{$self->{gamedescr}}; |
|
0
|
|
|
|
|
0
|
|
592
|
|
|
|
|
|
|
} |
593
|
|
|
|
|
|
|
|
594
|
|
|
|
|
|
|
=item read_all() |
595
|
|
|
|
|
|
|
|
596
|
|
|
|
|
|
|
Will read and parse all the games in the current file and return a reference to an array of hashes. |
597
|
|
|
|
|
|
|
Each hash item contains both the raw data and the parsed moves and comments |
598
|
|
|
|
|
|
|
|
599
|
|
|
|
|
|
|
Same parameters as for parse_game(). Default : discard comments |
600
|
|
|
|
|
|
|
|
601
|
|
|
|
|
|
|
my $games_ref = $pgn->read_all(); |
602
|
|
|
|
|
|
|
|
603
|
|
|
|
|
|
|
=cut |
604
|
|
|
|
|
|
|
|
605
|
|
|
|
|
|
|
sub read_all { |
606
|
1
|
|
|
1
|
1
|
43
|
my $self=shift; |
607
|
1
|
|
|
|
|
2
|
my $params = shift; |
608
|
1
|
|
|
|
|
3
|
my @games =(); |
609
|
1
|
|
|
|
|
11
|
while ($self->read_game()) { |
610
|
1
|
|
|
|
|
6
|
$self->parse_game($params); |
611
|
1
|
|
|
|
|
1
|
my %gd = %{$self->{gamedescr}}; |
|
1
|
|
|
|
|
17
|
|
612
|
1
|
|
|
|
|
4
|
$gd{GameComments} = $self->{GameComments}; |
613
|
1
|
|
|
|
|
3
|
$gd{GameErrors} = $self->{GameErrors}; |
614
|
1
|
|
|
|
|
5
|
$gd{GameMoves} = $self->{GameMoves}; |
615
|
1
|
|
|
|
|
5
|
push @games, \%gd; |
616
|
|
|
|
|
|
|
} |
617
|
1
|
|
|
|
|
1349
|
return \@games; |
618
|
|
|
|
|
|
|
} |
619
|
|
|
|
|
|
|
|
620
|
|
|
|
|
|
|
=item quick_read_all() |
621
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
Will read and quick parse all the games in the current file and return a reference to an array of hashes. |
623
|
|
|
|
|
|
|
Each hash item contains both the raw data and the parsed moves |
624
|
|
|
|
|
|
|
Comments are discarded. Same parameters as for quick_parse_game(). |
625
|
|
|
|
|
|
|
|
626
|
|
|
|
|
|
|
my $games_ref = $pgn->quick_read_all(); |
627
|
|
|
|
|
|
|
|
628
|
|
|
|
|
|
|
=cut |
629
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
sub quick_read_all { |
631
|
1
|
|
|
1
|
1
|
86
|
my $self=shift; |
632
|
1
|
|
|
|
|
4
|
my $params = shift; |
633
|
1
|
|
|
|
|
4
|
my @games =(); |
634
|
1
|
|
|
|
|
6
|
while ($self->read_game()) { |
635
|
20
|
|
|
|
|
50
|
$self->quick_parse_game($params); |
636
|
20
|
|
|
|
|
27
|
my %gd = %{$self->{gamedescr}}; |
|
20
|
|
|
|
|
346
|
|
637
|
20
|
|
|
|
|
58
|
$gd{GameMoves} = $self->{GameMoves}; |
638
|
20
|
|
|
|
|
73
|
push @games, \%gd; |
639
|
|
|
|
|
|
|
} |
640
|
1
|
|
|
|
|
8
|
return \@games; |
641
|
|
|
|
|
|
|
} |
642
|
|
|
|
|
|
|
|
643
|
|
|
|
|
|
|
=item smart_read_all() |
644
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
Will read and quick parse all the games in the current file and return a reference to an array of hashes. |
646
|
|
|
|
|
|
|
Each hash item contains both the raw data and the parsed moves |
647
|
|
|
|
|
|
|
Comments are discarded. Calls smart_read_game() to decide which method is best to parse each given game. |
648
|
|
|
|
|
|
|
|
649
|
|
|
|
|
|
|
my $games_ref = $pgn->smart_read_all(); |
650
|
|
|
|
|
|
|
|
651
|
|
|
|
|
|
|
=cut |
652
|
|
|
|
|
|
|
|
653
|
|
|
|
|
|
|
sub smart_read_all { |
654
|
0
|
|
|
0
|
1
|
0
|
my $self=shift; |
655
|
0
|
|
|
|
|
0
|
my $params = shift; |
656
|
0
|
|
|
|
|
0
|
my @games =(); |
657
|
0
|
|
|
|
|
0
|
while ($self->read_game()) { |
658
|
0
|
|
|
|
|
0
|
$self->smart_parse_game($params); |
659
|
0
|
|
|
|
|
0
|
my %gd = %{$self->{gamedescr}}; |
|
0
|
|
|
|
|
0
|
|
660
|
0
|
|
|
|
|
0
|
$gd{GameMoves} = $self->{GameMoves}; |
661
|
0
|
|
|
|
|
0
|
push @games, \%gd; |
662
|
|
|
|
|
|
|
} |
663
|
0
|
|
|
|
|
0
|
return \@games; |
664
|
|
|
|
|
|
|
} |
665
|
|
|
|
|
|
|
|
666
|
|
|
|
|
|
|
|
667
|
|
|
|
|
|
|
=item read_game() |
668
|
|
|
|
|
|
|
|
669
|
|
|
|
|
|
|
reads the next game from the given PGN file. |
670
|
|
|
|
|
|
|
Returns TRUE (1) if successful (= a game was read) |
671
|
|
|
|
|
|
|
or FALSE (0) if no more games are available or |
672
|
|
|
|
|
|
|
an unexpected EOF occurred before the end of parsing |
673
|
|
|
|
|
|
|
|
674
|
|
|
|
|
|
|
while ($pgn->read_game()) { |
675
|
|
|
|
|
|
|
do_something_smart; |
676
|
|
|
|
|
|
|
} |
677
|
|
|
|
|
|
|
|
678
|
|
|
|
|
|
|
It can read standard and in some cases even non-standard PGN |
679
|
|
|
|
|
|
|
games. The following deviance from the standard are handled: |
680
|
|
|
|
|
|
|
|
681
|
|
|
|
|
|
|
1. no blank line between tags and moves; |
682
|
|
|
|
|
|
|
2. no blank line between games |
683
|
|
|
|
|
|
|
3. blank line(s) before a game (start of file) |
684
|
|
|
|
|
|
|
4. multiple tags in the same line |
685
|
|
|
|
|
|
|
5. tags spanning over more lines |
686
|
|
|
|
|
|
|
(can't cumulate with rule 4) |
687
|
|
|
|
|
|
|
6. No tags (only moves). |
688
|
|
|
|
|
|
|
(can't cumulate with rule 2) |
689
|
|
|
|
|
|
|
7. comments (starting with ";") outside the game text |
690
|
|
|
|
|
|
|
|
691
|
|
|
|
|
|
|
=cut |
692
|
|
|
|
|
|
|
|
693
|
|
|
|
|
|
|
# |
694
|
|
|
|
|
|
|
# read_game() introduced in 0.07 |
695
|
|
|
|
|
|
|
# |
696
|
|
|
|
|
|
|
{ #closure for read_game |
697
|
|
|
|
|
|
|
# this is the memory between loops. The |
698
|
|
|
|
|
|
|
# reading engine recognizes some elements |
699
|
|
|
|
|
|
|
# one line after. |
700
|
|
|
|
|
|
|
# For example, game text ends when we |
701
|
|
|
|
|
|
|
# read tags from the input. At this moment, |
702
|
|
|
|
|
|
|
# we have to return from the method, but |
703
|
|
|
|
|
|
|
# we must keep in memory what we have last read. |
704
|
|
|
|
|
|
|
# This structure will also take care of the |
705
|
|
|
|
|
|
|
# tags spanning over several lines. |
706
|
|
|
|
|
|
|
my %memory = ( |
707
|
|
|
|
|
|
|
tag => q{}, |
708
|
|
|
|
|
|
|
utag => 0, # = unfinished tag |
709
|
|
|
|
|
|
|
game => q{}, |
710
|
|
|
|
|
|
|
tag_printed => 0, |
711
|
|
|
|
|
|
|
game_printed => 0, |
712
|
|
|
|
|
|
|
); |
713
|
|
|
|
|
|
|
|
714
|
|
|
|
|
|
|
sub _process_game { |
715
|
21
|
|
|
21
|
|
32
|
my $self = shift; |
716
|
21
|
50
|
|
|
|
42
|
return 0 unless $memory{game}; |
717
|
21
|
50
|
|
|
|
58
|
$self->{gamedescr}{missing} .= 'tags' unless $memory{tag_printed}; |
718
|
21
|
|
|
|
|
30
|
$memory{tag_printed} = 0; |
719
|
21
|
|
|
|
|
82
|
$self->{gamedescr}{Game} .= $memory{game}; |
720
|
21
|
|
|
|
|
28
|
$memory{game} = q{}; |
721
|
21
|
|
|
|
|
23
|
$memory{game_printed} =1; |
722
|
21
|
|
|
|
|
72
|
return 1; |
723
|
|
|
|
|
|
|
} |
724
|
|
|
|
|
|
|
|
725
|
|
|
|
|
|
|
sub _process_tag { |
726
|
232
|
|
|
232
|
|
361
|
my $self = shift; |
727
|
232
|
50
|
|
|
|
493
|
if ($memory{game}) { |
728
|
0
|
|
|
|
|
0
|
$self->_process_game; |
729
|
|
|
|
|
|
|
} |
730
|
232
|
50
|
|
|
|
511
|
return 0 if $memory{utag}; |
731
|
232
|
50
|
|
|
|
788
|
if ($memory{tag} =~ tr/]// > 1) { |
732
|
|
|
|
|
|
|
# deals with multiple tags in one line |
733
|
0
|
|
|
|
|
0
|
$memory{tag} =~ s/\]\s?/\]\n/g; |
734
|
|
|
|
|
|
|
} |
735
|
232
|
|
|
|
|
1553
|
while ($memory{tag} =~ /\[(\w+)\s+"(.*)"\]\s*/g) { |
736
|
232
|
|
|
|
|
3279
|
$self->{gamedescr}{$1} = $2; |
737
|
|
|
|
|
|
|
} |
738
|
232
|
|
|
|
|
282
|
$memory{tag_printed} =1; |
739
|
232
|
|
|
|
|
614
|
$memory{tag} = q{}; |
740
|
232
|
|
|
|
|
383
|
$memory{game_printed} = 0; |
741
|
232
|
|
|
|
|
869
|
return; |
742
|
|
|
|
|
|
|
} |
743
|
|
|
|
|
|
|
|
744
|
|
|
|
|
|
|
sub read_game { |
745
|
23
|
|
|
23
|
1
|
35
|
my $self = shift; |
746
|
23
|
|
|
|
|
26
|
my $fh = ${$self->{fh}}; |
|
23
|
|
|
|
|
76
|
|
747
|
23
|
|
|
|
|
55
|
$self->_init(); |
748
|
23
|
50
|
|
|
|
58
|
$self->_process_tag if $memory{tag}; |
749
|
23
|
50
|
|
|
|
164
|
return $self->_process_game if $memory{game}; |
750
|
23
|
|
|
|
|
216
|
while (<$fh>) { |
751
|
|
|
|
|
|
|
# handle semicolon comments |
752
|
416
|
50
|
|
|
|
856
|
if (/^;/) { |
753
|
0
|
0
|
0
|
|
|
0
|
if ($memory{game_printed} or (! $memory{game})) { # between games |
|
|
0
|
|
|
|
|
|
754
|
0
|
|
|
|
|
0
|
chomp; |
755
|
0
|
|
|
|
|
0
|
$self->{gamedescr}{Comment} .= $_ ; |
756
|
|
|
|
|
|
|
# comments between games are saved as tags |
757
|
|
|
|
|
|
|
} |
758
|
|
|
|
|
|
|
elsif ($memory{game}){ |
759
|
0
|
|
|
|
|
0
|
$memory{game} .= $_; |
760
|
|
|
|
|
|
|
} |
761
|
0
|
|
|
|
|
0
|
next; # anything else is discarded. |
762
|
|
|
|
|
|
|
} |
763
|
|
|
|
|
|
|
# normalize tagless games |
764
|
416
|
100
|
|
|
|
1496
|
if (/^\s*$/) { |
765
|
41
|
100
|
|
|
|
85
|
if ($memory{game}) { |
766
|
|
|
|
|
|
|
# handles comments with embedded newlines. |
767
|
20
|
50
|
|
|
|
85
|
if (($memory{game} =~ tr/\{//) < ($memory{game} =~ tr/\}//) ) { |
768
|
0
|
|
|
|
|
0
|
next; |
769
|
|
|
|
|
|
|
} |
770
|
20
|
|
|
|
|
114
|
return $self->_process_game; |
771
|
|
|
|
|
|
|
} |
772
|
21
|
|
|
|
|
235
|
next; |
773
|
|
|
|
|
|
|
} |
774
|
|
|
|
|
|
|
# deals with multi-line tags |
775
|
375
|
50
|
66
|
|
|
3469
|
if ($memory{utag}) { |
|
|
100
|
|
|
|
|
|
776
|
0
|
|
|
|
|
0
|
chomp; |
777
|
0
|
|
|
|
|
0
|
$memory{tag} .= $_; |
778
|
0
|
|
|
|
|
0
|
my $left_brackets = ($memory{tag} =~ tr/\[//); |
779
|
0
|
|
|
|
|
0
|
my $right_brackets = ($memory{tag} =~ tr/\]//); |
780
|
0
|
0
|
|
|
|
0
|
if ( $left_brackets == $right_brackets ) { |
781
|
0
|
|
|
|
|
0
|
$memory{utag} = 0; |
782
|
0
|
|
|
|
|
0
|
$memory{tag_printed} = 0; |
783
|
0
|
|
|
|
|
0
|
$memory{tag} .= "\n"; |
784
|
|
|
|
|
|
|
} |
785
|
|
|
|
|
|
|
} |
786
|
|
|
|
|
|
|
elsif (/^\[/ && (! $memory{game})) { |
787
|
232
|
|
|
|
|
305
|
my $left_brackets = tr/\[//; |
788
|
232
|
|
|
|
|
300
|
my $right_brackets = tr/\]//; |
789
|
232
|
50
|
|
|
|
376
|
if ($left_brackets == $right_brackets) { |
|
|
0
|
|
|
|
|
|
790
|
232
|
|
|
|
|
440
|
$memory{tag} = $_; |
791
|
|
|
|
|
|
|
} |
792
|
|
|
|
|
|
|
elsif ($right_brackets > $left_brackets) { |
793
|
0
|
|
|
|
|
0
|
warn "Parsing error at line $.\n"; |
794
|
|
|
|
|
|
|
} |
795
|
|
|
|
|
|
|
else { |
796
|
0
|
|
|
|
|
0
|
$memory{utag} = 1; |
797
|
0
|
|
|
|
|
0
|
chomp; |
798
|
0
|
|
|
|
|
0
|
$memory{tag} = $_; |
799
|
0
|
|
|
|
|
0
|
$memory{tag_printed} =0; |
800
|
|
|
|
|
|
|
} |
801
|
|
|
|
|
|
|
} |
802
|
|
|
|
|
|
|
else { |
803
|
143
|
|
|
|
|
632
|
s/^\s*//; |
804
|
143
|
|
|
|
|
263
|
$memory{game} .= $_; |
805
|
|
|
|
|
|
|
} |
806
|
375
|
100
|
|
|
|
1132
|
if ($memory{tag}) { |
807
|
232
|
50
|
|
|
|
616
|
return $self->_process_game if $memory{game}; |
808
|
232
|
|
|
|
|
451
|
$self->_process_tag; |
809
|
|
|
|
|
|
|
} |
810
|
|
|
|
|
|
|
} |
811
|
3
|
50
|
|
|
|
13
|
if ($memory{tag}) { |
812
|
0
|
|
|
|
|
0
|
$self->_process_tag; |
813
|
|
|
|
|
|
|
} |
814
|
3
|
100
|
|
|
|
9
|
if ($memory{game}) { |
815
|
1
|
|
|
|
|
5
|
return $self->_process_game; |
816
|
|
|
|
|
|
|
} |
817
|
2
|
|
|
|
|
9
|
return 0; |
818
|
|
|
|
|
|
|
} |
819
|
|
|
|
|
|
|
} # end read_game() closure |
820
|
|
|
|
|
|
|
|
821
|
|
|
|
|
|
|
=item read_standard_game() |
822
|
|
|
|
|
|
|
|
823
|
|
|
|
|
|
|
reads the next game from the given PGN file. |
824
|
|
|
|
|
|
|
Returns TRUE (1) if successful (= a game was read) |
825
|
|
|
|
|
|
|
or FALSE (0) if no more games are available or |
826
|
|
|
|
|
|
|
an unexpected EOF occurred before the end of parsing |
827
|
|
|
|
|
|
|
|
828
|
|
|
|
|
|
|
while ($pgn->read_standard_game()) { |
829
|
|
|
|
|
|
|
do_something_smart; |
830
|
|
|
|
|
|
|
} |
831
|
|
|
|
|
|
|
|
832
|
|
|
|
|
|
|
This method deals only with well formed PGN games. Use |
833
|
|
|
|
|
|
|
the more forgiving read_game() for PGN files that don't |
834
|
|
|
|
|
|
|
fully respect the PGN standard. |
835
|
|
|
|
|
|
|
|
836
|
|
|
|
|
|
|
=cut |
837
|
|
|
|
|
|
|
|
838
|
|
|
|
|
|
|
sub read_standard_game { |
839
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
840
|
0
|
|
|
|
|
0
|
my $fh = ${$self->{fh}}; |
|
0
|
|
|
|
|
0
|
|
841
|
0
|
|
|
|
|
0
|
$self->_init(); |
842
|
0
|
|
|
|
|
0
|
my $block = 1; |
843
|
|
|
|
|
|
|
#return 0 if eof($fh); # changed in version 0.06 |
844
|
0
|
|
|
|
|
0
|
while (<$fh>) { |
845
|
0
|
0
|
|
|
|
0
|
return 0 unless defined $_; # 0.06 |
846
|
0
|
|
|
|
|
0
|
chomp; |
847
|
0
|
0
|
|
|
|
0
|
$block = 0 if /^\s*$/; |
848
|
0
|
0
|
|
|
|
0
|
last unless $block; |
849
|
0
|
0
|
|
|
|
0
|
last unless /\[(\w+)/; |
850
|
0
|
|
|
|
|
0
|
my $tag = $1; |
851
|
0
|
0
|
|
|
|
0
|
last unless /\"(.*)\"/; |
852
|
0
|
|
|
|
|
0
|
my $value = $1; |
853
|
0
|
|
|
|
|
0
|
$self->{gamedescr}{$tag} = $value; |
854
|
|
|
|
|
|
|
} |
855
|
0
|
|
|
|
|
0
|
$block = 1; |
856
|
|
|
|
|
|
|
#return 0 if eof($fh); # changed in version 0.06 |
857
|
0
|
0
|
|
|
|
0
|
return 0 unless defined $_; # 0.06 |
858
|
0
|
|
|
|
|
0
|
while (<$fh>) { |
859
|
0
|
0
|
|
|
|
0
|
return 0 unless defined $_; # 0.06 |
860
|
0
|
0
|
|
|
|
0
|
$block = 0 if /^\s*$/; |
861
|
0
|
0
|
|
|
|
0
|
last unless $block; |
862
|
0
|
|
|
|
|
0
|
$self->{gamedescr}{Game} .= $_; |
863
|
|
|
|
|
|
|
} |
864
|
0
|
|
|
|
|
0
|
return 1; |
865
|
|
|
|
|
|
|
} |
866
|
|
|
|
|
|
|
|
867
|
|
|
|
|
|
|
=for internal use |
868
|
|
|
|
|
|
|
|
869
|
|
|
|
|
|
|
_get_tags() returns a list of tags depending on the parameters |
870
|
|
|
|
|
|
|
|
871
|
|
|
|
|
|
|
_get_format() returns a format to be used when printing tags |
872
|
|
|
|
|
|
|
|
873
|
|
|
|
|
|
|
_get_formatted_tag() returns a tag formatted according to the |
874
|
|
|
|
|
|
|
given template. |
875
|
|
|
|
|
|
|
|
876
|
|
|
|
|
|
|
=cut |
877
|
|
|
|
|
|
|
|
878
|
|
|
|
|
|
|
sub _get_tags { |
879
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
880
|
0
|
|
|
|
|
0
|
my $params = shift; |
881
|
0
|
|
|
|
|
0
|
my @newtags=(); |
882
|
0
|
|
|
|
|
0
|
my %seen = (Game =>1); |
883
|
0
|
0
|
0
|
|
|
0
|
if (exists $params->{all_tags} |
|
|
0
|
|
|
|
|
|
884
|
|
|
|
|
|
|
and ($params->{all_tags} =~ /^(?:[Yy][Ee][Ss]|1)$/)) |
885
|
|
|
|
|
|
|
{ |
886
|
0
|
|
|
|
|
0
|
for (@seven_tags_roster) { |
887
|
0
|
|
|
|
|
0
|
push @newtags, $_; |
888
|
0
|
|
|
|
|
0
|
$seen{$_}++; |
889
|
|
|
|
|
|
|
} |
890
|
0
|
|
|
|
|
0
|
for (sort {lc $a cmp lc $b} keys %{$self->{gamedescr}}) { |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
891
|
0
|
0
|
|
|
|
0
|
push @newtags, $_ unless $seen{$_}; |
892
|
|
|
|
|
|
|
} |
893
|
|
|
|
|
|
|
} |
894
|
|
|
|
|
|
|
elsif (exists $params->{tags}) { |
895
|
0
|
|
|
|
|
0
|
for (@{$params->{tags}}) { |
|
0
|
|
|
|
|
0
|
|
896
|
0
|
|
|
|
|
0
|
push @newtags, $_; |
897
|
|
|
|
|
|
|
} |
898
|
|
|
|
|
|
|
} |
899
|
|
|
|
|
|
|
else { |
900
|
0
|
|
|
|
|
0
|
@newtags = @seven_tags_roster; |
901
|
|
|
|
|
|
|
} |
902
|
0
|
|
|
|
|
0
|
return @newtags; |
903
|
|
|
|
|
|
|
} |
904
|
|
|
|
|
|
|
|
905
|
|
|
|
|
|
|
|
906
|
|
|
|
|
|
|
sub _get_left_right { |
907
|
0
|
|
|
0
|
|
0
|
my $pattern = shift; |
908
|
0
|
|
|
|
|
0
|
my $format = shift; |
909
|
0
|
|
|
|
|
0
|
my $left_delimiter = shift; |
910
|
0
|
|
|
|
|
0
|
my $right_delimiter = shift; |
911
|
0
|
0
|
|
|
|
0
|
if (defined $pattern) { |
912
|
0
|
0
|
|
|
|
0
|
if (length($pattern) == 1) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
913
|
0
|
|
|
|
|
0
|
$format = $pattern . $format .$pattern; |
914
|
|
|
|
|
|
|
} |
915
|
|
|
|
|
|
|
elsif (length($pattern) == 2) { |
916
|
0
|
|
|
|
|
0
|
my @chars = split //, $pattern; |
917
|
0
|
|
|
|
|
0
|
$left_delimiter = $chars[0]; |
918
|
0
|
|
|
|
|
0
|
$right_delimiter= $chars[1]; |
919
|
|
|
|
|
|
|
} |
920
|
|
|
|
|
|
|
elsif ($pattern =~ /^(.*)\|(.*)$/) { |
921
|
0
|
|
|
|
|
0
|
$left_delimiter = $1; |
922
|
0
|
|
|
|
|
0
|
$right_delimiter = $2; |
923
|
|
|
|
|
|
|
} |
924
|
|
|
|
|
|
|
} |
925
|
0
|
|
|
|
|
0
|
$format = $left_delimiter . $format . $right_delimiter; |
926
|
0
|
|
|
|
|
0
|
return $format; |
927
|
|
|
|
|
|
|
} |
928
|
|
|
|
|
|
|
|
929
|
|
|
|
|
|
|
sub _get_format { |
930
|
0
|
|
|
0
|
|
0
|
my $params = shift; |
931
|
0
|
|
|
|
|
0
|
my $format = _get_left_right($params->{quotes}, q{#value#},q{"},q{"}); |
932
|
0
|
|
|
|
|
0
|
$format = _get_left_right($params->{brackets},q{#tag# }.$format,q{[},q{]}); |
933
|
0
|
|
|
|
|
0
|
return $format; |
934
|
|
|
|
|
|
|
} |
935
|
|
|
|
|
|
|
|
936
|
|
|
|
|
|
|
sub _formatted_tag { |
937
|
0
|
|
|
0
|
|
0
|
my ($format, $tag, $value) = @_; |
938
|
0
|
|
|
|
|
0
|
my $subst = $format; |
939
|
0
|
|
|
|
|
0
|
$subst =~ s/#tag#/$tag/; |
940
|
0
|
|
|
|
|
0
|
$subst =~ s/#value#/$value/; |
941
|
0
|
|
|
|
|
0
|
return $subst; |
942
|
|
|
|
|
|
|
} |
943
|
|
|
|
|
|
|
|
944
|
|
|
|
|
|
|
=item standard_PGN() |
945
|
|
|
|
|
|
|
|
946
|
|
|
|
|
|
|
returns a string containing all current PGN tags, including |
947
|
|
|
|
|
|
|
the game. |
948
|
|
|
|
|
|
|
Parameters are passed through a hash reference. None is |
949
|
|
|
|
|
|
|
required. |
950
|
|
|
|
|
|
|
|
951
|
|
|
|
|
|
|
tags => [tag list], # default is the Seven Tags Roster. |
952
|
|
|
|
|
|
|
# You may specify only the tags you want to |
953
|
|
|
|
|
|
|
# print |
954
|
|
|
|
|
|
|
# tags => [qw(White Black Result)] |
955
|
|
|
|
|
|
|
|
956
|
|
|
|
|
|
|
all_tags => 'no', # default 'no'. If yes (or 1), it outputs all the tags |
957
|
|
|
|
|
|
|
# if 'tags' and 'all_tags' are used, 'all_tags' |
958
|
|
|
|
|
|
|
# prevails |
959
|
|
|
|
|
|
|
|
960
|
|
|
|
|
|
|
nl => q{\n}, # default '\n'. Tag separator. Can be changed |
961
|
|
|
|
|
|
|
# according to your needs. |
962
|
|
|
|
|
|
|
# nl => ' \n' is a good candidate for HTML |
963
|
|
|
|
|
|
|
# output. |
964
|
|
|
|
|
|
|
|
965
|
|
|
|
|
|
|
brackets => q{[]}, # default '[]'. Output tags within brackets. |
966
|
|
|
|
|
|
|
# Bracketing can be as creative as you want. |
967
|
|
|
|
|
|
|
# If the left and rigth bracketing sequence are |
968
|
|
|
|
|
|
|
# longer than one character, they must be separated |
969
|
|
|
|
|
|
|
# by a pipe (|) symbol. |
970
|
|
|
|
|
|
|
# '()', '(|)\t,'{|}\n' and '{}' are valid |
971
|
|
|
|
|
|
|
# sequences. |
972
|
|
|
|
|
|
|
# |
973
|
|
|
|
|
|
|
# '|' will output HTML header 1 |
974
|
|
|
|
|
|
|
# '{|}\n' will enclose each tag |
975
|
|
|
|
|
|
|
# between bold braces. |
976
|
|
|
|
|
|
|
|
977
|
|
|
|
|
|
|
quotes => q{"}, # default '"'. Quote tags values. |
978
|
|
|
|
|
|
|
# As for brackets, quotes can be specified in |
979
|
|
|
|
|
|
|
# pairs: '<>' and '<|>' are equivalent. |
980
|
|
|
|
|
|
|
# If the quoting sequence is more than one char, |
981
|
|
|
|
|
|
|
# the pipe symbol is needed to separate the left |
982
|
|
|
|
|
|
|
# quote from the right one. |
983
|
|
|
|
|
|
|
# '|' will produce HTML italicized text. |
984
|
|
|
|
|
|
|
|
985
|
|
|
|
|
|
|
game => 'yes', # default 'yes'. Output the game text |
986
|
|
|
|
|
|
|
# If the game was parsed, returns a clean list |
987
|
|
|
|
|
|
|
# of moves, else the unparsed text |
988
|
|
|
|
|
|
|
|
989
|
|
|
|
|
|
|
comments => 'no' # Default 'no'. Output the game comments. |
990
|
|
|
|
|
|
|
# Requires the 'game' option |
991
|
|
|
|
|
|
|
|
992
|
|
|
|
|
|
|
=cut |
993
|
|
|
|
|
|
|
|
994
|
|
|
|
|
|
|
my %switchcolor = ('w' => 'b', 'b' => 'w'); |
995
|
|
|
|
|
|
|
sub standard_PGN { |
996
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
997
|
0
|
|
|
|
|
0
|
my $params = shift; |
998
|
0
|
|
|
|
|
0
|
my %seen =(Game =>1); |
999
|
0
|
|
|
|
|
0
|
my @tags = $self->_get_tags($params); |
1000
|
0
|
|
|
|
|
0
|
my $out = q{}; |
1001
|
0
|
|
|
|
|
0
|
my $nl ="\n"; |
1002
|
0
|
|
|
|
|
0
|
my $out_game = 'yes'; |
1003
|
0
|
0
|
0
|
|
|
0
|
$out_game = 0 if # 0.11 |
1004
|
|
|
|
|
|
|
exists $params->{game} |
1005
|
|
|
|
|
|
|
and (lc($params->{game}) ne 'yes'); |
1006
|
|
|
|
|
|
|
|
1007
|
0
|
|
|
|
|
0
|
my $out_comments = 0; # 0.11 |
1008
|
0
|
0
|
0
|
|
|
0
|
$out_comments = 'yes' if $out_game # 0.11 |
|
|
|
0
|
|
|
|
|
1009
|
|
|
|
|
|
|
and (exists $params->{comments} |
1010
|
|
|
|
|
|
|
and (lc($params->{comments}) eq 'yes')); |
1011
|
|
|
|
|
|
|
|
1012
|
0
|
0
|
|
|
|
0
|
$nl = $params->{nl} if exists $params->{nl}; |
1013
|
0
|
|
|
|
|
0
|
my $format = _get_format($params); |
1014
|
0
|
|
|
|
|
0
|
for (@tags) { |
1015
|
0
|
0
|
|
|
|
0
|
$self->{gamedescr}{$_}=q{?} unless exists $self->{gamedescr}{$_}; |
1016
|
|
|
|
|
|
|
#$out .= qq/[$_ "$self->{gamedescr}{$_}"]\n/; |
1017
|
0
|
|
|
|
|
0
|
$out .= _formatted_tag($format, $_, $self->{gamedescr}{$_}); |
1018
|
0
|
|
|
|
|
0
|
$out .= $nl; |
1019
|
|
|
|
|
|
|
} |
1020
|
0
|
0
|
|
|
|
0
|
if (@tags) { |
1021
|
0
|
|
|
|
|
0
|
$out .= $nl; |
1022
|
|
|
|
|
|
|
} |
1023
|
0
|
0
|
|
|
|
0
|
return $out unless $out_game; |
1024
|
0
|
0
|
|
|
|
0
|
if (defined $self->{GameMoves}) { # if parsed |
1025
|
0
|
|
|
|
|
0
|
my $count = 0; |
1026
|
0
|
|
|
|
|
0
|
my $color = 'w'; |
1027
|
0
|
0
|
0
|
|
|
0
|
if ((defined $self->{gamedescr}{FirstMove}) # 0.07 |
1028
|
|
|
|
|
|
|
and ($self->{gamedescr}{FirstMove} =~ m/(\d+)([bw])/)) # 0.07 |
1029
|
|
|
|
|
|
|
{ |
1030
|
0
|
|
|
|
|
0
|
$count = $1; # 0.07 |
1031
|
0
|
|
|
|
|
0
|
$color = $2; # 0.07 |
1032
|
0
|
0
|
|
|
|
0
|
$out .= "$count\.\.\." if $color eq 'b'; # 0.07 |
1033
|
|
|
|
|
|
|
} |
1034
|
0
|
|
|
|
|
0
|
my $len = 0; |
1035
|
0
|
|
|
|
|
0
|
for (@{$self->moves}) { # |
|
0
|
|
|
|
|
0
|
|
1036
|
0
|
0
|
|
|
|
0
|
if ($color eq 'w') { |
1037
|
0
|
|
|
|
|
0
|
$count++; |
1038
|
0
|
0
|
0
|
|
|
0
|
$out .= q{ } and $len++ if $len and ($count > 1); |
|
|
|
0
|
|
|
|
|
1039
|
0
|
|
|
|
|
0
|
$out .= $count . q{ }; |
1040
|
0
|
|
|
|
|
0
|
$len += length($count) +2; |
1041
|
|
|
|
|
|
|
} |
1042
|
|
|
|
|
|
|
else { |
1043
|
0
|
|
|
|
|
0
|
$out .= q{ }; |
1044
|
0
|
|
|
|
|
0
|
$len++; |
1045
|
|
|
|
|
|
|
} |
1046
|
0
|
|
|
|
|
0
|
$out .= $_; |
1047
|
0
|
|
|
|
|
0
|
$len += length($_); |
1048
|
0
|
0
|
0
|
|
|
0
|
if ($out_comments # 0.11 |
1049
|
|
|
|
|
|
|
&& exists $self->comments->{($count-1)."${color}"}) { # 0.12 |
1050
|
0
|
|
|
|
|
0
|
my $comment = $self->comments->{($count-1)."${color}"}; # 0.12 |
1051
|
0
|
|
|
|
|
0
|
my $needs_nl = $comment =~ /^\s*;/; |
1052
|
|
|
|
|
|
|
# |
1053
|
|
|
|
|
|
|
# deal with comment length here |
1054
|
|
|
|
|
|
|
# |
1055
|
0
|
0
|
|
|
|
0
|
if ($len >= 75) { |
1056
|
0
|
|
|
|
|
0
|
$len = 0; |
1057
|
0
|
|
|
|
|
0
|
$out .= $nl; |
1058
|
|
|
|
|
|
|
} |
1059
|
0
|
|
|
|
|
0
|
while ($len + length($comment) > 75) { |
1060
|
0
|
|
|
|
|
0
|
my $delta = 75 - $len; |
1061
|
0
|
0
|
|
|
|
0
|
$delta = 0 if $delta < 0; |
1062
|
0
|
|
|
|
|
0
|
my ($portion) = $comment =~ /^(.{1,$delta})\W/; |
1063
|
0
|
|
|
|
|
0
|
$out .= $portion; |
1064
|
0
|
|
|
|
|
0
|
$len = 0; |
1065
|
0
|
|
|
|
|
0
|
$out .= $nl; |
1066
|
0
|
|
|
|
|
0
|
$comment = substr($comment, length($portion) +1); |
1067
|
|
|
|
|
|
|
} |
1068
|
0
|
|
|
|
|
0
|
$out .= $comment; |
1069
|
0
|
0
|
|
|
|
0
|
$out .= $nl if $needs_nl; |
1070
|
0
|
|
|
|
|
0
|
$len += length($comment); |
1071
|
|
|
|
|
|
|
} |
1072
|
0
|
|
|
|
|
0
|
$color = $switchcolor{$color}; |
1073
|
0
|
0
|
|
|
|
0
|
if ($len >= 75) { |
1074
|
0
|
|
|
|
|
0
|
$len = 0; |
1075
|
0
|
|
|
|
|
0
|
$out .= $nl; |
1076
|
|
|
|
|
|
|
} |
1077
|
|
|
|
|
|
|
} |
1078
|
0
|
|
|
|
|
0
|
$out .=" $self->{gamedescr}{Result}$nl"; |
1079
|
|
|
|
|
|
|
} |
1080
|
|
|
|
|
|
|
else { # not parsed - returns game text |
1081
|
0
|
|
|
|
|
0
|
$out .= $self->{gamedescr}{Game}; |
1082
|
|
|
|
|
|
|
} |
1083
|
0
|
|
|
|
|
0
|
return $out; |
1084
|
|
|
|
|
|
|
} |
1085
|
|
|
|
|
|
|
|
1086
|
|
|
|
|
|
|
=item smart_parse_game() |
1087
|
|
|
|
|
|
|
|
1088
|
|
|
|
|
|
|
Parses the current game, returning the moves only. |
1089
|
|
|
|
|
|
|
Uses by default quick_parse_game(), unless recursive comments are found in the source game. |
1090
|
|
|
|
|
|
|
|
1091
|
|
|
|
|
|
|
=cut |
1092
|
|
|
|
|
|
|
|
1093
|
|
|
|
|
|
|
sub smart_parse_game { |
1094
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
1095
|
0
|
|
|
|
|
0
|
my $params = shift; |
1096
|
0
|
0
|
|
|
|
0
|
if ($self->{gamedescr}{Game} =~ /\(/) { |
1097
|
0
|
|
|
|
|
0
|
$self->parse_game($params) |
1098
|
|
|
|
|
|
|
} |
1099
|
|
|
|
|
|
|
else { |
1100
|
0
|
|
|
|
|
0
|
$self->quick_parse_game($params) |
1101
|
|
|
|
|
|
|
} |
1102
|
0
|
|
|
|
|
0
|
return; |
1103
|
|
|
|
|
|
|
} |
1104
|
|
|
|
|
|
|
|
1105
|
|
|
|
|
|
|
=item quick_parse_game() |
1106
|
|
|
|
|
|
|
|
1107
|
|
|
|
|
|
|
Parses the current game, returning the moves only. |
1108
|
|
|
|
|
|
|
Comments are discarded. |
1109
|
|
|
|
|
|
|
This function does FAIL on Recursive Annotated Variation or nested comments. |
1110
|
|
|
|
|
|
|
Parameters (passed as a hash reference): check_moves = 'yes'|'no'. Default : no. If requested, each move is checked against a RegEx, to filter off possible unbraced comments. |
1111
|
|
|
|
|
|
|
|
1112
|
|
|
|
|
|
|
=cut |
1113
|
|
|
|
|
|
|
|
1114
|
|
|
|
|
|
|
# ============================================== |
1115
|
|
|
|
|
|
|
# These two regular expressions were produced by |
1116
|
|
|
|
|
|
|
# Damian Conway's module Regexp::Common |
1117
|
|
|
|
|
|
|
# ---------------------------------------------- |
1118
|
|
|
|
|
|
|
# On the author's suggestion, these lines |
1119
|
|
|
|
|
|
|
# |
1120
|
|
|
|
|
|
|
# use Regexp::Common; |
1121
|
|
|
|
|
|
|
# print "$RE{balanced}{-parens=>'()'}\n"; |
1122
|
|
|
|
|
|
|
# print "$RE{balanced}{-parens=>'{}'}\n"; |
1123
|
|
|
|
|
|
|
# |
1124
|
|
|
|
|
|
|
# produced the RegEx code, which was edited |
1125
|
|
|
|
|
|
|
# and inserted here for efficiency reasons. |
1126
|
|
|
|
|
|
|
# ============================================== |
1127
|
|
|
|
|
|
|
|
1128
|
|
|
|
|
|
|
our $re_parens; ## no critic |
1129
|
|
|
|
|
|
|
$re_parens = qr/ |
1130
|
|
|
|
|
|
|
(?:(?:(?:[(](?:(?>[^)(]+) |
1131
|
|
|
|
|
|
|
|(??{$re_parens}))*[)])) |
1132
|
|
|
|
|
|
|
|(?:(?!))) |
1133
|
|
|
|
|
|
|
/x; |
1134
|
|
|
|
|
|
|
|
1135
|
|
|
|
|
|
|
our $re_brace; ## no critic |
1136
|
|
|
|
|
|
|
$re_brace = qr/ |
1137
|
|
|
|
|
|
|
(?:(?:(?:[{](?:(?>[^}{]+) |
1138
|
|
|
|
|
|
|
|(??{$re_brace}))*[}])) |
1139
|
|
|
|
|
|
|
|(?:(?!))) |
1140
|
|
|
|
|
|
|
/x; |
1141
|
|
|
|
|
|
|
|
1142
|
|
|
|
|
|
|
# ============================================== |
1143
|
|
|
|
|
|
|
|
1144
|
|
|
|
|
|
|
# regular expressions for game parsing |
1145
|
|
|
|
|
|
|
my $re_result = qr{(?:1\-0|0\-1|1\/2\-1\/2|\*)}; |
1146
|
|
|
|
|
|
|
my $re_move = qr{[KQRBN]?[a-h]?[1-8]?x?[a-h][1-8](?:\=?[QRBN])?}; |
1147
|
|
|
|
|
|
|
# piece ^^^^^ |
1148
|
|
|
|
|
|
|
# unambiguous column or line ^^^ ^^^ |
1149
|
|
|
|
|
|
|
# capture ^ |
1150
|
|
|
|
|
|
|
# destination square ^^^ ^^^ |
1151
|
|
|
|
|
|
|
# promotion ^ ^^^^^ |
1152
|
|
|
|
|
|
|
my $re_castling = qr/O\-O(?:\-O)?/; |
1153
|
|
|
|
|
|
|
my $re_check = qr/(?:(?:\#|\+(\+)?))?/; |
1154
|
|
|
|
|
|
|
my $re_any_move = qr/(?:$re_move|$re_castling)$re_check/; |
1155
|
|
|
|
|
|
|
my $re_nag = qr/\$\d+/; |
1156
|
|
|
|
|
|
|
my $re_number = qr/\d+\.(?:\.\.)?/; |
1157
|
|
|
|
|
|
|
my $re_escape = qr/^\%[^\n]*\n/; |
1158
|
|
|
|
|
|
|
my $re_eol_comment= qr/;.*$/; |
1159
|
|
|
|
|
|
|
my $re_rav = $re_parens; |
1160
|
|
|
|
|
|
|
my $re_comment = $re_brace; |
1161
|
|
|
|
|
|
|
|
1162
|
|
|
|
|
|
|
sub quick_parse_game { |
1163
|
20
|
|
|
20
|
1
|
23
|
my $self = shift; |
1164
|
20
|
|
|
|
|
23
|
my $params = shift; # hash reference to parameters |
1165
|
20
|
|
|
|
|
158
|
$self->{gamedescr}{Game} =~ s/$re_eol_comment//mg; # rm EOL comments |
1166
|
20
|
|
|
|
|
88
|
$self->{gamedescr}{Game} =~ s/$re_escape//mgo; # rm escaped lines |
1167
|
20
|
|
|
|
|
1585
|
$self->{gamedescr}{Game} =~ |
1168
|
|
|
|
|
|
|
s/$re_comment//g; # remove comments |
1169
|
20
|
|
|
|
|
1917
|
$self->{gamedescr}{Game} =~ |
1170
|
|
|
|
|
|
|
s/$re_rav//g; # remove RAV |
1171
|
20
|
50
|
|
|
|
81
|
return 0 |
1172
|
|
|
|
|
|
|
if $self->{gamedescr}{Game} =~ |
1173
|
|
|
|
|
|
|
/\(/; # the game still contains RAV |
1174
|
20
|
50
|
|
|
|
70
|
return 0 |
1175
|
|
|
|
|
|
|
if $self->{gamedescr}{Game} =~ |
1176
|
|
|
|
|
|
|
/\{/; # undetected nested comments |
1177
|
20
|
|
|
|
|
172
|
$self->{gamedescr}{Game} =~ s/\n/ /g; # remove newlines |
1178
|
20
|
|
|
|
|
58
|
$self->{gamedescr}{Game} =~ |
1179
|
|
|
|
|
|
|
s/\r/ /g; # remove return chars (DOS) |
1180
|
20
|
|
|
|
|
49
|
$self->{gamedescr}{Game} =~ s/$re_nag//go; # remove NAG |
1181
|
20
|
|
|
|
|
1993
|
$self->{gamedescr}{Game} =~ s/\d+\.//g; # remove numbers |
1182
|
20
|
|
|
|
|
56
|
$self->{gamedescr}{Game} =~ s/\.\.(?:\.)?//g; # remove "..." |
1183
|
20
|
|
|
|
|
270
|
$self->{gamedescr}{Game} =~ s/$re_result\s*\Z//o; |
1184
|
20
|
|
|
|
|
65
|
my $re_filter = qr/\S/; |
1185
|
20
|
50
|
33
|
|
|
71
|
if (exists $params->{check_moves} |
1186
|
|
|
|
|
|
|
and ($params->{check_moves} =~ /^(?:yes|1)$/)) |
1187
|
|
|
|
|
|
|
{ |
1188
|
0
|
|
|
|
|
0
|
$re_filter = $re_any_move; |
1189
|
|
|
|
|
|
|
} |
1190
|
20
|
50
|
|
|
|
57
|
return unless $self->{gamedescr}{Game}; # discards empty games |
1191
|
1620
|
|
|
|
|
4926
|
$self->{GameMoves} = |
1192
|
20
|
|
|
|
|
541
|
[grep { m/$re_filter/o } split /\s+/, $self->{gamedescr}{Game}]; |
1193
|
20
|
|
|
|
|
127
|
return; |
1194
|
|
|
|
|
|
|
} |
1195
|
|
|
|
|
|
|
|
1196
|
|
|
|
|
|
|
=item parse_game() |
1197
|
|
|
|
|
|
|
|
1198
|
|
|
|
|
|
|
Parses the current game (after read_game() was called). |
1199
|
|
|
|
|
|
|
Accepts parameters as hash reference. |
1200
|
|
|
|
|
|
|
|
1201
|
|
|
|
|
|
|
$pgn->parse_game(); # default save_comments => 'no' |
1202
|
|
|
|
|
|
|
|
1203
|
|
|
|
|
|
|
$pgn->parse_game({ |
1204
|
|
|
|
|
|
|
save_comments => 'yes', |
1205
|
|
|
|
|
|
|
comments_struct => 'string'}); |
1206
|
|
|
|
|
|
|
|
1207
|
|
|
|
|
|
|
{comments_struct => 'string'} is the default value |
1208
|
|
|
|
|
|
|
When 'comments_struct' is 'string', multiple comments |
1209
|
|
|
|
|
|
|
for the same move are concatenated to one string |
1210
|
|
|
|
|
|
|
|
1211
|
|
|
|
|
|
|
{comments_struct => 'array'} |
1212
|
|
|
|
|
|
|
If 'array', comments are stored as an anonymous array, |
1213
|
|
|
|
|
|
|
one comment per element |
1214
|
|
|
|
|
|
|
|
1215
|
|
|
|
|
|
|
{comments_struct => 'hol'} |
1216
|
|
|
|
|
|
|
If 'hol', comments are stored as a hash of lists, where |
1217
|
|
|
|
|
|
|
there is a list of comments for each comment type |
1218
|
|
|
|
|
|
|
(NAG, RAV, braced, semicolon, escaped) |
1219
|
|
|
|
|
|
|
|
1220
|
|
|
|
|
|
|
$pgn->parse_game({save_comments => 'yes', |
1221
|
|
|
|
|
|
|
log_errors => 'yes'}); |
1222
|
|
|
|
|
|
|
|
1223
|
|
|
|
|
|
|
parse_game() implements a finite state machine on two assumptions: |
1224
|
|
|
|
|
|
|
|
1225
|
|
|
|
|
|
|
1. No moves or move numbers are truncated at the end of a line; |
1226
|
|
|
|
|
|
|
2. the possible states in a PGN game are: |
1227
|
|
|
|
|
|
|
|
1228
|
|
|
|
|
|
|
a. move number |
1229
|
|
|
|
|
|
|
b. move |
1230
|
|
|
|
|
|
|
c. braced comment |
1231
|
|
|
|
|
|
|
d. EOL comment |
1232
|
|
|
|
|
|
|
e. Numeric Annotation Glyph |
1233
|
|
|
|
|
|
|
f. Recursive Annotated Variation |
1234
|
|
|
|
|
|
|
g. Result |
1235
|
|
|
|
|
|
|
h. unbraced comments (barewords, "!?+-=") |
1236
|
|
|
|
|
|
|
|
1237
|
|
|
|
|
|
|
Items from "a" to "g" are actively parsed and recognized. |
1238
|
|
|
|
|
|
|
Anything unrecognized goes into the "h" state and discarded |
1239
|
|
|
|
|
|
|
(or stored, if log_errors was requested) |
1240
|
|
|
|
|
|
|
|
1241
|
|
|
|
|
|
|
=cut |
1242
|
|
|
|
|
|
|
|
1243
|
|
|
|
|
|
|
{ # start closure for parse_game |
1244
|
|
|
|
|
|
|
my %comment_types = ( |
1245
|
|
|
|
|
|
|
q{$} => 'NAG', |
1246
|
|
|
|
|
|
|
q{(} => 'RAV', |
1247
|
|
|
|
|
|
|
q[{] => 'brace', |
1248
|
|
|
|
|
|
|
q{%} => 'escaped', |
1249
|
|
|
|
|
|
|
q{;} => 'semicolon', |
1250
|
|
|
|
|
|
|
); |
1251
|
|
|
|
|
|
|
|
1252
|
|
|
|
|
|
|
sub parse_game { |
1253
|
1
|
|
|
1
|
1
|
2
|
my $self = shift; |
1254
|
1
|
|
|
|
|
2
|
my $params = shift; |
1255
|
1
|
|
33
|
|
|
25
|
my $save_comments = ((exists $params->{save_comments}) |
1256
|
|
|
|
|
|
|
and ($params->{save_comments} =~ /^(?:yes|1)$/)); |
1257
|
1
|
50
|
|
|
|
19
|
my $log_errors = (exists $params->{log_errors}) |
1258
|
|
|
|
|
|
|
and ($params->{log_errors} =~ /^(?:yes|1)$/); |
1259
|
1
|
50
|
|
|
|
5
|
return unless $self->{gamedescr}{Game}; |
1260
|
1
|
|
|
|
|
3
|
my $movecount = 0; |
1261
|
1
|
|
|
|
|
2
|
my $color = 'b'; |
1262
|
1
|
|
|
|
|
4
|
$self->{gamedescr}{Game} =~ s/0\-0\-0/O-O-O/g; |
1263
|
1
|
|
|
|
|
4
|
$self->{gamedescr}{Game} =~ s/0\-0/O-O/g; |
1264
|
1
|
|
|
|
|
74
|
$self->{gamedescr}{Game} =~ s/$re_result\s*\Z//o; |
1265
|
|
|
|
|
|
|
|
1266
|
1
|
|
|
|
|
4
|
my $comments_struct = 'string'; |
1267
|
1
|
50
|
33
|
|
|
14
|
$comments_struct = $params->{comments_struct} |
1268
|
|
|
|
|
|
|
if ($save_comments |
1269
|
|
|
|
|
|
|
and exists $params->{comments_struct}); |
1270
|
1
|
50
|
|
|
|
7
|
$comments_struct = 'string' |
1271
|
|
|
|
|
|
|
unless $comments_struct =~ /^(?:array|hol)$/; |
1272
|
1
|
|
|
|
|
3
|
my $plycount = 0; |
1273
|
1
|
|
|
|
|
2
|
my $countless =0; |
1274
|
1
|
|
|
|
|
90
|
$self->{gamedescr}{Game} =~ s/\s*\Z//; |
1275
|
1
|
|
|
|
|
6
|
$self->{gamedescr}{Game} =~ s/^\s*//; |
1276
|
1
|
50
|
|
|
|
11
|
if ($self->{gamedescr}{Game} !~ /\d\./) { |
1277
|
0
|
|
|
|
|
0
|
$countless = 1; |
1278
|
0
|
|
|
|
|
0
|
$movecount = 1; |
1279
|
|
|
|
|
|
|
} |
1280
|
|
|
|
|
|
|
|
1281
|
1
|
|
|
|
|
3
|
$self->{GameMoves} = []; |
1282
|
|
|
|
|
|
|
|
1283
|
1
|
|
|
|
|
8
|
for ($self->{gamedescr}{Game}) { |
1284
|
1
|
|
|
|
|
5
|
while (! /\G \s* \z/xgc ) { |
1285
|
92
|
100
|
|
|
|
3026
|
if ( m/\G($re_number)\s*/mgc) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
1286
|
22
|
|
|
|
|
35
|
my $num=$1; |
1287
|
22
|
100
|
|
|
|
50
|
if (( $num =~ tr/\.//d) > 1) { |
1288
|
3
|
|
|
|
|
51
|
$color = 'w'; |
1289
|
|
|
|
|
|
|
} |
1290
|
22
|
100
|
|
|
|
74
|
if ($movecount == 0) { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
1291
|
1
|
|
|
|
|
3
|
$movecount = $num; |
1292
|
1
|
50
|
|
|
|
8
|
$self->{gamedescr}{FirstMove} = |
1293
|
|
|
|
|
|
|
$num.$switchcolor{$color} # fixed 0.07 |
1294
|
|
|
|
|
|
|
unless $num.$switchcolor{$color} eq '1w'; |
1295
|
|
|
|
|
|
|
} |
1296
|
|
|
|
|
|
|
elsif ($movecount == ($num -1)) { |
1297
|
18
|
|
|
|
|
59
|
$movecount++; |
1298
|
|
|
|
|
|
|
} |
1299
|
|
|
|
|
|
|
elsif ($movecount != $num) { |
1300
|
0
|
|
|
|
|
0
|
$self->{GameErrors}->{$movecount.$color} |
1301
|
|
|
|
|
|
|
.= " invalid move sequence ($num <=> $movecount)"; |
1302
|
0
|
|
|
|
|
0
|
$movecount++; |
1303
|
|
|
|
|
|
|
} |
1304
|
|
|
|
|
|
|
} |
1305
|
|
|
|
|
|
|
elsif ( m/\G($re_any_move)\s*/mgc ) { |
1306
|
37
|
|
|
|
|
39
|
push @{$self->{GameMoves}}, $1; |
|
37
|
|
|
|
|
104
|
|
1307
|
37
|
|
|
|
|
61
|
$color = $switchcolor{$color}; |
1308
|
37
|
50
|
|
|
|
156
|
if ($countless) { |
1309
|
0
|
|
|
|
|
0
|
$plycount++; |
1310
|
0
|
0
|
|
|
|
0
|
if ($plycount == 2) { |
1311
|
0
|
|
|
|
|
0
|
$plycount =0; |
1312
|
0
|
|
|
|
|
0
|
$movecount++; |
1313
|
|
|
|
|
|
|
} |
1314
|
|
|
|
|
|
|
} |
1315
|
|
|
|
|
|
|
} |
1316
|
|
|
|
|
|
|
elsif ( |
1317
|
|
|
|
|
|
|
m/\G($re_comment |
1318
|
|
|
|
|
|
|
|$re_eol_comment |
1319
|
|
|
|
|
|
|
|$re_rav |
1320
|
|
|
|
|
|
|
|$re_nag|$re_escape)\s*/mgcx |
1321
|
|
|
|
|
|
|
) |
1322
|
|
|
|
|
|
|
{ |
1323
|
9
|
50
|
|
|
|
18
|
if ($save_comments) { |
1324
|
9
|
|
|
|
|
14
|
my $tempcomment = $1; |
1325
|
9
|
|
|
|
|
15
|
$tempcomment =~ tr/\r//d; |
1326
|
9
|
|
|
|
|
21
|
$tempcomment =~ s/\n/ /g; |
1327
|
9
|
|
|
|
|
18
|
$tempcomment =~ s/^\s+//; |
1328
|
9
|
|
|
|
|
27
|
$tempcomment =~ s/\s+$//; |
1329
|
9
|
50
|
|
|
|
18
|
if ($comments_struct eq 'string') { |
|
|
0
|
|
|
|
|
|
1330
|
9
|
|
|
|
|
88
|
$self->{GameComments}->{$movecount.$color} .= |
1331
|
|
|
|
|
|
|
q{ } . $tempcomment; |
1332
|
|
|
|
|
|
|
} |
1333
|
|
|
|
|
|
|
elsif ($comments_struct eq 'array') { |
1334
|
0
|
|
|
|
|
0
|
push @{$self->{GameComments}->{$movecount.$color}}, |
|
0
|
|
|
|
|
0
|
|
1335
|
|
|
|
|
|
|
$tempcomment; |
1336
|
|
|
|
|
|
|
} |
1337
|
|
|
|
|
|
|
else { # hol |
1338
|
0
|
|
|
|
|
0
|
$tempcomment =~ m/^(.)/; |
1339
|
0
|
|
|
|
|
0
|
my $comment_type ='unknown'; |
1340
|
0
|
0
|
0
|
|
|
0
|
$comment_type = $comment_types{$1} |
1341
|
|
|
|
|
|
|
if ($1 and exists $comment_types{$1}); |
1342
|
0
|
|
|
|
|
0
|
push @{$self->{GameComments}->{$movecount.$color}->{$comment_type}} , |
|
0
|
|
|
|
|
0
|
|
1343
|
|
|
|
|
|
|
$tempcomment; |
1344
|
|
|
|
|
|
|
} |
1345
|
|
|
|
|
|
|
} |
1346
|
|
|
|
|
|
|
} |
1347
|
|
|
|
|
|
|
elsif ( m/\G(\S+\s*)/mgc ) { |
1348
|
24
|
50
|
|
|
|
46
|
if ($log_errors) { |
1349
|
24
|
|
|
|
|
77
|
$self->{GameErrors}->{$movecount.$color} .= q{ } . $1; |
1350
|
24
|
|
|
|
|
53
|
$self->{GameErrors}->{$movecount.$color} =~ tr/\r//d; |
1351
|
24
|
|
|
|
|
3256
|
$self->{GameErrors}->{$movecount.$color} =~ s/\n/ /g; |
1352
|
|
|
|
|
|
|
} |
1353
|
|
|
|
|
|
|
} |
1354
|
|
|
|
|
|
|
} |
1355
|
|
|
|
|
|
|
} |
1356
|
1
|
|
|
|
|
4
|
return 1; |
1357
|
|
|
|
|
|
|
} |
1358
|
|
|
|
|
|
|
|
1359
|
|
|
|
|
|
|
=item add_comments() |
1360
|
|
|
|
|
|
|
|
1361
|
|
|
|
|
|
|
Allows inserting comments for an already parsed game; |
1362
|
|
|
|
|
|
|
it accepts comments passed as an anonymous hash. |
1363
|
|
|
|
|
|
|
An optional second parameter sets the storage type. |
1364
|
|
|
|
|
|
|
They are the same as for parse_game(); |
1365
|
|
|
|
|
|
|
'string' (default) all comments for a given move are |
1366
|
|
|
|
|
|
|
concatenated together |
1367
|
|
|
|
|
|
|
'array' each comment for a given move is stored as |
1368
|
|
|
|
|
|
|
an array element |
1369
|
|
|
|
|
|
|
'hol' Comments are stored in a hash of lists |
1370
|
|
|
|
|
|
|
different for each comment type. |
1371
|
|
|
|
|
|
|
|
1372
|
|
|
|
|
|
|
=cut |
1373
|
|
|
|
|
|
|
|
1374
|
|
|
|
|
|
|
sub add_comments { |
1375
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
1376
|
0
|
|
|
|
|
|
my $comments = shift; |
1377
|
0
|
|
|
|
|
|
my $comment_struct = shift; |
1378
|
0
|
0
|
0
|
|
|
|
$comment_struct = 'string' |
1379
|
|
|
|
|
|
|
unless ($comment_struct && ($comment_struct =~ /^hol|array$/)); |
1380
|
0
|
0
|
0
|
|
|
|
if ($self->moves && $comments && (ref $comments eq 'HASH')) { |
|
|
|
0
|
|
|
|
|
1381
|
0
|
|
|
|
|
|
for (keys %{ $comments } ) { |
|
0
|
|
|
|
|
|
|
1382
|
0
|
0
|
|
|
|
|
next unless /^\d+(?:w|b)$/; |
1383
|
0
|
0
|
|
|
|
|
if ($comment_struct eq 'string') { |
|
|
0
|
|
|
|
|
|
1384
|
0
|
|
|
|
|
|
$self->{GameComments}->{$_} .= |
1385
|
|
|
|
|
|
|
q{ } . $comments->{$_}; |
1386
|
|
|
|
|
|
|
} |
1387
|
|
|
|
|
|
|
elsif ($comment_struct eq 'array') { |
1388
|
0
|
|
|
|
|
|
push @{$self->{GameComments}->{$_}}, |
|
0
|
|
|
|
|
|
|
1389
|
|
|
|
|
|
|
$comments->{$_}; |
1390
|
|
|
|
|
|
|
} |
1391
|
|
|
|
|
|
|
else { # hol |
1392
|
0
|
|
|
|
|
|
$comments->{$_} =~ m/^(.)/; |
1393
|
0
|
|
|
|
|
|
my $comment_type ='unknown'; |
1394
|
0
|
0
|
0
|
|
|
|
$comment_type = $comment_types{$1} |
1395
|
|
|
|
|
|
|
if ($1 and exists $comment_types{$1}); |
1396
|
0
|
|
|
|
|
|
push @{$self->{GameComments}->{$_}->{$comment_type}} , |
|
0
|
|
|
|
|
|
|
1397
|
|
|
|
|
|
|
$comments->{$_}; |
1398
|
|
|
|
|
|
|
} |
1399
|
|
|
|
|
|
|
} |
1400
|
|
|
|
|
|
|
} |
1401
|
0
|
|
|
|
|
|
return $self->{GameComments}; |
1402
|
|
|
|
|
|
|
} |
1403
|
|
|
|
|
|
|
|
1404
|
|
|
|
|
|
|
} # end closure for parse_game() |
1405
|
|
|
|
|
|
|
|
1406
|
|
|
|
|
|
|
=item shrink_epd() |
1407
|
|
|
|
|
|
|
|
1408
|
|
|
|
|
|
|
Given a EPD (Extended Position Description) string, shrink_epd() will convert it into a bit string, which reduces the original by about 50%. |
1409
|
|
|
|
|
|
|
It can be restored to the original string by expand_epd() |
1410
|
|
|
|
|
|
|
|
1411
|
|
|
|
|
|
|
=cut |
1412
|
|
|
|
|
|
|
|
1413
|
|
|
|
|
|
|
# K k 0001 1001 001 |
1414
|
|
|
|
|
|
|
# Q q 0010 1010 010 |
1415
|
|
|
|
|
|
|
# R r 0011 1011 011 |
1416
|
|
|
|
|
|
|
# B b 0100 1100 100 |
1417
|
|
|
|
|
|
|
# N n 0101 1101 101 |
1418
|
|
|
|
|
|
|
# P p 0110 1110 110 |
1419
|
|
|
|
|
|
|
# E 0000 0000 000 |
1420
|
|
|
|
|
|
|
# 111 |
1421
|
|
|
|
|
|
|
# rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR b KQkq d3 (38 bytes) |
1422
|
|
|
|
|
|
|
# 1011 1101 1100 1010 1001 1100 1101 1011 4 |
1423
|
|
|
|
|
|
|
# 1110 1110 1110 1110 1110 1110 1110 1110 4 |
1424
|
|
|
|
|
|
|
# 11111000 1 |
1425
|
|
|
|
|
|
|
# 11111000 1 |
1426
|
|
|
|
|
|
|
# 11110011 0110 11110100 2.5 |
1427
|
|
|
|
|
|
|
# 11111000 1 |
1428
|
|
|
|
|
|
|
# 0110 0110 0110 11110001 0110 0110 0110 0110 4.5 |
1429
|
|
|
|
|
|
|
# 0011 0101 0100 0010 0001 0100 0101 0011 4 |
1430
|
|
|
|
|
|
|
# 22 |
1431
|
|
|
|
|
|
|
|
1432
|
|
|
|
|
|
|
{ #start EPD closure |
1433
|
|
|
|
|
|
|
my %pieces2bits = ( |
1434
|
|
|
|
|
|
|
K => 1, # 0001 |
1435
|
|
|
|
|
|
|
k => 9, # 1001 |
1436
|
|
|
|
|
|
|
Q => 2, # 0010 |
1437
|
|
|
|
|
|
|
q => 10, # 1010 |
1438
|
|
|
|
|
|
|
R => 3, # 0011 |
1439
|
|
|
|
|
|
|
r => 11, # 1011 |
1440
|
|
|
|
|
|
|
B => 4, # 0100 |
1441
|
|
|
|
|
|
|
b => 12, # 1100 |
1442
|
|
|
|
|
|
|
N => 5, # 0101 |
1443
|
|
|
|
|
|
|
n => 13, # 1101 |
1444
|
|
|
|
|
|
|
P => 6, # 0110 |
1445
|
|
|
|
|
|
|
p => 14, # 1110 |
1446
|
|
|
|
|
|
|
1 => 0, # 0000 |
1447
|
|
|
|
|
|
|
2 => 7, # 0111 |
1448
|
|
|
|
|
|
|
3 => 8, # 1000 |
1449
|
|
|
|
|
|
|
4 => 0xF4, # 1111 0100 |
1450
|
|
|
|
|
|
|
5 => 0xF5, # 1111 0101 |
1451
|
|
|
|
|
|
|
6 => 0xF6, # 1111 0110 |
1452
|
|
|
|
|
|
|
7 => 0xF7, # 1111 0111 |
1453
|
|
|
|
|
|
|
8 => 0xF8, # 1111 1000 |
1454
|
|
|
|
|
|
|
); |
1455
|
|
|
|
|
|
|
|
1456
|
|
|
|
|
|
|
my %castling2bits = ( |
1457
|
|
|
|
|
|
|
'KQkq' => 15, # 1111 F KQkq |
1458
|
|
|
|
|
|
|
'KQk' => 14, # 1110 E KQk- |
1459
|
|
|
|
|
|
|
'KQq' => 13, # 1101 D KQ-q |
1460
|
|
|
|
|
|
|
'KQ' => 12, # 1100 C KQ-- |
1461
|
|
|
|
|
|
|
'Kkq' => 11, # 1011 B K-kq |
1462
|
|
|
|
|
|
|
'Kk' => 10, # 1010 A K-k- |
1463
|
|
|
|
|
|
|
'Kq' => 9, # 1001 9 K--q |
1464
|
|
|
|
|
|
|
'K' => 8, # 1000 8 K--- |
1465
|
|
|
|
|
|
|
'Qkq' => 7, # 0111 7 -Qkq |
1466
|
|
|
|
|
|
|
'Qk' => 6, # 0110 6 -Qk- |
1467
|
|
|
|
|
|
|
'Qq' => 5, # 0101 5 -Q-q |
1468
|
|
|
|
|
|
|
'Q' => 4, # 0100 4 -Q-- |
1469
|
|
|
|
|
|
|
'kq' => 3, # 0011 3 --kq |
1470
|
|
|
|
|
|
|
'k' => 2, # 0010 2 --k- |
1471
|
|
|
|
|
|
|
'q' => 1, # 0001 1 ---q |
1472
|
|
|
|
|
|
|
q{-} => 0, # 0111 0 ---- |
1473
|
|
|
|
|
|
|
); |
1474
|
|
|
|
|
|
|
|
1475
|
|
|
|
|
|
|
my %ep2bits = ( |
1476
|
|
|
|
|
|
|
q{-} => 0, |
1477
|
|
|
|
|
|
|
'a' => 1, |
1478
|
|
|
|
|
|
|
'b' => 2, |
1479
|
|
|
|
|
|
|
'c' => 3, |
1480
|
|
|
|
|
|
|
'd' => 4, |
1481
|
|
|
|
|
|
|
'e' => 5, |
1482
|
|
|
|
|
|
|
'f' => 6, |
1483
|
|
|
|
|
|
|
'g' => 7, |
1484
|
|
|
|
|
|
|
'h' => 8, |
1485
|
|
|
|
|
|
|
); |
1486
|
|
|
|
|
|
|
my %color2bits = ('w' => 0, 'b' => 1 ); |
1487
|
|
|
|
|
|
|
my %bits2color = ( 0 => 'w', 1 => 'b'); |
1488
|
|
|
|
|
|
|
|
1489
|
|
|
|
|
|
|
my %bits2pieces = map { $pieces2bits{$_}, $_ } keys %pieces2bits; |
1490
|
|
|
|
|
|
|
my %bits2castling = map { $castling2bits{$_}, $_ } keys %castling2bits; |
1491
|
|
|
|
|
|
|
my %bits2ep = map { $ep2bits{$_}, $_ } keys %ep2bits; |
1492
|
|
|
|
|
|
|
|
1493
|
|
|
|
|
|
|
sub shrink_epd { |
1494
|
0
|
|
|
0
|
1
|
|
my $source = shift; |
1495
|
0
|
|
|
|
|
|
my $piece = q{}; |
1496
|
0
|
|
|
|
|
|
my $vecstring = q{}; |
1497
|
0
|
|
|
|
|
|
my $offset = 0; |
1498
|
0
|
|
|
|
|
|
my ($fen, $color, $castling, $ep) = split / /, $source; |
1499
|
0
|
|
|
|
|
|
while ($fen =~ /(.)/g) { |
1500
|
0
|
0
|
|
|
|
|
next if $1 eq q{/}; |
1501
|
0
|
|
|
|
|
|
$piece = $pieces2bits{$1}; |
1502
|
0
|
0
|
|
|
|
|
if ($piece < 0x0F) { |
1503
|
0
|
|
|
|
|
|
vec($vecstring, $offset++, 4) = $piece; |
1504
|
|
|
|
|
|
|
} |
1505
|
|
|
|
|
|
|
else { |
1506
|
0
|
|
|
|
|
|
vec($vecstring, $offset++, 4) = 0x0F; |
1507
|
0
|
|
|
|
|
|
vec($vecstring, $offset++, 4) = $1; |
1508
|
|
|
|
|
|
|
} |
1509
|
|
|
|
|
|
|
} |
1510
|
0
|
|
|
|
|
|
vec($vecstring, $offset++, 4) = $color2bits{$color}; |
1511
|
0
|
|
|
|
|
|
vec($vecstring, $offset++, 4) = $castling2bits{$castling}; |
1512
|
0
|
|
|
|
|
|
vec($vecstring, $offset++, 4) = $ep2bits{substr($ep,0,1)}; |
1513
|
0
|
|
|
|
|
|
return $vecstring; |
1514
|
|
|
|
|
|
|
} |
1515
|
|
|
|
|
|
|
|
1516
|
|
|
|
|
|
|
=item expand_epd() |
1517
|
|
|
|
|
|
|
|
1518
|
|
|
|
|
|
|
given a EPD bitstring created by shrink_epd(), expand_epd() will restore the original text. |
1519
|
|
|
|
|
|
|
|
1520
|
|
|
|
|
|
|
=cut |
1521
|
|
|
|
|
|
|
|
1522
|
|
|
|
|
|
|
sub expand_epd { |
1523
|
0
|
|
|
0
|
1
|
|
my $vecstring = shift; |
1524
|
0
|
|
|
|
|
|
my $piece = -1; |
1525
|
0
|
|
|
|
|
|
my $asciistr=q{}; |
1526
|
0
|
|
|
|
|
|
my $offset =0; |
1527
|
0
|
|
|
|
|
|
my $rowsum =0; |
1528
|
0
|
|
|
|
|
|
my $overall_sum =0; |
1529
|
0
|
|
|
|
|
|
while ($offset < length($vecstring)*2) { |
1530
|
0
|
|
|
|
|
|
$piece = vec($vecstring, $offset++, 4); |
1531
|
0
|
0
|
|
|
|
|
if ($piece == 0x0F) { |
1532
|
0
|
|
|
|
|
|
$piece = hex('F' . vec($vecstring,$offset++,4)); |
1533
|
|
|
|
|
|
|
} |
1534
|
0
|
|
|
|
|
|
$piece = $bits2pieces{$piece}; |
1535
|
0
|
|
|
|
|
|
$asciistr .= $piece; |
1536
|
0
|
0
|
|
|
|
|
if ($piece =~ /[1-8]/) { |
1537
|
0
|
|
|
|
|
|
$rowsum += $piece |
1538
|
|
|
|
|
|
|
} |
1539
|
|
|
|
|
|
|
else { |
1540
|
0
|
|
|
|
|
|
$rowsum++; |
1541
|
|
|
|
|
|
|
} |
1542
|
0
|
0
|
|
|
|
|
if ($rowsum == 8) { |
1543
|
0
|
|
|
|
|
|
$overall_sum += $rowsum; |
1544
|
0
|
|
|
|
|
|
$rowsum =0; |
1545
|
0
|
0
|
|
|
|
|
last if ($overall_sum >= 64); |
1546
|
0
|
|
|
|
|
|
$asciistr .=q{/}; |
1547
|
|
|
|
|
|
|
} |
1548
|
|
|
|
|
|
|
} |
1549
|
0
|
|
|
|
|
|
my $color = $bits2color{vec($vecstring,$offset++,4)}; |
1550
|
0
|
|
|
|
|
|
$asciistr .= q{ } . $color; |
1551
|
0
|
|
|
|
|
|
$asciistr .= q{ } . $bits2castling{vec($vecstring,$offset++,4)}; |
1552
|
0
|
|
|
|
|
|
my $ep = $bits2ep{vec($vecstring,$offset++,4)}; |
1553
|
0
|
0
|
|
|
|
|
if ($ep ne q{-}) { |
1554
|
0
|
0
|
|
|
|
|
$ep .= $color eq 'w' ? '6' : '3'; |
1555
|
|
|
|
|
|
|
} |
1556
|
0
|
|
|
|
|
|
$asciistr .= q{ } . $ep; |
1557
|
0
|
|
|
|
|
|
return $asciistr; |
1558
|
|
|
|
|
|
|
} |
1559
|
|
|
|
|
|
|
} # end EPD closure |
1560
|
|
|
|
|
|
|
=back |
1561
|
|
|
|
|
|
|
|
1562
|
|
|
|
|
|
|
=head1 AUTHOR |
1563
|
|
|
|
|
|
|
|
1564
|
|
|
|
|
|
|
Giuseppe Maxia, gmax@cpan.org |
1565
|
|
|
|
|
|
|
|
1566
|
|
|
|
|
|
|
=head1 THANKS |
1567
|
|
|
|
|
|
|
|
1568
|
|
|
|
|
|
|
Thanks to |
1569
|
|
|
|
|
|
|
- Hugh S. Myers for advice, support, testing and brainstorming; |
1570
|
|
|
|
|
|
|
- Damian Conway for the recursive Regular Expressions used to parse comments; |
1571
|
|
|
|
|
|
|
- all people at PerlMonks (www.perlmonks.org) for advice and good developing environment. |
1572
|
|
|
|
|
|
|
- Nathan Neff for pointing out an insidious, hard-to-spot bug in my RegExes. |
1573
|
|
|
|
|
|
|
|
1574
|
|
|
|
|
|
|
=head1 COPYRIGHT |
1575
|
|
|
|
|
|
|
|
1576
|
|
|
|
|
|
|
The Chess::PGN::Parse module is Copyright (c) 2002 Giuseppe Maxia, |
1577
|
|
|
|
|
|
|
Sardinia, Italy. All rights reserved. |
1578
|
|
|
|
|
|
|
|
1579
|
|
|
|
|
|
|
You may distribute this software under the terms of either the GNU |
1580
|
|
|
|
|
|
|
General Public License version 2 or the Artistic License, as |
1581
|
|
|
|
|
|
|
specified in the Perl README file. |
1582
|
|
|
|
|
|
|
The embedded and encosed documentation is released under |
1583
|
|
|
|
|
|
|
the GNU FDL Free Documentation License 1.1 |
1584
|
|
|
|
|
|
|
|
1585
|
|
|
|
|
|
|
=cut |
1586
|
|
|
|
|
|
|
|
1587
|
|
|
|
|
|
|
1; |
1588
|
|
|
|
|
|
|
__DATA__ |