File Coverage

blib/lib/Chess/PGN/Parse.pm
Criterion Covered Total %
statement 188 504 37.3
branch 58 226 25.6
condition 6 65 9.2
subroutine 18 58 31.0
pod 42 42 100.0
total 312 895 34.8


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   898 use strict;
  1         2  
  1         25  
16 1     1   5 use warnings;
  1         2  
  1         65  
17             use overload
18             q{<>} => sub {
19 2     2   5 return shift @{$_[0]};
  2         8  
20 1     1   1559 };
  1         1130  
  1         9  
21            
22             sub new {
23 1     1   2 my $class = shift;
24 1         5 return bless [split /^/xm, $_[0]], $class;
25             }
26       1     sub close { } ## no critic
27            
28             package Chess::PGN::Parse; ## no critic
29 1     1   31129 use English qw( -no_match_vars ) ;
  1         33579  
  1         11  
30              
31             require 5.006;
32 1     1   2174 use IO::File;
  1         13114  
  1         7245  
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.20'; # 23-Jan-2015
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 4     4 1 142 my $class = shift;
220 4         7 my $filename = shift;
221 4         5 my $fh = undef;
222 4 100       31 if (defined $filename) {
223 3   50     30 $fh = new IO::File "< $filename"
224             || return ;
225             }
226             else {
227 1         3 my $text = shift;
228 1         8 $fh = new StringHandle $text;
229             }
230 4         284 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             # this is the memory between loops. The
237             # reading engine recognizes some elements
238             # one line after.
239             # For example, game text ends when we
240             # read tags from the input. At this moment,
241             # we have to return from the method, but
242             # we must keep in memory what we have last read.
243             # This structure will also take care of the
244             # tags spanning over several lines.
245             memory => {
246             tag => q{},
247             utag => 0, # = unfinished tag
248             game => q{},
249             tag_printed => 0,
250             game_printed => 0,
251             }
252             }, $class;
253 4         11 return $self;
254             }
255              
256             =for internal use
257             the object destroyer cleans possible hanging references
258              
259             =cut
260              
261             sub DESTROY {
262 4     4   36 my $self = shift;
263 4         7 undef $self->{GameComments};
264 4         5 undef $self->{GameErrors};
265 4         16 undef $self->{gamedescr};
266 4         5 undef $self->{GameMoves};
267 4         7 eval {
268             #if (defined ${$self->{fh}}) {
269 4         6 ${$self->{fh}}->close();
  4         21  
270             #}
271             };
272 4         53 undef $self->{fh};
273 4         63 return;
274             }
275             my %symbolic_annotation_glyph = (
276             q{$1} => q{!},
277             q{$2} => q{?},
278             q{$3} => q{!!},
279             q{$4} => q{??},
280             q{$5} => q{!?},
281             q{$6} => q{?!},
282             );
283              
284             my %numeric_annotation_glyph = ();
285              
286             =item NAG()
287             returns the corresponding Numeric Annotation Glyph
288              
289             =cut
290              
291             sub NAG {
292 0     0 1 0 my $item = shift;
293 0 0       0 return unless $item =~ /\$?(\d+)/x;
294 0 0 0     0 return if ($1 > 139) or ($1 < 0);
295 0 0       0 unless (scalar keys %numeric_annotation_glyph) {
296 0         0 local $INPUT_RECORD_SEPARATOR = undef;
297 0         0 eval ; ## no critic
298             }
299 0         0 my $nag_ref = \%numeric_annotation_glyph;
300 0 0 0     0 if (($1 > 0) and ($1 <=6)) {
301 0         0 $nag_ref = \%symbolic_annotation_glyph
302             }
303 0 0       0 if ($item =~ /^\$/x) {
304 0         0 return $nag_ref->{$item}
305             }
306             else {
307 0         0 return $nag_ref->{q{$}.$item}
308             }
309             }
310              
311             =item STR()
312              
313             returns the Seven Tags Roster array
314              
315             @array = $pgn->STR();
316             @array = PGNParser::STR();
317              
318             =cut
319              
320             sub STR {
321 0     0 1 0 return @seven_tags_roster;
322             }
323              
324             =item event()
325              
326             returns the Event tag
327              
328             =item site()
329              
330             returns the Site tag
331              
332             =item date()
333              
334             returns the Date tag
335              
336             =item white()
337              
338             returns the White tag
339              
340             =item black()
341              
342             returns the Black tag
343              
344             =item result()
345              
346             returns the result tag
347              
348             =item round()
349              
350             returns the Round tag
351              
352             =item game()
353              
354             returns the unparsed game moves
355              
356             =item time()
357              
358             returns the Time tag
359              
360             =item eco()
361              
362             returns the ECO tag
363              
364             =item eventdate()
365              
366             returns the EventDate tag
367              
368             =item moves()
369              
370             returns an array reference to the game moves (no numbers)
371             =cut
372              
373             =item comments()
374              
375             returns a hash reference to the game comments (the key is the move number and the value are the comments for such move)
376              
377             =cut
378              
379             =item errors()
380              
381             returns a hash reference to the game errors (the key is the move number and the value are the errors for such move)
382              
383             =item set_event()
384              
385             returns or modifies the Event tag
386              
387             =item set_site()
388              
389             returns or modifies the Site tag
390              
391             =item set_date()
392              
393             returns or modifies the Date tag
394              
395             =item set_white()
396              
397             returns or modifies the White tag
398              
399             =item set_black()
400              
401             returns or modifies the Black tag
402              
403             =item set_result()
404              
405             returns or modifies the result tag
406              
407             =item set_round()
408              
409             returns or modifies the Round tag
410              
411             =item set_game()
412              
413             returns or modifies the unparsed game moves
414              
415             =item set_time()
416              
417             returns or modifies the Time tag
418              
419             =item set_eco()
420              
421             returns or modifies the ECO tag
422              
423             =item set_eventdate()
424              
425             returns or modifies the EventDate tag
426              
427              
428             =item set_moves()
429              
430             returns or modifies an array reference to the game moves (no numbers)
431              
432             =cut
433              
434             sub event {
435 0     0 1 0 my $self = shift;
436             return $self->{gamedescr}{Event}
437 0         0 }
438              
439             sub site {
440 0     0 1 0 my $self = shift;
441             return $self->{gamedescr}{Site}
442 0         0 }
443              
444             sub date {
445 0     0 1 0 my $self = shift;
446             return $self->{gamedescr}{Date}
447 0         0 }
448              
449             sub white {
450 0     0 1 0 my $self = shift;
451             return $self->{gamedescr}{White}
452 0         0 }
453              
454             sub black {
455 0     0 1 0 my $self = shift;
456             return $self->{gamedescr}{Black}
457 0         0 }
458              
459             sub result {
460 0     0 1 0 my $self = shift;
461             return $self->{gamedescr}{Result}
462 0         0 }
463              
464             sub round {
465 0     0 1 0 my $self = shift;
466             return $self->{gamedescr}{Round}
467 0         0 }
468              
469             ## no critic
470             sub time {
471 0     0 1 0 my $self = shift;
472             return $self->{gamedescr}{Time}
473 0         0 }
474             ## use critic
475              
476             sub eventdate {
477 0     0 1 0 my $self = shift;
478             return $self->{gamedescr}{EventDate}
479 0         0 }
480              
481             sub eco {
482 0     0 1 0 my $self = shift;
483             return $self->{gamedescr}{ECO}
484 0         0 }
485              
486             sub game {
487 0     0 1 0 my $self = shift;
488             return $self->{gamedescr}{Game}
489 0         0 }
490              
491             sub moves {
492 0     0 1 0 my $self = shift;
493 0         0 return $self->{GameMoves};
494             }
495              
496              
497             sub set_event {
498 0     0 1 0 my $self = shift;
499 0 0       0 $self->{gamedescr}{Event} = $_[0] if @_;
500             return $self->{gamedescr}{Event}
501 0         0 }
502              
503             sub set_site {
504 0     0 1 0 my $self = shift;
505 0 0       0 $self->{gamedescr}{Site} = shift if @_;
506             return $self->{gamedescr}{Site}
507 0         0 }
508              
509             sub set_date {
510 0     0 1 0 my $self = shift;
511 0 0       0 $self->{gamedescr}{Date} = shift if @_;
512             return $self->{gamedescr}{Date}
513 0         0 }
514              
515             sub set_white {
516 0     0 1 0 my $self = shift;
517 0 0       0 $self->{gamedescr}{White} = shift if @_;
518             return $self->{gamedescr}{White}
519 0         0 }
520              
521             sub set_black {
522 0     0 1 0 my $self = shift;
523 0 0       0 $self->{gamedescr}{Black} = shift if @_;
524             return $self->{gamedescr}{Black}
525 0         0 }
526              
527             sub set_result {
528 0     0 1 0 my $self = shift;
529 0 0       0 $self->{gamedescr}{Result} = shift if @_;
530             return $self->{gamedescr}{Result}
531 0         0 }
532              
533             sub set_round {
534 0     0 1 0 my $self = shift;
535 0 0       0 $self->{gamedescr}{Round} = shift if @_;
536             return $self->{gamedescr}{Round}
537 0         0 }
538              
539             sub set_time {
540 0     0 1 0 my $self = shift;
541 0 0       0 $self->{gamedescr}{Time} = shift if @_;
542             return $self->{gamedescr}{Time}
543 0         0 }
544              
545             sub set_eventdate {
546 0     0 1 0 my $self = shift;
547 0 0       0 $self->{gamedescr}{EventDate} = shift if @_;
548             return $self->{gamedescr}{EventDate}
549 0         0 }
550              
551             sub set_eco {
552 0     0 1 0 my $self = shift;
553 0 0       0 $self->{gamedescr}{ECO} = shift if @_;
554             return $self->{gamedescr}{ECO}
555 0         0 }
556              
557             sub set_game {
558 0     0 1 0 my $self = shift;
559 0 0       0 $self->{gamedescr}{Game} = shift if @_;
560             return $self->{gamedescr}{Game}
561 0         0 }
562              
563             sub set_moves {
564 0     0 1 0 my $self = shift;
565 0 0 0     0 $self->{GameMoves} = shift if (@_ && (ref $_[0] eq 'ARRAY')) ;
566 0         0 return $self->{GameMoves};
567             }
568              
569             sub errors {
570 0     0 1 0 my $self = shift;
571 0         0 return $self->{GameErrors};
572             }
573              
574             sub comments {
575 0     0 1 0 my $self = shift;
576 0         0 return $self->{GameComments};
577             }
578              
579             =for internal use
580             initialize the pgn object fields.
581              
582             =cut
583              
584             sub _init {
585 25     25   28 my $self = shift;
586 25         34 for (keys %{$self->{gamedescr}}) {
  25         102  
587 273         418 $self->{gamedescr}{$_} = q{};
588             }
589             delete $self->{gamedescr}{FirstMove}
590 25 50       79 if exists $self->{gamedescr}{FirstMove};
591 25         33 undef $self->{GameMoves};
592 25         36 undef $self->{GameComments};
593 25         35 undef $self->{GameErrors}; # 0.07
594 25         40 return;
595             }
596              
597             =item tags()
598            
599             returns a hash reference to all the parsed tags
600              
601             $hash_ref = $pgn->tags();
602              
603             =cut
604              
605             sub tags {
606 0     0 1 0 my $self = shift;
607 0         0 return \%{$self->{gamedescr}};
  0         0  
608             }
609              
610             =item read_all()
611              
612             Will read and parse all the games in the current file and return a reference to an array of hashes.
613             Each hash item contains both the raw data and the parsed moves and comments
614              
615             Same parameters as for parse_game(). Default : discard comments
616              
617             my $games_ref = $pgn->read_all();
618              
619             =cut
620              
621             sub read_all {
622 1     1 1 43 my $self=shift;
623 1         2 my $params = shift;
624 1         2 my @games =();
625 1         3 while ($self->read_game()) {
626 1         4 $self->parse_game($params);
627 1         1 my %gd = %{$self->{gamedescr}};
  1         11  
628 1         4 $gd{GameComments} = $self->{GameComments};
629 1         3 $gd{GameErrors} = $self->{GameErrors};
630 1         3 $gd{GameMoves} = $self->{GameMoves};
631 1         5 push @games, \%gd;
632             }
633 1         114 return \@games;
634             }
635              
636             =item quick_read_all()
637              
638             Will read and quick parse all the games in the current file and return a reference to an array of hashes.
639             Each hash item contains both the raw data and the parsed moves
640             Comments are discarded. Same parameters as for quick_parse_game().
641              
642             my $games_ref = $pgn->quick_read_all();
643              
644             =cut
645              
646             sub quick_read_all {
647 1     1 1 50 my $self=shift;
648 1         2 my $params = shift;
649 1         2 my @games =();
650 1         5 while ($self->read_game()) {
651 20         44 $self->quick_parse_game($params);
652 20         22 my %gd = %{$self->{gamedescr}};
  20         199  
653 20         59 $gd{GameMoves} = $self->{GameMoves};
654 20         63 push @games, \%gd;
655             }
656 1         5 return \@games;
657             }
658              
659             =item smart_read_all()
660              
661             Will read and quick parse all the games in the current file and return a reference to an array of hashes.
662             Each hash item contains both the raw data and the parsed moves
663             Comments are discarded. Calls smart_read_game() to decide which method is best to parse each given game.
664              
665             my $games_ref = $pgn->smart_read_all();
666              
667             =cut
668              
669             sub smart_read_all {
670 0     0 1 0 my $self=shift;
671 0         0 my $params = shift;
672 0         0 my @games =();
673 0         0 while ($self->read_game()) {
674 0         0 $self->smart_parse_game($params);
675 0         0 my %gd = %{$self->{gamedescr}};
  0         0  
676 0         0 $gd{GameMoves} = $self->{GameMoves};
677 0         0 push @games, \%gd;
678             }
679 0         0 return \@games;
680             }
681              
682              
683             =item read_game()
684              
685             reads the next game from the given PGN file.
686             Returns TRUE (1) if successful (= a game was read)
687             or FALSE (0) if no more games are available or
688             an unexpected EOF occurred before the end of parsing
689            
690             while ($pgn->read_game()) {
691             do_something_smart;
692             }
693            
694             It can read standard and in some cases even non-standard PGN
695             games. The following deviance from the standard are handled:
696            
697             1. no blank line between tags and moves;
698             2. no blank line between games
699             3. blank line(s) before a game (start of file)
700             4. multiple tags in the same line
701             5. tags spanning over more lines
702             (can't cumulate with rule 4)
703             6. No tags (only moves).
704             (can't cumulate with rule 2)
705             7. comments (starting with ";") outside the game text
706            
707             =cut
708              
709             #
710             # read_game() introduced in 0.07
711             #
712             sub _process_game {
713 22     22   29 my $self = shift;
714 22         30 my $memory = $self->{memory};
715 22 50       43 return 0 unless $memory->{game};
716 22 50       40 $self->{gamedescr}{missing} .= 'tags' unless $memory->{tag_printed};
717 22         27 $memory->{tag_printed} = 0;
718 22         63 $self->{gamedescr}{Game} .= $memory->{game};
719 22         28 $memory->{game} = q{};
720 22         32 $memory->{game_printed} =1;
721 22         71 return 1;
722             }
723              
724             sub _process_tag {
725 246     246   295 my $self = shift;
726 246         330 my $memory = $self->{memory};
727 246 50       416 if ($memory->{game}) {
728 0         0 $self->_process_game;
729             }
730 246 100       505 return 0 if $memory->{utag};
731 244 50       558 if ($memory->{tag} =~ tr/]// > 1) {
732             # deals with multiple tags in one line
733 0         0 $memory->{tag} =~ s/\]\s?/\]\n/g;
734             }
735 244         1087 while ($memory->{tag} =~ /\[(\w+)\s+"(.*)"\]\s*/g) {
736 244         936 $self->{gamedescr}{$1} = $2;
737             }
738 244         306 $memory->{tag_printed} =1;
739 244         372 $memory->{tag} = q{};
740 244         303 $memory->{game_printed} = 0;
741 244         867 return;
742             }
743              
744             sub read_game {
745 25     25 1 38 my $self = shift;
746 25         29 my $fh = ${$self->{fh}};
  25         46  
747 25         35 my $memory = $self->{memory};
748 25         67 $self->_init();
749 25 50       51 $self->_process_tag if $memory->{tag};
750 25 50       51 return $self->_process_game if $memory->{game};
751 25         239 while (<$fh>) {
752             # handle semicolon comments
753 434 50       846 if (/^;/) {
754 0 0 0     0 if ($memory->{game_printed} or (! $memory->{game})) { # between games
    0          
755 0         0 chomp;
756 0         0 $self->{gamedescr}{Comment} .= $_ ;
757             # comments between games are saved as tags
758             }
759             elsif ($memory->{game}){
760 0         0 $memory->{game} .= $_;
761             }
762 0         0 next; # anything else is discarded.
763             }
764             # normalize tagless games
765 434 100       1096 if (/^\s*$/) {
766 43 100       88 if ($memory->{game}) {
767             # handles comments with embedded newlines.
768 21 50       80 if (($memory->{game} =~ tr/\{//) < ($memory->{game} =~ tr/\}//) ) {
769 0         0 next;
770             }
771 21         50 return $self->_process_game;
772             }
773 22         134 next;
774             }
775             # deals with multi-line tags
776 391 50 66     1914 if ($memory->{utag}) {
    100          
777 0         0 chomp;
778 0         0 $memory->{tag} .= $_;
779 0         0 my $left_brackets = ($memory->{tag} =~ tr/\[//);
780 0         0 my $right_brackets = ($memory->{tag} =~ tr/\]//);
781 0 0       0 if ( $left_brackets == $right_brackets ) {
782 0         0 $memory->{utag} = 0;
783 0         0 $memory->{tag_printed} = 0;
784 0         0 $memory->{tag} .= "\n";
785             }
786             }
787             elsif (/^\[/ && (! $memory->{game})) {
788 245         399 my $left_brackets = tr/\[//;
789 245         355 my $right_brackets = tr/\]//;
790 245 100       424 if ($left_brackets == $right_brackets) {
    50          
791 244         455 $memory->{tag} = $_;
792             }
793             elsif ($right_brackets > $left_brackets) {
794 0         0 warn "Parsing error at line $.\n";
795             }
796             else {
797 1         3 $memory->{utag} = 1;
798 1         3 chomp;
799 1         3 $memory->{tag} = $_;
800 1         2 $memory->{tag_printed} =0;
801             }
802             }
803             else {
804 146         410 s/^\s*//;
805 146         265 $memory->{game} .= $_;
806             }
807 391 100       1005 if ($memory->{tag}) {
808 245 50       431 return $self->_process_game if $memory->{game};
809 245         461 $self->_process_tag;
810             }
811             }
812 4 100       10 if ($memory->{tag}) {
813 1         3 $self->_process_tag;
814             }
815 4 100       10 if ($memory->{game}) {
816 1         4 return $self->_process_game;
817             }
818 3         9 return 0;
819             }
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             $out_game = 0 if # 0.11
1004             exists $params->{game}
1005 0 0 0     0 and (lc($params->{game}) ne 'yes');
1006            
1007 0         0 my $out_comments = 0; # 0.11
1008             $out_comments = 'yes' if $out_game # 0.11
1009             and (exists $params->{comments}
1010 0 0 0     0 and (lc($params->{comments}) eq 'yes'));
      0        
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 24 my $self = shift;
1164 20         27 my $params = shift; # hash reference to parameters
1165 20         82 $self->{gamedescr}{Game} =~ s/$re_eol_comment//mg; # rm EOL comments
1166 20         71 $self->{gamedescr}{Game} =~ s/$re_escape//mgo; # rm escaped lines
1167             $self->{gamedescr}{Game} =~
1168 20         1200 s/$re_comment//g; # remove comments
1169             $self->{gamedescr}{Game} =~
1170 20         1179 s/$re_rav//g; # remove RAV
1171             return 0
1172             if $self->{gamedescr}{Game} =~
1173 20 50       67 /\(/; # the game still contains RAV
1174             return 0
1175             if $self->{gamedescr}{Game} =~
1176 20 50       56 /\{/; # undetected nested comments
1177 20         146 $self->{gamedescr}{Game} =~ s/\n/ /g; # remove newlines
1178             $self->{gamedescr}{Game} =~
1179 20         50 s/\r/ /g; # remove return chars (DOS)
1180 20         45 $self->{gamedescr}{Game} =~ s/$re_nag//go; # remove NAG
1181 20         739 $self->{gamedescr}{Game} =~ s/\d+\.//g; # remove numbers
1182 20         47 $self->{gamedescr}{Game} =~ s/\.\.(?:\.)?//g; # remove "..."
1183 20         168 $self->{gamedescr}{Game} =~ s/$re_result\s*\Z//o;
1184 20         62 my $re_filter = qr/\S/;
1185 20 50 33     60 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       49 return unless $self->{gamedescr}{Game}; # discards empty games
1191             $self->{GameMoves} =
1192 20         825 [grep { m/$re_filter/o } split /\s+/, $self->{gamedescr}{Game}];
  1620         3412  
1193 20         121 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 3 my $self = shift;
1254 1         2 my $params = shift;
1255             my $save_comments = ((exists $params->{save_comments})
1256 1   33     10 and ($params->{save_comments} =~ /^(?:yes|1)$/));
1257             my $log_errors = (exists $params->{log_errors})
1258 1 50       7 and ($params->{log_errors} =~ /^(?:yes|1)$/);
1259 1 50       4 return unless $self->{gamedescr}{Game};
1260 1         2 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         3 $self->{gamedescr}{Game} =~ s/0\-0/O-O/g;
1264 1         51 $self->{gamedescr}{Game} =~ s/$re_result\s*\Z//o;
1265              
1266 1         3 my $comments_struct = 'string';
1267             $comments_struct = $params->{comments_struct}
1268             if ($save_comments
1269 1 50 33     8 and exists $params->{comments_struct});
1270 1 50       6 $comments_struct = 'string'
1271             unless $comments_struct =~ /^(?:array|hol)$/;
1272 1         2 my $plycount = 0;
1273 1         2 my $countless =0;
1274 1         76 $self->{gamedescr}{Game} =~ s/\s*\Z//;
1275 1         11 $self->{gamedescr}{Game} =~ s/^\s*//;
1276 1 50       6 if ($self->{gamedescr}{Game} !~ /\d\./) {
1277 0         0 $countless = 1;
1278 0         0 $movecount = 1;
1279             }
1280            
1281 1         2 $self->{GameMoves} = [];
1282            
1283 1         4 for ($self->{gamedescr}{Game}) {
1284 1         4 while (! /\G \s* \z/xgc ) {
1285 92 100       2485 if ( m/\G($re_number)\s*/mgc) {
    100          
    100          
    50          
1286 22         42 my $num=$1;
1287 22 100       71 if (( $num =~ tr/\.//d) > 1) {
1288 3         6 $color = 'w';
1289             }
1290 22 100       76 if ($movecount == 0) {
    100          
    50          
1291 1         2 $movecount = $num;
1292             $self->{gamedescr}{FirstMove} =
1293             $num.$switchcolor{$color} # fixed 0.07
1294 1 50       9 unless $num.$switchcolor{$color} eq '1w';
1295             }
1296             elsif ($movecount == ($num -1)) {
1297 18         60 $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         48 push @{$self->{GameMoves}}, $1;
  37         91  
1307 37         75 $color = $switchcolor{$color};
1308 37 50       153 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       21 if ($save_comments) {
1324 9         19 my $tempcomment = $1;
1325 9         13 $tempcomment =~ tr/\r//d;
1326 9         21 $tempcomment =~ s/\n/ /g;
1327 9         18 $tempcomment =~ s/^\s+//;
1328 9         25 $tempcomment =~ s/\s+$//;
1329 9 50       17 if ($comments_struct eq 'string') {
    0          
1330 9         80 $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             $comment_type = $comment_types{$1}
1341 0 0 0     0 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       47 if ($log_errors) {
1349 24         74 $self->{GameErrors}->{$movecount.$color} .= q{ } . $1;
1350 24         50 $self->{GameErrors}->{$movecount.$color} =~ tr/\r//d;
1351 24         218 $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             $self->{GameComments}->{$_} .=
1385 0           q{ } . $comments->{$_};
1386             }
1387             elsif ($comment_struct eq 'array') {
1388 0           push @{$self->{GameComments}->{$_}},
1389 0           $comments->{$_};
1390             }
1391             else { # hol
1392 0           $comments->{$_} =~ m/^(.)/;
1393 0           my $comment_type ='unknown';
1394             $comment_type = $comment_types{$1}
1395 0 0 0       if ($1 and exists $comment_types{$1});
1396 0           push @{$self->{GameComments}->{$_}->{$comment_type}} ,
1397 0           $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__