File Coverage

blib/lib/Chess/PGN/EPD.pm
Criterion Covered Total %
statement 7 8 87.5
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 11 90.9


line stmt bran cond sub pod time code
1 10     10   878160 use strict;
  10         15  
  10         276  
2 10     10   32 use warnings;
  10         11  
  10         382  
3            
4             package Chess::PGN::EPD;
5            
6 10     10   2965 use v5.24;
  0            
7             use Chess::PGN::Moves;
8             use File::ShareDir qw(dist_file);
9             use File::Slurper qw(read_text);
10             use Cpanel::JSON::XS qw(decode_json);
11            
12             require Exporter;
13            
14             my ( $hECO, $hNIC, $hOpening );
15             my %hash = (
16             ECO => \$hECO,
17             NIC => \$hNIC,
18             Opening => \$hOpening
19             );
20            
21             my ( $ECO_path, $NIC_path, $Opening_path ) = _GetPaths('Chess-PGN-EPD');
22            
23             $hECO = decode_json read_text($ECO_path);
24             $hNIC = decode_json read_text($NIC_path);
25             $hOpening = decode_json read_text($Opening_path);
26            
27             sub _GetPaths {
28             my $dist = shift;
29             my $dbECO = dist_file( $dist, 'ECO.db' );
30             my $dbNIC = dist_file( $dist, 'NIC.db' );
31             my $dbOpening = dist_file( $dist, 'Opening.db' );
32            
33             return ( $dbECO, $dbNIC, $dbOpening );
34             }
35            
36             our @ISA = qw(Exporter);
37             our @EXPORT = qw(
38             epdcode
39             epdset
40             epdfromto
41             epdstr
42             epdlist
43             epdgetboard
44             epdTaxonomy
45             psquares
46             %font2map
47             );
48             our $VERSION = '0.32';
49            
50             our %font2map = (
51             'Chess Cases' => 'leschemelle',
52             'Chess Adventurer' => 'marroquin',
53             'Chess Alfonso-X' => 'marroquin',
54             'Chess Alpha' => 'bentzen1',
55             'Chess Berlin' => 'bentzen2',
56             'Chess Condal' => 'marroquin',
57             'Chess Harlequin' => 'marroquin',
58             'Chess Kingdom' => 'marroquin',
59             'Chess Leipzig' => 'marroquin',
60             'Chess Line' => 'marroquin',
61             'Chess Lucena' => 'marroquin',
62             'Chess Magnetic' => 'marroquin',
63             'Chess Mark' => 'marroquin',
64             'Chess Marroquin' => 'marroquin',
65             'Chess Maya' => 'marroquin',
66             'Chess Mediaeval' => 'marroquin',
67             'Chess Merida' => 'marroquin',
68             'Chess Millennia' => 'marroquin',
69             'Chess Miscel' => 'marroquin',
70             'Chess Montreal' => 'katch',
71             'Chess Motif' => 'marroquin',
72             'Chess Plain' => 'hickey',
73             'Chess Regular' => 'scott1',
74             'Chess Usual' => 'scott2',
75             'Chess Utrecht' => 'bodlaender',
76             'Tilburg' => 'tilburg',
77             'Traveller Standard V3' => 'cowderoy',
78             );
79            
80             my %board = qw(
81             a1 R a2 P a7 p a8 r
82             b1 N b2 P b7 p b8 n
83             c1 B c2 P c7 p c8 b
84             d1 Q d2 P d7 p d8 q
85             e1 K e2 P e7 p e8 k
86             f1 B f2 P f7 p f8 b
87             g1 N g2 P g7 p g8 n
88             h1 R h2 P h7 p h8 r
89             );
90             my $Kc = 1;
91             my $Qc = 1;
92             my $kc = 1;
93             my $qc = 1;
94             my $w = 1;
95            
96             my @onwhite = (
97             1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0,
98             1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1,
99             0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1,
100             );
101            
102             my %FontMap = (
103             hicky => {
104             OnBlack => 'OMASTLPNBRQK@',
105             OnWhite => 'omastlpnbrqk:',
106             SingleBox => '12345678',
107             DoubleBox => '!"#$%&\'(',
108             SingleRounded => '[]\^',
109             DoubleRounded => '<>;=/',
110             SingleLeftLegend => 'cdefghij',
111             DoubleLeftLegend => 'CDEFGHIJ',
112             SingleBottomLegend => 'wxyz{|}~',
113             DoubleBottomLegend => ')*+,-./0',
114             },
115             marroquin => {
116             OnBlack => 'OMVTWLPNBRQK+',
117             OnWhite => 'omvtwlpnbrqk ',
118             SingleBox => '12345789',
119             DoubleBox => '!"#$%/()',
120             SingleRounded => 'asdf',
121             DoubleRounded => 'ASDF',
122             SingleLeftLegend => "\300\301\302\303\034\305\306\307",
123             DoubleLeftLegend => "\340\341\342\343\344\345\346\347",
124             SingleBottomLegend => "\310\311\312\313\314\315\316\317",
125             DoubleBottomLegend => "\350\351\352\353\354\355\356\357",
126             },
127             leschemelle => {
128             OnBlack => 'OMVTWLPNBRQK+',
129             OnWhite => 'omvtwlpnbrqk ',
130             SingleBox => '12345789',
131             DoubleBox => '!"#$%/()',
132             SingleRounded => 'asdf',
133             DoubleRounded => 'ASDF',
134             SingleLeftLegend => "\300\301\302\303\034\305\306\307",
135             DoubleLeftLegend => "\340\341\342\343\344\345\346\347",
136             SingleBottomLegend => "\310\311\312\313\314\315\316\317",
137             DoubleBottomLegend => "\350\351\352\353\354\355\356\357",
138             },
139             linares => {
140             OnBlack => '0hg41i)HG$!Id',
141             OnWhite => 'pnbrqkPNBRQKw',
142             SingleBox => 'W_W[]W-W',
143             DoubleBox => 'cuC{}vlV',
144             SingleRounded => 'WWWW',
145             DoubleRounded => 'cCvV',
146             SingleLeftLegend => "\332\333\334\335\336\337\340\341",
147             DoubleLeftLegend => '(765&32%',
148             SingleBottomLegend => "\301\302\303\304\305\306\307\310",
149             DoubleBottomLegend => ',./9EFJM',
150             },
151             linares1 => {
152             OnBlack => '0hg41i)HG$!Id',
153             OnWhite => 'pnbrqkPNBRQKw',
154             SingleBox => '>;?:
155             DoubleBox => '>;?:
156             SingleRounded => '>?A@',
157             DoubleRounded => '>?A@',
158             SingleLeftLegend => '::::::::',
159             DoubleLeftLegend => '::::::::',
160             SingleBottomLegend => '========',
161             DoubleBottomLegend => '========',
162             },
163             linares2 => {
164             OnBlack => '0hg41i)HG$!Id',
165             OnWhite => 'pnbrqkPNBRQKw',
166             SingleBox => '^xY|yUz\\',
167             DoubleBox => '^xY|yUz\\',
168             SingleRounded => '^YU\\',
169             DoubleRounded => '^YU\\',
170             SingleLeftLegend => '||||||||',
171             DoubleLeftLegend => '||||||||',
172             SingleBottomLegend => 'zzzzzzzz',
173             DoubleBottomLegend => 'zzzzzzzz',
174             },
175             cowderoy => {
176             OnBlack => '$#!&%"*)\',+(0',
177             OnWhite => 'pnbrqkPNBRQK ',
178             SingleBox => '78946123',
179             DoubleBox => '78946123',
180             SingleRounded => '7913',
181             DoubleRounded => '7913',
182             SingleLeftLegend => '44444444',
183             DoubleLeftLegend => '44444444',
184             SingleBottomLegend => '22222222',
185             DoubleBottomLegend => '22222222',
186             },
187             bentzen1 => {
188             OnBlack => 'OJNTWLPHBRQK+',
189             OnWhite => 'ojntwlphbrqk ',
190             SingleBox => '!"#$%&\'(',
191             DoubleBox => '12345789',
192             SingleRounded => '!#&(',
193             DoubleRounded => '1379',
194             SingleLeftLegend => "\340\341\342\343\344\345\346\347",
195             DoubleLeftLegend => "\300\301\302\303\304\305\306\307",
196             SingleBottomLegend => "\350\351\352\353\354\355\356\357",
197             DoubleBottomLegend => "\310\311\312\313\314\315\316\317",
198             },
199             bentzen2 => {
200             OnBlack => 'OJNTWLPHBRQK+',
201             OnWhite => 'ojntwlphbrqk ',
202             SingleBox => '12345789',
203             DoubleBox => '12345789',
204             SingleRounded => '1379',
205             DoubleRounded => '1379',
206             SingleLeftLegend => '44444444',
207             DoubleLeftLegend => '44444444',
208             SingleBottomLegend => '88888888',
209             DoubleBottomLegend => '88888888',
210             },
211             scott1 => {
212             OnBlack => 'OJNTWLPHBRQK+',
213             OnWhite => 'ojntwlphbrqk*',
214             SingleBox => '(-)/\[_]',
215             DoubleBox => '(-)/\[_]',
216             SingleRounded => '(-)/\[_]',
217             DoubleRounded => '(-)/\[_]',
218             SingleLeftLegend => '////////',
219             DoubleLeftLegend => '////////',
220             SingleBottomLegend => '________',
221             DoubleBottomLegend => '________',
222             },
223             scott2 => {
224             OnBlack => 'OMVTWLPNBRQK+',
225             OnWhite => 'omvtwlpnbrqk ',
226             SingleBox => '12345789',
227             DoubleBox => '!"#$%/()',
228             SingleRounded => 'asdf',
229             DoubleRounded => 'ASDF',
230             SingleLeftLegend => '44444444',
231             DoubleLeftLegend => '$$$$$$$$',
232             SingleBottomLegend => '44444444',
233             DoubleBottomLegend => '$$$$$$$$',
234             },
235             bodlaender => {
236             OnBlack => 'OMVTWLomvtwl/',
237             OnWhite => 'PNBRQKpnbrqk ',
238             SingleBox => '51632748',
239             DoubleBox => '51632748',
240             SingleRounded => '51632748',
241             DoubleRounded => '51632748',
242             SingleLeftLegend => '33333333',
243             DoubleLeftLegend => '33333333',
244             SingleBottomLegend => '44444444',
245             DoubleBottomLegend => '44444444',
246             },
247             katch => {
248             OnBlack => 'OMVTWLPNBRQK/',
249             OnWhite => 'omvtwlpnbrqk ',
250             SingleBox => '12345789',
251             DoubleBox => '12345789',
252             SingleRounded => '12345789',
253             DoubleRounded => '12345789',
254             SingleLeftLegend => '44444444',
255             DoubleLeftLegend => '44444444',
256             SingleBottomLegend => '88888888',
257             DoubleBottomLegend => '88888888',
258             },
259             dummy => {
260             OnBlack => '',
261             OnWhite => '',
262             SingleBox => '',
263             DoubleBox => '',
264             SingleRounded => '',
265             DoubleRounded => '',
266             SingleLeftLegend => '',
267             DoubleLeftLegend => '',
268             SingleBottomLegend => '',
269             DoubleBottomLegend => '',
270             },
271             );
272            
273             my %convertPalView = (
274             'r',
275             '',
276             'n',
277             '',
278             'b',
279             '',
280             'q',
281             '',
282             'k',
283             '',
284             'p',
285             '',
286             'R',
287             '',
288             'N',
289             '',
290             'B',
291             '',
292             'Q',
293             '',
294             'K',
295             '',
296             'P',
297             '',
298             ' ',
299             '',
300             '-',
301             '',
302             );
303            
304             sub epdcode {
305             my $key = shift;
306             my $epd = shift;
307             my $code;
308             my $h = ${ $hash{$key} };
309            
310             for ( @{$epd} ) {
311             $code = $h->{$_}; ## no critic
312             last if $code;
313             }
314             return ( $code or 'Unknown' );
315             }
316            
317             sub epdset {
318             if ( my $epd = shift ) {
319             my @array = split( /\/|\s/, $epd );
320             my $file = '8';
321            
322             %board = ();
323             $Kc = 0;
324             $Qc = 0;
325             $kc = 0;
326             $qc = 0;
327             for ( 0 .. 7 ) {
328             $array[$_] =~ s/(\d+)/'_' x $1/ge;
329             my @row = split( '', $array[$_] );
330             my $rank = 'a';
331             for my $piece (@row) {
332             $board{"$rank$file"} = $piece if $piece ne '_';
333             $rank++;
334             }
335             $file--;
336             }
337             $w = ( $array[8] eq 'w' );
338             for ( split( '', $array[9] ) ) {
339             if ( $_ eq 'K' ) {
340             $Kc = 1;
341             }
342             elsif ( $_ eq 'Q' ) {
343             $Qc = 1;
344             }
345             elsif ( $_ eq 'k' ) {
346             $kc = 1;
347             }
348             elsif ( $_ eq 'q' ) {
349             $qc = 1;
350             }
351             }
352             }
353             else {
354             %board = qw(
355             a1 R a2 P a7 p a8 r
356             b1 N b2 P b7 p b8 n
357             c1 B c2 P c7 p c8 b
358             d1 Q d2 P d7 p d8 q
359             e1 K e2 P e7 p e8 k
360             f1 B f2 P f7 p f8 b
361             g1 N g2 P g7 p g8 n
362             h1 R h2 P h7 p h8 r
363             );
364             $w = 1;
365             $Kc = 1;
366             $Qc = 1;
367             $kc = 1;
368             $qc = 1;
369             }
370             return;
371             }
372            
373             sub epdstr {
374             my %parameters = @_;
375             if ( $parameters{'board'} ) {
376             my %board;
377             my $hashref = $parameters{'board'};
378            
379             for ( keys %$hashref ) {
380             $board{$_} = $$hashref{$_};
381             }
382             $parameters{'epd'} = epd( 0, 0, 0, 0, 0, 0, %board );
383             }
384             my $epd = $parameters{'epd'};
385             my $type = lc( $parameters{'type'} );
386             my ( $border, $corner, $legend ) = ( 'single', 'square', 'no' );
387            
388             $border = lc( $parameters{'border'} ) if exists( $parameters{'border'} );
389             $corner = lc( $parameters{'corner'} ) if exists( $parameters{'corner'} );
390             $legend = lc( $parameters{'legend'} ) if exists( $parameters{'legend'} );
391             my @array = split( /\/|\s/, $epd );
392             my @board;
393             if ( $type eq 'diagram' ) {
394             for ( 0 .. 7 ) {
395             $array[$_] =~ s/(\d+)/'_' x $1/ge;
396             $array[$_]
397             =~ s/_/(((pos $array[$_]) % 2) xor ($_ % 2)) ? '-' : ' '/ge;
398             push( @board, 8 - $_ . " " . $array[$_] );
399             }
400             push( @board, ' abcdefgh' );
401             }
402             elsif ( $type eq 'text' ) {
403             for ( 0 .. 7 ) {
404             $array[$_] =~ s/(\d+)/'_' x $1/ge;
405             $array[$_]
406             =~ s/_/(((pos $array[$_]) % 2) xor ($_ % 2)) ? '-' : ' '/ge;
407             push( @board, $array[$_] );
408             }
409             }
410             elsif ( $type eq 'palview' ) {
411             my @diagram;
412             my $table;
413            
414             for ( 0 .. 7 ) {
415             $array[$_] =~ s/(\d+)/'_' x $1/ge;
416             $array[$_]
417             =~ s/_/(((pos $array[$_]) % 2) xor ($_ % 2)) ? '-' : ' '/ge;
418             push( @diagram, $array[$_] );
419             }
420             for (@diagram) {
421             for ( split(//) ) {
422             $table .= $convertPalView{$_};
423             }
424             $table .= "
";
425             push( @board, $table );
426             $table = '';
427             }
428             }
429             elsif ( $type eq 'latex' ) {
430             push( @board, '\\begin{diagram}' );
431             push( @board, '\\board' );
432             for ( 0 .. 7 ) {
433             $array[$_] =~ s/(\d+)/'_' x $1/ge;
434             $array[$_]
435             =~ s/_/(((pos $array[$_]) % 2) xor ($_ % 2)) ? '*' : ' '/ge;
436             push( @board, '{' . $array[$_] . '}' );
437             }
438             push( @board, '\\end{diagram}' );
439             }
440             elsif ( $type eq 'tilburg' ) {
441             for ( 0 .. 7 ) {
442             $array[$_] =~ s/(\d+)/'_' x $1/ge;
443             $array[$_]
444             =~ s/([pnbrqkPNBRQK_])/_mappiece(pos $array[$_],$_,$1,"\341\345\351\355\361\365\337\343\347\353\357\363
445             \335","\340\344\350\354\360\364\336\342\346\352\356\362\334")/ge;
446             push( @board, $array[$_] );
447             }
448             }
449             else {
450             @board = _configureboard( $type, $border, $corner, $legend );
451             for ( 0 .. 7 ) {
452             $array[$_] =~ s/(\d+)/'_' x $1/ge;
453             $array[$_]
454             =~ s/([pnbrqkPNBRQK_])/_mappiece(pos $array[$_],$_,$1,$FontMap{$type}{'OnBlack'},$FontMap{$type}
455             {'OnWhite'})/ge;
456             substr( $board[ $_ + 1 ], 1, 8 ) = $array[$_];
457             }
458             }
459             return @board;
460             }
461            
462             sub _configureboard {
463             my $type = shift;
464             my $border = shift;
465             my $corner = shift;
466             my $legend = shift;
467             my $single = $border eq 'single';
468             my $box = $FontMap{$type}{ $single ? 'SingleBox' : 'DoubleBox' };
469             my @board;
470            
471             if ( $corner eq 'rounded' ) {
472             my $corners
473             = $FontMap{$type}{ $single ? 'SingleRounded' : 'DoubleRounded' };
474            
475             substr( $box, 0, 1 ) = substr( $corners, 0, 1 );
476             substr( $box, 2, 1 ) = substr( $corners, 1, 1 );
477             substr( $box, 5, 1 ) = substr( $corners, 2, 1 );
478             substr( $box, 7, 1 ) = substr( $corners, 3, 1 );
479             }
480             push( @board,
481             substr( $box, 0, 1 )
482             . substr( $box, 1, 1 ) x 8
483             . substr( $box, 2, 1 ) );
484             for ( 0 .. 7 ) {
485             push( @board, substr( $box, 3, 1 ) . ' ' x 8 . substr( $box, 4, 1 ) );
486             }
487             push( @board,
488             substr( $box, 5, 1 )
489             . substr( $box, 6, 1 ) x 8
490             . substr( $box, 7, 1 ) );
491             if ( $legend eq 'yes' ) {
492             my $left = $FontMap{$type}{
493             $single
494             ? 'SingleLeftLegend'
495             : 'DoubleLeftLegend'
496             };
497             my $bottom = $FontMap{$type}{
498             $single
499             ? 'SingleBottomLegend'
500             : 'DoubleBottomLegend'
501             };
502            
503             for ( 1 .. 8 ) {
504             substr( $board[$_], 0, 1 ) = substr( $left, $_ - 1, 1 );
505             }
506             substr( $board[-1], 1, 8 ) = $bottom;
507            
508             }
509             return @board;
510             }
511            
512             sub _mappiece {
513             my $x = shift;
514             my $y = shift;
515             my $piece = shift;
516             my $ifonblack = shift;
517             my $ifonwhite = shift;
518             my $onwhite = $onwhite[ ( $y * 8 ) + $x ];
519             my $which = index( 'pnbrqkPNBRQK_', $piece );
520            
521             return substr( $onwhite ? $ifonwhite : $ifonblack, $which, 1 );
522             }
523            
524             sub epdgetboard {
525             if ( my $epd = shift ) {
526             epdset($epd);
527             }
528             return $w, $Kc, $Qc, $kc, $qc, %board;
529             }
530            
531             sub epdfromto {
532             my @moves = @_;
533             my @movelist;
534            
535             epdset();
536             for (@moves) {
537             if ($_) {
538             my ( $piece, $to, $from, $promotion ) = _movetype( $w, $_ );
539             my $enpassant;
540             my $ep = '-';
541             my $castles = /O/ ? $_ : '';
542            
543             $Kc = 0 if $to eq 'h1';
544             $Qc = 0 if $to eq 'a1';
545             $kc = 0 if $to eq 'h8';
546             $qc = 0 if $to eq 'a8';
547            
548             if ( $piece eq "P" ) {
549             $piece = "p" if not $w;
550             $promotion = lc($promotion) if $promotion and not $w;
551             if ($from) {
552             $from .= substr( $to, 1, 1 );
553             if ($w) {
554             substr( $from, 1, 1 ) -= 1;
555             }
556             else {
557             $from++;
558             }
559             }
560             else {
561             $from = $to;
562            
563             if ($w) {
564             substr( $from, 1, 1 ) -= 1;
565             unless ( $board{$from} ) {
566             $ep = $from;
567             substr( $from, 1, 1 ) -= 1;
568             }
569             }
570             else {
571             $from++;
572             unless ( $board{$from} ) {
573             $ep = $from;
574             $from++;
575             }
576             }
577             }
578            
579             if ( substr( $from, 0, 1 ) ne substr( $to, 0, 1 ) ) {
580             if ( not $board{$to} ) {
581             $enpassant = $to;
582             if ($w) {
583             substr( $enpassant, 1, 1 )
584             = chr(
585             ord( substr( $enpassant, 1, 1 ) ) - 1 );
586             }
587             else {
588             substr( $enpassant, 1, 1 )
589             = chr(
590             ord( substr( $enpassant, 1, 1 ) ) + 1 );
591             }
592             $board{$enpassant} = undef;
593             $enpassant = defined($enpassant) ? $enpassant : '';
594             $from = defined($from) ? $from : '';
595             $to = defined($to) ? $to : '';
596             }
597             }
598             ( $board{$to}, $board{$from} )
599             = ( $promotion ? $promotion : $board{$from}, undef );
600             $piece = defined($piece) ? $piece : '';
601             $from = defined($from) ? $from : '';
602             $to = defined($to) ? $to : '';
603             $promotion = defined($promotion) ? $promotion : '';
604             }
605             elsif ( $piece eq "KR" ) {
606             my ( $k_from, $r_from ) = unpack( "A2A2", $from );
607             my ( $k_to, $r_to ) = unpack( "A2A2", $to );
608            
609             ( $board{$k_to}, $board{$k_from} )
610             = ( $board{$k_from}, undef );
611             ( $board{$r_to}, $board{$r_from} )
612             = ( $board{$r_from}, undef );
613             if ($w) {
614             $Kc = $Qc = 0;
615             }
616             else {
617             $kc = $qc = 0;
618             }
619             }
620             else {
621             my @piece_at;
622             my @fromlist;
623            
624             $piece = lc($piece) if not $w;
625             @piece_at = psquares( $piece, %board );
626             if ($from) {
627             my @tmp;
628            
629             $from = defined($from) ? $from : '';
630             if ( $from =~ /[a-h]/ ) {
631             for (@piece_at) {
632             push( @tmp, $_ )
633             if ( substr( $_, 0, 1 ) eq $from );
634             }
635             }
636             else {
637            
638             for (@piece_at) {
639             push( @tmp, $_ )
640             if ( substr( $_, 1, 1 ) eq $from );
641             }
642             }
643             @piece_at = @tmp;
644             }
645            
646             for my $square (@piece_at) {
647             for ( @{ $move_table{ uc($piece) }{$square} } ) {
648             push( @fromlist, $square ) if $_ eq $to;
649             }
650             }
651             if ( scalar(@fromlist) != 1 ) {
652             for (@fromlist) {
653             if ( _canmove( $piece, $to, $_, %board )
654             and _isLegal( $w, $_, $to, %board ) )
655             {
656             $from = $_;
657             last;
658             }
659             }
660             }
661             else {
662             $from = $fromlist[0];
663             }
664             if ( $piece =~ /[RrKk]/ ) {
665             if ( $piece eq 'R' ) {
666             $Kc = 0 if $from eq 'h1';
667             $Qc = 0 if $from eq 'a1';
668             }
669             elsif ( $piece eq 'r' ) {
670             $kc = 0 if $from eq 'h8';
671             $qc = 0 if $from eq 'a8';
672             }
673             elsif ( $piece eq 'K' ) {
674             $Kc = $Qc = 0;
675             }
676             else {
677             $kc = $qc = 0;
678             }
679             }
680             ( $board{$to}, $board{$from} ) = ( $board{$from}, undef );
681             $piece = defined($piece) ? $piece : '';
682             $from = defined($from) ? $from : '';
683             $to = defined($to) ? $to : '';
684             }
685             my $movehash = {
686             piece => defined($piece) ? $piece : '',
687             from => defined($from) ? $from : '',
688             to => defined($to) ? $to : '',
689             promotion => defined($promotion) ? $promotion : '',
690             enpassant => defined($enpassant) ? $enpassant : '',
691             castles => defined($castles) ? $castles : '',
692             };
693             push( @movelist, $movehash );
694             $w ^= 1;
695             }
696             }
697             %board = ();
698             return @movelist;
699             }
700            
701             sub epdlist {
702             my @moves = @_;
703             my $debug = 0;
704             my @epdlist;
705             my $lineno = 1;
706            
707             if ( scalar @moves and $moves[-1] eq '1' ) {
708             $debug = 1;
709             pop @moves;
710             if (%board) {
711             print "\%board initialized\n";
712             }
713             else {
714             print "\%board uninitialized\n";
715             }
716             }
717             epdset();
718             for (@moves) {
719             _Print(%board) if $debug;
720             if ($_) {
721             my ( $piece, $to, $from, $promotion ) = _movetype( $w, $_ );
722             my $enpassant;
723             my $ep = '-';
724            
725             $Kc = 0 if $to eq 'h1';
726             $Qc = 0 if $to eq 'a1';
727             $kc = 0 if $to eq 'h8';
728             $qc = 0 if $to eq 'a8';
729            
730             if ($debug) {
731             print "Move[$lineno]='$_'";
732             $lineno++;
733             if ($piece) {
734             print ", piece='$piece'";
735             print ", to='$to'" if $to;
736             print ", from='$from'" if $from;
737             print ", promotion='$promotion'" if $promotion;
738             }
739             print "\n";
740             }
741            
742             if ( $piece eq "P" ) {
743             $piece = "p" if not $w;
744             $promotion = lc($promotion) if $promotion and not $w;
745             if ($from) {
746             $from .= substr( $to, 1, 1 );
747             if ($w) {
748             substr( $from, 1, 1 ) -= 1;
749             }
750             else {
751            
752             $from++;
753             }
754             }
755             else {
756             $from = $to;
757            
758             if ($w) {
759             substr( $from, 1, 1 ) -= 1;
760             unless ( $board{$from} ) {
761             $ep = $from;
762             substr( $from, 1, 1 ) -= 1;
763             }
764             }
765             else {
766             $from++;
767             unless ( $board{$from} ) {
768             $ep = $from;
769             $from++;
770             }
771             }
772             }
773            
774             if ( substr( $from, 0, 1 ) ne substr( $to, 0, 1 ) ) {
775             if ( not $board{$to} ) {
776             $enpassant = $to;
777             if ($w) {
778             substr( $enpassant, 1, 1 )
779             = chr(
780             ord( substr( $enpassant, 1, 1 ) ) - 1 );
781             }
782             else {
783             substr( $enpassant, 1, 1 )
784             = chr(
785             ord( substr( $enpassant, 1, 1 ) ) + 1 );
786             }
787             $board{$enpassant} = undef;
788             if ($debug) {
789             print "\$enpassant='$enpassant' " if $enpassant;
790             print "\$from='$from' " if $from;
791             print "\$to='$to'" if $to;
792             print "\n";
793             }
794             }
795             }
796             ( $board{$to}, $board{$from} )
797             = ( $promotion ? $promotion : $board{$from}, undef );
798             if ($debug) {
799             print "\$piece='$piece' " if $piece;
800             print "\$from='$from' " if $from;
801             print "\$to='$to' " if $to;
802             print "\$promotion='$promotion' " if $promotion;
803             }
804             push( @epdlist, epd( $w, $Kc, $Qc, $kc, $qc, $ep, %board ) );
805             if ($debug) {
806             print "$epdlist[-1]\n";
807             }
808             }
809             elsif ( $piece eq "KR" ) {
810             my ( $k_from, $r_from ) = unpack( "A2A2", $from );
811             my ( $k_to, $r_to ) = unpack( "A2A2", $to );
812            
813             ( $board{$k_to}, $board{$k_from} )
814             = ( $board{$k_from}, undef );
815             ( $board{$r_to}, $board{$r_from} )
816             = ( $board{$r_from}, undef );
817             if ($w) {
818             $Kc = $Qc = 0;
819             }
820             else {
821             $kc = $qc = 0;
822             }
823             if ($debug) {
824             print $w ? "White" : "Black",
825             " castles from $k_from to $k_to\n";
826             }
827             push( @epdlist, epd( $w, $Kc, $Qc, $kc, $qc, $ep, %board ) );
828             if ($debug) {
829             print "$epdlist[-1]\n";
830             }
831             }
832             else {
833             my @piece_at;
834             my @fromlist;
835            
836             $piece = lc($piece) if not $w;
837             @piece_at = psquares( $piece, %board );
838             if ($debug) {
839             print "\@piece_at=", join( ",", @piece_at ), "\n"
840             if @piece_at;
841             }
842             if ($from) {
843             my @tmp;
844            
845             if ($debug) {
846             print "\$from='$from'\n" if $from;
847             }
848             if ( $from =~ /[a-h]/ ) {
849             for (@piece_at) {
850             push( @tmp, $_ )
851             if ( substr( $_, 0, 1 ) eq $from );
852             }
853             }
854             else {
855            
856             for (@piece_at) {
857             push( @tmp, $_ )
858             if ( substr( $_, 1, 1 ) eq $from );
859             }
860             }
861             @piece_at = @tmp;
862             }
863            
864             for my $square (@piece_at) {
865             for ( @{ $move_table{ uc($piece) }{$square} } ) {
866             push( @fromlist, $square ) if $_ eq $to;
867             }
868             }
869             print "scalar \@fromlist = ", scalar(@fromlist), "\n"
870             if $debug;
871             if ( scalar(@fromlist) != 1 ) {
872             if ($debug) {
873             print "\@fromlist=", join( ",", @fromlist ), "\n"
874             if @fromlist;
875             }
876             for (@fromlist) {
877             if ( _canmove( $piece, $to, $_, %board )
878             and _isLegal( $w, $_, $to, %board ) )
879             {
880             $from = $_;
881             last;
882             }
883             }
884             }
885             else {
886             $from = $fromlist[0];
887             }
888             if ( $piece =~ /[RrKk]/ ) {
889             if ( $piece eq 'R' ) {
890             $Kc = 0 if $from eq 'h1';
891             $Qc = 0 if $from eq 'a1';
892             }
893             elsif ( $piece eq 'r' ) {
894             $kc = 0 if $from eq 'h8';
895             $qc = 0 if $from eq 'a8';
896             }
897             elsif ( $piece eq 'K' ) {
898             $Kc = $Qc = 0;
899             }
900             else {
901             $kc = $qc = 0;
902             }
903             }
904             ( $board{$to}, $board{$from} ) = ( $board{$from}, undef );
905             if ($debug) {
906             print "\@piece_at=", join( ",", @piece_at ), "\n"
907             if @piece_at;
908             print "\$piece='$piece' " if $piece;
909             print "\$from='$from' " if $from;
910             print "\$to='$to' " if $to;
911             }
912             push( @epdlist, epd( $w, $Kc, $Qc, $kc, $qc, $ep, %board ) );
913             if ($debug) {
914             print "$epdlist[-1]\n";
915             }
916             if ( not $from ) {
917             _ShowPieces(%board);
918             _Print(%board);
919             print "From undefined\n";
920             exit;
921             }
922             }
923             $w ^= 1;
924             }
925             }
926             %board = ();
927             return @epdlist;
928             }
929            
930             sub _isLegal {
931             my ( $w, $from, $to, %board ) = @_;
932             my %board_copy = %board;
933             my $kings_square;
934             my @attack_list;
935            
936             ( $board_copy{$to}, $board_copy{$from} ) = ( $board_copy{$from}, undef );
937             my $findking = $w ? 'K' : 'k';
938             for ( keys %board_copy ) {
939             if ( $board_copy{$_} and ( $board_copy{$_} eq $findking ) ) {
940             $kings_square = $_;
941             last;
942             }
943             }
944             my $mask = $w ? 'qrnbp' : 'QRNBP';
945             for my $square ( keys %board_copy ) {
946             if ( $board_copy{$square} and $mask =~ /$board_copy{$square}/ ) {
947             for ( @{ $move_table{ uc( $board_copy{$square} ) }{$square} } ) {
948             push( @attack_list, $square ) if $_ eq $kings_square;
949             }
950             }
951             }
952             for (@attack_list) {
953             if ( _canmove( $board_copy{$_}, $kings_square, $_, %board_copy ) ) {
954             return 0;
955             }
956             }
957             return 1;
958             }
959            
960             sub _ShowPieces {
961             my %board = @_;
962            
963             for my $square ( keys %board ) {
964             my $piece = $board{$square};
965             next unless $piece;
966             print "'$square' == ", $piece, "\n";
967             }
968             return;
969             }
970            
971             sub _Print {
972             my (%board) = @_;
973             my $whitesquare = 1;
974             my @rows = (
975             [qw(a8 b8 c8 d8 e8 f8 g8 h8)], [qw(a7 b7 c7 d7 e7 f7 g7 h7)],
976             [qw(a6 b6 c6 d6 e6 f6 g6 h6)], [qw(a5 b5 c5 d5 e5 f5 g5 h5)],
977             [qw(a4 b4 c4 d4 e4 f4 g4 h4)], [qw(a3 b3 c3 d3 e3 f3 g3 h3)],
978             [qw(a2 b2 c2 d2 e2 f2 g2 h2)], [qw(a1 b1 c1 d1 e1 f1 g1 h1)]
979             );
980            
981             for ( 0 .. 7 ) {
982             print "\n", 8 - $_, " ";
983             for ( @{ $rows[$_] } ) {
984             if ( $board{$_} ) {
985             print $board{$_};
986             }
987             elsif ($whitesquare) {
988             print ' ';
989             }
990             else {
991             print '-';
992             }
993             $whitesquare ^= 1;
994             }
995             $whitesquare ^= 1;
996             }
997             print "\n abcdefgh\n\n";
998             return;
999             }
1000            
1001             sub _movetype {
1002             my ( $w, $move ) = @_;
1003             my @result = "'$move':Not yet handled";
1004             my $from;
1005             my $to;
1006            
1007             if ( $move =~ /^O-O(?:\+|\#)?$/ ) {
1008             if ($w) {
1009             $from = "e1h1";
1010             $to = "g1f1";
1011             }
1012             else {
1013             $from = "e8h8";
1014             $to = "g8f8";
1015             }
1016             @result = ( "KR", $to, $from );
1017             }
1018             elsif ( $move =~ /^O-O-O(?:\+|\#)?$/ ) {
1019            
1020             if ($w) {
1021             $from = "e1a1";
1022             $to = "c1d1";
1023             }
1024             else {
1025             $from = "e8a8";
1026             $to = "c8d8";
1027             }
1028             @result = ( "KR", $to, $from );
1029             }
1030             elsif ( $move =~ /^([2-7])([a-h][1-8])(?:\+|\#)?$/ ) {
1031             @result = ( "P", $2 );
1032             }
1033             elsif ( $move =~ /^([a-h][1-8])(?:\+|\#)?$/ ) {
1034             @result = ( "P", $1 );
1035             }
1036             elsif ( $move =~ /^([a-h])x?([a-h][1-8])(?:\+|\#)?$/ ) {
1037             @result = ( "P", $2, $1 );
1038             }
1039             elsif ( $move =~ /^([a-h][18])=?([RNBQ])(?:\+|\#)?$/ ) {
1040             @result = ( "P", $1, undef, $2 );
1041             }
1042             elsif ( $move =~ /^([a-h])x([a-h][18])=?([RNBQ])(?:\+|\#)?$/ ) {
1043             @result = ( "P", $2, $1, $3 );
1044             }
1045             elsif ( $move =~ /^([RNBQK])([a-h][1-8])(?:\+|\#)?$/ ) {
1046             @result = ( $1, $2 );
1047             }
1048             elsif ( $move =~ /^([RNBQK])x([a-h][1-8])(?:\+|\#)?$/ ) {
1049             @result = ( $1, $2 );
1050             }
1051             elsif ( $move =~ /^([RNBQK])([a-h]|[1-8])([a-h][1-8])(?:\+|\#)?$/ ) {
1052             @result = ( $1, $3, $2 );
1053             }
1054             elsif ( $move =~ /^([RNBQK])([a-h][1-8])([a-h][1-8])(?:\+|\#)?$/ ) {
1055             @result = ( $1, $3, $2 );
1056             }
1057             elsif ( $move =~ /^([RNBQK])([a-h]|[1-8])x([a-h][1-8])(?:\+|\#)?$/ ) {
1058             @result = ( $1, $3, $2 );
1059             }
1060             elsif ( $move =~ /^([RNBQK])([a-h][1-8])x([a-h][1-8])(?:\+|\#)?$/ ) {
1061             @result = ( $1, $3, $2 );
1062             }
1063             return @result;
1064             }
1065            
1066             sub psquares {
1067             my ( $piece, %board ) = @_;
1068            
1069             return grep { $_ and $board{$_} and ( $board{$_} eq $piece ) }
1070             sort keys %board;
1071             }
1072            
1073             sub epd {
1074             my ( $w, $Kc, $Qc, $kc, $qc, $ep, %board ) = @_;
1075             my @key = qw(
1076             a8 b8 c8 d8 e8 f8 g8 h8
1077             a7 b7 c7 d7 e7 f7 g7 h7
1078             a6 b6 c6 d6 e6 f6 g6 h6
1079             a5 b5 c5 d5 e5 f5 g5 h5
1080             a4 b4 c4 d4 e4 f4 g4 h4
1081             a3 b3 c3 d3 e3 f3 g3 h3
1082             a2 b2 c2 d2 e2 f2 g2 h2
1083             a1 b1 c1 d1 e1 f1 g1 h1
1084             );
1085             my $n;
1086             my $piece;
1087             my $epd;
1088            
1089             for ( 0 .. 63 ) {
1090             if ( $_ and ( $_ % 8 ) == 0 ) {
1091             if ($n) {
1092             $epd .= "$n";
1093             $n = 0;
1094             }
1095             $epd .= "/";
1096             }
1097             $piece = $board{ $key[$_] };
1098            
1099             if ($piece) {
1100             if ($n) {
1101             $epd .= "$n";
1102             $n = 0;
1103             }
1104             $epd .= $piece;
1105             }
1106             else {
1107             $n++;
1108             }
1109             }
1110            
1111             $epd .= "$n" if $n;
1112             $epd .= ( $w ? " b" : " w" );
1113            
1114             if ( $Kc or $Qc or $kc or $qc ) {
1115             $epd .= " ";
1116             $epd .= "K" if $Kc;
1117             $epd .= "Q" if $Qc;
1118             $epd .= "k" if $kc;
1119             $epd .= "q" if $qc;
1120             }
1121             else {
1122             $epd .= " -";
1123             }
1124             $epd .= " $ep";
1125             return $epd;
1126             }
1127            
1128             sub _canmove {
1129             my ( $piece, $to, $from, %board ) = @_;
1130             my $lto;
1131             my $rto;
1132             my $lfrom;
1133             my $rfrom;
1134             my $result = 1;
1135             my $offset = 1;
1136             my $roffset = 1;
1137             my $loffset = 1;
1138             my $c = 0;
1139            
1140             $to =~ /(.)(.)/;
1141             ( $lto, $rto ) = ( $1, $2 );
1142             $from =~ /(.)(.)/;
1143             ( $lfrom, $rfrom ) = ( $1, $2 );
1144            
1145             if ( $board{$from} and $board{to} ) {
1146             if ( defined( $board{$from} ) and defined( $board{$to} ) ) {
1147             if ( $board{$from}->color() == $board{$to}->color() ) {
1148             $result = 0;
1149             }
1150             }
1151             }
1152             elsif ( ( $rto eq $rfrom ) or ( $lto eq $lfrom ) ) {
1153            
1154             if ( ( $rto eq $rfrom and $lto lt $lfrom )
1155             or ( $lto eq $lfrom and $rto lt $rfrom ) )
1156             {
1157             $offset = -1;
1158             }
1159            
1160             if ( $lto eq $lfrom ) {
1161             $c = 1;
1162             }
1163             while ( $from ne $to ) {
1164             substr( $from, $c, 1 )
1165             = chr( ord( substr( $from, $c, 1 ) ) + $offset );
1166             if ( defined( $board{$from} ) ) {
1167             $result = 0 if ( $from ne $to );
1168             last;
1169             }
1170             }
1171             }
1172             elsif ( $piece =~ /[bq]/i ) {
1173            
1174             if ( $rto lt $rfrom ) {
1175             $roffset = -1;
1176             }
1177             if ( $lto lt $lfrom ) {
1178             $loffset = -1;
1179             }
1180             while ( $from ne $to ) {
1181             substr( $from, 0, 1 )
1182             = chr( ord( substr( $from, 0, 1 ) ) + $loffset );
1183             substr( $from, 1, 1 )
1184             = chr( ord( substr( $from, 1, 1 ) ) + $roffset );
1185             if ( defined( $board{$from} ) ) {
1186             $result = 0 if ( $from ne $to );
1187             last;
1188             }
1189             }
1190             }
1191             return $result;
1192             }
1193            
1194             sub epdTaxonomy {
1195             my (%options) = @_;
1196             my @moves = @{ $options{'moves'} };
1197             my @results;
1198             my ( $eco, $nic, $opening );
1199             my @epd = reverse( epdlist(@moves) );
1200            
1201             if ( $options{'all'} ) {
1202             $eco = epdcode( 'ECO', \@epd );
1203             $nic = epdcode( 'NIC', \@epd );
1204             $opening = epdcode( 'Opening', \@epd );
1205             }
1206             else {
1207             for ( lc( keys %options ) ) {
1208             if ( $_ eq 'eco' ) {
1209             $eco = epdcode( 'ECO', \@epd );
1210             }
1211             elsif ( $_ eq 'nic' ) {
1212             $nic = epdcode( 'NIC', \@epd );
1213             }
1214             elsif ( $_ eq 'Opening' ) {
1215             $opening = epdcode( 'Opening', \@epd );
1216             }
1217             }
1218             }
1219             if ( $options{'astags'} ) {
1220             push( @results, "[ECO \"$eco\"]" ) if $eco;
1221             push( @results, "[NIC \"$nic\"]" ) if $nic;
1222             push( @results, "[Opening \"$opening\"]" ) if $opening;
1223             }
1224             else {
1225             push( @results, $eco ) if $eco;
1226             push( @results, $nic ) if $nic;
1227             push( @results, $opening ) if $opening;
1228             }
1229             return @results;
1230             }
1231            
1232             1;
1233             __END__