File Coverage

blib/lib/Games/Solitaire/Verify/Solution/ExpandMultiCardMoves.pm
Criterion Covered Total %
statement 160 171 93.5
branch 19 32 59.3
condition 18 21 85.7
subroutine 25 25 100.0
pod 1 1 100.0
total 223 250 89.2


line stmt bran cond sub pod time code
1             package Games::Solitaire::Verify::Solution::ExpandMultiCardMoves;
2             $Games::Solitaire::Verify::Solution::ExpandMultiCardMoves::VERSION = '0.2403';
3 2     2   109431 use warnings;
  2         13  
  2         73  
4 2     2   12 use strict;
  2         4  
  2         50  
5 2     2   45 use 5.014;
  2         7  
6              
7              
8 2     2   11 use parent 'Games::Solitaire::Verify::Solution::Base';
  2         4  
  2         20  
9              
10             # TODO : Merge with lib/Games/Solitaire/Verify/Solution.pm
11              
12 2     2   1227 use POSIX qw( ceil );
  2         11617  
  2         13  
13              
14 2     2   3487 use Games::Solitaire::Verify::Exception ();
  2         9  
  2         39  
15 2     2   1100 use Games::Solitaire::Verify::Card ();
  2         9  
  2         64  
16 2     2   915 use Games::Solitaire::Verify::Column ();
  2         9  
  2         49  
17 2     2   467 use Games::Solitaire::Verify::Move ();
  2         5  
  2         37  
18 2     2   1068 use Games::Solitaire::Verify::State ();
  2         5  
  2         71  
19              
20 2     2   14 use List::Util qw( min );
  2         5  
  2         3643  
