File Coverage

blib/lib/Games/LMSolve/Plank/Base.pm
Criterion Covered Total %
statement 199 238 83.6
branch 57 90 63.3
condition 21 30 70.0
subroutine 16 17 94.1
pod 9 9 100.0
total 302 384 78.6


line stmt bran cond sub pod time code
1             package Games::LMSolve::Plank::Base;
2             $Games::LMSolve::Plank::Base::VERSION = '0.14.0';
3 2     2   2222 use strict;
  2         11  
  2         58  
4 2     2   11 use warnings;
  2         4  
  2         66  
5              
6 2     2   11 use vars qw(@ISA);
  2         5  
  2         111  
7              
8 2     2   455 use Games::LMSolve::Base qw(%cell_dirs);
  2         4  
  2         211  
9              
10             @ISA = qw(Games::LMSolve::Base);
11              
12 2     2   917 use Games::LMSolve::Input;
  2         19  
  2         4947  
13              
14             sub initialize
15             {
16 1     1 1 3 my $self = shift;
17              
18 1         8 $self->SUPER::initialize(@_);
19              
20 1         8 $self->{'dirs'} = [qw(E W S N)];
21             }
22              
23             sub input_board
24             {
25 1     1 1 4 my $self = shift;
26              
27 1         2 my $filename = shift;
28              
29 1         11 my $spec = {
30             'dims' => { 'type' => "xy(integer)", 'required' => 1, },
31             'planks' => {
32             'type' => "array(start_end(xy(integer)))",
33             'required' => 1,
34             },
35             'layout' => { 'type' => "layout", 'required' => 1, },
36             };
37              
38 1         7 my $input_obj = Games::LMSolve::Input->new();
39              
40 1         5 my $input_fields = $input_obj->input_board( $filename, $spec );
41             my ( $width, $height ) =
42 1         3 @{ $input_fields->{'dims'}->{'value'} }{ 'x', 'y' };
  1         5  
43 1         3 my ( $goal_x, $goal_y );
44              
45 1 50       2 if ( scalar( @{ $input_fields->{'layout'}->{'value'} } ) < $height )
  1         5  
46             {
47 0         0 die
48             "Incorrect number of lines in board layout (does not match dimensions";
49             }
50 1         2 my @board;
51 1         3 my $lines = $input_fields->{'layout'}->{'value'};
52 1         4 for ( my $y = 0 ; $y < $height ; $y++ )
53             {
54 5         8 my $l = [];
55 5 50       14 if ( length( $lines->[$y] ) < $width )
56             {
57             die "Too few characters in board layout in line No. "
58 0         0 . ( $input_fields->{'layout'}->{'line_num'} + $y + 1 );
59             }
60 5         7 my $x = 0;
61 5         15 foreach my $c ( split( //, $lines->[$y] ) )
62             {
63 35         59 push @$l, ( $c ne " " );
64 35 100       61 if ( $c eq "G" )
65             {
66 1 50       14 if ( defined($goal_x) )
67             {
68 0         0 die "Goal was defined twice!";
69             }
70 1         10 ( $goal_x, $goal_y ) = ( $x, $y );
71             }
72 35         46 $x++;
73             }
74 5         16 push @board, $l;
75             }
76 1 50       3 if ( !defined($goal_x) )
77             {
78 0         0 die "The Goal was not defined in the layout";
79             }
80              
81 1         2 my $planks_in = $input_fields->{'planks'}->{'value'};
82              
83 1         2 my @planks;
84              
85             my $get_plank = sub {
86 4     4   5 my $p = shift;
87              
88             my ( $start_x, $start_y ) =
89 4         11 ( $p->{'start'}->{'x'}, $p->{'start'}->{'y'} );
90 4         9 my ( $end_x, $end_y ) = ( $p->{'end'}->{'x'}, $p->{'end'}->{'y'} );
91              
92             my $check_endpoints = sub {
93 4 50       8 if ( !$board[$start_y]->[$start_x] )
94             {
95 0         0 die "Plank cannot be placed at point ($start_x,$start_y)!";
96             }
97 4 50       10 if ( !$board[$end_y]->[$end_x] )
98             {
99 0         0 die "Plank cannot be placed at point ($end_x,$end_y)!";
100             }
101 4         13 };
102              
103 4         12 my $plank_str = "Plank ($start_x,$start_y) ==> ($end_x,$end_y)";
104              
105 4 50 33     29 if ( ( $start_x >= $width )
      33        
      33        
106             || ( $end_x >= $width )
107             || ( $start_y >= $height )
108             || ( $end_y >= $height ) )
109             {
110 0         0 die "$plank_str is out of the boundaries of the board";
111             }
112              
113 4 100       12 if ( $start_x == $end_x )
    50          
    0          
114             {
115 1 50       3 if ( $start_y == $end_y )
116             {
117 0         0 die "$plank_str has zero length!";
118             }
119 1         3 $check_endpoints->();
120 1 50       3 if ( $start_y > $end_y )
121             {
122 0         0 ( $start_y, $end_y ) = ( $end_y, $start_y );
123             }
124 1         4 foreach my $y ( ( $start_y + 1 ) .. ( $end_y - 1 ) )
125             {
126 0 0       0 if ( $board[$y]->[$start_x] )
127             {
128 0         0 die "$plank_str crosses logs!";
129             }
130             }
131             return {
132 1         8 'len' => ( $end_y - $start_y ),
133             'start' => { 'x' => $start_x, 'y' => $start_y },
134             'dir' => "S"
135             };
136             }
137             elsif ( $start_y == $end_y )
138             {
139 3         8 $check_endpoints->();
140 3 50       8 if ( $start_x > $end_x )
141             {
142 0         0 ( $start_x, $end_x ) = ( $end_x, $start_x );
143             }
144 3         9 foreach my $x ( ( $start_x + 1 ) .. ( $end_x - 1 ) )
145             {
146 3 50       8 if ( $board[$start_y]->[$x] )
147             {
148 0         0 die "$plank_str crosses logs!";
149             }
150             }
151             return {
152 3         22 'len' => ( $end_x - $start_x ),
153             'start' => { 'x' => $start_x, 'y' => $start_y },
154             'dir' => "E"
155             };
156             }
157             elsif ( ( $end_x - $start_x ) == ( $end_y - $start_y ) )
158             {
159 0         0 $check_endpoints->();
160 0 0       0 if ( $start_x > $end_x )
161             {
162 0         0 ( $start_x, $end_x ) = ( $end_x, $start_x );
163 0         0 ( $start_y, $end_y ) = ( $end_y, $start_y );
164             }
165 0         0 foreach my $i ( 1 .. ( $end_x - $start_x - 1 ) )
166             {
167 0 0       0 if ( $board[ $start_y + $i ]->[ $start_x + $i ] )
168             {
169 0         0 die "$plank_str crosses logs!";
170             }
171             }
172 0 0       0 if ( !grep { $_ eq "SE" } @{ $self->{'dirs'} } )
  0         0  
  0         0  
173             {
174 0         0 die "$plank_str is not aligned horizontally or vertically.";
175             }
176             return {
177 0         0 'len' => ( $end_x - $start_x ),
178             'start' => {
179             'x' => $start_x,
180             'y' => $start_y,
181             },
182             'dir' => "SE",
183             };
184             }
185             else
186             {
187 0         0 die "$plank_str is not aligned horizontally or vertically.";
188             }
189 1         7 };
190              
191 1         3 foreach my $p (@$planks_in)
192             {
193 4         9 push @planks, $get_plank->($p);
194             }
195              
196 1         3 $self->{'width'} = $width;
197 1         3 $self->{'height'} = $height;
198 1         2 $self->{'goal_x'} = $goal_x;
199 1         4 $self->{'goal_y'} = $goal_y;
200 1         2 $self->{'board'} = \@board;
201 1         3 $self->{'plank_lens'} = [ map { $_->{'len'} } @planks ];
  4         9  
202              
203             my $state = [
204             0,
205             (
206             map
207             {
208 1         4 (
209             $_->{'start'}->{'x'},
210             $_->{'start'}->{'y'},
211             (
212             ( $_->{'dir'} eq "E" ) ? 0
213 4 50       26 : ( $_->{'dir'} eq "SE" ) ? 2
    100          
214             : 1
215             )
216             )
217             } @planks
218             )
219             ];
220 1         8 $self->_process_plank_data($state);
221              
222             #{
223             # use Data::Dumper;
224             #
225             # my $d = Data::Dumper->new([$self, $state], ["\$self", "\$state"]);
226             # print $d->Dump();
227             #}
228              
229 1         38 return $state;
230             }
231              
232             sub _process_plank_data
233             {
234 642     642   879 my $self = shift;
235              
236 642         844 my $state = shift;
237              
238 642         882 my $active = $state->[0];
239              
240             my @planks = (
241             map {
242             {
243 2568         7874 'len' => $self->{'plank_lens'}->[$_],
244             'x' => $state->[ $_ * 3 + 1 ],
245             'y' => $state->[ $_ * 3 + 1 + 1 ],
246             'dir' => $state->[ $_ * 3 + 2 + 1 ],
247             'active' => 0,
248             }
249 642         929 } ( 0 .. ( scalar( @{ $self->{'plank_lens'} } ) - 1 ) )
  642         1254  
250             );
251              
252 642         1246 foreach my $p (@planks)
253             {
254 2568         3459 my $p_dir = $p->{'dir'};
255 2568 50       4835 my $dir = ( $p_dir == 0 ) ? "E" : ( $p_dir == 1 ) ? "S" : "SE";
    100          
256 2568         3526 $p->{'dir'} = $dir;
257              
258 2568         4294 $p->{'end_x'} = $p->{'x'} + $cell_dirs{$dir}->[0] * $p->{'len'};
259 2568         4600 $p->{'end_y'} = $p->{'y'} + $cell_dirs{$dir}->[1] * $p->{'len'};
260             }
261              
262             # $ap is short for active plank
263 642         868 my $ap = $planks[$active];
264 642         901 $ap->{'active'} = 1;
265              
266 642         854 my (@queue);
267 642         1653 push @queue, [ $ap->{'x'}, $ap->{'y'} ], [ $ap->{'end_x'}, $ap->{'end_y'} ];
268 642         979 undef($ap);
269 642         1337 while ( my $point = pop(@queue) )
270             {
271 2113         3630 my ( $x, $y ) = @$point;
272 2113         3015 foreach my $p (@planks)
273             {
274 8452 100       13651 if ( $p->{'active'} )
275             {
276 3901         6215 next;
277             }
278 4551 100 100     8787 if ( ( $p->{'x'} == $x ) && ( $p->{'y'} == $y ) )
279             {
280 464         652 $p->{'active'} = 1;
281 464         865 push @queue, [ $p->{'end_x'}, $p->{'end_y'} ];
282             }
283 4551 100 100     11175 if ( ( $p->{'end_x'} == $x ) && ( $p->{'end_y'} == $y ) )
284             {
285 365         520 $p->{'active'} = 1;
286 365         833 push @queue, [ $p->{'x'}, $p->{'y'} ];
287             }
288             }
289             }
290 642         1278 foreach my $i ( 0 .. $#planks )
291             {
292 964 100       1705 if ( $planks[$i]->{'active'} )
293             {
294 642         887 $state->[0] = $i;
295 642         1294 return \@planks;
296             }
297             }
298             }
299              
300             sub pack_state
301             {
302 239     239 1 357 my $self = shift;
303              
304 239         308 my $state_vector = shift;
305 239         809 return pack( "c*", @$state_vector );
306             }
307              
308             sub unpack_state
309             {
310 83     83 1 129 my $self = shift;
311 83         151 my $state = shift;
312 83         353 return [ unpack( "c*", $state ) ];
313             }
314              
315             sub display_state
316             {
317 0     0 1 0 my $self = shift;
318 0         0 my $packed_state = shift;
319              
320 0         0 my $state = $self->unpack_state($packed_state);
321              
322 0         0 my $plank_data = $self->_process_plank_data($state);
323              
324 0         0 my @strings;
325 0         0 foreach my $p (@$plank_data)
326             {
327             push @strings,
328             sprintf( "(%i,%i) -> (%i,%i) %s",
329             $p->{'x'}, $p->{'y'}, $p->{'end_x'}, $p->{'end_y'},
330 0 0       0 ( $p->{'active'} ? "[active]" : "" ) );
331             }
332 0         0 return join( " ; ", @strings );
333             }
334              
335             sub check_if_final_state
336             {
337 83     83 1 138 my $self = shift;
338              
339 83         112 my $state = shift;
340              
341 83         156 my $plank_data = $self->_process_plank_data($state);
342              
343 83         139 my $goal_x = $self->{'goal_x'};
344 83         134 my $goal_y = $self->{'goal_y'};
345              
346             return (
347             scalar(
348             grep {
349 83         139 ( ( $_->{'x'} == $goal_x ) && ( $_->{'y'} == $goal_y ) )
350             || ( ( $_->{'end_x'} == $goal_x )
351 332 50 66     1544 && ( $_->{'end_y'} == $goal_y ) )
      33        
352             } @$plank_data
353             ) > 0
354             );
355             }
356              
357             sub enumerate_moves
358             {
359 82     82 1 140 my $self = shift;
360              
361 82         98 my $state = shift;
362              
363 82         153 my $plank_data = $self->_process_plank_data($state);
364              
365             # Declare some accessors
366 82         141 my $board = $self->{'board'};
367 82         147 my $width = $self->{'width'};
368 82         127 my $height = $self->{'height'};
369              
370 82         119 my $dirs_ptr = $self->{'dirs'};
371              
372 82         129 my @moves;
373              
374             my %current;
375             my $serialize = sub {
376 2851     2851   7611 return join "\t", @_;
377 82         315 };
378              
379 82         157 foreach my $plank (@$plank_data)
380             {
381             $current{ $serialize->( @$plank{qw(x y end_x end_y)} ) } =
382 328         666 $current{ $serialize->( @$plank{qw(end_x end_y x y)} ) } = 1;
383             }
384              
385 82         173 for my $to_move_idx ( 0 .. $#$plank_data )
386             {
387 328         458 my $to_move = $plank_data->[$to_move_idx];
388 328         477 my $len = $to_move->{'len'};
389 328 100       585 if ( !( $to_move->{'active'} ) )
390             {
391 166         251 next;
392             }
393 162         259 foreach my $move_to (@$plank_data)
394             {
395 648 100       1105 if ( !( $move_to->{'active'} ) )
396             {
397 256         400 next;
398             }
399 392         1114 for my $point (
400             [ $move_to->{'x'}, $move_to->{'y'} ],
401             [ $move_to->{'end_x'}, $move_to->{'end_y'} ]
402             )
403             {
404 784         1350 my ( $x, $y ) = @$point;
405 784         1151 DIR_LOOP: for my $dir (@$dirs_ptr) # (qw(E W S N))
406             {
407             # Find the other ending points of the plank
408 3136         4824 my $other_x = $x + $cell_dirs{$dir}->[0] * $len;
409 3136         4785 my $other_y = $y + $cell_dirs{$dir}->[1] * $len;
410              
411             # Check if we are within bounds
412 3136 100 100     8276 if ( ( $other_x < 0 ) || ( $other_x >= $width ) )
413             {
414 348         538 next;
415             }
416 2788 100 100     7170 if ( ( $other_y < 0 ) || ( $other_y >= $height ) )
417             {
418 593         1041 next;
419             }
420              
421             # Check that we're not moving one plank on top of the
422             # other or itself.
423 2195 100       3611 if (
424             exists
425             $current{ $serialize->( $x, $y, $other_x, $other_y ) } )
426             {
427 558         1073 next DIR_LOOP;
428             }
429              
430             # Check if there is a stump at the other end-point
431 1637 100       3123 if ( !$board->[$other_y]->[$other_x] )
432             {
433 1378         2289 next;
434             }
435              
436             # Check the validity of the intermediate points.
437 259         532 for ( my $offset = 1 ; $offset < $len ; $offset++ )
438             {
439 276         434 my $ix = $x + $cell_dirs{$dir}->[0] * $offset;
440 276         387 my $iy = $y + $cell_dirs{$dir}->[1] * $offset;
441              
442 276 100       486 if ( $board->[$iy]->[$ix] )
443             {
444 15         31 next DIR_LOOP;
445             }
446              
447             # Check if another plank has this point in between
448 261         336 my $collision_plank_idx = 0;
449 261         427 for my $plank (@$plank_data)
450             {
451             # Make sure we don't test a plank against
452             # a collisions with itself.
453 1032 100       1733 if ( $collision_plank_idx == $to_move_idx )
454             {
455 261         346 next;
456             }
457 771         1134 my $p_x = $plank->{'x'};
458 771         1067 my $p_y = $plank->{'y'};
459 771         1006 my $plank_dir = $plank->{'dir'};
460 771         1209 for my $i ( 0 .. $plank->{'len'} )
461             {
462 1892 100 100     3657 if ( ( $p_x == $ix ) && ( $p_y == $iy ) )
463             {
464 6         16 next DIR_LOOP;
465             }
466             }
467             continue
468             {
469 1886         2497 $p_x += $cell_dirs{$plank_dir}->[0];
470 1886         2920 $p_y += $cell_dirs{$plank_dir}->[1];
471             }
472             }
473             continue
474             {
475 1026         1608 $collision_plank_idx++;
476             }
477             }
478              
479             # A perfectly valid move - let's add it.
480 238         883 push @moves,
481             {
482             'p' => $to_move_idx,
483             'x' => $x,
484             'y' => $y,
485             'dir' => $dir
486             };
487             }
488             }
489             }
490             }
491              
492 82         765 return @moves;
493             }
494              
495             sub perform_move
496             {
497 238     238 1 357 my $self = shift;
498              
499 238         323 my $state = shift;
500 238         311 my $m = shift;
501              
502 238         400 my $plank_data = $self->_process_plank_data($state);
503              
504 238         369 my ( $x, $y, $p, $dir ) = @{$m}{qw(x y p dir)};
  238         539  
505 238         328 my $dir_idx;
506 238 100       618 if ( $dir eq "S" )
    100          
    100          
    50          
    0          
    0          
507             {
508 34         51 $dir_idx = 1;
509             }
510             elsif ( $dir eq "E" )
511             {
512 21         36 $dir_idx = 0;
513             }
514             elsif ( $dir eq "N" )
515             {
516 76         101 $dir_idx = 1;
517 76         130 $y -= $self->{'plank_lens'}->[$p];
518             }
519             elsif ( $dir eq "W" )
520             {
521 107         164 $dir_idx = 0;
522 107         177 $x -= $self->{'plank_lens'}->[$p];
523             }
524             elsif ( $dir eq "NW" )
525             {
526 0         0 $dir_idx = 2;
527 0         0 $y -= $self->{'plank_lens'}->[$p];
528 0         0 $x -= $self->{'plank_lens'}->[$p];
529             }
530             elsif ( $dir eq "SE" )
531             {
532 0         0 $dir_idx = 2;
533             }
534              
535 238         485 my $new_state = [@$state];
536              
537 238         433 @$new_state[0] = $p;
538 238         607 @$new_state[ ( 1 + $p * 3 ) .. ( 1 + $p * 3 + 2 ) ] = ( $x, $y, $dir_idx );
539              
540 238         564 $self->_process_plank_data($new_state);
541              
542 238         1287 return $new_state;
543             }
544              
545             sub render_move
546             {
547 19     19 1 1567 my $self = shift;
548              
549 19         26 my $move = shift;
550              
551 19 50       33 if ($move)
552             {
553             return sprintf(
554             "Move the Plank of Length %i to (%i,%i) %s",
555             $self->{'plank_lens'}->[ $move->{'p'} ],
556 19         38 @{$move}{qw(x y dir)}
  19         102  
557             );
558             }
559             else
560             {
561 0           return "";
562             }
563             }
564              
565             1;
566              
567             __END__