File Coverage

blib/lib/Games/Solitaire/Verify/Golf.pm
Criterion Covered Total %
statement 155 168 92.2
branch 47 64 73.4
condition 23 28 82.1
subroutine 17 17 100.0
pod 1 1 100.0
total 243 278 87.4


line stmt bran cond sub pod time code
1             package Games::Solitaire::Verify::Golf;
2             $Games::Solitaire::Verify::Golf::VERSION = '0.2601';
3 3     3   518899 use strict;
  3         7  
  3         119  
4 3     3   15 use warnings;
  3         6  
  3         176  
5 3     3   89 use 5.014;
  3         14  
6 3     3   949 use autodie;
  3         32052  
  3         15  
7              
8              
9 3     3   20453 use Carp ();
  3         7  
  3         123  
10 3     3   18 use List::Util qw/ sum /;
  3         7  
  3         303  
11              
12 3     3   1992 use Games::Solitaire::Verify::Card ();
  3         13  
  3         142  
13 3     3   1818 use Games::Solitaire::Verify::Column ();
  3         11  
  3         91  
14 3     3   1679 use Games::Solitaire::Verify::Freecells ();
  3         10  
  3         131  
15 3     3   1736 use Games::Solitaire::Verify::LinesIter ();
  3         11  
  3         116  
16              
17 3     3   24 use parent 'Games::Solitaire::Verify::Base';
  3         5  
  3         22  
