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.1';
3 8     8   556852 use 5.014;
  8         43  
4              
5 8     8   41 use strict;
  8         16  
  8         323  
6 8     8   40 use warnings;
  8         16  
  8         573  
7              
8 8     8   1340 use integer;
  8         45  
  8         94  
9              
10 8     8   2584 use parent 'Games::ABC_Path::Generator::Base';
  8         677  
  8         72  
11              
12 8     8   448 use Games::ABC_Path::Solver::Constants;
  8         13  
  8         1091  
13 8     8   4606 use Games::ABC_Path::Solver::Board v0.1.0;
  8         317938  
  8         338  
14 8     8   4581 use Games::ABC_Path::MicrosoftRand ();
  8         26  
  8         232  
15 8     8   3629 use Games::ABC_Path::Generator::RiddleObj ();
  8         25  
  8         211  
16 8     8   3696 use Games::ABC_Path::Generator::FinalLayoutObj ();
  8         22  
  8         1156  
17 8     8   3783 use Games::ABC_Path::Generator::Coord ();
  8         25  
  8         15401  
18              
19              
20             sub _init
21             {
22 4     4   260825 my $self = shift;
23 4         10 my $args = shift;
24              
25 4         35 $self->{seed} = $args->{seed};
26              
27             $self->{rand} =
28 4         84 Games::ABC_Path::MicrosoftRand->new( seed => $self->{seed} );
29              
30 4         15 return;
31             }
32              
33             sub _shuffle
34             {
35 184     184   298 my ( $self, $deck ) = @_;
36              
37 184         555 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 1468     1468   2408 my ( $self, $l, $init_idx ) = @_;
65              
66 8004         13801 return [ grep { vec( $l, $_, 8 ) == 0 }
67 1468         1859 @{ $get_next_cells_lookup[$init_idx] } ];
  1468         2420  
68             }
69             }
70              
71             sub _add_next_state
72             {
73 112     112   379 my ( $self, $stack, $l, $cell_int ) = @_;
74              
75 112         406 vec( $l, $cell_int, 8 ) = 1 + @$stack;
76              
77 112         266 push @$stack,
78             [ $l, $self->_shuffle( $self->_get_next_cells( $l, $cell_int ) ) ];
79              
80 112         6697 return;
81             }
82              
83             sub _get_num_connected
84             {
85 120     120   195 my ( $self, $l ) = @_;
86              
87 120         228 my @connectivity_stack = ( index( $l, "\0" ) );
88              
89 120         190 my %connected;
90 120         194 while (@connectivity_stack)
91             {
92 2828         3965 my $int = pop(@connectivity_stack);
93 2828 100       6602 if ( !$connected{$int}++ )
94             {
95             push @connectivity_stack,
96 5416         12330 ( grep { !exists( $connected{$_} ) }
97 1356         1725 @{ $self->_get_next_cells( $l, $int ) } );
  1356         2265  
98             }
99             }
100              
101 120         673 return scalar keys %connected;
102             }
103              
104              
105             sub calc_final_layout
106             {
107 4     4 1 11 my $self = shift;
108              
109 4         13 my @dfs_stack;
110             $self->_add_next_state( \@dfs_stack, '',
111 4         39 $self->{rand}->max_rand($BOARD_SIZE) );
112              
113             DFS:
114 4         49 while (@dfs_stack)
115             {
116 124         170 my ( $l, $last_cells ) = @{ $dfs_stack[-1] };
  124         240  
117              
118 124 100       304 if ( @dfs_stack == $BOARD_SIZE )
119             {
120 4         103 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 120         201 my $next_idx = shift(@$last_cells);
129              
130 120 100 66     381 if (
131             ( !defined($next_idx) )
132             or ( $self->_get_num_connected($l) !=
133             ( $BOARD_SIZE - scalar(@dfs_stack) ) )
134             )
135             {
136 12         36 pop(@dfs_stack);
137             }
138             else
139             {
140 108         279 $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 96     96   154 my ( $self, $cb ) = @_;
151 96         197 return [ map { $cb->($_) } $self->_x_indexes() ];
  480         867  
152             }
153              
154             sub _calc_clue_positions
155             {
156 8     8   21 my $self = shift;
157             return [
158             map {
159 480         1831 [ map { $self->_xy_to_int($_) }
160 96         496 @{ $self->_gen_clue_positions($_) } ]
  96         175  
161             } (
162 40     40   144 sub { [ $_, $_ ]; },
163 40     40   87 sub { [ $_, 4 - $_ ]; },
164             (
165             map {
166 40         83 my $y = $_;
167 40     200   229 sub { [ $y, $_ ] };
  200         334  
168             } $self->_y_indexes()
169             ),
170             (
171             map {
172 8         143 my $x = $_;
  40         192  
173 40     200   184 sub { [ $_, $x ] };
  200         359  
174             } $self->_x_indexes()
175             ),
176             )
177             ];
178             }
179              
180             my @_clues_positions = @{ __PACKAGE__->_calc_clue_positions() };
181              
182             sub calc_riddle
183             {
184 3     3 1 17 my ($self) = @_;
185              
186 3         15 my $layout = $self->calc_final_layout();
187              
188 3         79 my $A_pos = $layout->get_A_pos;
189              
190             my %init_state = (
191             pos_taken => '',
192 3         15 clues => [ map { +{ num_remaining => 5, } } ( 1 .. $NUM_CLUES ), ]
  36         90  
193             );
194              
195             my $mark = sub {
196 75     75   121 my ( $state, $pos ) = @_;
197              
198 75         182 vec( $state->{pos_taken}, $pos, 1 ) = 1;
199              
200 75         217 my $coord = Games::ABC_Path::Generator::Coord->_from_int($pos);
201              
202 75 100       1645 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 180         2065 $state->{clues}->[$clue]->{num_remaining}--;
210             }
211 3         28 };
212              
213 3         16 $mark->( \%init_state, $A_pos );
214              
215 3         10 my @dfs_stack = ( \%init_state );
216              
217             DFS:
218 3         17 while (@dfs_stack)
219             {
220 39         74 my $last_state = $dfs_stack[-1];
221              
222 39 50       105 if ( !exists( $last_state->{chosen_clue} ) )
223             {
224             my @clues = (
225             sort {
226             ( $a->[1]->{num_remaining} <=> $b->[1]->{num_remaining} )
227 441 50       926 or ( $a->[0] <=> $b->[0] )
228             }
229 468         780 grep { !exists( $_->[1]->{cells} ) }
230 39         97 map { [ $_, $last_state->{clues}->[$_] ] }
  468         761  
231             ( 0 .. $NUM_CLUES - 1 )
232             );
233              
234 39 100       130 if ( !@clues )
235             {
236             # Yay! We found a configuration.
237             my $handle_clue = sub {
238 36     36   39 my @cells = @{ shift->{cells} };
  36         66  
239 72         1362 return [ map { $layout->get_cell_contents($_) }
240 36         45 @{ $self->_shuffle( \@cells ) } ];
  36         67  
241 3         83 };
242             my $riddle = Games::ABC_Path::Generator::RiddleObj->new(
243             {
244             solution => $layout,
245             clues => [
246 3         10 map { $handle_clue->($_) } @{ $last_state->{clues} }
  36         60  
  3         9  
247             ],
248             A_pos => Games::ABC_Path::Generator::Coord->_from_int(
249             $A_pos),
250             }
251             );
252              
253 3         35 my $riddle_string = $riddle->get_riddle_v1_string();
254              
255 3         31 my $solver =
256             Games::ABC_Path::Solver::Board->input_from_v1_string(
257             $riddle_string);
258              
259 3         131051 $solver->solve();
260              
261 3 50       2826911 if ( @{ $solver->get_successes_text_tables() } != 1 )
  3         21  
262             {
263             # The solution is ambiguous
264 0         0 pop(@dfs_stack);
265 0         0 next DFS;
266             }
267             else
268             {
269 3         55568 return $riddle;
270             }
271             }
272              
273             # Not enough for the clues there.
274 36 50       83 if ( $clues[0][1]->{num_remaining} < 2 )
275             {
276 0         0 pop(@dfs_stack);
277 0         0 next DFS;
278             }
279              
280 36         51 my $clue_idx = $clues[0][0];
281              
282 36         92 $last_state->{chosen_clue} = $clue_idx;
283              
284             my @positions =
285 180         303 ( grep { !vec( $last_state->{pos_taken}, $_, 1 ) }
286 36         62 @{ $_clues_positions[$clue_idx] } );
  36         77  
287              
288 36         53 my @pairs;
289              
290 36         85 foreach my $first_idx ( 0 .. $#positions - 1 )
291             {
292 66         116 foreach my $second_idx ( $first_idx + 1 .. $#positions )
293             {
294 105         235 push @pairs, [ @positions[ $first_idx, $second_idx ] ];
295             }
296             }
297              
298 36         94 $last_state->{pos_pairs} = $self->_shuffle( \@pairs );
299             }
300              
301 36         1992 my $chosen_clue = $last_state->{chosen_clue};
302 36         51 my $next_pair = shift( @{ $last_state->{pos_pairs} } );
  36         67  
303              
304 36 50       79 if ( !defined($next_pair) )
305             {
306 0         0 pop(@dfs_stack);
307 0         0 next DFS;
308             }
309              
310 36         47 my %new_state;
311 36         93 $new_state{pos_taken} = $last_state->{pos_taken};
312 36         51 $new_state{clues} = [ map { +{ %{$_} } } @{ $last_state->{clues} } ];
  432         504  
  432         962  
  36         66  
313 36         82 foreach my $pos (@$next_pair)
314             {
315 72         134 $mark->( \%new_state, $pos );
316             }
317 36         87 $new_state{clues}->[$chosen_clue]->{cells} = [@$next_pair];
318              
319 36         131 push @dfs_stack, ( \%new_state );
320             }
321             }
322              
323              
324             1; # End of Games::ABC_Path::Generator
325              
326             __END__