File Coverage

blib/lib/Games/ABC_Path/Solver/Board.pm
Criterion Covered Total %
statement 335 354 94.6
branch 77 96 80.2
condition 12 18 66.6
subroutine 61 61 100.0
pod 7 7 100.0
total 492 536 91.7


line stmt bran cond sub pod time code
1             package Games::ABC_Path::Solver::Board;
2             $Games::ABC_Path::Solver::Board::VERSION = '0.8.1';
3 7     7   392973 use warnings;
  7         15  
  7         503  
4 7     7   34 use strict;
  7         15  
  7         205  
5 7     7   148 use 5.014;
  7         29  
6 7     7   3397 use autodie;
  7         114515  
  7         25  
7              
8              
9 7     7   42397 use Carp;
  7         14  
  7         616  
10              
11 7     7   39 use parent 'Games::ABC_Path::Solver::Base';
  7         14  
  7         45  
12              
13 7     7   417 use Games::ABC_Path::Solver::Constants;
  7         11  
  7         941  
14 7     7   4056 use Games::ABC_Path::Solver::Move::LastRemainingCellForLetter;
  7         18  
  7         236  
15 7     7   3064 use Games::ABC_Path::Solver::Move::LastRemainingLetterForCell;
  7         20  
  7         195  
16 7     7   3112 use Games::ABC_Path::Solver::Move::LettersNotInVicinity;
  7         19  
  7         238  
17 7     7   3428 use Games::ABC_Path::Solver::Move::ResultsInAnError;
  7         19  
  7         225  
18 7     7   3434 use Games::ABC_Path::Solver::Move::ResultsInASuccess;
  7         21  
  7         223  
19 7     7   3562 use Games::ABC_Path::Solver::Move::TryingLetterForCell;
  7         19  
  7         249  
20              
21 7     7   3435 use Games::ABC_Path::Solver::Coord;
  7         21  
  7         269  