18              
19             __PACKAGE__->mk_acc_ref(
20             [
21             qw(
22             _columns
23             _foundation
24             _num_foundations
25             _place_queens_on_kings
26             _talon
27             _variant
28             _wrap_ranks
29             )
30             ]
31             );
32              
33             my $MAX_RANK = 13;
34             my $NUM_SUITS = 4;
35             my $CARD_RE = qr/[A23456789TJQK][HCDS]/;
36              
37             my %_VARIANTS = (
38             all_in_a_row => 1,
39             binary_star => 1,
40             black_hole => 1,
41             golf => 1,
42             );
43              
44             sub _is_binary_star
45             {
46 12     12   15 my $self = shift;
47              
48 12         31 return $self->_variant eq 'binary_star';
49             }
50              
51             sub _is_golf
52             {
53 6     6   8 my $self = shift;
54              
55 6         11 return $self->_variant eq 'golf';
56             }
57              
58             sub _read_foundation_line
59             {
60 5     5   21 my ( $self, $foundation_str ) = @_;
61 5         9 my $num_foundations = $self->_num_foundations();
62              
63 5 50       129 if ( my ($card_s) = $foundation_str =~
64             m#\AFoundations:((?: $CARD_RE){$num_foundations})\z# )
65             {
66 5 50       23 $card_s =~ s/\A //ms
67             or Carp::confess("_set_found_line: no leading space");
68 5         14 my @c = split( / /, $card_s );
69 5 50       12 if ( @c != $num_foundations )
70             {
71 0         0 Carp::confess( "num_foundations is "
72             . scalar(@c)
73             . " rather than $num_foundations" );
74             }
75 5         15 for my $i ( keys @c )
76             {
77 6         10 my $s = $c[$i];
78 6         23 $self->_set_found( $i,
79             Games::Solitaire::Verify::Card->new( { string => $s } ) );
80             }
81             }
82             else
83             {
84 0         0 Carp::confess("Foundations str is '$foundation_str'");
85             }
86 5         10 return;
87             }
88              
89             sub _init
90             {
91 6     6   15 my ( $self, $args ) = @_;
92              
93 6         39 my $variant = $self->_variant( $args->{variant} );
94 6 50       24 if ( not exists $_VARIANTS{$variant} )
95             {
96 0         0 Carp::confess("Unknown variant '$variant'!");
97             }
98 6         16 my $IS_BINARY_STAR = $self->_is_binary_star;
99 6   100     31 $self->_place_queens_on_kings( $args->{queens_on_kings} // '' );
100 6   100     26 $self->_wrap_ranks( $args->{wrap_ranks} // '' );
101 6 100       13 my $num_foundations = ( $IS_BINARY_STAR ? 2 : 1 );
102 6         13 $self->_num_foundations($num_foundations);
103 6         42 $self->_foundation(
104             Games::Solitaire::Verify::Freecells->new(
105             { count => $num_foundations, }
106             )
107             );
108 6         15 my $board_string = $args->{board_string};
109              
110 6         49 my @lines = split( /\n/, $board_string );
111 6         10 my $foundation_str = shift(@lines);
112 6 100       20 if ( $self->_variant eq 'golf' )
113             {
114 3 50       88 if ( $foundation_str !~ s#\ATalon: ((?:$CARD_RE ){15}$CARD_RE)#$1# )
115             {
116 0         0 Carp::confess("improper talon line <$foundation_str>!");
117             }
118             $self->_talon(
119             [
120 3         20 map { Games::Solitaire::Verify::Card->new( { string => $_ } ) }
  48         102  
121             split / /,
122             $foundation_str
123             ]
124             );
125              
126 3         9 $foundation_str = shift(@lines);
127 3         8 $self->_read_foundation_line($foundation_str);
128             }
129             else
130             {
131 3         11 $self->_talon( [] );
132 3 100       11 if ( $self->_variant eq "all_in_a_row" )
133             {
134 1 50       8 if ( $foundation_str ne "Foundations: -" )
135             {
136 0         0 Carp::confess("Foundations str is '$foundation_str'");
137             }
138             }
139             else
140             {
141 2         6 $self->_read_foundation_line($foundation_str);
142             }
143             }
144              
145             $self->_columns(
146             [
147             map {
148 6         12 Games::Solitaire::Verify::Column->new(
  68         185  
149             {
150             string => ": $_",
151             }
152             )
153             } @lines
154             ]
155             );
156 6 100       20 if ( $self->_wrap_ranks )
157             {
158 1         3 $self->_place_queens_on_kings(1);
159             }
160              
161 6         23 return;
162             }
163              
164             sub _set_found
165             {
166 345     345   576 my ( $self, $i, $card ) = @_;
167 345         1047 $self->_foundation->assign( $i, $card, );
168 345         484 return;
169             }
170              
171             sub process_solution
172             {
173 6     6 1 1711 my ( $self, $next_line_iter ) = @_;
174 6         21 my $columns = $self->_columns;
175 6         12 my $NUM_COLUMNS = @$columns;
176 6         55 my $it = Games::Solitaire::Verify::LinesIter->new(
177             { _get => $next_line_iter, } );
178 6         15 my $remaining_cards = sum( map { $_->len } @$columns );
  68         112  
179              
180 6         24 $it->_compare_line( "Solved!", "First line" );
181              
182 6         15 my $IS_BINARY_STAR = $self->_is_binary_star;
183 6         15 my $IS_GOLF = $self->_is_golf;
184 6   100     23 my $CHECK_EMPTY = ( $IS_GOLF or $self->_variant eq "black_hole" );
185 6         8 my $IS_DETAILED_MOVE = $IS_BINARY_STAR;
186 6         8 my $IS_DISPLAYED_BOARD = $IS_BINARY_STAR;
187 6         12 my $num_decks = $self->_num_foundations();
188 6         8 my $num_foundations = $self->_num_foundations();
189              
190             # As many moves as the number of cards.
191             MOVES:
192 6         21 for my $move_idx (
193             0 .. (
194             $num_decks * $MAX_RANK * $NUM_SUITS -
195             $num_foundations -
196             ( $num_foundations > 1 )
197             )
198             )
199             {
200 340         680 my ( $move_line, $move_line_idx ) = $it->_get_line;
201              
202 340         486 my $card;
203             my $col_idx;
204 340         433 my $foundation_idx = 0;
205 340         429 my $moved_card_str;
206 340 100 100     771 if ( $IS_GOLF
207             and $move_line =~ m/\ADeal talon\z/ )
208             {
209 39 50       40 if ( !@{ $self->_talon } )
  39         78  
210             {
211 0         0 Carp::confess("Talon is empty on line no. $move_line_idx");
212             }
213 39         40 $card = shift @{ $self->_talon };
  39         50  
214             }
215             else
216             {
217 301 100       2178 if (
    50          
218             not(
219             $IS_DETAILED_MOVE
220             ? ( ( $moved_card_str, $col_idx, $foundation_idx ) =
221             $move_line =~
222             m/\AMove ($CARD_RE) from stack ([0-9]+) to foundations ([0-9]+)\z/
223             )
224             : ( ($col_idx) =
225             $move_line =~
226             m/\AMove a card from stack ([0-9]+) to the foundations\z/
227             )
228             )
229             )
230             {
231 0         0 Carp::confess(
232             "Incorrect format for move line no. $move_line_idx - '$move_line'"
233             );
234             }
235             }
236              
237 340 100       675 if ( !defined $card )
238             {
239 301 50 33     1023 if ( ( $col_idx < 0 ) or ( $col_idx >= $NUM_COLUMNS ) )
240             {
241 0         0 Carp::confess(
242             "Invalid column index '$col_idx' at line no. $move_line_idx"
243             );
244             }
245             }
246              
247 340         769 $it->_assert_empty_line();
248 340         446 my ( $info_line, $info_line_idx );
249 340 100       571 if ( not $IS_DETAILED_MOVE )
250             {
251 238         307 ( $info_line, $info_line_idx ) = $it->_get_line;
252 238 50       980 if ( $info_line !~ m/\AInfo: Card moved is ($CARD_RE)\z/ )
253             {
254 0         0 Carp::confess(
255             "Invalid format for info line no. $info_line_idx - '$info_line'"
256             );
257             }
258              
259 238         380 $moved_card_str = $1;
260              
261 238         405 $it->_assert_empty_line();
262 238         400 $it->_assert_empty_line();
263              
264 238         342 my ( $sep_line, $sep_line_idx ) = $it->_get_line;
265              
266 238 50       558 if ( $sep_line !~ m/\A=+\z/ )
267             {
268 0         0 Carp::confess(
269             "Invalid format for separator line no. $sep_line_idx - '$sep_line'"
270             );
271             }
272              
273 238         340 $it->_assert_empty_line();
274             }
275              
276 340 100       613 if ( defined $card )
277             {
278 39         70 my $top_card_moved_str = $card->to_string();
279 39 50       61 if ( $top_card_moved_str ne $moved_card_str )
280             {
281 0         0 Carp::confess(
282             "Card moved should be '$top_card_moved_str', but the info says it is '$moved_card_str' at line $info_line_idx"
283             );
284             }
285             }
286             else
287             {
288 301         442 my $col = $columns->[$col_idx];
289 301         650 my $top_card = $col->top();
290 301         674 my $top_card_moved_str = $top_card->to_string();
291              
292 301 50       573 if ( $top_card_moved_str ne $moved_card_str )
293             {
294 0         0 Carp::confess(
295             "Card moved should be '$top_card_moved_str', but the info says it is '$moved_card_str' at line $info_line_idx"
296             );
297             }
298              
299 301         864 my $found_card = $self->_foundation->cell($foundation_idx);
300 301 100       543 if ( defined($found_card) )
301             {
302 300         425 my $found_rank = $found_card->rank();
303 300         421 my $src_rank = $top_card->rank();
304              
305 300         452 my $delta = abs( $src_rank - $found_rank );
306 300 100 66     1202 if (
      100        
      66        
      100        
      66        
307             not( $delta == 1 or $delta == ( $MAX_RANK - 1 ) )
308             or (
309             $IS_GOLF
310             and ( !$self->_wrap_ranks )
311             and (
312             (
313             $self->_place_queens_on_kings
314             ? ( $found_rank == $MAX_RANK )
315             : 0
316             )
317             or $delta != 1
318             )
319             )
320             )
321             {
322 1         3 Carp::confess(
323             "Cannot put $top_card_moved_str in the foundations that contain "
324             . $found_card->to_string() );
325             }
326 299 100       533 if ($IS_DISPLAYED_BOARD)
327             {
328 102         352 my $wanted_line = $self->_foundation->to_string();
329 102 50       636 $wanted_line =~ s#\AFreecells:#Foundations:#
330             or Carp::confess("Unimpl!");
331 102         465 $wanted_line =~ s# # #g;
332 102         308 my $fstr = $found_card->to_string();
333 102         224 my $tstr = $top_card->to_string();
334 102 50       7146 $wanted_line =~
335 102         368 s#\AFoundations:(?: $CARD_RE){$foundation_idx} \K(\Q$fstr\E)#my$c=$1;"[ $c -> $tstr ]"#e
  102         517  
336             or Carp::confess(
337             "Failed substitute! foundation_idx=$foundation_idx wanted_line=$wanted_line fstr='$fstr'"
338             );
339 102         568 $it->_compare_line( $wanted_line, "Foundations" );
340 102         343 for my $i ( keys @$columns )
341             {
342 1734         3062 my $col = $columns->[$i];
343 1734         4000 my $wanted_line = $col->to_string();
344 1734 100       3876 if ( $i == $col_idx )
345             {
346 102 50       2814 $wanted_line =~
347 102         302 s# \K(\Q$tstr\E)\z#my$c=$1;"[ $c -> ]"#e
  102         499  
348             or Carp::confess(
349             "Failed column substitute! foundation_idx=$foundation_idx wanted_line=$wanted_line tstr='$tstr'"
350             );
351             }
352 1734         5122 $it->_compare_line( $wanted_line, "Column $i" );
353             }
354 102         479 $it->_assert_empty_line();
355             }
356             }
357 300         670 $card = $col->pop;
358 300         472 --$remaining_cards;
359             }
360 339 50       629 if ( not defined $foundation_idx )
361             {
362 0         0 Carp::confess("\$foundation_idx not set");
363             }
364 339         768 $self->_set_found( $foundation_idx, $card, );
365 339 100       697 if ($CHECK_EMPTY)
366             {
367 185 100       434 if ( $remaining_cards == 0 )
368             {
369 3         10 last MOVES;
370             }
371             }
372             }
373 5         24 return;
374             }
375              
376             1;
377              
378             __END__