File Coverage

blib/lib/Games/ABC_Path/Solver/Board.pm
Criterion Covered Total %
statement 289 351 82.3
branch 62 96 64.5
condition 10 18 55.5
subroutine 58 60 96.6
pod 7 7 100.0
total 426 532 80.0


line stmt bran cond sub pod time code
1             package Games::ABC_Path::Solver::Board;
2              
3 2     2   66610 use warnings;
  2         14  
  2         66  
4 2     2   10 use strict;
  2         4  
  2         38  
5              
6 2     2   33 use 5.008;
  2         19  
7              
8              
9             our $VERSION = '0.4.1';
10              
11              
12 2     2   12 use Carp;
  2         5  
  2         136  
13              
14 2     2   323 use parent 'Games::ABC_Path::Solver::Base';
  2         214  
  2         9  
15              
16 2     2   104 use Games::ABC_Path::Solver::Constants;
  2         3  
  2         266  
17 2     2   820 use Games::ABC_Path::Solver::Move::LastRemainingCellForLetter;
  2         6  
  2         54  
18 2     2   798 use Games::ABC_Path::Solver::Move::LastRemainingLetterForCell;
  2         5  
  2         55  
19 2     2   806 use Games::ABC_Path::Solver::Move::LettersNotInVicinity;
  2         6  
  2         53  
20 2     2   790 use Games::ABC_Path::Solver::Move::ResultsInAnError;
  2         6  
  2         54  
21 2     2   765 use Games::ABC_Path::Solver::Move::ResultsInASuccess;
  2         14  
  2         57  
22 2     2   791 use Games::ABC_Path::Solver::Move::TryingLetterForCell;
  2         6  
  2         52  
23              
24 2     2   779 use Games::ABC_Path::Solver::Coord;
  2         6  
  2         55  
25              
26 2     2   11 use Scalar::Util qw(blessed);
  2         4  
  2         7863  
