File Coverage

lib/Games/Checkers/Board.pm
Criterion Covered Total %
statement 12 312 3.8
branch 0 184 0.0
condition 0 83 0.0
subroutine 4 46 8.7
pod 0 42 0.0
total 16 667 2.4


line stmt bran cond sub pod time code
1             # Games::Checkers, Copyright (C) 1996-2012 Mikhael Goikhman, migo@cpan.org
2             #
3             # This program is free software: you can redistribute it and/or modify
4             # it under the terms of the GNU General Public License as published by
5             # the Free Software Foundation, either version 3 of the License, or
6             # (at your option) any later version.
7             #
8             # This program is distributed in the hope that it will be useful,
9             # but WITHOUT ANY WARRANTY; without even the implied warranty of
10             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11             # GNU General Public License for more details.
12             #
13             # You should have received a copy of the GNU General Public License
14             # along with this program. If not, see .
15              
16 1     1   765 use strict;
  1         2  
  1         21  
17 1     1   4 use warnings;
  1         1  
  1         25  
18              
19             package Games::Checkers::Board;
20              
21 1     1   487 use Games::Checkers::Constants;
  1         2  
  1         18  
22 1     1   356 use Games::Checkers::Iterators;
  1         2  
  1         3857  
23              
24             sub init_default ($) {
25 0     0 0   my $self = shift;
26              
27 0           $self->init_empty;
28 0           my $locs = $self->locs;
29              
30 0           for (0 .. $self->default_rows * $self->size_x / 2 - 1) {
31 0           $self->set( $_, White, Pawn);
32 0           $self->set($locs - 1 - $_, Black, Pawn);
33             }
34             }
35              
36             sub init_empty ($) {
37 0     0 0   my $self = shift;
38              
39 0           vec($$self, $_, 1) = 0 for 0 .. 3 * $self->locs - 1;
40             }
41              
42             sub init ($$) {
43 0     0 0   my $self = shift;
44 0           my $board_or_locs = shift;
45              
46 0           my $param_class = ref($board_or_locs);
47              
48             # support Board param
49 0 0 0       if ($param_class && $param_class->isa('Games::Checkers::Board')) {
50 0           return $self->copy($board_or_locs);
51             }
52              
53 0 0 0       if (!$board_or_locs || $board_or_locs eq 'default') {
54 0           $self->init_default;
55 0           return $self;
56             }
57              
58 0           $self->init_empty;
59              
60             # support 'random', 'empty', FEN or "a1,a3/h6/h2/b8" or "/22,4,8/9"
61 0           delete $ENV{_WHITE_STARTS};
62 0 0         if (!$param_class) {
63 0           my @l;
64 0 0         if ($board_or_locs eq 'random') {
    0          
    0          
    0          
65 0   0       push @{$l[4 * rand() ** 2] ||= []}, $_ for grep { rand(2) > 1 } 1 .. $self->locs;
  0            
  0            
66             } elsif ($board_or_locs eq 'empty') {
67             } elsif ($board_or_locs =~ m!^([+-]?)((?:(?:\w?\d+,?)+|/)+)$!) {
68 0 0         $ENV{_WHITE_STARTS} = $1 eq '+' ? 1 : 0 if $1;
    0          
69 0           @l = map { [ split ',' ] } split '/', $2;
  0            
70             } elsif ($board_or_locs =~ m#^([WB]?):([WB])(K?\w?\d+(?:,K?\w?\d+)*):((?!\2)[WB])(K?\w?\d+(?:,K?\w?\d+)*)\.?$#) {
71 0 0         $ENV{_WHITE_STARTS} = $1 eq 'W' ? 1 : 0 if $1;
    0          
72 0 0         my $i = $2 eq 'W' ? 0 : 1;
73 0           my @locs1 = split(/,/, $3);
74 0           my @locs2 = split(/,/, $5);
75 0           $l[0 + $i] = [ grep !/^K/, @locs1 ];
76 0           $l[1 - $i] = [ grep !/^K/, @locs2 ];
77 0           $l[2 + $i] = [ map { /^K(.*)/ } grep /^K/, @locs1 ];
  0            
78 0           $l[3 - $i] = [ map { /^K(.*)/ } grep /^K/, @locs2 ];
  0            
79             } else {
80 0           die "Unsupported board position string ($board_or_locs)\n";
81             }
82 0           $board_or_locs = \@l;
83 0           $param_class = 'ARRAY';
84             }
85              
86 0 0         die "Unsupported $board_or_locs param in Board::init\n"
87             unless $param_class eq 'ARRAY';
88              
89             # support [ [], [ 22, 4, 8 ], [ 9 ] ] param
90 0           my @piece_color_locs = @$board_or_locs;
91 0           for my $piece (Pawn, King) {
92 0           for my $color (White, Black) {
93 0   0       my $locs = shift @piece_color_locs || [];
94 0           for my $loc (@$locs) {
95 0 0         $loc = ref($loc) eq 'ARRAY'
    0          
96             ? $self->arr_to_loc($loc->[0], $loc->[1])
97             : $loc =~ /^\d/
98             ? $self->num_to_loc($loc)
99             : $self->str_to_loc($loc);
100 0           $self->set($loc, $color, $piece);
101 0 0 0       $self->cnv($loc) if $piece == Pawn && $self->is_crowning->[$color][$loc];
102             }
103             }
104             }
105              
106 0           return $self;
107             }
108              
109             sub new ($;$$) {
110 0     0 0   my $class = shift;
111 0           my $board_or_locs = shift;
112 0   0       my $size = shift || $::RULES{BOARD_SIZE};
113              
114 0 0         if ($class eq __PACKAGE__) {
115 0 0 0       $size ||= $board_or_locs->size
      0        
116             if ref($board_or_locs) && $board_or_locs->isa('Games::Checkers::Board');
117 0   0       $size ||= 8;
118 0 0         $size = "${size}x$size" if $size =~ /^\d+$/;
119 0           $class = __PACKAGE__ . "::_$size";
120 0 0         eval "use $class"; die $@ if $@;
  0            
121             }
122              
123 0           my $data = '';
124 0           my $self = \$data;
125 0           bless $self, $class;
126              
127 0           return $self->init($board_or_locs);
128             }
129              
130             sub notation ($) {
131 0   0 0 0   my $bn = $::RULES{BOARD_NOTATION} || 'A1';
132             return
133 0 0         $bn eq 'BL' ? 1 :
    0          
    0          
    0          
134             $bn eq 'BR' ? 2 :
135             $bn eq 'TL' ? 3 :
136             $bn eq 'TR' ? 4 :
137             0;
138             }
139              
140             sub size ($) {
141 0     0 0   return $_[0]->size_x . "x" . $_[0]->size_y;
142             }
143              
144             sub size_x_1 ($) {
145 0     0 0   return $_[0]->size_x - 1;
146             }
147              
148             sub size_x_2 ($) {
149 0     0 0   return $_[0]->size_x / 2;
150             }
151              
152             sub size_y_1 ($) {
153 0     0 0   return $_[0]->size_y - 1;
154             }
155              
156             sub reflect_x ($$) {
157 0     0 0   my $self = shift;
158 0           my $x = shift;
159              
160 0 0         return $::RULES{BOTTOM_LEFT_CELL} ? $x : $self->size_x + 1 - $x;
161             }
162              
163             sub loc_to_arr ($$) {
164 0     0 0   my $self = shift;
165 0           my $loc = shift;
166              
167 0           my $size_x_2 = $self->size_x_2;
168              
169             return (
170 0           $self->reflect_x(int($loc % $size_x_2) * 2 + int(($loc / $size_x_2) % 2) + 1),
171             int($loc / $size_x_2) + 1
172             );
173             }
174              
175             sub arr_to_loc ($$$) {
176 0     0 0   my $self = shift;
177 0           my $x = $self->reflect_x(shift);
178 0           my $y = shift;
179              
180 0 0         return NL if ($x + $y) % 2;
181 0           return ($y - 1) * $self->size_x_2 + int(($x - 1) / 2);
182             }
183              
184             sub ind_to_chr ($$) {
185 0     0 0   my $self = shift;
186 0           my $ind = shift;
187              
188 0           return chr(ord('a') + $ind - 1 + ($ind >= 10));
189             }
190              
191             sub chr_to_ind ($$) {
192 0     0 0   my $self = shift;
193 0           my $chr = shift;
194              
195 0           return ord(lc($chr)) - ord('a') + 1 - ($chr ge 'j');
196             }
197              
198             sub loc_to_str ($$) {
199 0     0 0   my $self = shift;
200 0           my $loc = shift;
201              
202 0           my @c = $self->loc_to_arr($loc);
203              
204 0           return $self->ind_to_chr($c[0]) . $c[1];
205             }
206              
207             sub str_to_loc ($$) {
208 0     0 0   my $self = shift;
209 0           my $str = shift;
210              
211 0 0         $str =~ /^(\w)(\d)$/ || die "Invalid board coordinate string ($str)\n";
212              
213 0           return $self->arr_to_loc($self->chr_to_ind($1), $2);
214             }
215              
216             sub loc_to_num ($$) {
217 0     0 0   my $self = shift;
218 0           my $loc = shift;
219              
220 0           my $size_x_2 = $self->size_x_2;
221 0           my $notation = $self->notation;
222              
223 0 0 0       my $num = $notation == 2 || $notation == 3
224             ? (int($loc / $size_x_2) + 1) * $size_x_2 - $loc % $size_x_2
225             : $loc + 1;
226              
227 0 0         return $notation <= 2 ? $num : $self->locs - 1 - $num;
228             }
229              
230             sub num_to_loc ($$) {
231 0     0 0   my $self = shift;
232 0           my $num = shift;
233              
234 0           my $size_x_2 = $self->size_x_2;
235 0           my $notation = $self->notation;
236              
237 0 0 0       my $loc = $notation == 2 || $notation == 3
238             ? (int(($num - 1) / $size_x_2) + 1) * $size_x_2 - 1 - ($num - 1) % $size_x_2
239             : $num - 1;
240              
241 0 0         return $notation <= 2 ? $loc : $self->locs - 1 - $loc;
242             }
243              
244             sub occup ($$) {
245 0     0 0   my $self = shift;
246 0           my $loc = shift;
247 0           return vec($$self, $loc, 1);
248             }
249              
250             sub color ($$) {
251 0     0 0   my $self = shift;
252 0           my $loc = shift;
253 0           return vec($$self, $loc + $self->locs, 1);
254             }
255              
256             sub piece ($$) {
257 0     0 0   my $self = shift;
258 0           my $loc = shift;
259 0           return vec($$self, $loc + $self->locs * 2, 1);
260             }
261              
262             sub white ($$) {
263 0     0 0   my $self = shift;
264 0           my $loc = shift;
265 0   0       return $self->occup($loc) && $self->color($loc) == White;
266             }
267              
268             sub black ($$) {
269 0     0 0   my $self = shift;
270 0           my $loc = shift;
271 0   0       return $self->occup($loc) && $self->color($loc) == Black;
272             }
273              
274             sub clone ($) {
275 0     0 0   my $self = shift;
276              
277 0           return ref($self)->new($self);
278             }
279              
280             sub copy ($$) {
281 0     0 0   my $self = shift;
282 0           my $board = shift;
283              
284 0 0         die "Can't copy $board to $self\n" unless ref($self) eq ref($board);
285 0           $$self = $$board;
286              
287 0           return $self;
288             }
289              
290             sub equals ($$) {
291 0     0 0   my $self = shift;
292 0           my $board = shift;
293              
294 0   0       return ref($self) eq ref($board) && $$self eq $$board;
295             }
296              
297             sub clr_all ($) {
298 0     0 0   my $self = shift;
299 0           vec($$self, $_, 1) = 0 for 0 .. $self->locs - 1;
300             }
301              
302             sub clr ($$) {
303 0     0 0   my $self = shift;
304 0           my $loc = shift;
305 0           vec($$self, $loc, 1) = 0;
306             }
307              
308             sub cnv ($$) {
309 0     0 0   my $self = shift;
310 0           my $loc = shift;
311 0           vec($$self, $loc + 2 * $self->locs, 1) ^= 1;
312             }
313              
314             sub set ($$$$) {
315 0     0 0   my $self = shift;
316 0           my ($loc, $color, $piece) = @_;
317              
318 0           my $locs = $self->locs;
319              
320 0           vec($$self, $loc + 0 * $locs, 1) = 1;
321 0           vec($$self, $loc + 1 * $locs, 1) = $color;
322 0           vec($$self, $loc + 2 * $locs, 1) = $piece;
323             }
324              
325             sub chk ($$$$) {
326 0     0 0   my $self = shift;
327 0           my ($loc, $color, $piece) = @_;
328             return
329 0 0 0       $self->occup($loc) &&
330             $self->color($loc) == $color &&
331             $self->piece($loc) == $piece ? 1 : 0;
332             }
333              
334             sub get_score ($;$) {
335 0     0 0   my $self = shift;
336 0           my $color = shift;
337              
338 0           my $size_y_1 = $self->size_y_1;
339 0           my $size_x_2 = $self->size_x_2;
340              
341             # Count white & black figures
342             my (
343 0           $white_pawns, $white_kings, $white_bonus,
344             $black_pawns, $black_kings, $black_bonus
345             ) = (0) x 6;
346              
347 0           my $whites_iterator = new Games::Checkers::FigureIterator($self, White);
348 0           while ($whites_iterator->left) {
349 0           my $loc = $whites_iterator->next;
350 0           my $is_pawn = $self->piece($loc) == Pawn;
351 0 0         $is_pawn ? $white_pawns++ : $white_kings++;
352 0 0         $white_bonus += int($loc / $size_x_2) if $is_pawn;
353             }
354              
355 0           my $blacks_iterator = new Games::Checkers::FigureIterator($self, Black);
356 0           while ($blacks_iterator->left) {
357 0           my $loc = $blacks_iterator->next;
358 0           my $is_pawn = $self->piece($loc) == Pawn;
359 0 0         $is_pawn ? $black_pawns++ : $black_kings++;
360 0 0         $black_bonus += $size_y_1 - int($loc / $size_x_2) if $is_pawn;
361             }
362              
363 0 0         return 0 if $white_pawns + $white_kings + $black_pawns + $black_kings == 0;
364              
365 0 0         my $color_factor = !defined $color ? 0 : $color == White ? 1 : -1;
    0          
366 0 0         my $king_cost = $::RULES{KINGS_LONG_RANGED} ? $size_y_1 * 40 : 167;
367              
368 0           my $score =
369             + ($white_pawns - $black_pawns) * 100
370             + ($white_kings - $black_kings) * $king_cost
371             + ($white_bonus - $black_bonus) * 10
372             + $color_factor * 5;
373              
374 0 0         $score = MIN_SCORE / 10 - $color_factor if $white_pawns + $white_kings == 0;
375 0 0         $score = MAX_SCORE / 10 - $color_factor if $black_pawns + $black_kings == 0;
376              
377 0           return $score * 10 + $color_factor * int rand 10;
378             }
379              
380             sub step_destinations ($$;$$) {
381 0     0 0   my $self = shift;
382 0           my $loc = shift;
383 0           my $piece = shift;
384 0           my $color = shift;
385              
386             (defined $piece ? $piece : $self->piece($loc)) == Pawn
387             ? $self->pawn_step->[defined $color ? $color : $self->color($loc)][$loc]
388             : $::RULES{KINGS_LONG_RANGED}
389 0 0         ? $self->king_step->[$loc]
    0          
    0          
    0          
390             : $self->king_step_short->[$loc];
391             }
392              
393             sub beat_destinations ($$;$$) {
394 0     0 0   my $self = shift;
395 0           my $loc = shift;
396 0           my $piece = shift;
397 0           my $color = shift;
398              
399             (defined $piece ? $piece : $self->piece($loc)) == Pawn
400             ? $::RULES{CAPTURING_IN_8_DIRECTIONS}
401             ? $self->pawn_beat_8dirs->[$loc]
402             : $::RULES{PAWNS_CAPTURING_BACKWARDS}
403             ? $self->pawn_beat->[$loc]
404             : $self->pawn_beat_forward->[defined $color ? $color : $self->color($loc)][$loc]
405             : $::RULES{CAPTURING_IN_8_DIRECTIONS}
406             ? $self->king_beat_8dirs->[$loc]
407             : $::RULES{KINGS_LONG_RANGED}
408 0 0         ? $self->king_beat->[$loc]
    0          
    0          
    0          
    0          
    0          
    0          
409             : $self->king_beat_short->[$loc];
410             }
411              
412             sub apply_move ($) {
413 0     0 0   my $self = shift;
414 0           my $move = shift;
415              
416 0           my $src = $move->source;
417 0           my $dst = $move->destin(0);
418 0           my $beat = $move->is_beat;
419 0           my $color = $self->color($src);
420 0           my $piece = $self->piece($src);
421 0           for (my $n = 0; $dst != NL; $src = $dst, $dst = $move->destin(++$n)) {
422 0           $self->clr($src);
423 0           $self->set($dst, $color, $piece);
424 0 0         $self->clr($self->enclosed_figure($src, $dst)) if $beat;
425             # convert to king if needed
426 0 0 0       if ($piece == Pawn && $self->is_crowning->[$color][$dst]) {
427 0           $self->cnv($dst);
428 0           $piece ^= 1;
429             }
430             }
431             }
432              
433             sub can_piece_step ($$;$) {
434 0     0 0   my $self = shift;
435 0           my $src = shift;
436 0           my $dst0 = shift;
437              
438 0 0         if (!$self->occup($src)) {
439 0           warn("Internal error in can_piece_step, src=$src is not occupied");
440 0           &DIE_WITH_STACK();
441 0           return No;
442             }
443 0           for my $dst (@{$self->step_destinations($src)}) {
  0            
444 0 0 0       next if defined $dst0 && $dst != $dst0;
445 0 0         next if $self->occup($dst);
446 0 0         next if $self->enclosed_figure($src, $dst) != NL;
447 0           return Yes;
448             }
449 0           return No;
450             }
451              
452             sub can_piece_beat ($$;$) {
453 0     0 0   my $self = shift;
454 0           my $src = shift;
455 0           my $dst0 = shift;
456              
457 0 0         if (!$self->occup($src)) {
458 0           warn("Internal error in can_piece_beat, src=$src is not occupied");
459 0           &DIE_WITH_STACK();
460 0           return No;
461             }
462 0           my $color = $self->color($src);
463 0           for my $dst (@{$self->beat_destinations($src)}) {
  0            
464 0 0 0       next if defined $dst0 && $dst != $dst0;
465 0 0         next if $self->occup($dst);
466 0           my $enemy = $self->enclosed_figure($src, $dst);
467 0 0 0       next if $enemy == NL || $enemy == ML;
468 0 0         next if $self->color($enemy) == $color;
469             next
470             if $::RULES{PAWNS_CANT_CAPTURE_KINGS}
471 0 0 0       && $self->piece($src) == Pawn
      0        
472             && $self->piece($enemy) == King;
473             next
474             if $::RULES{CAPTURING_LEAVES_NO_GAP}
475 0 0 0       && $self->enclosed_locs->[$enemy]{$dst};
476 0           return Yes;
477             }
478 0           return No;
479             }
480              
481             sub can_color_step ($$) {
482 0     0 0   my $self = shift;
483 0           my $color = shift;
484 0           my $iterator = Games::Checkers::FigureIterator->new($self, $color);
485 0           while ($iterator->left) {
486 0 0         return Yes if $self->can_piece_step($iterator->next);
487             }
488 0           return No;
489             }
490              
491             sub can_color_beat ($$) {
492 0     0 0   my $self = shift;
493 0           my $color = shift;
494 0           my $iterator = Games::Checkers::FigureIterator->new($self, $color);
495 0           while ($iterator->left) {
496 0 0         return Yes if $self->can_piece_beat($iterator->next);
497             }
498 0           return No;
499             }
500              
501             sub can_color_move ($$) {
502 0     0 0   my $self = shift;
503 0           my $color = shift;
504 0   0       return $self->can_color_beat($color) || $self->can_color_step($color);
505             }
506              
507             sub enclosed_figure ($$$) {
508 0     0 0   my $self = shift;
509 0           my $src = shift;
510 0           my $dst = shift;
511              
512             my $locs = $::RULES{CAPTURING_IN_8_DIRECTIONS}
513             ? $self->enclosed_8dirs_locs->[$src]{$dst}
514 0 0         : $self->enclosed_locs->[$src]{$dst}
    0          
515             or return NL;
516              
517 0           my $figure_loc = NL;
518 0           for my $loc (@$locs) {
519 0 0         if ($self->occup($loc)) {
520 0 0         return ML if $figure_loc != NL;
521 0           $figure_loc = $loc;
522             }
523             }
524              
525 0           return $figure_loc;
526             }
527              
528             #
529             # +-------------------------------+
530             # 8 |###| @ |###| @ |###| @ |###| @ |
531             # |---+---+---+---+---+---+---+---|
532             # 7 | @ |###| @ |###| @ |###| @ |###|
533             # |---+---+---+---+---+---+---+---|
534             # 6 |###| @ |###| @ |###| @ |###| @ |
535             # |---+---+---+---+---+---+---+---|
536             # 5 | |###| |###| |###| |###|
537             # |---+---+---+---+---+---+---+---|
538             # 4 |###| |###| |###| |###| |
539             # |---+---+---+---+---+---+---+---|
540             # 3 | O |###| O |###| O |###| O |###|
541             # |---+---+---+---+---+---+---+---|
542             # 2 |###| O |###| O |###| O |###| O |
543             # |---+---+---+---+---+---+---+---|
544             # 1 | O |###| O |###| O |###| O |###|
545             # +-------------------------------+
546             # a b c d e f g h
547             #
548              
549             sub dump ($;$$$) {
550 0     0 0   my $self = shift;
551 0   0       my $sprefix = shift || "";
552 0   0       my $cprefix = shift || "";
553 0   0       my $compact = shift || $ENV{COMPACT_BOARD};
554              
555 0           my $char_sets = [
556             {
557             tlc => "+",
558             trc => "+",
559             blc => "+",
560             brc => "+",
561             vcl => "|",
562             vll => "|",
563             vrl => "|",
564             hcl => "-",
565             htl => "-",
566             hbl => "-",
567             ccl => "+",
568             bcs => "",
569             bce => "",
570             bcf => " ",
571             wcs => "",
572             wce => "",
573             wcf => "#",
574             },
575             {
576             tlc => "\e)0\016l\017",
577             trc => "\016k\017",
578             blc => "\016m\017",
579             brc => "\016j\017",
580             vcl => "\016x\017",
581             vll => "\016t\017",
582             vrl => "\016u\017",
583             hcl => "\016q\017",
584             htl => "\016w\017",
585             hbl => "\016v\017",
586             ccl => "\016n\017",
587             bcs => "\e[0;7m",
588             bce => "\e[0m",
589             bcs => "",
590             bce => "",
591             bcf => " ",
592             wcs => "",
593             wce => "",
594             wcs => "\e[0;7m",
595             wce => "\e[0m",
596             wcf => " ",
597             },
598             ];
599 0 0         my %ch = %{$char_sets->[$ENV{DUMB_CHARS} ? 0 : 1]};
  0            
600              
601 0           my $size_x = $self->size_x;
602 0           my $size_y = $self->size_y;
603 0           my $size_x_1 = $self->size_x_1;
604 0           my $size_x_2 = $self->size_x_2;
605              
606 0           my $str = "";
607 0           $str .= "\n";
608 0 0         $str .= " " . $ch{tlc} . ("$ch{hcl}$ch{hcl}$ch{hcl}$ch{htl}" x $size_x_1) . "$ch{hcl}$ch{hcl}$ch{hcl}$ch{trc}\n"
609             unless $compact;
610 0           for (my $y = $size_y; $y >= 1; $y--) {
611 0           $str .= sprintf("%2d", $y) . " $ch{vcl}";
612 0           for (my $x = 1; $x <= $size_x; $x++) {
613 0           my $loc = $self->arr_to_loc($x, $y);
614 0 0         if ($loc != NL) {
615 0           my $ch0 = $ch{bcf};
616 0           my $is_king = $self->piece($loc) == King;
617 0 0         $ch0 = $self->white($loc) ? $is_king ? "8" : "O" : $is_king ? "&" : "@"
    0          
    0          
    0          
618             if $self->occup($loc);
619 0 0         $ch0 = $self->white($loc) ? "\e[1m$ch0\e[0m" : "\e[4m$ch0\e[0m"
    0          
620             if $self->occup($loc);
621 0           $str .= "$ch{bcs}$ch{bcf}$ch0$ch{bcs}$ch{bcf}$ch{bce}";
622             } else {
623 0           $str .= "$ch{wcs}$ch{wcf}$ch{wcf}$ch{wcf}$ch{wce}";
624             }
625 0           $str .= $ch{vcl};
626             }
627 0           $str .= "\n";
628 0 0 0       $str .= " " . $ch{vll} . ("$ch{hcl}$ch{hcl}$ch{hcl}$ch{ccl}" x $size_x_1) . "$ch{hcl}$ch{hcl}$ch{hcl}$ch{vrl}\n"
629             unless $compact || $y == 1;
630             }
631 0 0         $str .= " " . $ch{blc} . ("$ch{hcl}$ch{hcl}$ch{hcl}$ch{hbl}" x $size_x_1) . "$ch{hcl}$ch{hcl}$ch{hcl}$ch{brc}\n"
632             unless $compact;
633 0           $str .= " " . join('', map { $self->ind_to_chr($_) . " " } 1 .. $size_x) . "\n";
  0            
634 0 0         $str .= "\n" unless $compact;
635              
636 0 0         $str =~ s/(?:^.|)(?:\e\)0)?((?:\e.*?m)*.(?:\e.*?m)*)(\016.\017|.)((?:\e.*?m)*)(\016.\017|.)((?:\e.*?m)*)(\016.\017|.)((?:\e.*?m)*)/$1$3$5$7/mg
637             if $compact;
638              
639             # prepare prefix for each board line, if any
640 0           my $lines = () = $str =~ /\n/g;
641 0 0 0       $sprefix = " " x ($size_x_2 / ($compact && 2.5 || 1) * $sprefix) if $sprefix =~ /^\d+$/;
642 0 0         my @cprefix = $cprefix ? $cprefix =~ /(\w\d[:-]\w\d|:\w\d|-\d{1,5}|\d{1,6})/g : ();
643 0           my $l = 0;
644             my @prefix = map {
645 0 0         my $p = $sprefix ? $sprefix =~ s!(.*)\n!! ? $1 : $sprefix : '';
  0 0          
646 0 0 0       $p .= sprintf " %6s ", @cprefix && $l++ >= ($lines - @cprefix) / 2 ? shift @cprefix : ''
    0          
647             if $cprefix;
648 0           $p
649             } 1 .. $lines;
650              
651 0           $str =~ s/^/shift @prefix/gme;
  0            
652              
653 0           return $str;
654             }
655              
656             1;