File Coverage

blib/lib/Games/Solitaire/Verify/Golf.pm
Criterion Covered Total %
statement 119 132 90.1
branch 33 46 71.7
condition 23 28 82.1
subroutine 16 16 100.0
pod 1 1 100.0
total 192 223 86.1


line stmt bran cond sub pod time code
1             package Games::Solitaire::Verify::Golf;
2             $Games::Solitaire::Verify::Golf::VERSION = '0.2403';
3 1     1   130609 use strict;
  1         5  
  1         30  
4 1     1   14 use warnings;
  1         2  
  1         24  
5 1     1   491 use autodie;
  1         15331  
  1         5  
6              
7              
8 1     1   6885 use Carp ();
  1         3  
  1         22  
9 1     1   5 use List::Util qw/ sum /;
  1         3  
  1         114  
10              
11 1     1   601 use Games::Solitaire::Verify::Card ();
  1         4  
  1         30  
12 1     1   555 use Games::Solitaire::Verify::Column ();
  1         3  
  1         24  
13 1     1   443 use Games::Solitaire::Verify::Freecells ();
  1         4  
  1         29  
14              
15 1     1   8 use parent 'Games::Solitaire::Verify::Base';
  1         2  
  1         4  
16              
17             __PACKAGE__->mk_acc_ref(
18             [
19             qw(
20             _columns
21             _foundation
22             _place_queens_on_kings
23             _talon
24             _variant
25             _wrap_ranks
26             )
27             ]
28             );
29              
30             my $MAX_RANK = 13;
31             my $NUM_SUITS = 4;
32              
33             sub _is_golf
34             {
35 5     5   10 my $self = shift;
36              
37 5         22 return $self->_variant eq 'golf';
38             }
39              
40             sub _init
41             {
42 5     5   15 my ( $self, $args ) = @_;
43              
44 5         23 my $variant = $self->_variant( $args->{variant} );
45 5 50       28 if (
46             not
47             exists { golf => 1, all_in_a_row => 1, black_hole => 1, }->{$variant} )
48             {
49 0         0 Carp::confess("Unknown variant '$variant'!");
50             }
51 5   100     32 $self->_place_queens_on_kings( $args->{queens_on_kings} // '' );
52 5   100     22 $self->_wrap_ranks( $args->{wrap_ranks} // '' );
53 5         51 $self->_foundation(
54             Games::Solitaire::Verify::Freecells->new( { count => 1 } ) );
55 5         12 my $board_string = $args->{board_string};
56              
57 5         47 my @lines = split( /\n/, $board_string );
58              
59             my $_set_found_line = sub {
60 4     4   14 my $foundation_str = shift;
61 4 50       28 if ( my ($card_s) = $foundation_str =~ m#\AFoundations: (\S{2})\z# )
62             {
63 4         18 $self->_set_found(
64             Games::Solitaire::Verify::Card->new( { string => $card_s } ) );
65             }
66             else
67             {
68 0         0 Carp::confess("Foundations str is '$foundation_str'");
69             }
70 4         10 return;
71 5         28 };
72 5         13 my $foundation_str = shift(@lines);
73 5 100       19 if ( $self->_variant eq 'golf' )
74             {
75 3 50       38 if ( $foundation_str !~ s#\ATalon: ((?:\S{2} ){15}\S{2})#$1# )
76             {
77 0         0 die "improper talon line <$foundation_str>!";
78             }
79             $self->_talon(
80             [
81 3         24 map { Games::Solitaire::Verify::Card->new( { string => $_ } ) }
  48         155  
82             split / /,
83             $foundation_str
84             ]
85             );
86              
87 3         10 $foundation_str = shift(@lines);
88 3         9 $_set_found_line->($foundation_str);
89              
90             }
91             else
92             {
93 2         7 $self->_talon( [] );
94 2 100       8 if ( $self->_variant eq "all_in_a_row" )
95             {
96 1 50       4 if ( $foundation_str ne "Foundations: -" )
97             {
98 0         0 Carp::confess("Foundations str is '$foundation_str'");
99             }
100             }
101             else
102             {
103 1         4 $_set_found_line->($foundation_str);
104             }
105             }
106              
107             $self->_columns(
108             [
109             map {
110 5         12 Games::Solitaire::Verify::Column->new(
  51         199  
111             {
112             string => ": $_",
113             }
114             )
115             } @lines
116             ]
117             );
118 5 100       18 if ( $self->_wrap_ranks )
119             {
120 1         4 $self->_place_queens_on_kings(1);
121             }
122              
123 5         35 return;
124             }
125              
126             sub _set_found
127             {
128 241     241   406 my ( $self, $card ) = @_;
129 241         877 $self->_foundation->assign( 0, $card );
130 241         365 return;
131             }
132              
133             sub process_solution
134             {
135 5     5 1 1454 my ( $self, $next_line_iter ) = @_;
136 5         63 my $columns = $self->_columns;
137 5         15 my $NUM_COLUMNS = @$columns;
138 5         8 my $line_num = 0;
139 5         13 my $remaining_cards = sum( map { $_->len } @$columns );
  51         102  
140              
141             my $get_line = sub {
142 1671     1671   2653 my $ret = $next_line_iter->();
143 1671         9007 return ( $ret, ++$line_num );
144 5         21 };
145              
146             my $assert_empty_line = sub {
147 952     952   1356 my ( $s, $line_idx ) = $get_line->();
148              
149 952 50       1867 if ( $s ne '' )
150             {
151 0         0 die "Line '$line_idx' is not empty, but '$s'";
152             }
153              
154 952         1364 return;
155 5         19 };
156              
157 5         11 my ( $l, $first_l ) = $get_line->();
158              
159 5 50       17 if ( $l ne "Solved!" )
160             {
161 0         0 die "First line is '$l' instead of 'Solved!'";
162             }
163 5         23 my $IS_GOLF = $self->_is_golf;
164 5   100     21 my $CHECK_EMPTY = ( $IS_GOLF or $self->_variant eq "black_hole" );
165              
166             # As many moves as the number of cards.
167             MOVES:
168 5         20 for my $move_idx ( 0 .. ( $MAX_RANK * $NUM_SUITS - 1 ) )
169             {
170 238         388 my ( $move_line, $move_line_idx ) = $get_line->();
171              
172 238         354 my $card;
173 238 100 100     1213 if ( $IS_GOLF
    50          
174             and $move_line =~ m/\ADeal talon\z/ )
175             {
176 39 50       57 if ( !@{ $self->_talon } )
  39         107  
177             {
178 0         0 die "Talon is empty on line no. $move_line_idx";
179             }
180 39         52 $card = shift @{ $self->_talon };
  39         71  
181             }
182             elsif ( $move_line !~
183             m/\AMove a card from stack ([0-9]+) to the foundations\z/ )
184             {
185 0         0 die
186             "Incorrect format for move line no. $move_line_idx - '$move_line'";
187             }
188              
189 238         513 my $col_idx = $1;
190              
191 238 100       499 if ( !defined $card )
192             {
193 199 50 33     640 if ( ( $col_idx < 0 ) or ( $col_idx >= $NUM_COLUMNS ) )
194             {
195 0         0 die "Invalid column index '$col_idx' at $move_line_idx";
196             }
197             }
198              
199 238         552 $assert_empty_line->();
200              
201 238         367 my ( $info_line, $info_line_idx ) = $get_line->();
202              
203 238 50       728 if ( $info_line !~ m/\AInfo: Card moved is ([A23456789TJQK][HCDS])\z/ )
204             {
205 0         0 die
206             "Invalid format for info line no. $info_line_idx - '$info_line'";
207             }
208              
209 238         518 my $moved_card_str = $1;
210              
211 238         488 $assert_empty_line->();
212 238         490 $assert_empty_line->();
213              
214 238         369 my ( $sep_line, $sep_line_idx ) = $get_line->();
215              
216 238 50       726 if ( $sep_line !~ m/\A=+\z/ )
217             {
218 0         0 die
219             "Invalid format for separator line no. $sep_line_idx - '$sep_line'";
220             }
221              
222 238         570 $assert_empty_line->();
223              
224 238 100       425 if ( defined $card )
225             {
226 39         90 my $top_card_moved_str = $card->to_string();
227 39 50       95 if ( $top_card_moved_str ne $moved_card_str )
228             {
229 0         0 die
230             "Card moved should be '$top_card_moved_str', but the info says it is '$moved_card_str' at line $info_line_idx";
231             }
232             }
233             else
234             {
235 199         315 my $col = $columns->[$col_idx];
236 199         440 my $top_card = $col->top();
237 199         454 my $top_card_moved_str = $top_card->to_string();
238              
239 199 50       412 if ( $top_card_moved_str ne $moved_card_str )
240             {
241 0         0 die
242             "Card moved should be '$top_card_moved_str', but the info says it is '$moved_card_str' at line $info_line_idx";
243             }
244              
245 199         522 my $found_card = $self->_foundation->cell(0);
246 199 100       396 if ( defined($found_card) )
247             {
248 198         334 my $found_rank = $found_card->rank();
249 198         299 my $src_rank = $top_card->rank();
250              
251 198         310 my $delta = abs( $src_rank - $found_rank );
252 198 100 66     1018 if (
      100        
      66        
      100        
      66        
253             not( $delta == 1 or $delta == ( $MAX_RANK - 1 ) )
254             or (
255             $IS_GOLF
256             and ( !$self->_wrap_ranks )
257             and (
258             (
259             $self->_place_queens_on_kings
260             ? ( $found_rank == $MAX_RANK )
261             : 0
262             )
263             or $delta != 1
264             )
265             )
266             )
267             {
268 1         8 die
269             "Cannot put $top_card_moved_str in the foundations that contain "
270             . $found_card->to_string();
271             }
272             }
273 198         452 $card = $col->pop;
274 198         325 --$remaining_cards;
275             }
276              
277 237         565 $self->_set_found($card);
278 237 100       503 if ($CHECK_EMPTY)
279             {
280 185 100       480 if ( $remaining_cards == 0 )
281             {
282 3         17 last MOVES;
283             }
284             }
285             }
286 4         33 return;
287             }
288              
289             1;
290              
291             __END__