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.2601';
3 5     5   506940 use warnings;
  5         15  
  5         369  
4 5     5   32 use strict;
  5         11  
  5         145  
5 5     5   113 use 5.014;
  5         20  
6              
7              
8 5     5   28 use parent 'Games::Solitaire::Verify::Solution::Base';
  5         24  
  5         39  
9              
10             # TODO : Merge with lib/Games/Solitaire/Verify/Solution.pm
11              
12 5     5   3255 use POSIX qw( ceil );
  5         40751  
  5         35  
13              
14 5     5   11799 use Games::Solitaire::Verify::Exception ();
  5         15  
  5         183  
15 5     5   3089 use Games::Solitaire::Verify::Card ();
  5         17  
  5         205  
16 5     5   2846 use Games::Solitaire::Verify::Column ();
  5         22  
  5         207  
17 5     5   2134 use Games::Solitaire::Verify::Move ();
  5         32  
  5         136  
18 5     5   3051 use Games::Solitaire::Verify::State ();
  5         21  
  5         259  
19              
20 5     5   44 use List::Util qw( min );
  5         8  
  5         11819  
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   8 my ( $self, $args ) = @_;
35              
36 2         16 $self->SUPER::_init($args);
37              
38 2         11 $self->_st(undef);
39 2         12 $self->_reached_end(0);
40 2         13 $self->_output_fh( $args->{output_fh} );
41              
42 2         6 return 0;
43             }
44              
45             sub _out
46             {
47 3748     3748   8015 my ( $self, $text ) = @_;
48              
49 3748         14488 $self->_output_fh()->print($text);
50              
51 3748         38457 return ();
52             }
53              
54             sub _out_line
55             {
56 2962     2962   6872 my ( $self, $line ) = @_;
57              
58 2962         6925 return $self->_out($line);
59             }
60              
61             sub _assign_read_new_state
62             {
63 217     217   471 my ( $self, $str ) = @_;
64              
65             my $new_state = Games::Solitaire::Verify::State->new(
66             {
67             string => $str,
68 217         412 @{ $self->_V },
  217         1598  
69             }
70             );
71              
72 217 100       1219 if ( !defined( $self->_st() ) )
73             {
74             # Do nothing.
75              
76             }
77             else
78             {
79 216 50       816 if ( $self->_st()->to_string() ne $str )
80             {
81 0         0 die "States don't match";
82             }
83             }
84 217         9256 $self->_st($new_state);
85              
86 217         654 return ();
87             }
88              
89             sub _read_state
90             {
91 434     434   884 my $self = shift;
92              
93 434         1507 my $line = $self->_l();
94              
95 434 50       1271 if ( $line ne "\n" )
96             {
97 0         0 die "Non empty line before state";
98             }
99              
100 434         1746 $self->_out_line($line);
101              
102 434         901 my $str = "";
103              
104 434   66     1230 while ( ( $line = $self->_l() ) && ( $line ne "\n" ) )
105             {
106 4340         11972 $str .= $line;
107             }
108              
109 434         1586 $self->_assign_read_new_state($str);
110              
111 434         1419 $self->_out($str);
112              
113 434         1388 $self->_out_line("\n");
114 434   66     1518 while ( defined( $line = $self->_l() ) && ( $line eq "\n" ) )
115             {
116 434         1113 $self->_out_line($line);
117             }
118              
119 434 50       2643 if ( $line !~ m{\A={3,}\n\z} )
120             {
121 0         0 die "No ======== separator";
122             }
123 434         2011 $self->_out_line($line);
124              
125 434         1303 return ();
126             }
127              
128             sub _read_move
129             {
130 434     434   814 my $self = shift;
131              
132 434         1165 my $line = $self->_l();
133              
134 434 50       1367 if ( $line ne "\n" )
135             {
136 0         0 die "No empty line before move";
137             }
138              
139 434         1265 $self->_out_line($line);
140              
141 434         1139 $line = $self->_l();
142              
143 434 100       1340 if ( $line eq "This game is solveable.\n" )
144             {
145 2         9 $self->_reached_end(1);
146 2         7 $self->_out_line($line);
147              
148 2         7 while ( defined( $line = $self->_l() ) )
149             {
150 4         12 $self->_out_line($line);
151             }
152              
153 2         12 return "END";
154             }
155              
156 432         1009 chomp($line);
157              
158 432         1378 $self->_move_line($line);
159              
160 432         2981 $self->_move(
161             Games::Solitaire::Verify::Move->new(
162             {
163             fcs_string => $line,
164             game => $self->_variant(),
165             }
166             )
167             );
168              
169 432         1781 return ();
170             }
171              
172              
173             sub _find_max_step
174             {
175 32     32   85 my ( $self, $n ) = @_;
176              
177 32         65 my $x = 1;
178              
179 32         113 while ( ( $x << 1 ) < $n )
180             {
181 6         19 $x <<= 1;
182             }
183              
184 32         84 return $x;
185             }
186              
187             sub _apply_move
188             {
189 432     432   1006 my $self = shift;
190              
191 432 100 100     3306 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         235 my $ultimate_num_cards = $self->_move->num_cards;
197 80         275 my $ultimate_source = $self->_move->source;
198 80         207 my $ultimate_dest = $self->_move->dest;
199              
200             # Need to process this move.
201 80         209 my @empty_fc_indexes;
202             my @empty_stack_indexes;
203              
204 80         329 foreach my $idx ( 0 .. ( $self->_st->num_freecells() - 1 ) )
205             {
206 320 100       914 if ( !defined( $self->_st->get_freecell($idx) ) )
207             {
208 150         401 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     2777 if ( ( $idx != $ultimate_dest )
      100        
215             && ( $idx != $ultimate_source )
216             && ( !$self->_st->get_column($idx)->len() ) )
217             {
218 32         81 push @empty_stack_indexes, $idx;
219             }
220             }
221              
222 80         192 my @num_cards_moved_at_each_stage;
223              
224 80         180 my $num_cards = 0;
225 80         229 push @num_cards_moved_at_each_stage, $num_cards;
226 80         183 my $step_width = 1 + @empty_fc_indexes;
227 80         464 while (
228             (
229             $num_cards =
230             min( $num_cards + $step_width, $ultimate_num_cards )
231             ) < $ultimate_num_cards
232             )
233             {
234 26         129 push @num_cards_moved_at_each_stage, $num_cards;
235             }
236 80         191 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   142 return ();
241 80         425 };
242              
243             my $past_first_output_state_promise = sub {
244 352     352   1490 $self->_out(
245             "\n" . $self->_st->to_string . "\n\n====================\n\n" );
246              
247 352         767 return ();
248 80         482 };
249              
250             my $add_move = sub {
251 432     432   1105 my ($move_line) = @_;
252              
253 432         1202 $output_state_promise->();
254              
255 432         1696 $self->_out_line( $move_line . "\n" );
256              
257 432 50       3169 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         2200 $output_state_promise = $past_first_output_state_promise;
275              
276 432         1187 return ();
277 80         663 };
278              
279             my $move_using_freecells = sub {
280 144     144   503 my ( $source, $dest, $count ) = @_;
281              
282 144         299 my $num_cards_thru_freecell = $count - 1;
283 144         402 for my $i ( 0 .. $num_cards_thru_freecell - 1 )
284             {
285 144         654 $add_move->(
286             "Move a card from stack $source to freecell $empty_fc_indexes[$i]"
287             );
288             }
289 144         573 $add_move->("Move 1 cards from stack $source to stack $dest");
290              
291 144         525 for my $i ( reverse( 0 .. $num_cards_thru_freecell - 1 ) )
292             {
293 144         563 $add_move->(
294             "Move a card from freecell $empty_fc_indexes[$i] to stack $dest"
295             );
296             }
297              
298 144         338 return ();
299 80         513 };
300              
301 80         162 my $recursive_move;
302             $recursive_move = sub {
303 144     144   449 my ( $source, $dest, $num_cards, $empty_cols ) = @_;
304              
305 144 50       380 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         339 my @running_empty_cols = @$empty_cols;
317 144         250 my @steps;
318              
319 144         911 while ( ceil( $num_cards / $step_width ) > 1 )
320             {
321             # Top power of two in $num_steps
322 32         169 my $rec_num_steps = $self->_find_max_step(
323             ceil( $num_cards / $step_width ) );
324 32         72 my $count_cards = $rec_num_steps * $step_width;
325 32         65 my $temp_dest = shift(@running_empty_cols);
326 32         183 $recursive_move->(
327             $source, $temp_dest, $count_cards,
328             [@running_empty_cols],
329             );
330              
331 32         165 push @steps,
332             +{
333             'source' => $source,
334             'dest' => $temp_dest,
335             count => $count_cards
336             };
337 32         199 $num_cards -= $count_cards;
338             }
339 144         503 $move_using_freecells->( $source, $dest, $num_cards );
340              
341 144         349 foreach my $s ( reverse(@steps) )
342             {
343             $recursive_move->(
344 32         145 $s->{dest}, $dest, $s->{count}, [@running_empty_cols]
345             );
346             @running_empty_cols =
347 32         149 ( sort { $a <=> $b } @running_empty_cols, $s->{dest} );
  8         40  
348             }
349 144         489 return ();
350             }
351 80         513 };
352              
353 80         314 $recursive_move->(
354             $ultimate_source, $ultimate_dest,
355             $ultimate_num_cards, [@empty_stack_indexes],
356             );
357             }
358             else
359             {
360 352         1657 $self->_out_line( $self->_move_line . "\n" );
361 352 50       1533 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         978 return ();
372             }
373              
374              
375             sub verify
376             {
377 2     2 1 18 my $self = shift;
378              
379 2         6 eval {
380              
381 2         12 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         14 $self->_read_state();
390              
391 2         12 while ( !defined( scalar( $self->_read_move() ) ) )
392             {
393 432         1522 $self->_apply_move();
394 432         1321 $self->_read_state();
395             }
396             };
397              
398 2         5 my $err;
399 2 50       9 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         11 return ();
417             }
418              
419             1; # End of Games::Solitaire::Verify::Solution::ExpandMultiCardMoves
420              
421             __END__