File Coverage

blib/lib/Games/Solitaire/Verify/State.pm
Criterion Covered Total %
statement 285 297 95.9
branch 75 86 87.2
condition n/a
subroutine 54 55 98.1
pod 18 18 100.0
total 432 456 94.7


line stmt bran cond sub pod time code
1             package Games::Solitaire::Verify::State;
2             $Games::Solitaire::Verify::State::VERSION = '0.2403';
3 8     8   235440 use warnings;
  8         45  
  8         279  
4 8     8   47 use strict;
  8         16  
  8         188  
5              
6              
7 8     8   1043 use parent 'Games::Solitaire::Verify::Base';
  8         656  
  8         41  
8              
9 8     8   1809 use Games::Solitaire::Verify::Exception ();
  8         21  
  8         196  
10 8     8   1804 use Games::Solitaire::Verify::Card ();
  8         21  
  8         205  
11 8     8   1500 use Games::Solitaire::Verify::Column ();
  8         18  
  8         153  
12 8     8   1410 use Games::Solitaire::Verify::Move ();
  8         21  
  8         169  
13 8     8   3757 use Games::Solitaire::Verify::Freecells ();
  8         22  
  8         198  
14 8     8   3703 use Games::Solitaire::Verify::Foundations ();
  8         25  
  8         192  
15 8     8   2519 use Games::Solitaire::Verify::VariantParams ();
  8         21  
  8         182  
16 8     8   2376 use Games::Solitaire::Verify::VariantsMap ();
  8         34  
  8         236  
17              
18 8     8   56 use List::Util qw(first);
  8         17  
  8         970  
19 8     8   3384 use POSIX qw();
  8         35724  
  8         28926  
