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.2401';
3 1     1   133077 use strict;
  1         5  
  1         30  
4 1     1   6 use warnings;
  1         2  
  1         24  
5 1     1   522 use autodie;
  1         15745  
  1         4  
6              
7              
8 1     1   6929 use Carp ();
  1         6  
  1         22  
9 1     1   5 use List::Util qw/ sum /;
  1         1  
  1         106  
10              
11 1     1   540 use Games::Solitaire::Verify::Card ();
  1         3  
  1         35  
12 1     1   620 use Games::Solitaire::Verify::Column ();
  1         2  
  1         26  
13 1     1   468 use Games::Solitaire::Verify::Freecells ();
  1         3  
  1         28  
14              
15 1     1   7 use parent 'Games::Solitaire::Verify::Base';
  1         2  
  1         5  
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   9 my $self = shift;
36              
37 5         17 return $self->_variant eq 'golf';
38             }
39              
40             sub _init
41             {
42 5     5   16 my ( $self, $args ) = @_;
43              
44 5         20 my $variant = $self->_variant( $args->{variant} );
45 5 50       27 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     30 $self->_place_queens_on_kings( $args->{queens_on_kings} // '' );
52 5   100     21 $self->_wrap_ranks( $args->{wrap_ranks} // '' );
53 5         37 $self->_foundation(
54             Games::Solitaire::Verify::Freecells->new( { count => 1 } ) );
55 5         11 my $board_string = $args->{board_string};
56              
57 5         45 my @lines = split( /\n/, $board_string );
58              
59             my $_set_found_line = sub {
60 4     4   8 my $foundation_str = shift;
61 4 50       29 if ( my ($card_s) = $foundation_str =~ m#\AFoundations: (\S{2})\z# )
62             {
63 4         16 $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         11 return;
71 5         26 };
72 5         19 my $foundation_str = shift(@lines);
73 5 100       18 if ( $self->_variant eq 'golf' )
74             {
75 3 50       37 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         23 map { Games::Solitaire::Verify::Card->new( { string => $_ } ) }
  48         196  
82             split / /,
83             $foundation_str
84             ]
85             );
86              
87 3         9 $foundation_str = shift(@lines);
88 3         13 $_set_found_line->($foundation_str);
89              
90             }
91             else
92             {
93 2         7 $self->_talon( [] );
94 2 100       7 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         3 $_set_found_line->($foundation_str);
104             }
105             }
106              
107             $self->_columns(
108             [
109             map {
110 5         12 Games::Solitaire::Verify::Column->new(
  51         183  
111             {
112             string => ": $_",
113             }
114             )
115             } @lines
116             ]
117             );
118 5 100       17 if ( $self->_wrap_ranks )
119             {
120 1         6 $self->_place_queens_on_kings(1);
121             }
122              
123 5         35 return;
124             }
125              
126             sub _set_found
127             {
128 241     241   424 my ( $self, $card ) = @_;
129 241         721 $self->_foundation->assign( 0, $card );
130 241         330 return;
131             }
132              
133             sub process_solution
134             {
135 5     5 1 1429 my ( $self, $next_line_iter ) = @_;
136 5         15 my $columns = $self->_columns;
137 5         9 my $NUM_COLUMNS = @$columns;
138 5         10 my $line_num = 0;
139 5         11 my $remaining_cards = sum( map { $_->len } @$columns );
  51         98  
140              
141             my $get_line = sub {
142 1671     1671   2682 my $ret = $next_line_iter->();
143 1671         9430 return ( $ret, ++$line_num );
144 5         29 };
145              
146             my $assert_empty_line = sub {
147 952     952   1504 my ( $s, $line_idx ) = $get_line->();
148              
149 952 50       1945 if ( $s ne '' )
150             {
151 0         0 die "Line '$line_idx' is not empty, but '$s'";
152             }
153              
154 952         1357 return;
155 5         20 };
156              
157 5         11 my ( $l, $first_l ) = $get_line->();
158              
159 5 50       23 if ( $l ne "Solved!" )
160             {
161 0         0 die "First line is '$l' instead of 'Solved!'";
162             }
163 5         14 my $IS_GOLF = $self->_is_golf;
164 5   100     23 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         405 my ( $move_line, $move_line_idx ) = $get_line->();
171              
172 238         372 my $card;
173 238 100 100     1210 if ( $IS_GOLF
    50          
174             and $move_line =~ m/\ADeal talon\z/ )
175             {
176 39 50       66 if ( !@{ $self->_talon } )
  39         108  
177             {
178 0         0 die "Talon is empty on line no. $move_line_idx";
179             }
180 39         52 $card = shift @{ $self->_talon };
  39         74  
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         521 my $col_idx = $1;
190              
191 238 100       444 if ( !defined $card )
192             {
193 199 50 33     688 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         528 $assert_empty_line->();
200              
201 238         416 my ( $info_line, $info_line_idx ) = $get_line->();
202              
203 238 50       730 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         468 my $moved_card_str = $1;
210              
211 238         492 $assert_empty_line->();
212 238         507 $assert_empty_line->();
213              
214 238         386 my ( $sep_line, $sep_line_idx ) = $get_line->();
215              
216 238 50       732 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         511 $assert_empty_line->();
223              
224 238 100       422 if ( defined $card )
225             {
226 39         87 my $top_card_moved_str = $card->to_string();
227 39 50       88 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         328 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       406 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         492 my $found_card = $self->_foundation->cell(0);
246 199 100       419 if ( defined($found_card) )
247             {
248 198         307 my $found_rank = $found_card->rank();
249 198         295 my $src_rank = $top_card->rank();
250              
251 198         344 my $delta = abs( $src_rank - $found_rank );
252 198 100 66     983 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         6 die
269             "Cannot put $top_card_moved_str in the foundations that contain "
270             . $found_card->to_string();
271             }
272             }
273 198         421 $card = $col->pop;
274 198         336 --$remaining_cards;
275             }
276              
277 237         531 $self->_set_found($card);
278 237 100       484 if ($CHECK_EMPTY)
279             {
280 185 100       481 if ( $remaining_cards == 0 )
281             {
282 3         12 last MOVES;
283             }
284             }
285             }
286 4         27 return;
287             }
288              
289             1;
290              
291             __END__