File Coverage

blib/lib/Games/Solitaire/BlackHole/Solver/App/Base.pm
Criterion Covered Total %
statement 344 409 84.1
branch 74 120 61.6
condition 10 14 71.4
subroutine 34 41 82.9
pod 1 1 100.0
total 463 585 79.1


line stmt bran cond sub pod time code
1             package Games::Solitaire::BlackHole::Solver::App::Base;
2             $Games::Solitaire::BlackHole::Solver::App::Base::VERSION = '0.18.0';
3 12     12   385774 use Moo;
  12         10939  
  12         87  
4 12     12   14836 use Getopt::Long qw/ GetOptions /;
  12         173114  
  12         116  
5 12     12   8737 use Pod::Usage qw/ pod2usage /;
  12         881347  
  12         1129  
6 12     12   6945 use Math::Random::MT ();
  12         15462  
  12         569  
7 12     12   108 use List::Util 1.34 qw/ any first max /;
  12         329  
  12         75055  
8              
9             extends('Exporter');
10              
11             # These attributes should remain constant during a solver's run.
12             has '_num_foundations' => ( default => 1, is => 'rw', );
13              
14             my $solver_const_attrs = [
15             '_bits_offset', '_display_boards',
16             '_do_not_err_on_exceeding_max_iters_limit', '_init_tasks_configs',
17             '_is_good_diff', '_max_iters_limit',
18             '_prelude', '_prelude_string',
19             '_talon_cards', '_quiet',
20             '_output_handle', '_output_fn',
21             '_should_show_maximal_num_played_cards',
22             ];
23              
24             my $board_const_attrs = [
25             '_board_cards', '_board_lines',
26             '_board_values', '_init_foundation',
27             '_init_foundation_cards', '_init_queue',
28             ];
29              
30             my $CHECK_SET_ONLY_ONCE_KEY = "BLACK_HOLE_SOLVER_RUNTIME_CHECKS";
31             my $CHECK_SET_ONLY_ONCE = $ENV{$CHECK_SET_ONLY_ONCE_KEY};
32             if ( not $CHECK_SET_ONLY_ONCE )
33             {
34             has [ @$board_const_attrs, @$solver_const_attrs ] => ( is => 'rw', );
35             }
36             else
37             {
38             foreach my $const_attr (@$solver_const_attrs)
39             {
40             has $const_attr => (
41             is => 'rw',
42             (
43             $CHECK_SET_ONLY_ONCE
44             ? (
45             trigger => sub {
46             my ( $self, $newval ) = @_;
47             die "self=$self const_attr=$const_attr"
48             if ( $self->{_SOLVER_CTR}->{$const_attr}++ );
49             return;
50             }
51             )
52             : ()
53             )
54             );
55             }
56             foreach my $const_attr (@$board_const_attrs)
57             {
58             has $const_attr => (
59             is => 'rw',
60             (
61             $CHECK_SET_ONLY_ONCE
62             ? (
63             trigger => sub {
64             my ( $self, $newval ) = @_;
65             die "self=$self const_attr=$const_attr"
66             if ( $self->{_BOARD_CTR}->{$const_attr}++ );
67             return;
68             }
69             )
70             : ()
71             )
72             );
73             }
74             }
75              
76             # These attributes mutate during a solver's run.
77             has [
78             '_active_record', '_active_task',
79             '_max_iters_limit_exceeded', '_maximal_num_played_cards__from_all_tasks',
80             '_num_traversed_positions', '_pending_board_lines',
81             '_prelude_iter', '_positions',
82             '_tasks', '_task_idx',
83             ] => ( is => 'rw' );
84              
85             our %EXPORT_TAGS = ( 'all' => [qw($card_re)] );
86             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
87              
88             my @ranks = ( "A", 2 .. 9, qw(T J Q K) );
89             my %ranks_to_n = ( map { $ranks[$_] => $_ } 0 .. $#ranks );
90              
91             sub _RANK_KING
92             {
93 11     11   44 return $ranks_to_n{'K'};
94             }
95              
96             my $card_re_str = '[' . join( "", @ranks ) . '][HSCD]';
97             our $card_re = qr{$card_re_str};
98              
99             sub _get_rank
100             {
101 676     676   963 shift;
102 676         1652 return $ranks_to_n{ substr( shift(), 0, 1 ) };
103             }
104              
105             sub _calc_lines
106             {
107 13     13   32 my $self = shift;
108 13         44 my $filename = shift;
109              
110 13         31 my @lines;
111 13 50       111 if ( $self->_pending_board_lines )
    50          
112             {
113 0         0 @lines = @{ $self->_pending_board_lines };
  0         0  
114 0         0 $self->_pending_board_lines(undef);
115             }
116             elsif ( $filename eq "-" )
117             {
118 0         0 @lines = ;
119             }
120             else
121             {
122 13 50       1016 open my $in, "<", $filename
123             or die
124             "Could not open $filename for inputting the board lines - $!";
125 13         561 @lines = <$in>;
126 13         237 close($in);
127             }
128 13         99 chomp @lines;
129 13         148 $self->_board_lines( \@lines );
130 13         59 return;
131             }
132              
133             sub _update_max_num_played_cards
134             {
135 15     15   42 my $self = shift;
136              
137 15         28 foreach my $task ( @{ $self->_tasks } )
  15         63  
138             {
139 15         141 $self->_update_max_reached_depths_stack_len($task);
140             }
141              
142 15         31 return;
143             }
144              
145             sub _output_board
146             {
147             my (
148 250     250   460 $self, $prev_state, $moves,
149             $changed_foundation, $col_idx, $foundation_str_ref
150             ) = @_;
151 250 100       513 if ( not $self->_display_boards )
152             {
153 148         260 return;
154             }
155 102         194 my $_num_foundations = $self->_num_foundations();
156 102         207 my $offset = $self->_bits_offset();
157 102         141 my $ret = '';
158 102         153 my $foundation_val;
159 102         142 while ( my ( $i, $col ) = each( @{ $self->_board_cards } ) )
  1836         4275  
160             {
161 1734         2501 my $prevlen = vec( $prev_state, $offset + $i, 4 );
162 1734         2282 my $height = $prevlen - 1;
163 1734         2335 my $iscurr = ( $col_idx == $i );
164 1734         3272 my @c = @$col[ 0 .. $height ];
165 1734 100       2933 if ($iscurr)
166             {
167 102         252 $foundation_val = $self->_board_values->[$col_idx][$height];
168 102         174 foreach my $x ( $c[-1] )
169             {
170 102         152 $$foundation_str_ref = $x;
171 102         199 $x = "[ $x -> ]";
172             }
173             }
174 1734         3622 $ret .= join( " ", ":", @c ) . "\n";
175             }
176              
177 102         612 push @$moves,
178             +{
179             type => "board",
180             str => $ret,
181             foundation_str => $$foundation_str_ref,
182             foundation_val => $foundation_val,
183             changed_foundation => $changed_foundation,
184             };
185 102         206 return;
186             }
187              
188             sub _trace_solution
189             {
190 6     6   99 my ( $self, $final_state ) = @_;
191              
192 6         30 my $_num_foundations = $self->_num_foundations();
193 6         24 my $offset = $self->_bits_offset();
194              
195 6         170 $self->_update_max_num_played_cards();
196 6         73 my $output_handle = $self->_output_handle;
197 6         217 $output_handle->print("Solved!\n");
198              
199 6 100       7629 return if $self->_quiet;
200              
201 5         13 my $state = $final_state;
202              
203 5         13 my @moves;
204             LOOP:
205 5         11 while ( my ( $prev_state, $col_idx ) = @{ $self->_positions->{$state} } )
  255         905  
206             {
207 255 100       466 last LOOP if not defined $prev_state;
208             my $changed_foundation =
209 250     250   620 first { vec( $state, $_, 8 ) ne vec( $prev_state, $_, 8 ) }
210 250         1039 ( 0 .. $_num_foundations - 1 );
211 250 50       813 if ( not defined($changed_foundation) )
212             {
213 0         0 die
214             "ERROR! Could not find changed_foundation. It must not have happened!";
215             }
216 250         335 my $foundation_str;
217 250         586 $self->_output_board(
218             $prev_state, \@moves, $changed_foundation,
219             $col_idx, \$foundation_str
220             );
221             push @moves,
222             +{
223             type => "card",
224             str => scalar(
225 250 100       397 ( $col_idx == @{ $self->_board_cards } ) ? "Deal talon "
  250 100       1369  
226             . $self->_talon_cards->[ vec( $prev_state, 1, 8 ) ]
227             : $self->_display_boards
228             ? sprintf( "Move %s from stack %d to foundations %d",
229             $foundation_str, $col_idx, $changed_foundation, )
230             : $self->_board_cards->[$col_idx]
231             [ vec( $prev_state, $offset + $col_idx, 4 ) - 1 ]
232             ),
233             };
234             }
235             continue
236             {
237 250         480 $state = $prev_state;
238             }
239              
240 5         13 my @foundation_cards = @{ $self->_init_foundation_cards() };
  5         34  
241 5         17 foreach my $move_rec ( reverse(@moves) )
242             {
243 352         510 my $type = $move_rec->{"type"};
244 352         447 my $str;
245 352 100       563 if ( $type eq "card" )
    50          
246             {
247 250         324 $str = "";
248 250         378 $str .= $move_rec->{"str"};
249             }
250             elsif ( $type eq "board" )
251             {
252 102 50       206 if ( not $self->_display_boards )
253             {
254 0         0 die;
255             }
256 102         135 my $i = $move_rec->{"changed_foundation"};
257 102         140 my $new = $move_rec->{"foundation_str"};
258 102         158 my @shown = @foundation_cards;
259 102         152 foreach my $x ( $shown[$i] )
260             {
261 102         152 $x = "[ $x -> $new ]";
262             }
263 102         128 $str = "";
264 102         140 $str .= "\n";
265 102         166 $str .= join( " ", "Foundations:", @shown ) . "\n";
266 102         140 $str .= $move_rec->{"str"};
267 102         157 $foundation_cards[$i] = $new;
268             }
269             else
270             {
271 0         0 die;
272             }
273 352         441 print {$output_handle} "$str\n";
  352         803  
274             }
275              
276 5         110 return;
277             }
278              
279             sub get_max_num_played_cards
280             {
281 9     9 1 18 my $self = shift;
282              
283 9         54 $self->_update_max_num_played_cards();
284              
285 9         22 my $ret = $self->_maximal_num_played_cards__from_all_tasks();
286 9 50       75 return ( ( $ret == 0 ) ? $ret : ( $ret - 1 ) );
287             }
288              
289             sub _end_report
290             {
291 12     12   36 my ( $self, $verdict, ) = @_;
292 12         51 my $output_handle = $self->_output_handle;
293              
294 12 100       81 if ( !$verdict )
295             {
296 7         25 my $SOFT_EXCEEDED = $self->_do_not_err_on_exceeding_max_iters_limit();
297 7 100 100     41 if ( $SOFT_EXCEEDED and $self->_max_iters_limit_exceeded() )
298             {
299 4         50 $output_handle->print("Exceeded max_iters_limit !\n");
300             }
301             else
302             {
303 3         22 $output_handle->print("Unsolved!\n");
304             }
305             }
306 12 100       119 if ( $self->_should_show_maximal_num_played_cards() )
307             {
308 9         50 $output_handle->printf(
309             "At most %u cards could be played.\n",
310             $self->get_max_num_played_cards()
311             );
312             }
313             $output_handle->printf(
314 12         187 "Total number of states checked is %u.\n",
315             scalar( $self->_num_traversed_positions() ),
316             );
317             $output_handle->printf(
318             "This scan generated %u states.\n",
319 12         103 scalar( keys %{ $self->_positions } ),
  12         97  
320             );
321              
322 12         98 return;
323             }
324              
325             sub _my_exit
326             {
327 9     9   27 my ( $self, $verdict, ) = @_;
328              
329 9 50       65 if ( defined( $self->_output_fn ) )
330             {
331 9         594 close( $self->_output_handle );
332             }
333              
334 9         0 exit( !$verdict );
335             }
336              
337             sub _parse_board
338             {
339 13     13   41 my ($self) = @_;
340 13         42 my $lines = $self->_board_lines;
341              
342 13         35 my $found_line = shift(@$lines);
343              
344 13         33 my $init_foundation;
345             my @init_foundation_cards;
346 13         46 my $_num_foundations = $self->_num_foundations();
347 13 50       576 if ( my ($card) =
348             $found_line =~ m{\AFoundations:((?: $card_re){$_num_foundations})\z} )
349             {
350 13 50       95 $card =~ s#\A ## or die "no whitespace";
351 13         48 @init_foundation_cards = split /\s+/, $card;
352             $init_foundation =
353 13         37 [ map { $self->_get_rank($_) } @init_foundation_cards ];
  13         80  
354             }
355             else
356             {
357 0         0 die "Could not match first foundation line!";
358             }
359 13         95 $self->_init_foundation_cards( \@init_foundation_cards );
360 13         58 $self->_init_foundation($init_foundation);
361              
362 13         52 $self->_board_cards( [ map { [ split /\s+/, $_ ] } @$lines ] );
  211         829  
363             $self->_board_values(
364             [
365             map {
366 211         409 [ map { $self->_get_rank($_) } @$_ ]
  647         1092  
367 13         36 } @{ $self->_board_cards }
  13         44  
368             ]
369             );
370 13         61 return;
371             }
372              
373             sub _set_up_initial_position
374             {
375 13     13   42 my ( $self, $talon_ptr ) = @_;
376              
377 13         85 $self->_max_iters_limit_exceeded(0);
378 13         83 $self->_num_traversed_positions(0);
379              
380 13         30 my $init_state = "";
381              
382 13         47 my $offset = $self->_bits_offset();
383 13         28 my $o = 0;
384 13         26 foreach my $x ( @{ $self->_init_foundation } )
  13         84  
385             {
386 13         85 vec( $init_state, $o++, 8 ) = $x;
387             }
388 13         53 vec( $init_state, $o, 8 ) = $talon_ptr;
389              
390 13         44 my $board_values = $self->_board_values;
391 13         50 foreach my $col_idx ( keys @$board_values )
392             {
393             vec( $init_state, $offset + $col_idx, 4 ) =
394 211         313 scalar( @{ $board_values->[$col_idx] } );
  211         532  
395             }
396              
397             # The values of $positions is an array reference with the 0th key being the
398             # previous state, and the 1th key being the column of the move.
399 13         20158 $self->_positions( +{ $init_state => [ undef, undef, 1, 0, ], } );
400              
401 13         94 $self->_init_queue( [$init_state] );
402              
403 13         36 return;
404             }
405              
406             sub _shuffle
407             {
408 0     0   0 my ( $self, $gen, $arr ) = @_;
409              
410 0         0 my $i = $#$arr;
411 0         0 while ( $i > 0 )
412             {
413 0         0 my $j = int( $gen->rand( $i + 1 ) );
414 0 0       0 if ( $i != $j )
415             {
416 0         0 @$arr[ $i, $j ] = @$arr[ $j, $i ];
417             }
418 0         0 --$i;
419             }
420 0         0 return;
421             }
422              
423             sub _set_bits_offset
424             {
425 9     9   49 my $self = shift;
426              
427 9         48 my $_num_foundations = $self->_num_foundations();
428 9         52 my $offset = 2 + 2 * $_num_foundations;
429 9         63 $self->_bits_offset($offset);
430              
431 9         19 return;
432             }
433              
434             my $TASK_NAME_RE = qr/[A-Za-z0-9_]+/;
435             my $TASK_ALLOC_RE = qr/[0-9]+\@$TASK_NAME_RE/;
436              
437             sub _process_cmd_line
438             {
439 9     9   40 my ( $self, $args ) = @_;
440              
441 9         25 my $_max_iters_limit = ( 1 << 31 );
442 9         21 my $_num_foundations = 1;
443 9         18 my $_prelude_string;
444 9         19 my $_should_show_maximal_num_played_cards = 0;
445 9         50 my $display_boards = '';
446 9         40 my $quiet = '';
447 9         20 my $output_fn;
448 9         46 my ( $help, $man, $version );
449 9         0 my @tasks;
450              
451             my $push_task = sub {
452 9     9   45 push @tasks,
453             +{
454             name => undef(),
455             seed => 0,
456             };
457 9         19 return;
458 9         48 };
459 9         38 $push_task->();
460             GetOptions(
461             "display-boards!" => \$display_boards,
462             "o|output=s" => \$output_fn,
463             "quiet!" => \$quiet,
464             "next-task" => sub {
465 0     0   0 $push_task->();
466 0         0 return;
467             },
468             "num-foundations=i" => sub {
469 0     0   0 my ( undef, $val ) = @_;
470 0 0 0     0 if ( not( ( $val eq "1" ) or ( $val eq "2" ) ) )
471             {
472 0         0 die;
473             }
474 0         0 $_num_foundations = $val;
475 0         0 return;
476             },
477             "prelude=s" => sub {
478 0     0   0 my ( undef, $val ) = @_;
479 0 0       0 if ( $val !~ /\A$TASK_ALLOC_RE(?:,$TASK_ALLOC_RE)*\z/ )
480             {
481 0         0 die "Invalid prelude string '$val' !";
482             }
483 0         0 $_prelude_string = $val;
484 0         0 return;
485             },
486             "task-name=s" => sub {
487 0     0   0 my ( undef, $val ) = @_;
488 0 0       0 if ( $val !~ /\A$TASK_NAME_RE\z/ )
489             {
490 0         0 die "Invalid task name '$val' - must be alphanumeric!";
491             }
492 0         0 $tasks[-1]->{name} = $val;
493 0         0 return;
494             },
495             "seed=i" => sub {
496 0     0   0 my ( undef, $val ) = @_;
497 0         0 $tasks[-1]->{seed} = $val;
498 0         0 return;
499             },
500             "show-max-num-played-cards!" => sub {
501 5     5   5188 my ( undef, $val ) = @_;
502 5         13 $_should_show_maximal_num_played_cards = $val;
503 5         45 return;
504             },
505             "max-iters=i" => sub {
506 3     3   5493 my ( undef, $val ) = @_;
507 3         7 $_max_iters_limit = $val;
508 3         38 return;
509             },
510             'help|h|?' => \$help,
511             'man' => \$man,
512             'version' => \$version,
513 9 50       176 %{ $args->{extra_flags} },
  9         75  
514             ) or pod2usage(2);
515 9 50       7097 if ( @tasks == 1 )
516             {
517 9         36 $tasks[-1]{name} = 'default';
518             }
519 9 50   9   82 if ( any { !defined $_->{name} } @tasks )
  9         41  
520             {
521 0         0 die "You did not specify the task-names for some tasks";
522             }
523 9         114 $self->_init_tasks_configs( \@tasks );
524              
525 9 50       31 pod2usage(1) if $help;
526 9 50       28 pod2usage( -exitstatus => 0, -verbose => 2 ) if $man;
527              
528 9 50       33 if ($version)
529             {
530 0         0 print
531             "black-hole-solve version $Games::Solitaire::BlackHole::Solver::App::Base::VERSION\n";
532 0         0 exit(0);
533             }
534              
535 9         92 $self->_display_boards($display_boards);
536 9         58 $self->_max_iters_limit($_max_iters_limit);
537 9         46 $self->_num_foundations($_num_foundations);
538 9 50       29 if ( defined($_prelude_string) )
539             {
540 0         0 $self->_prelude_string($_prelude_string);
541             }
542 9         117 $self->_quiet($quiet);
543 9         59 $self->_should_show_maximal_num_played_cards(
544             $_should_show_maximal_num_played_cards);
545 9         46 $self->_set_bits_offset();
546              
547 9         45 my $output_handle;
548              
549 9 50       35 if ( defined($output_fn) )
550             {
551 9 50       2171 open( $output_handle, ">", $output_fn )
552             or die "Could not open '$output_fn' for writing";
553             }
554             else
555             {
556             ## no critic
557             # open( $output_handle, ">&STDOUT" );
558 0         0 $output_handle = \*STDOUT;
559             ## use critic
560             # die $output_handle;"applj";
561             }
562 9         145 $self->_output_fn($output_fn);
563 9         47 $self->_output_handle($output_handle);
564              
565             # $self->_calc_lines( shift(@ARGV) );
566              
567 9         65 return;
568             }
569              
570             sub _set_up_tasks
571             {
572 13     13   36 my ($self) = @_;
573 13         61 $self->_maximal_num_played_cards__from_all_tasks(0);
574              
575 13         46 my @tasks;
576             my %tasks_by_names;
577 13         42 foreach my $task_rec ( @{ $self->_init_tasks_configs } )
  13         81  
578             {
579 13         38 my $iseed = $task_rec->{seed};
580 13         33 my $name = $task_rec->{name};
581 13         37 my $_task_idx = @tasks;
582 13   50     253 my $task_obj =
583             Games::Solitaire::BlackHole::Solver::App::Base::Task->new(
584             {
585             _name => $name,
586             _seed => $iseed,
587             _gen => Math::Random::MT->new( $iseed || 1 ),
588             _remaining_iters => 100,
589             _task_idx => $_task_idx,
590             }
591             );
592 13         252 $task_obj->_push_to_queue( $self->_init_queue );
593 13         26 push @tasks, $task_obj;
594 13 50       48 if ( exists $tasks_by_names{$name} )
595             {
596 0         0 die "Duplicate task-name '$name'!";
597             }
598 13         52 $tasks_by_names{$name} = $task_obj;
599             }
600 13         76 $self->_task_idx(0);
601 13         76 $self->_tasks( \@tasks );
602 13 100       103 if ( not $self->_prelude )
603             {
604 9         17 my @prelude;
605             my $process_item = sub {
606 0     0   0 my $s = shift;
607 0 0       0 if ( my ( $quota, $name ) = $s =~ /\A([0-9]+)\@($TASK_NAME_RE)\z/ )
608             {
609 0 0       0 if ( not exists $self->_tasks_by_names->{$name} )
610             {
611 0         0 die "Unknown task name $name in prelude!";
612             }
613 0         0 my $task_obj = $self->_tasks_by_names->{$name};
614             return
615 0         0 Games::Solitaire::BlackHole::Solver::App::Base::PreludeItem
616             ->new(
617             {
618             _quota => $quota,
619             _task => $task_obj,
620             _task_idx => $task_obj->_task_idx,
621             _task_name => $task_obj->_name,
622             }
623             );
624             }
625             else
626             {
627 0         0 die "foo";
628             }
629 9         59 };
630 9 50       58 if ( my $_prelude_string = $self->_prelude_string )
631             {
632             push @prelude,
633             (
634 0         0 map { $process_item->($_) }
  0         0  
635             split /,/, $_prelude_string
636             );
637             }
638 9         134 $self->_prelude( \@prelude );
639             }
640 13         82 $self->_prelude_iter(0);
641 13 50       22 if ( @{ $self->_prelude } )
  13         77  
642             {
643 0         0 $self->_next_task;
644             }
645 13         35 return;
646             }
647              
648             sub _update_max_reached_depths_stack_len
649             {
650 829     829   2117 my ( $self, $task ) = @_;
651              
652 829         6270 $self->_maximal_num_played_cards__from_all_tasks(
653             max(
654             $self->_maximal_num_played_cards__from_all_tasks,
655             $task->_max_reached_depths_stack_len
656             )
657             );
658              
659 829         2103 return;
660             }
661              
662             sub _next_task
663             {
664 814     814   2148 my ($self) = @_;
665 814 50       2745 if ( $self->_prelude_iter < @{ $self->_prelude } )
  814         4065  
666             {
667 0         0 my $alloc = $self->_prelude->[ $self->{_prelude_iter}++ ];
668 0         0 my $task = $alloc->_task;
669 0         0 $self->_update_max_reached_depths_stack_len($task);
670 0 0       0 if ( !@{ $task->_queue } )
  0         0  
671             {
672 0         0 return $self->_next_task;
673             }
674 0         0 $task->_remaining_iters( $alloc->_quota );
675 0         0 $self->_active_task($task);
676 0         0 return 1;
677             }
678 814         3212 my $tasks = $self->_tasks;
679 814 50       2772 return if !@$tasks;
680 814 50       1481 if ( !@{ $tasks->[ $self->_task_idx ]->_queue } )
  814         4833  
681             {
682 0         0 splice @$tasks, $self->_task_idx, 1;
683 0         0 return $self->_next_task;
684             }
685 814         2339 my $task = $tasks->[ $self->_task_idx ];
686 814         4205 $self->_update_max_reached_depths_stack_len($task);
687 814         2994 $self->_task_idx( ( $self->_task_idx + 1 ) % @$tasks );
688 814         2535 $task->_remaining_iters(100);
689 814         2085 $self->_active_task($task);
690              
691 814         4936 return 1;
692             }
693              
694             sub _get_next_state
695             {
696 80806     80806   139349 my ($self) = @_;
697              
698 80806         116431 my $l = @{ $self->_active_task->_queue };
  80806         194564  
699 80806         130292 my $ret = pop( @{ $self->_active_task->_queue } );
  80806         182752  
700 80806         175553 my $stack = $self->_active_task->_depths_stack;
701 80806   100     297700 while ( @$stack and ( $stack->[-1] == $l ) )
702             {
703 135904         430003 pop @$stack;
704             }
705 80806         143246 push @$stack, ( $l - 1 );
706 80806         222788 return $ret;
707             }
708              
709             sub _get_next_state_wrapper
710             {
711 80806     80806   158769 my ($self) = @_;
712              
713 80806         157094 my $positions = $self->_positions;
714              
715 80806         171698 while ( my $state = $self->_get_next_state )
716             {
717 80799         173064 my $rec = $positions->{$state};
718 80799         165749 $self->_active_record($rec);
719 80799 50       316705 return $state if $rec->[2];
720             }
721 7         27 return;
722             }
723              
724             sub _process_pending_items
725             {
726 80793     80793   170432 my ( $self, $_pending, $state ) = @_;
727              
728 80793         180568 my $rec = $self->_active_record;
729 80793         160230 my $task = $self->_active_task;
730              
731 80793 100       161193 if (@$_pending)
732             {
733 55399 50       148631 $self->_shuffle( $task->_gen, $_pending ) if $task->_seed;
734 55399         118062 $task->_push_to_queue( [ map { $_->[0] } @$_pending ] );
  81013         281843  
735 55399         131009 $rec->[3] += ( scalar grep { !$_->[1] } @$_pending );
  81013         177194  
736             }
737             else
738             {
739 25394         42415 my $parent = $state;
740 25394         37866 my $parent_rec = $rec;
741 25394         51228 my $positions = $self->_positions;
742              
743             PARENT:
744 25394   100     66203 while ( ( !$parent_rec->[3] ) or ( ! --$parent_rec->[3] ) )
745             {
746 80492         124922 $parent_rec->[2] = 0;
747 80492         132327 $parent = $parent_rec->[0];
748 80492 100       152226 last PARENT if not defined $parent;
749 80485         298741 $parent_rec = $positions->{$parent};
750             }
751             }
752 80793 100       200451 if ( not --$task->{_remaining_iters} )
753             {
754 801         3756 return $self->_next_task;
755             }
756 79992         371249 return 1;
757             }
758              
759             sub _find_moves
760             {
761 80799     80799   170990 my ( $self, $_pending, $state, $no_cards ) = @_;
762 80799         173673 my $board_values = $self->_board_values;
763 80799         161774 my $_num_foundations = $self->_num_foundations();
764 80799         155138 my $offset = $self->_bits_offset();
765 80799         131378 my $used = '';
766 80799         119166 my @fnd;
767 80799         182657 $self->_num_traversed_positions( 1 + $self->_num_traversed_positions() );
768 80799         191304 foreach my $i ( 0 .. $_num_foundations - 1 )
769             {
770 80799         149853 my $v = vec( $state, $i, 8 );
771 80799 50       194110 if ( not vec( $used, $v, 1 ) )
772             {
773 80799         227136 vec( $used, $v, 1 ) = 1;
774 80799         235262 push @fnd, [ $i, $v ];
775             }
776             }
777 80799         175344 my $max_iters_limit = $self->_max_iters_limit;
778 80799         144169 my $positions = $self->_positions;
779 80799         151217 my $_is_good_diff = $self->_is_good_diff;
780 80799         209426 foreach my $col_idx ( keys @$board_values )
781             {
782 1361957         2160191 my $pos = vec( $state, $offset + $col_idx, 4 );
783              
784 1361957 100       2580585 if ($pos)
785             {
786 756435         1129154 $$no_cards = 0;
787              
788 756435         1273412 my $card = $board_values->[$col_idx][ $pos - 1 ];
789 756435 50       1391738 die if not defined $card;
790 756435         1193224 foreach my $x (@fnd)
791             {
792 756435         1291485 my ( $i, $v ) = @$x;
793 756435 100       1872279 if ( exists( $_is_good_diff->{ $card - $v } ) )
794             {
795 119285         190056 my $next_s = $state;
796 119285         313056 vec( $next_s, $i, 8 ) = $card;
797 119285         304233 --vec( $next_s, $offset + $col_idx, 4 );
798 119285         290580 my $exists = exists( $positions->{$next_s} );
799 119285         181129 my $to_add = 0;
800 119285 100       270272 if ( !$exists )
    50          
801             {
802 80592         323675 $positions->{$next_s} = [ $state, $col_idx, 1, 0 ];
803 80592 100       193658 if ( keys(%$positions) >= $max_iters_limit )
804             {
805 165         334 $self->_max_iters_limit_exceeded(1);
806 165 50       378 if ( $self->_do_not_err_on_exceeding_max_iters_limit
807             )
808             {
809 165         501 return;
810             }
811 0         0 die "Exceeded max_iters_limit !";
812             }
813 80427         124604 $to_add = 1;
814             }
815             elsif ( $positions->{$next_s}->[2] )
816             {
817 0         0 $to_add = 1;
818             }
819 119120 100       255144 if ($to_add)
820             {
821 80427         312728 push( @$_pending, [ $next_s, $exists ] );
822             }
823             }
824             }
825             }
826             }
827              
828 80634         320924 return;
829             }
830              
831             sub _set_up_solver
832             {
833 13     13   41 my ( $self, $talon_ptr, $diffs ) = @_;
834              
835 13         77 $self->_parse_board;
836 13         76 $self->_set_up_initial_position($talon_ptr);
837 13         96 $self->_set_up_tasks;
838 13 100       106 if ( not $self->_is_good_diff )
839             {
840 9         32 $self->_is_good_diff( +{ map { $_ => 1 } map { $_, -$_ } @$diffs, } );
  34         110  
  17         82  
841             }
842              
843 13         49 return;
844             }
845              
846             package Games::Solitaire::BlackHole::Solver::App::Base::Task;
847             $Games::Solitaire::BlackHole::Solver::App::Base::Task::VERSION = '0.18.0';
848 12     12   126 use Moo;
  12         39  
  12         113  
849              
850             has '_queue' => ( is => 'ro', default => sub { return []; }, );
851             has '_depths_stack' => ( is => 'ro', default => sub { return []; }, );
852             has '_max_reached_depths_stack_len' => ( is => 'rw', default => 0 );
853             has [ '_gen', '_task_idx', '_name', '_remaining_iters', '_seed', ] =>
854             ( is => 'rw' );
855              
856             sub _push_to_queue
857             {
858 55412     55412   108175 my ( $self, $items ) = @_;
859 55412 50       120603 die if not @$items;
860 55412         104436 push @{ $self->_queue }, @$items;
  55412         153921  
861 55412         92346 push @{ $self->_depths_stack }, scalar( @{ $self->_queue } );
  55412         109884  
  55412         107606  
862 55412         87195 my $l = @{ $self->_depths_stack };
  55412         104889  
863 55412 100       158328 if ( $l > $self->_max_reached_depths_stack_len() )
864             {
865 517         999 $self->_max_reached_depths_stack_len($l);
866             }
867              
868 55412         100806 return;
869             }
870              
871             package Games::Solitaire::BlackHole::Solver::App::Base::PreludeItem;
872             $Games::Solitaire::BlackHole::Solver::App::Base::PreludeItem::VERSION = '0.18.0';
873 12     12   9589 use Moo;
  12         64  
  12         60  
874              
875             has [ '_quota', '_task', '_task_idx', '_task_name', ] => ( is => 'rw' );
876              
877             1;
878              
879             __END__