20              
21             __PACKAGE__->mk_acc_ref(
22             [
23             qw(
24             _columns
25             _freecells
26             _foundations
27             _variant
28             _variant_params
29             _temp_move
30             )
31             ]
32             );
33              
34              
35             sub set_freecells
36             {
37 274     274 1 572 my ( $self, $freecells ) = @_;
38              
39 274         603 $self->_freecells($freecells);
40              
41 274         461 return ();
42             }
43              
44             sub _assign_freecells_from_string
45             {
46 272     272   490 my $self = shift;
47 272         663 my $string = shift;
48              
49 272         629 $self->set_freecells(
50             Games::Solitaire::Verify::Freecells->new(
51             {
52             count => $self->num_freecells(),
53             string => $string,
54             }
55             )
56             );
57              
58 272         631 return ();
59             }
60              
61              
62             sub add_column
63             {
64 2246     2246 1 4446 my ( $self, $col ) = @_;
65              
66 2246         3365 push @{ $self->_columns() }, $col;
  2246         5373  
67              
68 2246         5738 return ();
69             }
70              
71              
72             sub set_foundations
73             {
74 281     281 1 562 my ( $self, $foundations ) = @_;
75              
76 281         609 $self->_foundations($foundations);
77              
78 281         492 return ();
79             }
80              
81             sub _get_suits_seq
82             {
83 0     0   0 my $class = shift;
84              
85 0         0 return Games::Solitaire::Verify::Card->get_suits_seq();
86             }
87              
88             sub _from_string
89             {
90 266     266   585 my ( $self, $str ) = @_;
91              
92 266         439 my $rank_re = '[0A1-9TJQK]';
93              
94 266 50       1434 if ( $str !~ m{\A(Foundations:[^\n]*)\n}gms )
95             {
96 0         0 Games::Solitaire::Verify::Exception::Parse::State::Foundations->throw(
97             error => "Wrong Foundations", );
98             }
99 266         829 my $founds_s = $1;
100              
101 266         609 $self->set_foundations(
102             Games::Solitaire::Verify::Foundations->new(
103             {
104             num_decks => $self->num_decks(),
105             string => $founds_s,
106             }
107             )
108             );
109              
110 266 50       1443 if ( $str !~ m{\G(Freecells:[^\n]*)\n}gms )
111             {
112 0         0 Games::Solitaire::Verify::Exception::Parse::State::Freecells->throw(
113             error => "Wrong Freecell String", );
114             }
115 266         839 $self->_assign_freecells_from_string($1);
116              
117 266         653 foreach my $col_idx ( 0 .. ( $self->num_columns() - 1 ) )
118             {
119 2128 100       10060 if ( $str !~ m{\G(:[^\n]*)\n}msg )
120             {
121 2         35 Games::Solitaire::Verify::Exception::Parse::State::Column->throw(
122             error => "Cannot parse column",
123             index => $col_idx,
124             );
125             }
126 2126         5608 my $column_str = $1;
127              
128 2126         7001 $self->add_column(
129             Games::Solitaire::Verify::Column->new(
130             {
131             string => $column_str,
132             }
133             )
134             );
135             }
136              
137 264         797 return ();
138             }
139              
140             sub _fill_non_custom_variant
141             {
142 272     272   429 my $self = shift;
143 272         407 my $variant = shift;
144              
145 272         735 my $variants_map = Games::Solitaire::Verify::VariantsMap->new();
146              
147 272         709 my $params = $variants_map->get_variant_by_id($variant);
148              
149 272 50       655 if ( !defined($params) )
150             {
151 0         0 Games::Solitaire::Verify::Exception::Variant::Unknown->throw(
152             error => "Unknown/Unsupported Variant",
153             variant => $variant,
154             );
155             }
156 272         592 $self->_variant_params($params);
157 272         623 $self->_variant($variant);
158              
159 272         630 return ();
160             }
161              
162             sub _set_variant
163             {
164 281     281   478 my $self = shift;
165 281         422 my $args = shift;
166              
167 281         512 my $variant = $args->{variant};
168              
169 281 100       605 if ( $variant eq "custom" )
170             {
171 9         44 $self->_variant($variant);
172 9         31 $self->_variant_params( $args->{variant_params} );
173             }
174             else
175             {
176 272         611 $self->_fill_non_custom_variant($variant);
177             }
178              
179 281         505 return ();
180             }
181              
182             sub _init
183             {
184 281     281   602 my ( $self, $args ) = @_;
185              
186             # Set the variant
187 281         787 $self->_set_variant($args);
188              
189 281         640 $self->_columns( [] );
190              
191 281 100       672 if ( exists( $args->{string} ) )
192             {
193 272         711 return $self->_from_string( $args->{string} );
194             }
195 9         18 return ();
196             }
197              
198              
199             sub get_freecell
200             {
201 1939     1939 1 3601 my ( $self, $index ) = @_;
202              
203 1939         5001 return $self->_freecells()->cell($index);
204             }
205              
206              
207             sub set_freecell
208             {
209 590     590 1 1191 my ( $self, $index, $card ) = @_;
210              
211 590         1581 return $self->_freecells->assign( $index, $card );
212             }
213              
214              
215             sub get_foundation_value
216             {
217 866     866 1 1765 my ( $self, $suit, $idx ) = @_;
218              
219 866         2513 return $self->_foundations()->value( $suit, $idx );
220             }
221              
222              
223             sub increment_foundation_value
224             {
225 833     833 1 1622 my ( $self, $suit, $idx ) = @_;
226              
227 833         2660 $self->_foundations()->increment( $suit, $idx );
228              
229 833         1310 return ();
230             }
231              
232              
233             sub num_decks
234             {
235 1066     1066 1 1913 my $self = shift;
236              
237 1066         4570 return $self->_variant_params->num_decks();
238             }
239              
240              
241             sub num_freecells
242             {
243 487     487 1 806 my $self = shift;
244              
245 487         2439 return $self->_variant_params->num_freecells();
246             }
247              
248              
249             sub num_empty_freecells
250             {
251 489     489 1 778 my $self = shift;
252              
253 489         1533 return $self->_freecells->num_empty();
254             }
255              
256              
257             sub num_columns
258             {
259 1168     1168 1 2000 my $self = shift;
260              
261 1168         4240 return $self->_variant_params->num_columns();
262             }
263              
264              
265             sub get_column
266             {
267 13203     13203 1 19486 my $self = shift;
268 13203         19069 my $index = shift;
269              
270 13203         33317 return $self->_columns->[$index];
271             }
272              
273              
274             sub num_empty_columns
275             {
276 452     452 1 858 my $self = shift;
277              
278 452         697 my $count = 0;
279              
280 452         927 foreach my $idx ( 0 .. ( $self->num_columns() - 1 ) )
281             {
282 3776 100       6338 if ( !$self->get_column($idx)->len() )
283             {
284 328         550 ++$count;
285             }
286             }
287 452         1507 return $count;
288             }
289              
290              
291             sub clone
292             {
293 7     7 1 35 my $self = shift;
294              
295 7         19 my $variant = $self->_variant;
296 7 100       40 my $copy = Games::Solitaire::Verify::State->new(
297             {
298             variant => $variant,
299             (
300             ( $variant eq "custom" )
301             ? ( variant_params => $self->_variant_params() )
302             : ()
303             ),
304             }
305             );
306              
307 7         23 foreach my $idx ( 0 .. ( $self->num_columns() - 1 ) )
308             {
309 56         113 $copy->add_column( $self->get_column($idx)->clone() );
310             }
311              
312 7         60 $copy->set_foundations( $self->_foundations()->clone() );
313              
314 7         35 $copy->_freecells( $self->_freecells()->clone() );
315              
316 7         27 return $copy;
317             }
318              
319              
320             # Returns 0 on success, else the error.
321             sub _can_put_into_empty_column
322             {
323 225     225   474 my ( $self, $card ) = @_;
324              
325 225 100       783 if ( $self->_variant_params->empty_stacks_filled_by() eq "kings" )
326             {
327 13 100       46 if ( $card->rank() != 13 )
328             {
329             return
330 2         53 Games::Solitaire::Verify::Exception::Move::Dest::Col::OnlyKingsCanFillEmpty
331             ->new(
332             error => "Non-king on an empty stack",
333             move => $self->_temp_move(),
334             );
335             }
336             }
337 223         625 return 0;
338             }
339              
340             sub _is_matching_color
341             {
342 1422     1422   2479 my ( $self, $parent, $child ) = @_;
343              
344 1422         3047 my $rules = $self->_variant_params()->rules();
345 1422         2779 my $sbb = $self->_variant_params()->seq_build_by();
346              
347 1422 50       4404 my $verdict = (
    100          
    100          
348             ( $rules eq "simple_simon" ) ? 0
349             : ( $sbb eq "alt_color" ) ? ( $parent->color() eq $child->color() )
350             : ( $sbb eq "suit" ) ? ( $parent->suit() ne $child->suit() )
351             : 0
352             );
353              
354 1422 100       2906 if ($verdict)
355             {
356             return
357 3         73 Games::Solitaire::Verify::Exception::Move::Dest::Col::NonMatchSuits
358             ->new(
359             seq_build_by => $sbb,
360             move => $self->_temp_move(),
361             );
362             }
363              
364 1419         3357 return 0;
365             }
366              
367             sub _can_put_on_top
368             {
369 1425     1425   2590 my ( $self, $parent, $child ) = @_;
370              
371 1425 100       3775 if ( $parent->rank() != $child->rank() + 1 )
372             {
373             return
374 3         65 Games::Solitaire::Verify::Exception::Move::Dest::Col::RankMismatch
375             ->new(
376             error => "Rank mismatch between parent and child cards",
377             move => $self->_temp_move(),
378             );
379             }
380              
381 1422 100       2875 if ( my $ret = $self->_is_matching_color( $parent, $child ) )
382             {
383 3         14 return $ret;
384             }
385              
386 1419         3375 return 0;
387             }
388              
389             sub _can_put_on_column
390             {
391 1088     1088   2171 my ( $self, $col_idx, $card ) = @_;
392              
393             return (
394 1088 100       2152 ( $self->get_column($col_idx)->len() == 0 )
395             ? $self->_can_put_into_empty_column($card)
396             : $self->_can_put_on_top( $self->get_column($col_idx)->top(), $card )
397             );
398             }
399              
400             sub _calc_freecell_max_seq_move
401             {
402 451     451   771 my ( $self, $args ) = @_;
403 451 50       1053 my $to_empty = ( defined( $args->{to_empty} ) ? $args->{to_empty} : 0 );
404              
405 451         975 return ( ( $self->num_empty_freecells() + 1 )
406             << ( $self->num_empty_columns() - $to_empty ) );
407             }
408              
409             sub _calc_empty_stacks_filled_by_any_card_max_seq_move
410             {
411 451     451   819 my ( $self, $args ) = @_;
412              
413 451         898 return $self->_calc_freecell_max_seq_move($args);
414             }
415              
416             sub _calc_max_sequence_move
417             {
418 615     615   1210 my ( $self, $args ) = @_;
419              
420 615 100       2548 return +( $self->_variant_params->sequence_move() eq "unlimited" )
    100          
421             ? POSIX::INT_MAX()
422             : ( $self->_variant_params->empty_stacks_filled_by() eq "any" )
423             ? $self->_calc_empty_stacks_filled_by_any_card_max_seq_move($args)
424             : ( $self->num_empty_freecells() + 1 );
425             }
426              
427             sub _is_sequence_in_column
428             {
429 625     625   1374 my ( $self, $source_idx, $num_cards, $num_seq_components_ref ) = @_;
430              
431 625         1240 my $col = $self->get_column($source_idx);
432 625         1351 my $len = $col->len();
433              
434 625         1609 my $rules = $self->_variant_params()->rules();
435              
436 625         1023 my $num_comps = 1;
437              
438 625         1707 foreach my $card_idx ( 0 .. ( $num_cards - 2 ) )
439             {
440 562         1406 my $parent = $col->pos( $len - 1 - $card_idx - 1 );
441 562         1259 my $child = $col->pos( $len - 1 - $card_idx );
442              
443 562 100       1093 if ( $self->_can_put_on_top( $parent, $child ) )
444             {
445             return
446 1         18 Games::Solitaire::Verify::Exception::Move::Src::Col::NonSequence
447             ->new(
448             move => $self->_temp_move(),
449             pos => $card_idx,
450             );
451             }
452              
453             $num_comps += (
454 561 100       1487 ( $rules eq "simple_simon" )
    100          
455             ? ( ( $parent->suit() ne $child->suit() ) ? 1 : 0 )
456             : 1
457             );
458             }
459              
460 624         923 ${$num_seq_components_ref} = $num_comps;
  624         1205  
461              
462 624         1592 return 0;
463             }
464              
465              
466             sub clear_freecell
467             {
468 578     578 1 1111 my ( $self, $index ) = @_;
469              
470 578         1672 return $self->_freecells->clear($index);
471             }
472              
473             sub verify_and_perform_move
474             {
475 2458     2458 1 12635 my ( $self, $move ) = @_;
476              
477 2458 50       11545 if ( my $method =
478             $self->can( "_mv_" . $move->source_type . "_to_" . $move->dest_type ) )
479             {
480 2458         5176 $self->_temp_move($move);
481 2458         4608 my $ret = $method->($self);
482 2458         13012 $self->_temp_move( undef() );
483 2458         8234 return $ret;
484             }
485             else
486             {
487 0         0 die "Cannot handle this move type";
488             }
489             }
490              
491             sub _mv_stack_to_foundation
492             {
493 657     657   1083 my $self = shift;
494              
495 657         1494 my $move = $self->_temp_move();
496              
497 657         1151 my $col_idx = $move->source();
498 657         1296 my $card = $self->get_column($col_idx)->top();
499              
500 657         1353 my $rank = $card->rank();
501 657         1330 my $suit = $card->suit();
502              
503 657     657   1515 my $f_idx = first { $self->get_foundation_value( $suit, $_ ) == $rank - 1 }
504 657         3400 ( 0 .. ( $self->num_decks() - 1 ) );
505              
506 657 100       2397 if ( defined($f_idx) )
507             {
508 656         1370 $self->get_column($col_idx)->pop();
509 656         1778 $self->increment_foundation_value( $suit, $f_idx );
510 656         2210 return 0;
511             }
512             else
513             {
514             return
515 1         6 Games::Solitaire::Verify::Exception::Move::Dest::Foundation->new(
516             move => $move );
517             }
518             }
519              
520             sub _mv_stack_seq_to_foundation
521             {
522 7     7   230 my $self = shift;
523              
524 7         30 my $move = $self->_temp_move();
525              
526 7         24 my $rules = $self->_variant_params()->rules();
527              
528 7 100       23 if ( $rules ne "simple_simon" )
529             {
530 1         18 return Games::Solitaire::Verify::Exception::Move::Variant::Unsupported
531             ->new( move => $move );
532             }
533              
534 6         18 my $col_idx = $move->source();
535              
536 6         12 my $num_seq_components;
537 6         21 my $verdict =
538             $self->_is_sequence_in_column( $col_idx, 13, \$num_seq_components, );
539              
540 6 50       18 if ($verdict)
541             {
542 0         0 return $verdict;
543             }
544              
545 6 100       21 if ( $num_seq_components != 1 )
546             {
547 1         31 return Games::Solitaire::Verify::Exception::Move::Src::Col::NotTrueSeq
548             ->new( move => $move );
549             }
550              
551 5         14 my $card = $self->get_column($col_idx)->top();
552              
553 5         22 my $suit = $card->suit();
554              
555 5     5   18 my $f_idx = first { $self->get_foundation_value( $suit, $_ ) == 0 }
556 5         40 ( 0 .. ( $self->num_decks() - 1 ) );
557              
558 5 50       31 if ( defined($f_idx) )
559             {
560 5         16 foreach my $card_idx ( 1 .. 13 )
561             {
562 65         115 $self->get_column($col_idx)->pop();
563 65         140 $self->increment_foundation_value( $suit, $f_idx );
564             }
565 5         18 return 0;
566             }
567             else
568             {
569             return
570 0         0 Games::Solitaire::Verify::Exception::Move::Dest::Foundation->new(
571             move => $move );
572             }
573             }
574              
575             sub _mv_stack_to_freecell
576             {
577 589     589   921 my $self = shift;
578 589         1063 my $move = $self->_temp_move();
579              
580 589         1041 my $col_idx = $move->source();
581 589         1044 my $fc_idx = $move->dest();
582              
583 589 100       1178 if ( !$self->get_column($col_idx)->len() )
584             {
585             return
586 1         21 Games::Solitaire::Verify::Exception::Move::Src::Col::NoCards->new(
587             move => $move, );
588             }
589              
590 588 100       1354 if ( defined( $self->get_freecell($fc_idx) ) )
591             {
592 1         29 return Games::Solitaire::Verify::Exception::Move::Dest::Freecell->new(
593             move => $move, );
594             }
595              
596 587         1254 $self->set_freecell( $fc_idx, $self->get_column($col_idx)->pop() );
597              
598 587         1134 return 0;
599             }
600              
601             sub _mv_stack_to_stack
602             {
603 620     620   1031 my $self = shift;
604 620         1132 my $move = $self->_temp_move();
605              
606 620         1233 my $source = $move->source();
607 620         1110 my $dest = $move->dest();
608 620         1433 my $num_cards = $move->num_cards();
609              
610             # Source column
611 620         1264 my $sc = $self->get_column($source);
612 620         1192 my $dc = $self->get_column($dest);
613              
614 620         1601 my $source_len = $sc->len();
615              
616 620 100       1558 if ( $source_len < $num_cards )
617             {
618             return
619 1         19 Games::Solitaire::Verify::Exception::Move::Src::Col::NotEnoughCards
620             ->new( move => $move, );
621             }
622              
623 619         919 my $num_seq_components;
624 619 100       1499 if (
625             my $verdict = $self->_is_sequence_in_column(
626             $source, $num_cards, \$num_seq_components,
627             )
628             )
629             {
630 1         19 return $verdict;
631             }
632              
633 618 100       1593 if (
634             my $verdict = $self->_can_put_on_column(
635             $dest, $sc->pos( $source_len - $num_cards )
636             )
637             )
638             {
639 3         9 return $verdict;
640             }
641              
642             # Now let's see if we have enough resources
643             # to move the cards.
644              
645 615 100       1573 if (
646             $num_seq_components > $self->_calc_max_sequence_move(
647             {
648             to_empty => ( $dc->len() == 0 ),
649             }
650             )
651             )
652             {
653             return
654 3         73 Games::Solitaire::Verify::Exception::Move::NotEnoughEmpties->new(
655             move => $move, );
656             }
657              
658             # Now let's actually move them.
659 612         1950 $dc->append_cards( $sc->popN($num_cards) );
660              
661 612         1400 return 0;
662             }
663              
664             sub _mv_freecell_to_foundation
665             {
666 114     114   223 my $self = shift;
667 114         218 my $move = $self->_temp_move();
668              
669 114         267 my $fc_idx = $move->source();
670 114         284 my $card = $self->get_freecell($fc_idx);
671              
672 114 100       358 if ( !defined($card) )
673             {
674             return
675 1         8 Games::Solitaire::Verify::Exception::Move::Src::Freecell::Empty
676             ->new( move => $move, );
677             }
678              
679 113         258 my $rank = $card->rank();
680 113         246 my $suit = $card->suit();
681              
682 113     113   273 my $f_idx = first { $self->get_foundation_value( $suit, $_ ) == $rank - 1 }
683 113         593 ( 0 .. ( $self->num_decks() - 1 ) );
684              
685 113 100       498 if ( defined($f_idx) )
686             {
687 112         352 $self->clear_freecell($fc_idx);
688 112         319 $self->increment_foundation_value( $suit, $f_idx );
689 112         366 return 0;
690             }
691             else
692             {
693             return
694 1         20 Games::Solitaire::Verify::Exception::Move::Dest::Foundation->new(
695             move => $move );
696             }
697             }
698              
699             sub _mv_freecell_to_stack
700             {
701 471     471   817 my $self = shift;
702 471         790 my $move = $self->_temp_move();
703              
704 471         845 my $fc_idx = $move->source();
705 471         834 my $col_idx = $move->dest();
706              
707 471         951 my $card = $self->get_freecell($fc_idx);
708              
709 471 100       1125 if ( !defined($card) )
710             {
711 1         22 return Games::Solitaire::Verify::Exception::Move::Src::Freecell::Empty
712             ->new( move => $move, );
713             }
714              
715 470 100       1049 if ( my $verdict = $self->_can_put_on_column( $col_idx, $card ) )
716             {
717 4         13 return $verdict;
718             }
719              
720 466         1016 $self->get_column($col_idx)->push($card);
721 466         1261 $self->clear_freecell($fc_idx);
722              
723 466         896 return 0;
724             }
725              
726              
727             my @SS = ( @{ Games::Solitaire::Verify::Card->get_suits_seq() } );
728              
729             sub verify_contents
730             {
731 16     16 1 39 my ( $self, $args ) = @_;
732              
733 16         33 my $MAX_RANK = $args->{max_rank};
734 16         35 my $found = {};
735             my $register = sub {
736 832     832   1214 my $card = shift;
737 832 50       1578 if ( $card->rank > $MAX_RANK )
738             {
739 0         0 die Games::Solitaire::Verify::Exception::State::TooHighRank->new(
740             cards => [$card], );
741             }
742 832         1468 my $s = $card->fast_s;
743 832 50       2412 if ( ( ++$found->{$s} ) > 1 )
744             {
745 0         0 die Games::Solitaire::Verify::Exception::State::ExtraCards->new(
746             cards => [$card], );
747             }
748 832         1426 return ();
749 16         123 };
750 16         50 for my $fc ( 0 .. $self->num_freecells - 1 )
751             {
752 64         115 my $card = $self->get_freecell($fc);
753 64 100       169 if ( defined $card )
754             {
755 14         27 $register->($card);
756             }
757             }
758 16         74 foreach my $suit (@SS)
759             {
760 64         156 for my $rank ( 1 .. $self->get_foundation_value( $suit, 0 ) )
761             {
762 0         0 $register->(
763             Games::Solitaire::Verify::Card->new(
764             {
765             string => (
766             Games::Solitaire::Verify::Card->rank_to_string(
767             $rank)
768             . $suit
769             )
770             }
771             ),
772             );
773             }
774             }
775              
776 16         60 foreach my $idx ( 0 .. ( $self->num_columns() - 1 ) )
777             {
778 132         243 my $col = $self->get_column($idx);
779 132         316 for my $pos ( 0 .. $col->len - 1 )
780             {
781 818         1605 $register->( $col->pos($pos) );
782             }
783             }
784              
785 16 50       103 if ( scalar( keys %$found ) != $MAX_RANK * 4 )
786             {
787 0         0 die Games::Solitaire::Verify::Exception::State::MissingCards->new(
788             cards => [], );
789             }
790              
791 16         576 return ();
792             }
793              
794              
795             sub to_string
796             {
797 2226     2226 1 4043 my $self = shift;
798              
799             return join(
800             "\n",
801             (
802 22606         44146 map { $_->to_string() } $self->_foundations(),
803             $self->_freecells(),
804 2226         4712 @{ $self->_columns() }
  2226         4767  
805             ),
806             ""
807             );
808             }
809              
810             1; # End of Games::Solitaire::Verify::State
811              
812             __END__