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