File Coverage

lib/Games/Nonogram/Clue.pm
Criterion Covered Total %
statement 170 236 72.0
branch 63 124 50.8
condition 10 18 55.5
subroutine 22 26 84.6
pod 18 18 100.0
total 283 422 67.0


line stmt bran cond sub pod time code
1             package Games::Nonogram::Clue;
2            
3 5     5   124916 use strict;
  5         11  
  5         181  
4 5     5   27 use warnings;
  5         9  
  5         235  
5 5     5   27 use base qw( Games::Nonogram::Base );
  5         7  
  5         3118  
6            
7 5     5   2639 use Games::Nonogram::Line;
  5         13  
  5         141  
8 5     5   2807 use Games::Nonogram::Block;
  5         13  
  5         15525  
9            
10             sub new {
11 8     8 1 2093 my ($class, %options) = @_;
12            
13 8         22 my $size = $options{size};
14 8         65 my $line = Games::Nonogram::Line->new( size => $size );
15            
16 8         3073 my $self = bless {
17             id => $options{id},
18             size => $size,
19             line => $line,
20             is_done => 0,
21             }, $class;
22            
23 8 100       110 if ( $options{blocks} ) {
24 5 50       9 $self->set( @{ $options{blocks} || [] } );
  5         63  
25             }
26            
27 8         34 $self;
28             }
29            
30 3 50   3 1 18 sub id { shift->{id} || '' }
31            
32             sub set {
33 8     8 1 36 my ($self, @clues) = @_;
34            
35 8         12 my $id = 0;
36 19         70 my @blocks = map {
37 8         22 Games::Nonogram::Block->new(
38             id => ++$id,
39             length => $_,
40             line_size => $self->size,
41             )
42             } @clues;
43 8         26 $self->{blocks} = \@blocks;
44            
45 8         31 $self->reset_blocks;
46            
47 8         13 my $free;
48 8 50       24 unless ( @blocks ) {
49 0         0 $self->line->off( from => 1, length => $self->size );
50 0         0 $free = 0;
51             }
52             else {
53 8         27 $free = $blocks[0]->right - $blocks[0]->length;
54             }
55            
56 8         31 $self->{free} = $free;
57             }
58            
59             sub reset_blocks {
60 13     13 1 26 my $self = shift;
61            
62 13         19 my $left = 1;
63 13         36 foreach my $block ( $self->blocks ) {
64 34         96 $block->clear;
65 34         98 $block->left( $left );
66 34         135 $left += ( $block->length + 1 );
67             }
68            
69 13         40 my $right = $self->size;
70 13         30 foreach my $block ( reverse $self->blocks ) {
71 34         88 $block->right( $right );
72 34         94 $right -= ( $block->length + 1 );
73             }
74            
75 13         34 $self->{is_done} = 0;
76             }
77            
78 236 50   236 1 244 sub blocks { @{ shift->{blocks} || [] } }
  236         898  