27              
28             my $ABCP_VERDICT_NO = 0;
29             my $ABCP_VERDICT_MAYBE = 1;
30             my $ABCP_VERDICT_YES = 2;
31              
32             my %letters_map = (map { $letters[$_] => $_ } (0 .. $ABCP_MAX_LETTER));
33              
34             sub _get_letter_numeric
35             {
36 25     25   40 my ($self, $letter_ascii) = @_;
37              
38 25         45 my $index = $letters_map{$letter_ascii};
39              
40 25 50       37 if (!defined ($index))
41             {
42 0         0 confess "Unknown letter '$letter_ascii'";
43             }
44              
45 25         34 return $index;
46             }
47              
48             sub _iter_changed {
49 178     178   208 my $self = shift;
50              
51 178 100       271 if (@_) {
52 90         150 $self->{_iter_changed} = shift;
53             }
54              
55 178         328 return $self->{_iter_changed};
56             }
57              
58             sub _moves {
59 86     86   119 my $self = shift;
60              
61 86 100       154 if (@_) {
62 2         4 $self->{_moves} = shift;
63             }
64              
65 86         163 return $self->{_moves};
66             }
67              
68             sub _error {
69 10385     10385   12679 my $self = shift;
70              
71 10385 50       15266 if (@_) {
72 0         0 $self->{_error} = shift;
73             }
74              
75 10385         17308 return $self->{_error};
76             }
77              
78              
79             sub _inc_changed {
80 84     84   115 my ($self) = @_;
81              
82 84         141 $self->_iter_changed($self->_iter_changed+1);
83              
84 84         108 return;
85             }
86              
87             sub _flush_changed {
88 4     4   10 my ($self) = @_;
89              
90 4         11 my $ret = $self->_iter_changed;
91              
92 4         79 $self->_iter_changed(0);
93              
94 4         15 return $ret;
95             }
96              
97             sub _add_move {
98 84     84   120 my ($self, $move) = @_;
99              
100 84         103 push @{$self->_moves()}, $move;
  84         159  
101              
102 84         201 $self->_inc_changed;
103              
104 84         189 return;
105             }
106              
107              
108             sub get_successful_layouts {
109 2     2 1 7 my ($self) = @_;
110              
111 2         3 return [@{$self->_successful_layouts}];
  2         4  
112             }
113              
114             sub _successful_layouts {
115 5     5   7 my $self = shift;
116              
117 5 100       8 if (@_) {
118 3         5 $self->{_successful_layouts} = shift;
119             }
120              
121 5         13 return $self->{_successful_layouts};
122             }
123              
124              
125             sub _layout {
126 11424     11424   13053 my $self = shift;
127              
128 11424 100       16827 if (@_) {
129 2         6 $self->{_layout} = shift;
130             }
131              
132 11424         20104 return $self->{_layout};
133             }
134              
135             # The letter indexes.
136             sub _l_indexes
137             {
138 179     179   372 return (0 .. $ABCP_MAX_LETTER);
139             }
140              
141             sub _init
142             {
143 2     2   4 my ($self, $args) = @_;
144              
145 2         5 my $layout_string = $args->{layout};
146              
147 2 100       7 if (!defined($layout_string))
148             {
149 1         2 $layout_string = '';
150             }
151              
152 2         6 $self->_layout(\$layout_string);
153 2         7 $self->_successful_layouts([]);
154 2         8 $self->_moves([]);
155 2         7 $self->_iter_changed(0);
156              
157 2         4 return;
158             }
159              
160             sub _calc_offset
161             {
162 11421     11421   15899 my ($self, $letter, $xy) = @_;
163              
164 11421 50 33     28302 if (($letter < 0) or ($letter >= 25))
165             {
166 0         0 confess "Letter $letter out of range.";
167             }
168              
169 11421         20668 return $letter * $BOARD_SIZE + $self->_xy_to_int([$xy->y, $xy->x]);
170             }
171              
172             sub _get_verdict
173             {
174 9536     9536   13805 my ($self, $letter, $xy) = @_;
175              
176             return vec(
177 9536         10739 ${$self->_layout},
  9536         13450  
178             $self->_calc_offset($letter, $xy,),
179             2
180             );
181             }
182              
183             sub _set_verdict
184             {
185 1885     1885   2535 my ($self, $letter, $xy, $verdict) = @_;
186              
187             # Temporary - remove later.
188 1885 50       2640 if (@_ != 4)
189             {
190 0         0 confess "_set_verdict has wrong number of args.";
191             }
192              
193 1885 50 100     2913 if (not
      66        
194             (($verdict == $ABCP_VERDICT_NO)
195             || ($verdict == $ABCP_VERDICT_MAYBE)
196             || ($verdict == $ABCP_VERDICT_YES))
197             )
198             {
199 0         0 confess "Invalid verdict $verdict .";
200             }
201              
202 1885         1893 vec(${$self->_layout}, $self->_calc_offset($letter, $xy), 2)
  1885         2213  
203             = $verdict;
204              
205 1885         4020 return;
206             }
207              
208             sub _xy_loop
209             {
210 346     346   586 my ($self, $sub_ref) = (@_);
211              
212 346         691 foreach my $y ($self->_y_indexes)
213             {
214 1730 50       2975 if ($self->_error())
215             {
216 0         0 return;
217             }
218 1730         3316 foreach my $x ($self->_x_indexes)
219             {
220 8650 50       13571 if ($self->_error())
221             {
222 0         0 return;
223             }
224 8650         21444 $sub_ref->(Games::ABC_Path::Solver::Coord->new({x => $x, y => $y}));
225             }
226             }
227 346         1462 return;
228             }
229              
230              
231             sub _set_verdicts_for_letter_sets
232             {
233 12     12   19 my ($self, $letter_list, $maybe_list) = @_;
234              
235 12         19 my %cell_is_maybe = (map { $_->_to_s() => 1 } @$maybe_list);
  60         99  
236              
237 12         27 foreach my $letter_ascii (@$letter_list)
238             {
239 24         36 my $letter = $self->_get_letter_numeric($letter_ascii);
240              
241             $self->_xy_loop(
242             sub {
243 600     600   717 my ($xy) = @_;
244              
245             $self->_set_verdict($letter, $xy,
246 600 100       911 ((exists $cell_is_maybe{$xy->_to_s()})
247             ? $ABCP_VERDICT_MAYBE
248             : $ABCP_VERDICT_NO
249             )
250             );
251             }
252 24         80 );
253             }
254              
255 12         35 return;
256             }
257              
258             sub _set_conclusive_verdict_for_letter
259             {
260 25     25   41 my ($self, $letter, $l_xy) = @_;
261              
262             $self->_xy_loop(sub {
263 625     625   766 my ($xy) = @_;
264              
265 625 100       988 $self->_set_verdict($letter, $xy,
266             ($l_xy->_equal($xy)
267             ? $ABCP_VERDICT_YES
268             : $ABCP_VERDICT_NO
269             )
270             );
271             }
272 25         123 );
273              
274             OTHER_LETTER:
275 25         93 foreach my $other_letter ($self->_l_indexes)
276             {
277 625 100       906 if ($other_letter == $letter)
278             {
279 25         45 next OTHER_LETTER;
280             }
281 600         802 $self->_set_verdict($other_letter, $l_xy, $ABCP_VERDICT_NO);
282             }
283              
284 25         43 return;
285             }
286              
287             sub _get_possible_letter_indexes
288             {
289 150     150   201 my ($self, $xy) = @_;
290              
291             return
292             [
293 150         222 grep { $self->_get_verdict($_, $xy) != $ABCP_VERDICT_NO }
  3750         5708  
294             $self->_l_indexes()
295             ];
296             }
297              
298              
299             sub get_possible_letters_for_cell
300             {
301 25     25 1 32 my ($self, $x, $y) = @_;
302              
303 25         26 return [@letters[@{$self->_get_possible_letter_indexes(Games::ABC_Path::Solver::Coord->new({x => $x, y => $y}))}]];
  25         55  
304             }
305              
306             sub _get_possible_letters_string
307             {
308 25     25   29 my ($self, $xy) = @_;
309              
310 25         30 return join(',', @{$self->get_possible_letters_for_cell($xy->x, $xy->y)});
  25         34  
311             }
312              
313              
314             sub _infer_letters
315             {
316 4     4   11 my ($self) = @_;
317              
318 4         19 foreach my $letter ($self->_l_indexes)
319             {
320 100         172 my @true_cells;
321              
322             $self->_xy_loop(sub {
323 2500     2500   3720 my ($xy) = @_;
324              
325 2500         3957 my $ver = $self->_get_verdict($letter, $xy);
326 2500 100 100     9284 if ( ($ver == $ABCP_VERDICT_YES)
327             || ($ver == $ABCP_VERDICT_MAYBE))
328             {
329 172         400 push @true_cells, $xy;
330             }
331 100         528 });
332              
333 100 50       547 if (! @true_cells)
    100          
334             {
335 0         0 $self->_error(['letter', $letter]);
336 0         0 return;
337             }
338             elsif (@true_cells == 1)
339             {
340 63         118 my $xy = $true_cells[0];
341 63 100       130 if ($self->_get_verdict($letter, $xy) ==
342             $ABCP_VERDICT_MAYBE)
343             {
344 8         30 $self->_set_conclusive_verdict_for_letter($letter, $xy);
345 8         66 $self->_add_move(
346             Games::ABC_Path::Solver::Move::LastRemainingCellForLetter->new(
347             {
348             vars =>
349             {
350             letter => $letter,
351             coords => $xy,
352             },
353             }
354             )
355             );
356             }
357             }
358              
359 100         269 my @neighbourhood = (map { [(0) x $LEN] } ($self->_y_indexes));
  500         1154  
360              
361 100         233 foreach my $true (@true_cells)
362             {
363 172         332 foreach my $coords
364             (
365             grep {
366 1548 100       2830 $self->_x_in_range($_->[0]) and $self->_y_in_range($_->[1])
367             }
368 1548         2688 map { [$true->x + $_->[0], $true->y + $_->[1]] }
369 516         648 map { my $d = $_; map { [$_, $d] } (-1 .. 1) }
  516         706  
  1548         2791  
370             (-1 .. 1)
371             )
372             {
373 1216         2032 $neighbourhood[$coords->[1]][$coords->[0]] = 1;
374             }
375             }
376              
377 100 100       354 foreach my $neighbour_letter (
    100          
378             (($letter > 0) ? ($letter-1) : ()),
379             (($letter < $ABCP_MAX_LETTER) ? ($letter+1) : ()),
380             )
381             {
382             $self->_xy_loop(sub {
383 4800     4800   6935 my ($xy) = @_;
384              
385 4800 100       8433 if ($neighbourhood[$xy->y][$xy->x])
386             {
387 1656         3637 return;
388             }
389              
390 3144         5526 my $existing_verdict =
391             $self->_get_verdict($neighbour_letter, $xy);
392              
393 3144 50       5488 if ($existing_verdict == $ABCP_VERDICT_YES)
394             {
395 0         0 $self->_error(['mismatched_verdict', $xy]);
396 0         0 return;
397             }
398              
399 3144 100       8200 if ($existing_verdict == $ABCP_VERDICT_MAYBE)
400             {
401 60         127 $self->_set_verdict($neighbour_letter, $xy, $ABCP_VERDICT_NO);
402 60         231 $self->_add_move(
403             Games::ABC_Path::Solver::Move::LettersNotInVicinity->new(
404             {
405             vars =>
406             {
407             target => $neighbour_letter,
408             coords => $xy,
409             source => $letter,
410             },
411             }
412             )
413             );
414             }
415 192         900 });
416             }
417             }
418              
419 4         14 return;
420             }
421              
422             sub _infer_cells
423             {
424 4     4   9 my ($self) = @_;
425              
426             $self->_xy_loop(sub {
427 100     100   147 my ($xy) = @_;
428              
429 100         182 my $letters_aref = $self->_get_possible_letter_indexes($xy);
430              
431 100 50       348 if (! @$letters_aref)
    100          
432             {
433 0         0 $self->_error(['cell', $xy]);
434 0         0 return;
435             }
436             elsif (@$letters_aref == 1)
437             {
438 79         110 my $letter = $letters_aref->[0];
439              
440 79 100       141 if ($self->_get_verdict($letter, $xy) == $ABCP_VERDICT_MAYBE)
441             {
442 16         33 $self->_set_conclusive_verdict_for_letter($letter, $xy);
443 16         67 $self->_add_move(
444             Games::ABC_Path::Solver::Move::LastRemainingLetterForCell->new(
445             {
446             vars =>
447             {
448             coords => $xy,
449             letter => $letter,
450             },
451             },
452             )
453             );
454             }
455             }
456 4         30 });
457              
458 4         29 return;
459             }
460              
461              
462             sub _inference_iteration
463             {
464 4     4   10 my ($self) = @_;
465              
466 4         17 $self->_infer_letters;
467              
468 4         16 $self->_infer_cells;
469              
470 4         13 return $self->_flush_changed;
471             }
472              
473             sub _neighbourhood_and_individuality_inferring
474             {
475 1     1   2 my ($self) = @_;
476              
477 1         3 my $num_changed = 0;
478              
479 1         5 while (my $iter_changed = $self->_inference_iteration())
480             {
481 3 50       7 if ($self->_error())
482             {
483 0         0 return;
484             }
485 3         11 $num_changed += $iter_changed;
486             }
487              
488 1         3 return $num_changed;
489             }
490              
491             sub _clone
492             {
493 1     1   2 my ($self) = @_;
494              
495             return
496             ref($self)->new(
497             {
498 1         2 layout => ${$self->_layout()},
  1         2  
499             }
500             );
501             }
502              
503              
504             sub solve
505             {
506 1     1 1 473 my ($self) = @_;
507              
508 1         12 my $error = $self->_solve_wrapper;
509              
510             return [map {
511 1         2 my $obj = $_;
  1         2  
512 1 50 33     8 (blessed($obj) && $obj->isa('Games::ABC_Path::Solver::Coord'))
513             ? ($obj->x, $obj->y)
514             : ($obj)
515             } @$error];
516             }
517              
518             sub _solve_wrapper
519             {
520 1     1   4 my ($self) = @_;
521              
522 1         7 $self->_neighbourhood_and_individuality_inferring;
523              
524 1 50       4 if ($self->_error)
525             {
526 0         0 return $self->_error;
527             }
528              
529 1         3 my @min_coords;
530             my @min_options;
531              
532             $self->_xy_loop(sub {
533 25     25   32 my ($xy) = @_;
534              
535 25         42 my $letters_aref = $self->_get_possible_letter_indexes($xy);
536              
537 25 50       68 if (! @$letters_aref)
    50          
538             {
539 0         0 $self->_error(['cell', $xy]);
540             }
541             elsif (@$letters_aref > 1)
542             {
543 0 0 0     0 if ((!@min_coords) or (@$letters_aref < @min_options))
544             {
545 0         0 @min_options = @$letters_aref;
546 0         0 @min_coords = ($xy);
547             }
548             }
549              
550 25         59 return;
551 1         6 });
552              
553 1 50       10 if ($self->_error)
554             {
555 0         0 return $self->_error;
556             }
557              
558 1 50       5 if (@min_coords)
559             {
560 0         0 my ($xy) = @min_coords;
561             # We have at least one multiple rank cell. Let's recurse there:
562 0         0 foreach my $letter (@min_options)
563             {
564 0         0 my $recurse_solver = $self->_clone;
565              
566 0         0 $self->_add_move(
567             Games::ABC_Path::Solver::Move::TryingLetterForCell->new(
568             {
569             vars => { letter => $letter, coords => $xy, },
570             }
571             ),
572             );
573              
574 0         0 $recurse_solver->_set_conclusive_verdict_for_letter(
575             $letter, $xy
576             );
577              
578 0         0 $recurse_solver->_solve_wrapper;
579              
580 0         0 foreach my $move (@{ $recurse_solver->get_moves })
  0         0  
581             {
582 0         0 $self->_add_move($move->bump());
583             }
584              
585 0 0       0 if ($recurse_solver->_error())
586             {
587 0         0 $self->_add_move(
588             Games::ABC_Path::Solver::Move::ResultsInAnError->new(
589             {
590             vars =>
591             {
592             letter => $letter,
593             coords => $xy,
594             },
595             }
596             )
597             );
598             }
599             else
600             {
601 0         0 $self->_add_move(
602             Games::ABC_Path::Solver::Move::ResultsInASuccess->new(
603             {
604             vars => { letter => $letter, coords => $xy,},
605             }
606             )
607             );
608 0         0 push @{$self->_successful_layouts},
609 0         0 @{$recurse_solver->get_successful_layouts()};
  0         0  
610             }
611             }
612              
613 0         0 my $count = @{$self->_successful_layouts()};
  0         0  
614 0 0       0 if (! $count)
    0          
615             {
616 0         0 return ['all_options_bad'];
617             }
618             elsif ($count == 1)
619             {
620 0         0 return ['success'];
621             }
622             else
623             {
624 0         0 return ['success_multiple'];
625             }
626             }
627             else
628             {
629 1         7 $self->_successful_layouts([$self->_clone()]);
630 1         4 return ['success'];
631             }
632             }
633              
634             my $letter_re_s = join('', map { quotemeta($_) } @letters);
635             my $letter_re = qr{[$letter_re_s]};
636             my $letter_and_space_re = qr{[ $letter_re_s]};
637             my $top_bottom_re = qr/^${letter_re}{7}\n/ms;
638             my $inner_re = qr/^${letter_re}${letter_and_space_re}{5}${letter_re}\n/ms;
639              
640             sub _assert_letters_appear_once
641             {
642 1     1   3 my ($self, $layout_string) = @_;
643              
644 1         3 my %count_letters = (map { $_ => 0 } @letters);
  25         38  
645 1         19 foreach my $letter ($layout_string =~ m{($letter_re)}g)
646             {
647 25 50       34 if ($count_letters{$letter}++)
648             {
649 0         0 confess "Letter '$letter' encountered twice in the layout.";
650             }
651             }
652              
653 1         4 return;
654             }
655              
656             sub _process_major_diagonal
657             {
658 1     1   2 my ($self, $args) = @_;
659              
660 1         1 my @major_diagonal_letters;
661              
662 1         11 $args->{top} =~ m{\A($letter_re)};
663              
664 1         3 push @major_diagonal_letters, $1;
665              
666 1         9 $args->{bottom} =~ m{($letter_re)\z};
667              
668 1         3 push @major_diagonal_letters, $1;
669              
670             $self->_set_verdicts_for_letter_sets(
671             \@major_diagonal_letters,
672             [map
673 1         6 { Games::ABC_Path::Solver::Coord->new({x => $_, y => $_}) }
  5         15  
674             $self->_y_indexes
675             ],
676             );
677              
678 1         4 return;
679             }
680              
681             sub _process_minor_diagonal
682             {
683 1     1   2 my ($self, $args) = @_;
684              
685 1         3 my @minor_diagonal_letters;
686              
687 1         16 $args->{top} =~ m{($letter_re)\z};
688              
689 1         3 push @minor_diagonal_letters, $1;
690              
691 1         11 $args->{bottom} =~ m{\A($letter_re)};
692              
693 1         3 push @minor_diagonal_letters, $1;
694              
695             $self->_set_verdicts_for_letter_sets(
696             \@minor_diagonal_letters,
697 1         4 [map { Games::ABC_Path::Solver::Coord->new({x => $_, y => 4-$_}) } ($self->_y_indexes)]
  5         12  
698             );
699              
700 1         3 return;
701             }
702              
703             sub _process_input_columns
704             {
705 1     1   2 my ($self, $args) = @_;
706              
707 1         3 my $top_row = $args->{top};
708 1         2 my $bottom_row = $args->{bottom};
709              
710 1         3 foreach my $x ($self->_x_indexes)
711             {
712             $self->_set_verdicts_for_letter_sets(
713             [substr($top_row, $x+1, 1), substr($bottom_row, $x+1, 1),],
714 5         19 [map { Games::ABC_Path::Solver::Coord->new({x =>$x, y => $_}) } $self->_y_indexes],
  25         55  
715             );
716             }
717              
718 1         2 return;
719             }
720              
721             sub _process_input_rows_and_initial_letter_clue
722             {
723 1     1   3 my ($self, $args) = @_;
724              
725 1         2 my $rows = $args->{rows};
726              
727 1         2 my ($clue_x, $clue_y, $clue_letter);
728              
729 1         4 foreach my $y ($self->_y_indexes)
730             {
731 5         11 my $row = $rows->[$y];
732             $self->_set_verdicts_for_letter_sets(
733             [substr($row, 0, 1), substr($row, -1),],
734 5         15 [map { Games::ABC_Path::Solver::Coord->new({x => $_,y => $y}) } $self->_x_indexes],
  25         48  
735             );
736              
737 5         17 my $s = substr($row, 1, -1);
738 5 100       43 if ($s =~ m{($letter_re)}g)
739             {
740 1         6 my ($l, $x_plus_1) = ($1, pos($s));
741 1 50       4 if (defined($clue_letter))
742             {
743 0         0 confess "Found more than one clue letter in the layout!";
744             }
745 1         5 ($clue_x, $clue_y, $clue_letter) = ($x_plus_1-1, $y, $l);
746             }
747             }
748              
749 1 50       7 if (!defined ($clue_letter))
750             {
751 0         0 confess "Did not find any clue letters inside the layout.";
752             }
753              
754             $self->_set_conclusive_verdict_for_letter(
755 1         3 $self->_get_letter_numeric($clue_letter),
756             Games::ABC_Path::Solver::Coord->new({x => $clue_x, y => $clue_y}),
757             );
758              
759 1         2 return;
760             }
761              
762             sub _input
763             {
764 1     1   2 my ($self, $args) = @_;
765              
766 1 50       4 if ($args->{version} ne 1)
767             {
768 0         0 die "Can only handle version 1";
769             }
770              
771 1         2 my $layout_string = $args->{layout};
772 1 50       37 if ($layout_string !~ m/\A${top_bottom_re}${inner_re}{5}${top_bottom_re}\z/ms)
773             {
774 0         0 die "Invalid format. Should be Letter{7}\n(Letter{spaces or one letter}{5}Letter){5}\nLetter{7}";
775             }
776              
777 1         6 my @rows = split(/\n/, $layout_string);
778              
779 1         2 my $top_row = shift(@rows);
780 1         2 my $bottom_row = pop(@rows);
781              
782             # Now let's process the layout string and populate the verdicts table.
783 1         4 $self->_assert_letters_appear_once($layout_string);
784              
785 1         4 my $parse_context =
786             { top => $top_row, bottom => $bottom_row, rows => \@rows, }
787             ;
788              
789 1         4 $self->_process_major_diagonal($parse_context);
790              
791 1         4 $self->_process_minor_diagonal($parse_context);
792              
793 1         4 $self->_process_input_columns($parse_context);
794              
795 1         5 $self->_process_input_rows_and_initial_letter_clue($parse_context);
796              
797              
798 1         5 return;
799             }
800              
801             sub _get_results_text_table
802             {
803 1     1   3 my ($self) = @_;
804              
805             my $render_row = sub {
806 6     6   6 my $cols = shift;
807              
808             return
809             "| " .
810             join(
811             " | ",
812 6 100       11 map { length($_) == 1 ? " $_ " : $_ } @$cols
  30         69  
813             ) . " |\n";
814 1         5 };
815              
816             return join('',
817 6         10 map { $render_row->($_) }
818             (
819 5         14 [map { sprintf("X = %d", $_+1) } $self->_x_indexes ],
820 1         5 map { my $y = $_;
  5         8  
821             [
822             map
823 5         10 { $self->_get_possible_letters_string(Games::ABC_Path::Solver::Coord->new({x => $_, y => $y})) }
  25         64  
824             $self->_x_indexes
825             ]
826             }
827             $self->_y_indexes
828             )
829             );
830             }
831              
832              
833             sub get_successes_text_tables
834             {
835 1     1 1 3 my ($self) = @_;
836              
837 1         2 return [map { $_->_get_results_text_table() } @{$self->get_successful_layouts()}];
  1         4  
  1         3  
838             }
839              
840              
841             sub input_from_file
842             {
843 0     0 1 0 my ($class, $board_fn) = @_;
844              
845 0 0       0 open my $in_fh, "<", $board_fn
846             or die "Cannot open '$board_fn' - $!";
847              
848 0         0 my $first_line = <$in_fh>;
849 0         0 chomp($first_line);
850              
851 0         0 my $magic = 'ABC Path Solver Layout Version 1:';
852 0 0       0 if ($first_line !~ m{\A\Q$magic\E\s*\z})
853             {
854 0         0 die "Can only process files whose first line is '$magic'!";
855             }
856              
857 0         0 my $layout_string = '';
858 0         0 foreach my $line_idx (1 .. 7)
859             {
860 0         0 chomp(my $line = <$in_fh>);
861 0         0 $layout_string .= "$line\n";
862             }
863 0         0 close($in_fh);
864              
865 0         0 return $class->input_from_v1_string($layout_string);
866             }
867              
868              
869             sub input_from_v1_string
870             {
871 1     1 1 79 my ($class, $layout_string) = @_;
872              
873 1         10 my $self = $class->new;
874              
875 1         5 $self->_input({ layout => $layout_string, version => 1});
876              
877 1         5 return $self;
878             }
879              
880              
881             sub get_moves
882             {
883 0     0 1   my ($self) = @_;
884              
885 0           return [@{ $self->_moves }];
  0            
886             }
887              
888              
889             1; # End of Games::ABC_Path::Solver::Board
890              
891             __END__