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.2401';
3 2     2   107057 use warnings;
  2         13  
  2         69  
4 2     2   10 use strict;
  2         5  
  2         47  
5 2     2   41 use 5.014;
  2         8  
6              
7              
8 2     2   12 use parent 'Games::Solitaire::Verify::Solution::Base';
  2         4  
  2         19  
9              
10             # TODO : Merge with lib/Games/Solitaire/Verify/Solution.pm
11              
12 2     2   1195 use POSIX qw( ceil );
  2         12090  
  2         11  
13              
14 2     2   3406 use Games::Solitaire::Verify::Exception ();
  2         7  
  2         35  
15 2     2   979 use Games::Solitaire::Verify::Card ();
  2         7  
  2         53  
16 2     2   931 use Games::Solitaire::Verify::Column ();
  2         6  
  2         53  
17 2     2   453 use Games::Solitaire::Verify::Move ();
  2         5  
  2         36  
18 2     2   1012 use Games::Solitaire::Verify::State ();
  2         8  
  2         85  
19              
20 2     2   17 use List::Util qw( min );
  2         7  
  2         3734  
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         15 $self->SUPER::_init($args);
37              
38 2         11 $self->_st(undef);
39 2         11 $self->_reached_end(0);
40 2         11 $self->_output_fh( $args->{output_fh} );
41              
42 2         4 return 0;
43             }
44              
45             sub _out
46             {
47 3748     3748   7096 my ( $self, $text ) = @_;
48              
49 3748         11670 $self->_output_fh()->print($text);
50              
51 3748         32445 return;
52             }
53              
54             sub _out_line
55             {
56 2962     2962   6039 my ( $self, $line ) = @_;
57              
58 2962         5835 return $self->_out($line);
59             }
60              
61             sub _assign_read_new_state
62             {
63 217     217   439 my ( $self, $str ) = @_;
64              
65             my $new_state = Games::Solitaire::Verify::State->new(
66             {
67             string => $str,
68 217         372 @{ $self->_V },
  217         1017  
69             }
70             );
71              
72 217 100       832 if ( !defined( $self->_st() ) )
73             {
74             # Do nothing.
75              
76             }
77             else
78             {
79 216 50       646 if ( $self->_st()->to_string() ne $str )
80             {
81 0         0 die "States don't match";
82             }
83             }
84 217         5522 $self->_st($new_state);
85              
86 217         466 return;
87             }
88              
89             sub _read_state
90             {
91 434     434   807 my $self = shift;
92              
93 434         1571 my $line = $self->_l();
94              
95 434 50       1080 if ( $line ne "\n" )
96             {
97 0         0 die "Non empty line before state";
98             }
99              
100 434         1199 $self->_out_line($line);
101              
102 434         754 my $str = "";
103              
104 434   66     1056 while ( ( $line = $self->_l() ) && ( $line ne "\n" ) )
105             {
106 4340         11004 $str .= $line;
107             }
108              
109 434         1367 $self->_assign_read_new_state($str);
110              
111 434         1202 $self->_out($str);
112              
113 434         1149 $self->_out_line("\n");
114 434   66     1214 while ( defined( $line = $self->_l() ) && ( $line eq "\n" ) )
115             {
116 434         1006 $self->_out_line($line);
117             }
118              
119 434 50       2131 if ( $line !~ m{\A={3,}\n\z} )
120             {
121 0         0 die "No ======== separator";
122             }
123 434         1167 $self->_out_line($line);
124              
125 434         1025 return;
126             }
127              
128             sub _read_move
129             {
130 434     434   752 my $self = shift;
131              
132 434         1024 my $line = $self->_l();
133              
134 434 50       1055 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         957 $line = $self->_l();
142              
143 434 100       1118 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         10 while ( defined( $line = $self->_l() ) )
149             {
150 4         11 $self->_out_line($line);
151             }
152              
153 2         14 return "END";
154             }
155              
156 432         829 chomp($line);
157              
158 432         1158 $self->_move_line($line);
159              
160 432         2096 $self->_move(
161             Games::Solitaire::Verify::Move->new(
162             {
163             fcs_string => $line,
164             game => $self->_variant(),
165             }
166             )
167             );
168              
169 432         1402 return;
170             }
171              
172              
173             sub _find_max_step
174             {
175 32     32   70 my ( $self, $n ) = @_;
176              
177 32         73 my $x = 1;
178              
179 32         90 while ( ( $x << 1 ) < $n )
180             {
181 6         18 $x <<= 1;
182             }
183              
184 32         65 return $x;
185             }
186              
187             sub _apply_move
188             {
189 432     432   760 my $self = shift;
190              
191 432 100 100     2613 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         222 my $ultimate_num_cards = $self->_move->num_cards;
197 80         196 my $ultimate_source = $self->_move->source;
198 80         190 my $ultimate_dest = $self->_move->dest;
199              
200             # Need to process this move.
201 80         167 my @empty_fc_indexes;
202             my @empty_stack_indexes;
203              
204 80         266 foreach my $idx ( 0 .. ( $self->_st->num_freecells() - 1 ) )
205             {
206 320 100       783 if ( !defined( $self->_st->get_freecell($idx) ) )
207             {
208 150         404 push @empty_fc_indexes, $idx;
209             }
210             }
211              
212 80         277 foreach my $idx ( 0 .. ( $self->_st->num_columns() - 1 ) )
213             {
214 640 100 100     2403 if ( ( $idx != $ultimate_dest )
      100        
215             && ( $idx != $ultimate_source )
216             && ( !$self->_st->get_column($idx)->len() ) )
217             {
218 32         75 push @empty_stack_indexes, $idx;
219             }
220             }
221              
222 80         175 my @num_cards_moved_at_each_stage;
223              
224 80         155 my $num_cards = 0;
225 80         178 push @num_cards_moved_at_each_stage, $num_cards;
226 80         173 my $step_width = 1 + @empty_fc_indexes;
227 80         442 while (
228             (
229             $num_cards =
230             min( $num_cards + $step_width, $ultimate_num_cards )
231             ) < $ultimate_num_cards
232             )
233             {
234 26         77 push @num_cards_moved_at_each_stage, $num_cards;
235             }
236 80         195 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         381 };
242              
243             my $past_first_output_state_promise = sub {
244 352     352   1060 $self->_out(
245             "\n" . $self->_st->to_string . "\n\n====================\n\n" );
246              
247 352         765 return;
248 80         372 };
249              
250             my $add_move = sub {
251 432     432   999 my ($move_line) = @_;
252              
253 432         1111 $output_state_promise->();
254              
255 432         1416 $self->_out_line( $move_line . "\n" );
256              
257 432 50       2163 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         1566 $output_state_promise = $past_first_output_state_promise;
275              
276 432         1095 return;
277 80         434 };
278              
279             my $move_using_freecells = sub {
280 144     144   430 my ( $source, $dest, $count ) = @_;
281              
282 144         265 my $num_cards_thru_freecell = $count - 1;
283 144         382 for my $i ( 0 .. $num_cards_thru_freecell - 1 )
284             {
285 144         611 $add_move->(
286             "Move a card from stack $source to freecell $empty_fc_indexes[$i]"
287             );
288             }
289 144         592 $add_move->("Move 1 cards from stack $source to stack $dest");
290              
291 144         438 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         307 return;
299 80         511 };
300              
301 80         183 my $recursive_move;
302             $recursive_move = sub {
303 144     144   418 my ( $source, $dest, $num_cards, $empty_cols ) = @_;
304              
305 144 50       373 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         316 my @running_empty_cols = @$empty_cols;
317 144         244 my @steps;
318              
319 144         797 while ( ceil( $num_cards / $step_width ) > 1 )
320             {
321             # Top power of two in $num_steps
322 32         127 my $rec_num_steps = $self->_find_max_step(
323             ceil( $num_cards / $step_width ) );
324 32         80 my $count_cards = $rec_num_steps * $step_width;
325 32         61 my $temp_dest = shift(@running_empty_cols);
326 32         157 $recursive_move->(
327             $source, $temp_dest, $count_cards,
328             [@running_empty_cols],
329             );
330              
331 32         129 push @steps,
332             +{
333             'source' => $source,
334             'dest' => $temp_dest,
335             count => $count_cards
336             };
337 32         166 $num_cards -= $count_cards;
338             }
339 144         474 $move_using_freecells->( $source, $dest, $num_cards );
340              
341 144         360 foreach my $s ( reverse(@steps) )
342             {
343             $recursive_move->(
344 32         128 $s->{dest}, $dest, $s->{count}, [@running_empty_cols]
345             );
346             @running_empty_cols =
347 32         156 ( sort { $a <=> $b } @running_empty_cols, $s->{dest} );
  8         37  
348             }
349 144         424 return;
350             }
351 80         488 };
352              
353 80         259 $recursive_move->(
354             $ultimate_source, $ultimate_dest,
355             $ultimate_num_cards, [@empty_stack_indexes],
356             );
357             }
358             else
359             {
360 352         1314 $self->_out_line( $self->_move_line . "\n" );
361 352 50       1147 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         868 return;
372             }
373              
374              
375             sub verify
376             {
377 2     2 1 15 my $self = shift;
378              
379 2         7 eval {
380              
381 2         13 my $line = $self->_l();
382              
383 2 50       20 if ( $line !~ m{\A(-=)+-\n\z} )
384             {
385 0         0 die "Incorrect start";
386             }
387 2         12 $self->_out_line($line);
388              
389 2         12 $self->_read_state();
390              
391 2         12 while ( !defined( scalar( $self->_read_move() ) ) )
392             {
393 432         1186 $self->_apply_move();
394 432         1104 $self->_read_state();
395             }
396             };
397              
398 2         5 my $err;
399 2 50       14 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         12 return;
417             }
418              
419             1; # End of Games::Solitaire::Verify::Solution::ExpandMultiCardMoves
420              
421             __END__