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