22              
23 7     7   48 use Scalar::Util qw(blessed);
  7         21  
  7         36611  
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 75     75   195 my ( $self, $letter_ascii ) = @_;
34              
35 75         219 my $index = $letters_map{$letter_ascii};
36              
37 75 50       206 if ( !defined($index) )
38             {
39 0         0 confess "Unknown letter '$letter_ascii'";
40             }
41              
42 75         183 return $index;
43             }
44              
45             sub _iter_changed
46             {
47 584     584   744 my $self = shift;
48              
49 584 100       1015 if (@_)
50             {
51 296         450 $self->{_iter_changed} = shift;
52             }
53              
54 584         1145 return $self->{_iter_changed};
55             }
56              
57             sub _moves
58             {
59 284     284   418 my $self = shift;
60              
61 284 100       583 if (@_)
62             {
63 8         23 $self->{_moves} = shift;
64             }
65              
66 284         776 return $self->{_moves};
67             }
68              
69             sub _error
70             {
71 40164     40164   60031 my $self = shift;
72              
73 40164 100       73054 if (@_)
74             {
75 1         4 $self->{_error} = shift;
76             }
77              
78 40164         86566 return $self->{_error};
79             }
80              
81             sub _inc_changed
82             {
83 272     272   437 my ($self) = @_;
84              
85 272         559 $self->_iter_changed( $self->_iter_changed + 1 );
86              
87 272         365 return;
88             }
89              
90             sub _flush_changed
91             {
92 16     16   44 my ($self) = @_;
93              
94 16         57 my $ret = $self->_iter_changed;
95              
96 16         53 $self->_iter_changed(0);
97              
98 16         74 return $ret;
99             }
100              
101             sub _add_move
102             {
103 272     272   475 my ( $self, $move ) = @_;
104              
105 272         346 push @{ $self->_moves() }, $move;
  272         539  
106              
107 272         742 $self->_inc_changed;
108              
109 272         811 return;
110             }
111              
112              
113             sub get_successful_layouts
114             {
115 5     5 1 19 my ($self) = @_;
116              
117 5         12 return [ @{ $self->_successful_layouts } ];
  5         15  
118             }
119              
120             sub _successful_layouts
121             {
122 18     18   41 my $self = shift;
123              
124 18 100       54 if (@_)
125             {
126 11         30 $self->{_successful_layouts} = shift;
127             }
128              
129 18         78 return $self->{_successful_layouts};
130             }
131              
132             sub _layout
133             {
134 43705     43705   63678 my $self = shift;
135              
136 43705 100       83815 if (@_)
137             {
138 8         39 $self->{_layout} = shift;
139             }
140              
141 43705         101005 return $self->{_layout};
142             }
143              
144             # The letter indexes.
145             sub _l_indexes
146             {
147 667     667   2222 return ( 0 .. $ABCP_MAX_LETTER );
148             }
149              
150             sub _init
151             {
152 8     8   27 my ( $self, $args ) = @_;
153              
154 8         24 my $layout_string = $args->{layout};
155              
156 8 100       38 if ( !defined($layout_string) )
157             {
158 3         8 $layout_string = '';
159             }
160              
161 8         36 $self->_layout( \$layout_string );
162 8         37 $self->_successful_layouts( [] );
163 8         38 $self->_moves( [] );
164 8         64 $self->_iter_changed(0);
165              
166 8         28 return;
167             }
168              
169             sub _calc_offset
170             {
171 43692     43692   74664 my ( $self, $letter, $xy ) = @_;
172              
173 43692 50 33     136526 if ( ( $letter < 0 ) or ( $letter >= 25 ) )
174             {
175 0         0 confess "Letter $letter out of range.";
176             }
177              
178 43692         99867 return $letter * $BOARD_SIZE + $self->_xy_to_int( [ $xy->y, $xy->x ] );
179             }
180              
181             sub _get_verdict
182             {
183 37882     37882   66509 my ( $self, $letter, $xy ) = @_;
184              
185 37882         52188 return vec( ${ $self->_layout }, $self->_calc_offset( $letter, $xy, ), 2 );
  37882         67637  
186             }
187              
188             sub _set_verdict
189             {
190 5810     5810   10283 my ( $self, $letter, $xy, $verdict ) = @_;
191              
192             # Temporary - remove later.
193 5810 50       10627 if ( @_ != 4 )
194             {
195 0         0 confess "_set_verdict has wrong number of args.";
196             }
197              
198 5810 50 100     12298 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 5810         7934 vec( ${ $self->_layout }, $self->_calc_offset( $letter, $xy ), 2 ) =
  5810         10166  
208             $verdict;
209              
210 5810         17213 return;
211             }
212              
213             sub _xy_loop
214             {
215 1338     1338   2682 my ( $self, $sub_ref ) = (@_);
216              
217 1338         3394 foreach my $y ( $self->_y_indexes )
218             {
219 6690 50       14181 if ( $self->_error() )
220             {
221 0         0 return;
222             }
223 6690         15053 foreach my $x ( $self->_x_indexes )
224             {
225 33449 100       65487 if ( $self->_error() )
226             {
227 1         3 return;
228             }
229 33448         106601 $sub_ref->(
230             Games::ABC_Path::Solver::Coord->new( { x => $x, y => $y } ) );
231             }
232             }
233 1337         20868 return;
234             }
235              
236             sub _set_verdicts_for_letter_sets
237             {
238 36     36   90 my ( $self, $letter_list, $maybe_list ) = @_;
239              
240 36         85 my %cell_is_maybe = ( map { $_->_to_s() => 1 } @$maybe_list );
  180         412  
241              
242 36         119 foreach my $letter_ascii (@$letter_list)
243             {
244 72         201 my $letter = $self->_get_letter_numeric($letter_ascii);
245              
246             $self->_xy_loop(
247             sub {
248 1800     1800   3442 my ($xy) = @_;
249              
250             $self->_set_verdict(
251             $letter, $xy,
252             (
253 1800 100       4442 ( exists $cell_is_maybe{ $xy->_to_s() } )
254             ? $ABCP_VERDICT_MAYBE
255             : $ABCP_VERDICT_NO
256             )
257             );
258             }
259 72         471 );
260             }
261              
262 36         225 return;
263             }
264              
265             sub _set_conclusive_verdict_for_letter
266             {
267 78     78   199 my ( $self, $letter, $l_xy ) = @_;
268              
269             $self->_xy_loop(
270             sub {
271 1950     1950   2831 my ($xy) = @_;
272              
273 1950 100       3645 $self->_set_verdict(
274             $letter, $xy,
275             (
276             $l_xy->_equal($xy)
277             ? $ABCP_VERDICT_YES
278             : $ABCP_VERDICT_NO
279             )
280             );
281             }
282 78         554 );
283              
284             OTHER_LETTER:
285 78         466 foreach my $other_letter ( $self->_l_indexes )
286             {
287 1950 100       3214 if ( $other_letter == $letter )
288             {
289 78         182 next OTHER_LETTER;
290             }
291 1872         2797 $self->_set_verdict( $other_letter, $l_xy, $ABCP_VERDICT_NO );
292             }
293              
294 78         201 return;
295             }
296              
297             sub _get_possible_letter_indexes
298             {
299 573     573   1093 my ( $self, $xy ) = @_;
300              
301 573         1302 return [ grep { $self->_get_verdict( $_, $xy ) != $ABCP_VERDICT_NO }
  14325         26814  
302             $self->_l_indexes() ];
303             }
304              
305              
306             sub get_possible_letters_for_cell
307             {
308 75     75 1 196 my ( $self, $x, $y ) = @_;
309              
310             return [
311             @letters[
312             @{
313 75         138 $self->_get_possible_letter_indexes(
  75         267  
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 75     75   171 my ( $self, $xy ) = @_;
324              
325             return
326             join( ',',
327 75         124 @{ $self->get_possible_letters_for_cell( $xy->x, $xy->y ) } );
  75         197  
328             }
329              
330             sub _infer_letters
331             {
332 16     16   41 my ($self) = @_;
333              
334 16         44 foreach my $letter ( $self->_l_indexes )
335             {
336 400         800 my @true_cells;
337              
338             $self->_xy_loop(
339             sub {
340 10000     10000   16706 my ($xy) = @_;
341              
342 10000         21593 my $ver = $self->_get_verdict( $letter, $xy );
343 10000 100 100     45036 if ( ( $ver == $ABCP_VERDICT_YES )
344             || ( $ver == $ABCP_VERDICT_MAYBE ) )
345             {
346 627         1728 push @true_cells, $xy;
347             }
348             }
349 400         2909 );
350              
351 400 50       3058 if ( !@true_cells )
    100          
352             {
353 0         0 $self->_error( [ 'letter', $letter ] );
354 0         0 return;
355             }
356             elsif ( @true_cells == 1 )
357             {
358 270         584 my $xy = $true_cells[0];
359 270 100       605 if ( $self->_get_verdict( $letter, $xy ) == $ABCP_VERDICT_MAYBE )
360             {
361 26         113 $self->_set_conclusive_verdict_for_letter( $letter, $xy );
362 26         279 $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 400         1175 my @neighbourhood = ( map { [ (0) x $LEN ] } ( $self->_y_indexes ) );
  2000         5118  
377              
378 400         1038 foreach my $true (@true_cells)
379             {
380 627         1487 foreach my $coords (
381             grep {
382 5643 100       11223 $self->_x_in_range( $_->[0] )
383             and $self->_y_in_range( $_->[1] )
384             }
385 5643         10479 map { [ $true->x + $_->[0], $true->y + $_->[1] ] }
386             map {
387 1881         2558 my $d = $_;
388 1881         2948 map { [ $_, $d ] } ( -1 .. 1 )
  5643         12854  
389             } ( -1 .. 1 )
390             )
391             {
392 4360         9161 $neighbourhood[ $coords->[1] ][ $coords->[0] ] = 1;
393             }
394             }
395              
396 400 100       1721 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 19200     19200   31410 my ($xy) = @_;
404              
405 19200 100       37740 if ( $neighbourhood[ $xy->y ][ $xy->x ] )
406             {
407 6228         16069 return;
408             }
409              
410 12972         28770 my $existing_verdict =
411             $self->_get_verdict( $neighbour_letter, $xy );
412              
413 12972 50       26802 if ( $existing_verdict == $ABCP_VERDICT_YES )
414             {
415 0         0 $self->_error( [ 'mismatched_verdict', $xy ] );
416 0         0 return;
417             }
418              
419 12972 100       42367 if ( $existing_verdict == $ABCP_VERDICT_MAYBE )
420             {
421 188         495 $self->_set_verdict( $neighbour_letter, $xy,
422             $ABCP_VERDICT_NO );
423 188         1128 $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 768         14748 );
438             }
439             }
440              
441 16         75 return;
442             }
443              
444             sub _infer_cells
445             {
446 16     16   48 my ($self) = @_;
447              
448             $self->_xy_loop(
449             sub {
450 398     398   756 my ($xy) = @_;
451              
452 398         924 my $letters_aref = $self->_get_possible_letter_indexes($xy);
453              
454 398 100       1945 if ( !@$letters_aref )
    100          
455             {
456 1         54 $self->_error( [ 'cell', $xy ] );
457 1         6 return;
458             }
459             elsif ( @$letters_aref == 1 )
460             {
461 315         633 my $letter = $letters_aref->[0];
462              
463 315 100       701 if (
464             $self->_get_verdict( $letter, $xy ) == $ABCP_VERDICT_MAYBE )
465             {
466 47         127 $self->_set_conclusive_verdict_for_letter( $letter, $xy );
467 47         346 $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 16         134 );
482              
483 16         163 return;
484             }
485              
486             sub _inference_iteration
487             {
488 16     16   35 my ($self) = @_;
489              
490 16         69 $self->_infer_letters;
491              
492 16         84 $self->_infer_cells;
493              
494 16         92 return $self->_flush_changed;
495             }
496              
497             sub _neighbourhood_and_individuality_inferring
498             {
499 5     5   11 my ($self) = @_;
500              
501 5         12 my $num_changed = 0;
502              
503 5         39 while ( my $iter_changed = $self->_inference_iteration() )
504             {
505 12 100       73 if ( $self->_error() )
506             {
507 1         5 return;
508             }
509 11         46 $num_changed += $iter_changed;
510             }
511              
512 4         18 return $num_changed;
513             }
514              
515             sub _clone
516             {
517 5     5   20 my ($self) = @_;
518              
519             return ref($self)->new(
520             {
521 5         17 layout => ${ $self->_layout() },
  5         33  
522             }
523             );
524             }
525              
526              
527             sub solve
528             {
529 3     3 1 505 my ($self) = @_;
530              
531 3         47 my $error = $self->_solve_wrapper;
532              
533             return [
534             map {
535 3         12 my $obj = $_;
  3         8  
536 3 50 33     33 ( 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 5     5   16 my ($self) = @_;
546              
547 5         26 $self->_neighbourhood_and_individuality_inferring;
548              
549 5 100       22 if ( $self->_error )
550             {
551 1         5 return $self->_error;
552             }
553              
554 4         12 my @min_coords;
555             my @min_options;
556              
557             $self->_xy_loop(
558             sub {
559 100     100   207 my ($xy) = @_;
560              
561 100         271 my $letters_aref = $self->_get_possible_letter_indexes($xy);
562              
563 100 50       461 if ( !@$letters_aref )
    100          
564             {
565 0         0 $self->_error( [ 'cell', $xy ] );
566             }
567             elsif ( @$letters_aref > 1 )
568             {
569 4 100 66     24 if ( ( !@min_coords ) or ( @$letters_aref < @min_options ) )
570             {
571 1         4 @min_options = @$letters_aref;
572 1         3 @min_coords = ($xy);
573             }
574             }
575              
576 100         488 return;
577             }
578 4         47 );
579              
580 4 50       60 if ( $self->_error )
581             {
582 0         0 return $self->_error;
583             }
584              
585 4 100       21 if (@min_coords)
586             {
587 1         4 my ($xy) = @min_coords;
588              
589             # We have at least one multiple rank cell. Let's recurse there:
590 1         5 foreach my $letter (@min_options)
591             {
592 2         59 my $recurse_solver = $self->_clone;
593              
594 2         41 $self->_add_move(
595             Games::ABC_Path::Solver::Move::TryingLetterForCell->new(
596             {
597             vars => { letter => $letter, coords => $xy, },
598             }
599             ),
600             );
601              
602 2         13 $recurse_solver->_set_conclusive_verdict_for_letter( $letter, $xy );
603              
604 2         14 $recurse_solver->_solve_wrapper;
605              
606 2         6 foreach my $move ( @{ $recurse_solver->get_moves } )
  2         14  
607             {
608 7         77 $self->_add_move( $move->bump() );
609             }
610              
611 2 100       9 if ( $recurse_solver->_error() )
612             {
613 1         38 $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 1         22 $self->_add_move(
627             Games::ABC_Path::Solver::Move::ResultsInASuccess->new(
628             {
629             vars => { letter => $letter, coords => $xy, },
630             }
631             )
632             );
633 1         4 push @{ $self->_successful_layouts },
634 1         4 @{ $recurse_solver->get_successful_layouts() };
  1         4  
635             }
636             }
637              
638 1         2 my $count = @{ $self->_successful_layouts() };
  1         3  
639 1 50       6 if ( !$count )
    50          
640             {
641 0         0 return ['all_options_bad'];
642             }
643             elsif ( $count == 1 )
644             {
645 1         5 return ['success'];
646             }
647             else
648             {
649 0         0 return ['success_multiple'];
650             }
651             }
652             else
653             {
654 3         69 $self->_successful_layouts( [ $self->_clone() ] );
655 3         16 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 3     3   13 my ( $self, $layout_string ) = @_;
668              
669 3         11 my %count_letters = ( map { $_ => 0 } @letters );
  75         186  
670 3         158 foreach my $letter ( $layout_string =~ m{($letter_re)}g )
671             {
672 75 50       197 if ( $count_letters{$letter}++ )
673             {
674 0         0 confess "Letter '$letter' encountered twice in the layout.";
675             }
676             }
677              
678 3         26 return;
679             }
680              
681             sub _process_major_diagonal
682             {
683 3     3   9 my ( $self, $args ) = @_;
684              
685 3         7 my @major_diagonal_letters;
686              
687 3         95 $args->{top} =~ m{\A($letter_re)};
688              
689 3         18 push @major_diagonal_letters, $1;
690              
691 3         95 $args->{bottom} =~ m{($letter_re)\z};
692              
693 3         14 push @major_diagonal_letters, $1;
694              
695             $self->_set_verdicts_for_letter_sets(
696             \@major_diagonal_letters,
697             [
698 3         38 map { Games::ABC_Path::Solver::Coord->new( { x => $_, y => $_ } ) }
  15         82  
699             $self->_y_indexes
700             ],
701             );
702              
703 3         22 return;
704             }
705              
706             sub _process_minor_diagonal
707             {
708 3     3   11 my ( $self, $args ) = @_;
709              
710 3         8 my @minor_diagonal_letters;
711              
712 3         156 $args->{top} =~ m{($letter_re)\z};
713              
714 3         20 push @minor_diagonal_letters, $1;
715              
716 3         98 $args->{bottom} =~ m{\A($letter_re)};
717              
718 3         14 push @minor_diagonal_letters, $1;
719              
720             $self->_set_verdicts_for_letter_sets(
721             \@minor_diagonal_letters,
722             [
723             map {
724 3         19 Games::ABC_Path::Solver::Coord->new( { x => $_, y => 4 - $_ } )
  15         55  
725             } ( $self->_y_indexes )
726             ]
727             );
728              
729 3         21 return;
730             }
731              
732             sub _process_input_columns
733             {
734 3     3   10 my ( $self, $args ) = @_;
735              
736 3         12 my $top_row = $args->{top};
737 3         9 my $bottom_row = $args->{bottom};
738              
739 3         14 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 15         163 Games::ABC_Path::Solver::Coord->new( { x => $x, y => $_ } )
  75         261  
748             } $self->_y_indexes
749             ],
750             );
751             }
752              
753 3         15 return;
754             }
755              
756             sub _process_input_rows_and_initial_letter_clue
757             {
758 3     3   11 my ( $self, $args ) = @_;
759              
760 3         12 my $rows = $args->{rows};
761              
762 3         12 my ( $clue_x, $clue_y, $clue_letter );
763              
764 3         29 foreach my $y ( $self->_y_indexes )
765             {
766 15         48 my $row = $rows->[$y];
767             $self->_set_verdicts_for_letter_sets(
768             [ substr( $row, 0, 1 ), substr( $row, -1 ), ],
769             [
770             map {
771 15         85 Games::ABC_Path::Solver::Coord->new( { x => $_, y => $y } )
  75         233  
772             } $self->_x_indexes
773             ],
774             );
775              
776 15         99 my $s = substr( $row, 1, -1 );
777 15 100       286 if ( $s =~ m{($letter_re)}g )
778             {
779 3         23 my ( $l, $x_plus_1 ) = ( $1, pos($s) );
780 3 50       13 if ( defined($clue_letter) )
781             {
782 0         0 confess "Found more than one clue letter in the layout!";
783             }
784 3         41 ( $clue_x, $clue_y, $clue_letter ) = ( $x_plus_1 - 1, $y, $l );
785             }
786             }
787              
788 3 50       16 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 3         28 $self->_get_letter_numeric($clue_letter),
795             Games::ABC_Path::Solver::Coord->new( { x => $clue_x, y => $clue_y } ),
796             );
797              
798 3         14 return;
799             }
800              
801             sub _input
802             {
803 3     3   9 my ( $self, $args ) = @_;
804              
805 3 50       22 if ( $args->{version} ne 1 )
806             {
807 0         0 die "Can only handle version 1";
808             }
809              
810 3         9 my $layout_string = $args->{layout};
811 3 50       349 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 3         22 my @rows = split( /\n/, $layout_string );
819              
820 3         10 my $top_row = shift(@rows);
821 3         8 my $bottom_row = pop(@rows);
822              
823             # Now let's process the layout string and populate the verdicts table.
824 3         40 $self->_assert_letters_appear_once($layout_string);
825              
826 3         23 my $parse_context =
827             { top => $top_row, bottom => $bottom_row, rows => \@rows, };
828              
829 3         17 $self->_process_major_diagonal($parse_context);
830              
831 3         17 $self->_process_minor_diagonal($parse_context);
832              
833 3         19 $self->_process_input_columns($parse_context);
834              
835 3         23 $self->_process_input_rows_and_initial_letter_clue($parse_context);
836              
837 3         23 return;
838             }
839              
840             sub _get_results_text_table
841             {
842 3     3   11 my ($self) = @_;
843              
844             my $render_row = sub {
845 18     18   31 my $cols = shift;
846              
847             return
848             "| "
849 18 100       38 . join( " | ", map { length($_) == 1 ? " $_ " : $_ } @$cols )
  90         859  
850             . " |\n";
851 3         24 };
852              
853             return join(
854             '',
855 18         47 map { $render_row->($_) } (
856 15         60 [ map { sprintf( "X = %d", $_ + 1 ) } $self->_x_indexes ],
857             map {
858 3         20 my $y = $_;
  15         39  
859             [
860             map {
861 15         47 $self->_get_possible_letters_string(
  75         440  
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 3     3 1 12 my ($self) = @_;
877              
878 3         16 return [ map { $_->_get_results_text_table() }
879 3         7 @{ $self->get_successful_layouts() } ];
  3         16  
880             }
881              
882              
883             sub input_from_file
884             {
885 2     2 1 7 my ( $class, $board_fn ) = @_;
886              
887 2 50       13 open my $in_fh, "<", $board_fn
888             or die "Cannot open '$board_fn' - $!";
889              
890 2         3960 my $first_line = <$in_fh>;
891 2         5 chomp($first_line);
892              
893 2         6 my $magic = 'ABC Path Solver Layout Version 1:';
894 2 50       103 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 2         11 my $layout_string = '';
900 2         10 foreach my $line_idx ( 1 .. 7 )
901             {
902 14         34 chomp( my $line = <$in_fh> );
903 14         31 $layout_string .= "$line\n";
904             }
905 2         14 close($in_fh);
906              
907 2         2277 return $class->input_from_v1_string($layout_string);
908             }
909              
910              
911             sub input_from_v1_string
912             {
913 3     3 1 387209 my ( $class, $layout_string ) = @_;
914              
915 3         41 my $self = $class->new;
916              
917 3         27 $self->_input( { layout => $layout_string, version => 1 } );
918              
919 3         36 return $self;
920             }
921              
922              
923             sub get_moves
924             {
925 4     4 1 11 my ($self) = @_;
926              
927 4         10 return [ @{ $self->_moves } ];
  4         14  
928             }
929              
930              
931             1; # End of Games::ABC_Path::Solver::Board
932              
933             __END__