File Coverage

blib/lib/Games/ABC_Path/Generator.pm
Criterion Covered Total %
statement 146 153 95.4
branch 17 22 77.2
condition 2 3 66.6
subroutine 26 26 100.0
pod 2 2 100.0
total 193 206 93.6


line stmt bran cond sub pod time code
1             package Games::ABC_Path::Generator;
2             $Games::ABC_Path::Generator::VERSION = '0.6.0';
3 2     2   163896 use 5.006;
  2         27  
4              
5 2     2   12 use strict;
  2         3  
  2         57  
6 2     2   11 use warnings;
  2         4  
  2         87  
7              
8 2     2   553 use integer;
  2         18  
  2         10  
9              
10 2     2   1019 use parent 'Games::ABC_Path::Generator::Base';
  2         719  
  2         10  
11              
12 2     2   86 use Games::ABC_Path::Solver::Constants;
  2         4  
  2         221  
13 2     2   1249 use Games::ABC_Path::Solver::Board '0.1.0';
  2         21978  
  2         94  
14 2     2   1200 use Games::ABC_Path::MicrosoftRand ();
  2         6  
  2         54  
15 2     2   963 use Games::ABC_Path::Generator::RiddleObj ();
  2         6  
  2         47  
16 2     2   934 use Games::ABC_Path::Generator::FinalLayoutObj ();
  2         6  
  2         44  
17 2     2   885 use Games::ABC_Path::Generator::Coord ();
  2         6  
  2         3399  
