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.2402';
3 2     2   108174 use warnings;
  2         14  
  2         77  
4 2     2   12 use strict;
  2         4  
  2         54  
5 2     2   50 use 5.014;
  2         8  
6              
7              
8 2     2   14 use parent 'Games::Solitaire::Verify::Solution::Base';
  2         11  
  2         14  
9              
10             # TODO : Merge with lib/Games/Solitaire/Verify/Solution.pm
11              
12 2     2   1149 use POSIX qw( ceil );
  2         11732  
  2         12  
13              
14 2     2   3741 use Games::Solitaire::Verify::Exception ();
  2         8  
  2         39  
15 2     2   1035 use Games::Solitaire::Verify::Card ();
  2         6  
  2         64  
16 2     2   926 use Games::Solitaire::Verify::Column ();
  2         5  
  2         49  
17 2     2   452 use Games::Solitaire::Verify::Move ();
  2         5  
  2         78  
18 2     2   1080 use Games::Solitaire::Verify::State ();
  2         6  
  2         68  
19              
20 2     2   16 use List::Util qw( min );
  2         4  
  2         3459  
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         14 $self->SUPER::_init($args);
37              
38 2         12 $self->_st(undef);
39 2         11 $self->_reached_end(0);
40 2         12 $self->_output_fh( $args->{output_fh} );
41              
42 2         6 return 0;
43             }
44              
45             sub _out
46             {
47 3748     3748   7548 my ( $self, $text ) = @_;
48              
49 3748         12127 $self->_output_fh()->print($text);
50              
51 3748         32574 return;
52             }
53              
54             sub _out_line
55             {
56 2962     2962   5948 my ( $self, $line ) = @_;
57              
58 2962         5615 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         393 @{ $self->_V },
  217         1097  
69             }
70             );
71              
72 217 100       895 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         5835 $self->_st($new_state);
85              
86 217         494 return;
87             }
88              
89             sub _read_state
90             {
91 434     434   797 my $self = shift;
92              
93 434         1183 my $line = $self->_l();
94              
95 434 50       1125 if ( $line ne "\n" )
96             {
97 0         0 die "Non empty line before state";
98             }
99              
100 434         1278 $self->_out_line($line);
101              
102 434         738 my $str = "";
103              
104 434   66     1090 while ( ( $line = $self->_l() ) && ( $line ne "\n" ) )
105             {
106 4340         11154 $str .= $line;
107             }
108              
109 434         1436 $self->_assign_read_new_state($str);
110              
111 434         1216 $self->_out($str);
112              
113 434         1160 $self->_out_line("\n");
114 434   66     1146 while ( defined( $line = $self->_l() ) && ( $line eq "\n" ) )
115             {
116 434         1167 $self->_out_line($line);
117             }
118              
119 434 50       2084 if ( $line !~ m{\A={3,}\n\z} )
120             {
121 0         0 die "No ======== separator";
122             }
123 434         1228 $self->_out_line($line);
124              
125 434         1065 return;
126             }
127              
128             sub _read_move
129             {
130 434     434   828 my $self = shift;
131              
132 434         990 my $line = $self->_l();
133              
134 434 50       1155 if ( $line ne "\n" )
135             {
136 0         0 die "No empty line before move";
137             }
138              
139 434         1138 $self->_out_line($line);
140              
141 434         946 $line = $self->_l();
142              
143 434 100       1057 if ( $line eq "This game is solveable.\n" )
144             {
145 2         7 $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         10 return "END";
154             }
155              
156 432         960 chomp($line);
157              
158 432         1181 $self->_move_line($line);
159              
160 432         1983 $self->_move(
161             Games::Solitaire::Verify::Move->new(
162             {
163             fcs_string => $line,
164             game => $self->_variant(),
165             }
166             )
167             );
168              
169 432         1300 return;
170             }
171              
172              
173             sub _find_max_step
174             {
175 32     32   68 my ( $self, $n ) = @_;
176              
177 32         58 my $x = 1;
178              
179 32         89 while ( ( $x << 1 ) < $n )
180             {
181 6         21 $x <<= 1;
182             }
183              
184 32         64 return $x;
185             }
186              
187             sub _apply_move
188             {
189 432     432   843 my $self = shift;
190              
191 432 100 100     2683 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         223 my $ultimate_num_cards = $self->_move->num_cards;
197 80         179 my $ultimate_source = $self->_move->source;
198 80         192 my $ultimate_dest = $self->_move->dest;
199              
200             # Need to process this move.
201 80         168 my @empty_fc_indexes;
202             my @empty_stack_indexes;
203              
204 80         315 foreach my $idx ( 0 .. ( $self->_st->num_freecells() - 1 ) )
205             {
206 320 100       802 if ( !defined( $self->_st->get_freecell($idx) ) )
207             {
208 150         362 push @empty_fc_indexes, $idx;
209             }
210             }
211              
212 80         282 foreach my $idx ( 0 .. ( $self->_st->num_columns() - 1 ) )
213             {
214 640 100 100     2389 if ( ( $idx != $ultimate_dest )
      100        
215             && ( $idx != $ultimate_source )
216             && ( !$self->_st->get_column($idx)->len() ) )
217             {
218 32         82 push @empty_stack_indexes, $idx;
219             }
220             }
221              
222 80         164 my @num_cards_moved_at_each_stage;
223              
224 80         146 my $num_cards = 0;
225 80         170 push @num_cards_moved_at_each_stage, $num_cards;
226 80         160 my $step_width = 1 + @empty_fc_indexes;
227 80         414 while (
228             (
229             $num_cards =
230             min( $num_cards + $step_width, $ultimate_num_cards )
231             ) < $ultimate_num_cards
232             )
233             {
234 26         76 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         396 };
242              
243             my $past_first_output_state_promise = sub {
244 352     352   1152 $self->_out(
245             "\n" . $self->_st->to_string . "\n\n====================\n\n" );
246              
247 352         737 return;
248 80         366 };
249              
250             my $add_move = sub {
251 432     432   902 my ($move_line) = @_;
252              
253 432         1056 $output_state_promise->();
254              
255 432         1458 $self->_out_line( $move_line . "\n" );
256              
257 432 50       2149 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         1564 $output_state_promise = $past_first_output_state_promise;
275              
276 432         1088 return;
277 80         399 };
278              
279             my $move_using_freecells = sub {
280 144     144   386 my ( $source, $dest, $count ) = @_;
281              
282 144         281 my $num_cards_thru_freecell = $count - 1;
283 144         352 for my $i ( 0 .. $num_cards_thru_freecell - 1 )
284             {
285 144         663 $add_move->(
286             "Move a card from stack $source to freecell $empty_fc_indexes[$i]"
287             );
288             }
289 144         586 $add_move->("Move 1 cards from stack $source to stack $dest");
290              
291 144         410 for my $i ( reverse( 0 .. $num_cards_thru_freecell - 1 ) )
292             {
293 144         564 $add_move->(
294             "Move a card from freecell $empty_fc_indexes[$i] to stack $dest"
295             );
296             }
297              
298 144         284 return;
299 80         433 };
300              
301 80         153 my $recursive_move;
302             $recursive_move = sub {
303 144     144   414 my ( $source, $dest, $num_cards, $empty_cols ) = @_;
304              
305 144 50       328 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         292 my @running_empty_cols = @$empty_cols;
317 144         244 my @steps;
318              
319 144         761 while ( ceil( $num_cards / $step_width ) > 1 )
320             {
321             # Top power of two in $num_steps
322 32         122 my $rec_num_steps = $self->_find_max_step(
323             ceil( $num_cards / $step_width ) );
324 32         93 my $count_cards = $rec_num_steps * $step_width;
325 32         63 my $temp_dest = shift(@running_empty_cols);
326 32         163 $recursive_move->(
327             $source, $temp_dest, $count_cards,
328             [@running_empty_cols],
329             );
330              
331 32         126 push @steps,
332             +{
333             'source' => $source,
334             'dest' => $temp_dest,
335             count => $count_cards
336             };
337 32         157 $num_cards -= $count_cards;
338             }
339 144         440 $move_using_freecells->( $source, $dest, $num_cards );
340              
341 144         333 foreach my $s ( reverse(@steps) )
342             {
343             $recursive_move->(
344 32         138 $s->{dest}, $dest, $s->{count}, [@running_empty_cols]
345             );
346             @running_empty_cols =
347 32         133 ( sort { $a <=> $b } @running_empty_cols, $s->{dest} );
  8         38  
348             }
349 144         419 return;
350             }
351 80         450 };
352              
353 80         248 $recursive_move->(
354             $ultimate_source, $ultimate_dest,
355             $ultimate_num_cards, [@empty_stack_indexes],
356             );
357             }
358             else
359             {
360 352         1299 $self->_out_line( $self->_move_line . "\n" );
361 352 50       1238 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         906 return;
372             }
373              
374              
375             sub verify
376             {
377 2     2 1 15 my $self = shift;
378              
379 2         5 eval {
380              
381 2         13 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         11 while ( !defined( scalar( $self->_read_move() ) ) )
392             {
393 432         1375 $self->_apply_move();
394 432         1068 $self->_read_state();
395             }
396             };
397              
398 2         6 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__