79 84     84 1 2748 sub size { shift->{size} }
80 264     264 1 1361 sub line { shift->{line} }
81            
82             sub block {
83 138     138 1 297 my ($self, $id) = @_;
84 138         731 $self->{blocks}->[$id];
85             }
86            
87             sub as_string {
88 3     3 1 7 my $self = shift;
89            
90 3         7 my $str = $self->line->as_string;
91            
92 3 50       24 if ( $self->debug ) {
93 0         0 $str .= ' '. join ' ', map { $_->length } $self->blocks;
  0         0  
94             }
95 3         9 return $str;
96             }
97            
98             sub dump_blocks {
99 3     3 1 11 my $self = shift;
100            
101 3         10 my $str = $self->id . "\n";
102 3         8 foreach my $block ( $self->blocks ) {
103 6         14 for my $ct ( 1 .. $self->size ) {
104 120 100       540 if ( $block->must_have( $ct ) ) {
    100          
105 3         7 $str .= "X";
106             }
107             elsif ( $block->might_have( $ct ) ) {
108 38         213 $str .= "_";
109             }
110             else {
111 79         132 $str .= ".";
112             }
113             }
114 6         23 $str .= sprintf " (%s, %s: %s)\n", $block->left, $block->right, $block->length;
115             }
116            
117 3         14 $str .= $self->as_string . "\n";
118            
119 3 50       1005 defined wantarray ? return $str : print $str;
120             }
121            
122             sub is_done {
123 0     0 1 0 my $self = shift;
124            
125 0 0       0 unless ( $self->{is_done} ) {
126 0 0       0 return unless $self->line->is_done;
127 0         0 $self->die_if_invalid;
128            
129 0         0 $self->{is_done} = 1;
130             }
131 0         0 $self->{is_done};
132             }
133            
134             sub die_if_invalid {
135 0     0 1 0 my $self = shift;
136            
137 0         0 my $ct = 1;
138 0         0 foreach my $block ( $self->blocks ) {
139 0         0 while ( $ct < $block->left ) {
140 0         0 my $value = $self->line->value( $ct );
141 0 0       0 if ( $value != 0 ) {
142 0 0       0 $self->dump_blocks if $self->debug;
143 0         0 die "failed at $ct: $value != 0";
144             }
145 0         0 $ct++;
146             }
147 0         0 while ( $ct <= $block->right ) {
148 0         0 my $value = $self->line->value( $ct );
149 0 0       0 if ( $value != 1 ) {
150 0 0       0 $self->dump_blocks if $self->debug;
151 0         0 die "failed at $ct: $value != 1";
152             }
153 0         0 $ct++;
154             }
155             }
156 0         0 while ( $ct <= $self->size ) {
157 0         0 my $value = $self->line->value( $ct );
158 0 0       0 if ( $value != 0 ) {
159 0 0       0 $self->dump_blocks if $self->debug;
160 0         0 die "failed at $ct: $value != 0";
161             }
162 0         0 $ct++;
163             }
164 0         0 return 1;
165             }
166            
167             sub on {
168 8     8 1 11 my ($self, $id) = @_;
169            
170 8         19 $self->line->on( $id );
171            
172 8         22 my $block = $self->might_have( $id );
173            
174 8 100       22 return unless ref $block;
175            
176 7         18 my $offset = $block->length - 1;
177            
178 7         13 my $left = $id - $offset;
179 7 100       15 $block->left( $left ) if $block->left < $left;
180            
181 7         11 my $right = $id + $offset;
182 7 100       23 $block->right( $right ) if $block->right > $right;
183            
184 7 100       17 if ( $block->left == $id ) {
185 1         4 my $left = $block->left;
186 1         4 $self->line->on( $_ ) for ( $left .. $left + $offset );
187             }
188            
189 7 100       19 if ( $block->right == $id ) {
190 2         3 my $right = $block->right;
191 2         7 $self->line->on( $_ ) for ( $right - $offset .. $right );
192             }
193             }
194            
195             sub off {
196 42     42 1 159 my ($self, $id) = @_;
197            
198 42         77 $self->line->off( $id );
199            
200 42         91 foreach my $block ( $self->blocks ) {
201 97         304 $block->cant_have( $id );
202             }
203            
204 42         105 my $block = $self->might_have( $id );
205            
206 42 50       10643 return unless ref $block;
207            
208 0         0 my $offset = $block->length - 1;
209            
210 0         0 my $left = $id + 1;
211 0 0       0 $block->left( $left ) if $block->left + $offset > $left;
212            
213 0         0 my $right = $id - 1;
214 0 0       0 $block->right( $right ) if $block->right - $offset < $right;
215             }
216            
217             sub value {
218 0     0 1 0 my ($self, $id, $value) = @_;
219            
220 0 0       0 if ( defined $value ) {
221 0 0       0 if ( $value == 0 ) { $self->off( $id ) }
  0 0       0  
222 0         0 elsif ( $value == 1 ) { $self->on( $id ) }
223             }
224             else {
225 0         0 $self->line->value( $id );
226             }
227             }
228            
229             sub might_have {
230 140     140 1 218 my ($self, $id) = @_;
231            
232 140         135 my $hit;
233 140         242 foreach my $block ( $self->blocks ) {
234 347 100       816 if ( $block->might_have( $id ) ) {
235 77 100       151 return -1 if $hit; # multiple candidates; cannot decide
236            
237 67         215 $hit = $block;
238             }
239             }
240 130 100       340 return $hit ? $hit : 0;
241             }
242            
243             sub update {
244 7     7 1 14 my ($self, $mode) = @_;
245            
246 7 50       15 unless ( $mode ) {
    0          
247 7         32 $self->_update_basic;
248             }
249             elsif ( $mode eq 'more' ) {
250 0         0 $self->_update_more;
251             }
252            
253 7         23 foreach my $ct ( 1 .. $self->size ) {
254 90         166 my $block = $self->might_have( $ct );
255            
256 90 100       266 unless ( $block ) {
    100          
257 31         67 $self->off( $ct );
258             }
259             elsif ( ref $block ) {
260 50         302 my $value = $self->line->value( $ct );
261 50 100       110 if ( $value == -1 ) {
    50          
    50          
262 43 100       105 $self->on( $ct ) if $block->must_have( $ct );
263             }
264             elsif ( $value == 0 ) {
265 0         0 $self->off( $ct );
266             }
267             elsif ( $value == 1 ) {
268 7 100       18 $self->on( $ct ) unless $block->must_have( $ct );
269             }
270             }
271             }
272             }
273            
274             sub _update_basic {
275 7     7   11 my $self = shift;
276            
277 7         10 my $left = 1;
278 7         19 foreach my $block ( $self->blocks ) {
279 19 100       44 $left = $block->left if $block->left > $left;
280 19   100     38 while (
      33        
281             $self->line->value( $left ) == 0
282             or
283             $left > 1 && $self->line->value( $left - 1 ) == 1
284             ) {
285 1         1 $left++;
286 1 50       9 if ( $left > $self->size ) {
287 0         0 die "puzzle data may be broken, unless you're trying to solve by brute force";
288             }
289             }
290            
291 19         52 $block->left( $left );
292 19         47 $left += ( $block->length + 1 );
293             }
294            
295 7         22 my $right = $self->size;
296 7         17 foreach my $block ( reverse $self->blocks ) {
297 19 100       45 $right = $block->right if $block->right < $right;
298            
299 19   66     39 while (
      66        
300             $self->line->value( $right ) == 0
301             or
302             $right < $self->size && $self->line->value( $right + 1 ) == 1
303             ) {
304 2         3 $right--;
305 2 50       8 if ( $right < 1 ) {
306 0         0 die "puzzle data may be broken, unless you're trying to solve by brute force";
307             }
308             }
309            
310 19         72 $block->right( $right );
311 19         44 $right -= ( $block->length + 1 );
312             }
313            
314 7         33 foreach my $block ( $self->blocks ) {
315 19         25 my ($from, $length) = (0, 0);
316 19         48 foreach my $ct ( $block->left .. $block->right ) {
317 76         124 my $value = $self->line->value( $ct );
318 76 100       127 if ( $value == 1 ) {
319 9   66     46 $from ||= $ct;
320 9         25 $length++;
321             }
322             else {
323 67         158 $block->try( $from, $length );
324 67         141 ($from, $length) = (0, 0);
325             }
326             }
327 19         56 $block->try( $from, $length );
328             }
329             }
330            
331             sub _update_more {
332 0     0   0 my $self = shift;
333            
334 0         0 my ($toggle, $from, @blocks);
335 0         0 foreach my $ct ( 1 .. $self->size ) {
336 0   0     0 $from ||= $ct;
337            
338 0         0 my $value = $self->line->value( $ct );
339 0 0       0 if ( $value == 1 ) {
    0          
340 0         0 $toggle = 1;
341             }
342             elsif ( $value == 0 ) {
343 0 0       0 if ( $toggle ) {
344 0         0 push @blocks, { from => $from, to => $ct - 1 };
345             }
346 0         0 $from = $toggle = 0;
347             }
348             }
349 0 0       0 if ( $toggle ) {
350 0         0 push @blocks, { from => $from, to => $self->size };
351             }
352            
353 0 0       0 if ( @blocks == $self->blocks ) {
354 0         0 foreach my $block ( $self->blocks ) {
355 0         0 my $href = shift @blocks;
356 0 0       0 $block->left( $href->{from} ) if $block->left < $href->{from};
357 0 0       0 $block->right( $href->{to} ) if $block->right > $href->{to};
358             }
359             }
360             }
361            
362             sub candidates {
363 1     1 1 5 my $self = shift;
364            
365 1         7 my @candidates = $self->_candidates(
366             $self->line->clone,
367             1,
368             $self->{free},
369             $self->blocks
370             );
371             }
372            
373             sub _candidates {
374 6     6   12 my ($self, $line, $pos, $free, @blocks) = @_;
375            
376 6         16 my $clone = $line->clone;
377 6 100       14 unless ( $free ) {
378 2         7 while( my $block = shift @blocks ) {
379 3         8 foreach my $ct ( 0 .. $block->length - 1 ) {
380 9 50       22 return if $clone->value( $pos + $ct ) == 0; # conflicted;
381 9         22 $clone->on( $pos + $ct );
382             }
383 3         23 $pos += $block->length;
384 3 100       6 unless ( $pos > $self->size ) {
385 1 50       4 return if $clone->value( $pos ) == 1; # conflicted;
386 1         3 $clone->off( $pos );
387             }
388 3         9 $pos++;
389             }
390             }
391 6 100       34 unless ( @blocks ) {
392 3         5 foreach my $ct ( $pos .. $self->size ) {
393 0 0       0 return if $clone->value( $ct ) == 1; # conflicted
394 0         0 $clone->off( $ct );
395             }
396 3         8 return $clone->as_vec;
397             }
398            
399 3         2 my @candidates;
400             LOOP:
401 3         7 foreach my $ct ( 0 .. $free ) {
402 6         16 $clone = $line->clone;
403 6         21 my @clone_blocks = @blocks;
404 6         8 my $clone_pos = $pos;
405            
406 6         12 foreach my $space_ct ( 1 .. $ct ) {
407 3 50       11 next LOOP if $clone->value( $clone_pos ) == 1; # conflicted
408 3         9 $clone->off( $clone_pos );
409 3         7 $clone_pos++;
410             }
411            
412 6         7 my $block = shift @clone_blocks;
413 6         17 foreach my $block_ct ( 1 .. $block->length ) {
414 14 50       30 next LOOP if $clone->value( $clone_pos ) == 0; # conflicted
415 14         35 $clone->on( $clone_pos );
416 14         26 $clone_pos++;
417             }
418 6 100       14 unless ( $clone_pos > $self->size ) {
419 5 50       19 next LOOP if $clone->value( $clone_pos ) == 1; # conflicted
420 5         16 $clone->off( $clone_pos );
421 5         7 $clone_pos++;
422            
423 5         35 push @candidates, $self->_candidates(
424             $clone,
425             $clone_pos,
426             $free - $ct,
427             @clone_blocks
428             );
429             }
430             else {
431 1         4 push @candidates, $clone->as_vec;
432             }
433             }
434 3         19 return @candidates;
435             }
436            
437             1;
438            
439             __END__