21              
22             __PACKAGE__->mk_acc_ref(
23             [
24             qw(
25             _move_line
26             _output_fh
27             )
28             ]
29             );
30              
31              
32             sub _init
33             {
34 2     2   7 my ( $self, $args ) = @_;
35              
36 2         15 $self->SUPER::_init($args);
37              
38 2         13 $self->_st(undef);
39 2         12 $self->_reached_end(0);
40 2         12 $self->_output_fh( $args->{output_fh} );
41              
42 2         4 return 0;
43             }
44              
45             sub _out
46             {
47 3748     3748   7025 my ( $self, $text ) = @_;
48              
49 3748         11786 $self->_output_fh()->print($text);
50              
51 3748         33342 return ();
52             }
53              
54             sub _out_line
55             {
56 2962     2962   5978 my ( $self, $line ) = @_;
57              
58 2962         5936 return $self->_out($line);
59             }
60              
61             sub _assign_read_new_state
62             {
63 217     217   446 my ( $self, $str ) = @_;
64              
65             my $new_state = Games::Solitaire::Verify::State->new(
66             {
67             string => $str,
68 217         375 @{ $self->_V },
  217         1021  
69             }
70             );
71              
72 217 100       873 if ( !defined( $self->_st() ) )
73             {
74             # Do nothing.
75              
76             }
77             else
78             {
79 216 50       661 if ( $self->_st()->to_string() ne $str )
80             {
81 0         0 die "States don't match";
82             }
83             }
84 217         5663 $self->_st($new_state);
85              
86 217         502 return ();
87             }
88              
89             sub _read_state
90             {
91 434     434   789 my $self = shift;
92              
93 434         1194 my $line = $self->_l();
94              
95 434 50       1074 if ( $line ne "\n" )
96             {
97 0         0 die "Non empty line before state";
98             }
99              
100 434         1200 $self->_out_line($line);
101              
102 434         732 my $str = "";
103              
104 434   66     1003 while ( ( $line = $self->_l() ) && ( $line ne "\n" ) )
105             {
106 4340         10757 $str .= $line;
107             }
108              
109 434         1445 $self->_assign_read_new_state($str);
110              
111 434         1223 $self->_out($str);
112              
113 434         1251 $self->_out_line("\n");
114 434   66     1149 while ( defined( $line = $self->_l() ) && ( $line eq "\n" ) )
115             {
116 434         1050 $self->_out_line($line);
117             }
118              
119 434 50       2316 if ( $line !~ m{\A={3,}\n\z} )
120             {
121 0         0 die "No ======== separator";
122             }
123 434         1306 $self->_out_line($line);
124              
125 434         1055 return ();
126             }
127              
128             sub _read_move
129             {
130 434     434   736 my $self = shift;
131              
132 434         1033 my $line = $self->_l();
133              
134 434 50       1098 if ( $line ne "\n" )
135             {
136 0         0 die "No empty line before move";
137             }
138              
139 434         1150 $self->_out_line($line);
140              
141 434         967 $line = $self->_l();
142              
143 434 100       987 if ( $line eq "This game is solveable.\n" )
144             {
145 2         10 $self->_reached_end(1);
146 2         8 $self->_out_line($line);
147              
148 2         16 while ( defined( $line = $self->_l() ) )
149             {
150 4         11 $self->_out_line($line);
151             }
152              
153 2         12 return "END";
154             }
155              
156 432         1242 chomp($line);
157              
158 432         1111 $self->_move_line($line);
159              
160 432         2114 $self->_move(
161             Games::Solitaire::Verify::Move->new(
162             {
163             fcs_string => $line,
164             game => $self->_variant(),
165             }
166             )
167             );
168              
169 432         1336 return ();
170             }
171              
172              
173             sub _find_max_step
174             {
175 32     32   73 my ( $self, $n ) = @_;
176              
177 32         57 my $x = 1;
178              
179 32         96 while ( ( $x << 1 ) < $n )
180             {
181 6         17 $x <<= 1;
182             }
183              
184 32         85 return $x;
185             }
186              
187             sub _apply_move
188             {
189 432     432   779 my $self = shift;
190              
191 432 100 100     2667 if ( ( $self->_move->source_type eq "stack" )
      100        
      66        
192             && ( $self->_move->dest_type eq "stack" )
193             && ( $self->_move->num_cards > 1 )
194             && ( $self->_variant_params->sequence_move() eq "limited" ) )
195             {
196 80         250 my $ultimate_num_cards = $self->_move->num_cards;
197 80         203 my $ultimate_source = $self->_move->source;
198 80         191 my $ultimate_dest = $self->_move->dest;
199              
200             # Need to process this move.
201 80         181 my @empty_fc_indexes;
202             my @empty_stack_indexes;
203              
204 80         276 foreach my $idx ( 0 .. ( $self->_st->num_freecells() - 1 ) )
205             {
206 320 100       815 if ( !defined( $self->_st->get_freecell($idx) ) )
207             {
208 150         385 push @empty_fc_indexes, $idx;
209             }
210             }
211              
212 80         320 foreach my $idx ( 0 .. ( $self->_st->num_columns() - 1 ) )
213             {
214 640 100 100     2361 if ( ( $idx != $ultimate_dest )
      100        
215             && ( $idx != $ultimate_source )
216             && ( !$self->_st->get_column($idx)->len() ) )
217             {
218 32         70 push @empty_stack_indexes, $idx;
219             }
220             }
221              
222 80         199 my @num_cards_moved_at_each_stage;
223              
224 80         149 my $num_cards = 0;
225 80         175 push @num_cards_moved_at_each_stage, $num_cards;
226 80         182 my $step_width = 1 + @empty_fc_indexes;
227 80         371 while (
228             (
229             $num_cards =
230             min( $num_cards + $step_width, $ultimate_num_cards )
231             ) < $ultimate_num_cards
232             )
233             {
234 26         82 push @num_cards_moved_at_each_stage, $num_cards;
235             }
236 80         181 push @num_cards_moved_at_each_stage, $num_cards;
237              
238             # Initialised to the null sub.
239             my $output_state_promise = sub {
240 80     80   143 return ();
241 80         394 };
242              
243             my $past_first_output_state_promise = sub {
244 352     352   1181 $self->_out(
245             "\n" . $self->_st->to_string . "\n\n====================\n\n" );
246              
247 352         729 return ();
248 80         361 };
249              
250             my $add_move = sub {
251 432     432   891 my ($move_line) = @_;
252              
253 432         1138 $output_state_promise->();
254              
255 432         1549 $self->_out_line( $move_line . "\n" );
256              
257 432 50       2166 if (
258             my $verdict = $self->_st()->verify_and_perform_move(
259             Games::Solitaire::Verify::Move->new(
260             {
261             fcs_string => $move_line,
262             game => $self->_variant(),
263             }
264             )
265             )
266             )
267             {
268 0         0 Games::Solitaire::Verify::Exception::VerifyMove->throw(
269             error => "Wrong Move",
270             problem => $verdict,
271             );
272             }
273              
274 432         1643 $output_state_promise = $past_first_output_state_promise;
275              
276 432         1008 return ();
277 80         419 };
278              
279             my $move_using_freecells = sub {
280 144     144   398 my ( $source, $dest, $count ) = @_;
281              
282 144         280 my $num_cards_thru_freecell = $count - 1;
283 144         367 for my $i ( 0 .. $num_cards_thru_freecell - 1 )
284             {
285 144         617 $add_move->(
286             "Move a card from stack $source to freecell $empty_fc_indexes[$i]"
287             );
288             }
289 144         634 $add_move->("Move 1 cards from stack $source to stack $dest");
290              
291 144         429 for my $i ( reverse( 0 .. $num_cards_thru_freecell - 1 ) )
292             {
293 144         556 $add_move->(
294             "Move a card from freecell $empty_fc_indexes[$i] to stack $dest"
295             );
296             }
297              
298 144         289 return ();
299 80         459 };
300              
301 80         149 my $recursive_move;
302             $recursive_move = sub {
303 144     144   419 my ( $source, $dest, $num_cards, $empty_cols ) = @_;
304              
305 144 50       345 if ( $num_cards <= 0 )
306             {
307             # Do nothing - the no-op.
308             #$move_using_freecells->($source, $dest,
309             # $num_cards_moved_at_each_stage[$depth] -
310             # $num_cards_moved_at_each_stage[$depth-1]
311             #);
312 0         0 return ();
313             }
314             else
315             {
316 144         299 my @running_empty_cols = @$empty_cols;
317 144         226 my @steps;
318              
319 144         759 while ( ceil( $num_cards / $step_width ) > 1 )
320             {
321             # Top power of two in $num_steps
322 32         116 my $rec_num_steps = $self->_find_max_step(
323             ceil( $num_cards / $step_width ) );
324 32         75 my $count_cards = $rec_num_steps * $step_width;
325 32         59 my $temp_dest = shift(@running_empty_cols);
326 32         161 $recursive_move->(
327             $source, $temp_dest, $count_cards,
328             [@running_empty_cols],
329             );
330              
331 32         151 push @steps,
332             +{
333             'source' => $source,
334             'dest' => $temp_dest,
335             count => $count_cards
336             };
337 32         169 $num_cards -= $count_cards;
338             }
339 144         455 $move_using_freecells->( $source, $dest, $num_cards );
340              
341 144         370 foreach my $s ( reverse(@steps) )
342             {
343             $recursive_move->(
344 32         142 $s->{dest}, $dest, $s->{count}, [@running_empty_cols]
345             );
346             @running_empty_cols =
347 32         180 ( sort { $a <=> $b } @running_empty_cols, $s->{dest} );
  8         44  
348             }
349 144         429 return ();
350             }
351 80         470 };
352              
353 80         263 $recursive_move->(
354             $ultimate_source, $ultimate_dest,
355             $ultimate_num_cards, [@empty_stack_indexes],
356             );
357             }
358             else
359             {
360 352         1324 $self->_out_line( $self->_move_line . "\n" );
361 352 50       1202 if ( my $verdict =
362             $self->_st()->verify_and_perform_move( $self->_move() ) )
363             {
364 0         0 Games::Solitaire::Verify::Exception::VerifyMove->throw(
365             error => "Wrong Move",
366             problem => $verdict,
367             );
368             }
369             }
370              
371 432         826 return ();
372             }
373              
374              
375             sub verify
376             {
377 2     2 1 17 my $self = shift;
378              
379 2         6 eval {
380              
381 2         14 my $line = $self->_l();
382              
383 2 50       21 if ( $line !~ m{\A(-=)+-\n\z} )
384             {
385 0         0 die "Incorrect start";
386             }
387 2         13 $self->_out_line($line);
388              
389 2         15 $self->_read_state();
390              
391 2         12 while ( !defined( scalar( $self->_read_move() ) ) )
392             {
393 432         1265 $self->_apply_move();
394 432         1064 $self->_read_state();
395             }
396             };
397              
398 2         5 my $err;
399 2 50       15 if ( !$@ )
    0          
400             {
401             # Do nothing - no exception was thrown.
402             }
403             elsif (
404             $err = Exception::Class->caught(
405             'Games::Solitaire::Verify::Exception::VerifyMove')
406             )
407             {
408 0         0 return { error => $err, line_num => $self->_ln(), };
409             }
410             else
411             {
412 0         0 $err = Exception::Class->caught();
413 0 0       0 ref $err ? $err->rethrow : die $err;
414             }
415              
416 2         9 return ();
417             }
418              
419             1; # End of Games::Solitaire::Verify::Solution::ExpandMultiCardMoves
420              
421             __END__