18              
19              
20             sub _init
21             {
22 1     1   106 my $self = shift;
23 1         2 my $args = shift;
24              
25 1         6 $self->{seed} = $args->{seed};
26              
27             $self->{rand} =
28 1         15 Games::ABC_Path::MicrosoftRand->new( seed => $self->{seed} );
29              
30 1         3 return;
31             }
32              
33             sub _shuffle
34             {
35 52     52   89 my ( $self, $deck ) = @_;
36              
37 52         130 return $self->{'rand'}->shuffle($deck);
38             }
39              
40             {
41             my @get_next_cells_lookup = (
42             map {
43             my $start = Games::ABC_Path::Generator::Coord->_from_int($_);
44             [
45             map {
46             my ( $y, $x ) =
47             ( $start->y() + $_->[$Y], $start->x() + $_->[$X] );
48             (
49             (
50             __PACKAGE__->_x_in_range($x)
51             && __PACKAGE__->_y_in_range($y)
52             ) ? ( __PACKAGE__->_xy_to_int( [ $y, $x ] ) ) : ()
53             )
54             } (
55             [ -1, -1 ], [ -1, 0 ], [ -1, 1 ], [ 0, -1 ],
56             [ 0, 1 ], [ 1, -1 ], [ 1, 0 ], [ 1, 1 ]
57             )
58             ]
59             } ( 0 .. $BOARD_SIZE - 1 )
60             );
61              
62             sub _get_next_cells
63             {
64 367     367   607 my ( $self, $l, $init_idx ) = @_;
65              
66 2001         3791 return [ grep { vec( $l, $_, 8 ) == 0 }
67 367         475 @{ $get_next_cells_lookup[$init_idx] } ];
  367         592  
68             }
69             }
70              
71             sub _add_next_state
72             {
73 28     28   72 my ( $self, $stack, $l, $cell_int ) = @_;
74              
75 28         81 vec( $l, $cell_int, 8 ) = 1 + @$stack;
76              
77 28         69 push @$stack,
78             [ $l, $self->_shuffle( $self->_get_next_cells( $l, $cell_int ) ) ];
79              
80 28         800 return;
81             }
82              
83             sub _get_num_connected
84             {
85 30     30   55 my ( $self, $l ) = @_;
86              
87 30         62 my @connectivity_stack = ( index( $l, "\0" ) );
88              
89 30         40 my %connected;
90 30         57 while (@connectivity_stack)
91             {
92 707         1091 my $int = pop(@connectivity_stack);
93 707 100       1551 if ( !$connected{$int}++ )
94             {
95             push @connectivity_stack,
96 1354         3005 ( grep { !exists( $connected{$_} ) }
97 339         461 @{ $self->_get_next_cells( $l, $int ) } );
  339         583  
98             }
99             }
100              
101 30         138 return scalar keys %connected;
102             }
103              
104              
105             sub calc_final_layout
106             {
107 1     1 1 25 my $self = shift;
108              
109 1         2 my @dfs_stack;
110             $self->_add_next_state( \@dfs_stack, '',
111 1         7 $self->{rand}->max_rand($BOARD_SIZE) );
112              
113             DFS:
114 1         4 while (@dfs_stack)
115             {
116 31         49 my ( $l, $last_cells ) = @{ $dfs_stack[-1] };
  31         61  
117              
118 31 100       62 if ( @dfs_stack == $BOARD_SIZE )
119             {
120 1         14 return Games::ABC_Path::Generator::FinalLayoutObj->new(
121             { layout_string => $l, }, );
122             }
123              
124             # print "Depth = " . scalar(@dfs_stack) . "\n";
125             # print "Last state = " . Dumper($last_state) . "\n";
126             # print "Layout = \n" . $self->get_layout_as_string($last_state->{layout}) . "\n";
127              
128 30         47 my $next_idx = shift(@$last_cells);
129              
130 30 100 66     73 if (
131             ( !defined($next_idx) )
132             or ( $self->_get_num_connected($l) !=
133             ( $BOARD_SIZE - scalar(@dfs_stack) ) )
134             )
135             {
136 3         8 pop(@dfs_stack);
137             }
138             else
139             {
140 27         61 $self->_add_next_state( \@dfs_stack, $l, $next_idx );
141             }
142             }
143              
144 0         0 die "Not found!";
145             }
146              
147              
148             sub _gen_clue_positions
149             {
150 24     24   47 my ( $self, $cb ) = @_;
151 24         52 return [ map { $cb->($_) } $self->_x_indexes() ];
  120         251  
152             }
153              
154             sub _calc_clue_positions
155             {
156 2     2   5 my $self = shift;
157             return [
158             map {
159 120         498 [ map { $self->_xy_to_int($_) }
160 24         128 @{ $self->_gen_clue_positions($_) } ]
  24         39  
161             } (
162 10     10   35 sub { [ $_, $_ ]; },
163 10     10   25 sub { [ $_, 4 - $_ ]; },
164             (
165             map {
166 10         25 my $y = $_;
167 10     50   41 sub { [ $y, $_ ] };
  50         82  
168             } $self->_y_indexes()
169             ),
170             (
171             map {
172 2         49 my $x = $_;
  10         22  
173 10     50   40 sub { [ $_, $x ] };
  50         93  
174             } $self->_x_indexes()
175             ),
176             )
177             ];
178             }
179              
180             my @_clues_positions = @{ __PACKAGE__->_calc_clue_positions() };
181              
182             sub calc_riddle
183             {
184 1     1 1 10 my ($self) = @_;
185              
186 1         4 my $layout = $self->calc_final_layout();
187              
188 1         13 my $A_pos = $layout->get_A_pos;
189              
190             my %init_state = (
191             pos_taken => '',
192 1         4 clues => [ map { +{ num_remaining => 5, } } ( 1 .. $NUM_CLUES ), ]
  12         25  
193             );
194              
195             my $mark = sub {
196 25     25   43 my ( $state, $pos ) = @_;
197              
198 25         61 vec( $state->{pos_taken}, $pos, 1 ) = 1;
199              
200 25         75 my $coord = Games::ABC_Path::Generator::Coord->_from_int($pos);
201              
202 25 100       587 foreach my $clue (
    100          
203             ( ( $coord->y == $coord->x ) ? 0 : () ),
204             ( ( $coord->y == ( 5 - 1 ) - $coord->x ) ? 1 : () ),
205             ( 2 + $coord->y ),
206             ( ( 2 + 5 ) + $coord->x ),
207             )
208             {
209 60         757 $state->{clues}->[$clue]->{num_remaining}--;
210             }
211 1         7 };
212              
213 1         4 $mark->( \%init_state, $A_pos );
214              
215 1         3 my @dfs_stack = ( \%init_state );
216              
217             DFS:
218 1         5 while (@dfs_stack)
219             {
220 13         19 my $last_state = $dfs_stack[-1];
221              
222 13 50       28 if ( !exists( $last_state->{chosen_clue} ) )
223             {
224             my @clues = (
225             sort {
226             ( $a->[1]->{num_remaining} <=> $b->[1]->{num_remaining} )
227 147 50       304 or ( $a->[0] <=> $b->[0] )
228             }
229 156         293 grep { !exists( $_->[1]->{cells} ) }
230 13         30 map { [ $_, $last_state->{clues}->[$_] ] }
  156         299  
231             ( 0 .. $NUM_CLUES - 1 )
232             );
233              
234 13 100       40 if ( !@clues )
235             {
236             # Yay! We found a configuration.
237             my $handle_clue = sub {
238 12     12   14 my @cells = @{ shift->{cells} };
  12         25  
239 24         236 return [ map { $layout->get_cell_contents($_) }
240 12         18 @{ $self->_shuffle( \@cells ) } ];
  12         22  
241 1         6 };
242             my $riddle = Games::ABC_Path::Generator::RiddleObj->new(
243             {
244             solution => $layout,
245             clues => [
246 1         3 map { $handle_clue->($_) } @{ $last_state->{clues} }
  12         22  
  1         3  
247             ],
248             A_pos => Games::ABC_Path::Generator::Coord->_from_int(
249             $A_pos),
250             }
251             );
252              
253 1         8 my $riddle_string = $riddle->get_riddle_v1_string();
254              
255 1         9 my $solver =
256             Games::ABC_Path::Solver::Board->input_from_v1_string(
257             $riddle_string);
258              
259 1         48069 $solver->solve();
260              
261 1 50       822538 if ( @{ $solver->get_successes_text_tables() } != 1 )
  1         6  
262             {
263             # The solution is ambiguous
264 0         0 pop(@dfs_stack);
265 0         0 next DFS;
266             }
267             else
268             {
269 1         16929 return $riddle;
270             }
271             }
272              
273             # Not enough for the clues there.
274 12 50       27 if ( $clues[0][1]->{num_remaining} < 2 )
275             {
276 0         0 pop(@dfs_stack);
277 0         0 next DFS;
278             }
279              
280 12         21 my $clue_idx = $clues[0][0];
281              
282 12         18 $last_state->{chosen_clue} = $clue_idx;
283              
284             my @positions =
285 60         122 ( grep { !vec( $last_state->{pos_taken}, $_, 1 ) }
286 12         20 @{ $_clues_positions[$clue_idx] } );
  12         22  
287              
288 12         22 my @pairs;
289              
290 12         28 foreach my $first_idx ( 0 .. $#positions - 1 )
291             {
292 22         41 foreach my $second_idx ( $first_idx + 1 .. $#positions )
293             {
294 35         75 push @pairs, [ @positions[ $first_idx, $second_idx ] ];
295             }
296             }
297              
298 12         28 $last_state->{pos_pairs} = $self->_shuffle( \@pairs );
299             }
300              
301 12         322 my $chosen_clue = $last_state->{chosen_clue};
302 12         17 my $next_pair = shift( @{ $last_state->{pos_pairs} } );
  12         22  
303              
304 12 50       26 if ( !defined($next_pair) )
305             {
306 0         0 pop(@dfs_stack);
307 0         0 next DFS;
308             }
309              
310 12         20 my %new_state;
311 12         25 $new_state{pos_taken} = $last_state->{pos_taken};
312 12         18 $new_state{clues} = [ map { +{ %{$_} } } @{ $last_state->{clues} } ];
  144         195  
  144         380  
  12         23  
313 12         31 foreach my $pos (@$next_pair)
314             {
315 24         42 $mark->( \%new_state, $pos );
316             }
317 12         28 $new_state{clues}->[$chosen_clue]->{cells} = [@$next_pair];
318              
319 12         46 push @dfs_stack, ( \%new_state );
320             }
321             }
322              
323              
324             1; # End of Games::ABC_Path::Generator
325              
326             __END__