File Coverage

blib/lib/Games/YASudoku/Board.pm
Criterion Covered Total %
statement 160 184 86.9
branch 32 44 72.7
condition 7 21 33.3
subroutine 17 18 94.4
pod 13 14 92.8
total 229 281 81.4


line stmt bran cond sub pod time code
1             package Games::YASudoku::Board;
2              
3             =head1 MODULE
4              
5             Games::YASudoku::Board
6              
7             =head1 DESCRIPTION
8              
9             This module defines the Sudoku board.
10              
11             =head1 METHODS
12              
13             =over
14              
15             =cut
16              
17              
18 1     1   1018 use strict;
  1         2  
  1         45  
19 1     1   1037 use Games::YASudoku::Square;
  1         2  
  1         1642  
20              
21              
22             my $GROUPS = [
23             [ 0, 1, 2, 9,10,11,18,19,20 ],
24             [ 3, 4, 5,12,13,14,21,22,23 ],
25             [ 6, 7, 8,15,16,17,24,25,26 ],
26             [ 27,28,29,36,37,38,45,46,47 ],
27             [ 30,31,32,39,40,41,48,49,50 ],
28             [ 33,34,35,42,43,44,51,52,53 ],
29             [ 54,55,56,63,64,65,72,73,74 ],
30             [ 57,58,59,66,67,68,75,76,77 ],
31             [ 60,61,62,69,70,71,78,79,80 ],
32             ];
33              
34              
35             sub new {
36 4     4 0 2850 my $proto = shift;
37 4   33     26 my $class = ref( $proto ) || $proto;
38              
39 4         9 my $self = [];
40              
41 4         15 bless $self, $class;
42 4         11 $self->_init();
43              
44 4         12 return $self;
45             }
46              
47              
48             =item B<_init>
49              
50             initialize a new board
51             The game board is 9 rows by 9 columns, but we will store the board in
52             a one dimensional array - For example, element 9 will map to row 2,
53             column 1 (keep in mind that the first element of the array is 0).
54              
55             =cut
56              
57             sub _init {
58 4     4   5 my $self = shift;
59            
60 4         11 for my $i ( 0 .. 80 ){
61 324         344 push @{$self}, Games::YASudoku::Square->new( $i );
  324         1461  
62             }
63              
64 4         11 return $self;
65             }
66              
67              
68             =item B
69              
70             Get rows will return a ref to an array of all the rows. get_row will
71             just return one row - valid row numbers are 1 - 9.
72              
73             =cut
74              
75             sub get_rows {
76 1     1 1 460 my $self = shift;
77              
78 1         2 my @rows;
79 1         4 for my $i ( 1 .. 9 ){
80 9         21 push @rows, $self->get_row( $i );
81             }
82              
83 1         5 return \@rows;
84             }
85              
86              
87              
88             sub get_row {
89 1642     1642 1 5371 my $self = shift;
90 1642         1924 my $row_num = shift;
91              
92 1642 50 33     10363 return undef unless $row_num && ( $row_num >= 1 ) && ( $row_num <= 9);
      33        
93              
94 1642         2574 my $start = ( $row_num - 1 ) * 9;
95 1642         1693 my @row;
96              
97 1642         2719 for my $i ( $start .. ( $start + 8 ) ){
98 14778         20565 push @row, $self->[ $i ];
99             }
100 1642         5203 return \@row;
101             }
102              
103              
104              
105             =item B
106              
107             Get cols will return a ref to an array of all the cols. get_col will
108             just return one row - valid col numbers are 1 - 9.
109              
110             =cut
111              
112             sub get_cols {
113 1     1 1 347 my $self = shift;
114              
115 1         3 my @cols;
116 1         4 for my $i ( 1 .. 9 ){
117 9         2946 push @cols, $self->get_col( $i );
118             }
119              
120 1         7 return \@cols;
121             }
122              
123             sub get_col {
124 1642     1642 1 2294 my $self = shift;
125 1642         1667 my $col_num = shift;
126              
127 1642 50 33     9099 return undef unless $col_num && ( $col_num >= 1 ) && ( $col_num <= 9);
      33        
128              
129 1642         2002 my $start = $col_num - 1;
130 1642         1460 my @col;
131              
132 1642         2230 foreach my $i ( 0 .. 8 ){
133 14778         14642 my $column = $start + ( $i * 9 );
134 14778         22192 push @col, $self->[ $column ];
135             }
136 1642         4673 return \@col;
137             }
138              
139              
140             =item B
141              
142             Groups are defined as a set of nine boxes group in squares, there are
143             three rows of groups and three groups in each row. They are numbered
144             as follows.
145              
146             1 | 2 | 3
147             ---|---|---
148             4 | 5 | 6
149             ---|---|---
150             7 | 8 | 9
151              
152             get_grps will return a ref to an array of all the groups. get_grp will
153             just return one group which can be specified by one of the numbers above.
154              
155             =cut
156              
157             sub get_grps {
158 1     1 1 637 my $self = shift;
159              
160 1         4 my @groups;
161 1         5 for my $i ( 1 .. 9 ){
162 9         20 push @groups, $self->get_grp( $i );
163             }
164              
165 1         4 return \@groups;
166             }
167              
168             sub get_grp {
169 1642     1642 1 2499 my $self = shift;
170 1642         1546 my $grp_num = shift;
171              
172 1642 50 33     8785 return undef unless $grp_num && ( $grp_num >= 1 ) && ( $grp_num <= 9);
      33        
173              
174 1642         1564 my @grp;
175              
176 1642         1660 foreach my $i ( @{ $GROUPS->[ $grp_num - 1 ] } ){
  1642         2957  
177 14778         19613 push @grp, $self->[ $i ];
178             }
179 1642         4040 return \@grp;
180             }
181              
182             =item B
183              
184             return a list of all the squares with values set
185              
186             =cut
187              
188             sub get_values {
189 82     82 1 122 my $self = shift;
190              
191 82         107 my @values;
192 82         87 foreach my $square ( @{ $self } ){
  82         161  
193 6642 100       14217 push @values, $square if ( $square->value );
194             }
195              
196 82         395 return \@values;
197             }
198              
199            
200              
201             =item B
202              
203             This method will return the three groups that the element is a member of.
204             One row, one column and one group.
205              
206             =cut
207              
208             sub get_element_membership {
209 1632     1632 1 1970 my $self = shift;
210 1632         1559 my $element = shift;
211              
212 1632         4047 my $element_id = $element->id;
213              
214             # what row is the element in
215 1632         3292 my $row = int( $element_id / 9 ) + 1;
216 1632         2105 my $col = ($element_id % 9) + 1;
217              
218 1632         1548 my $grp;
219 1632         2627 for my $i ( 0 .. 8 ){
220 14688 100       14183 $grp = ($i + 1) if grep /^$element_id$/, @{ $GROUPS->[ $i ] };
  14688         151792  
221             }
222              
223 1632         2053 my @membership;
224 1632         3369 push @membership, $self->get_row( $row ),
225             $self->get_col( $col ),
226             $self->get_grp( $grp );
227              
228 1632         3467 return \@membership;
229             }
230              
231              
232             =item B
233              
234             This method displays the current state of the board
235              
236             =cut
237              
238             sub show_board {
239 2     2 1 413 my $self = shift;
240              
241 2         5 my $board_text;
242 2         7 my $reg_row_mark = '+...' x 9 . '+';
243 2         5 my $big_row_mark = '+---' x 9 . '+';
244              
245 2         7 for my $i ( 0 .. 80 ){
246 162 100       339 if ( ($i % 9) == 0 ) {
247 18 100       37 if ( ($i % 27 ) == 0 ){
248 6         18 $board_text .= "\n$big_row_mark\n|";
249             } else {
250 12         28 $board_text .= "\n$reg_row_mark\n|";
251             }
252             }
253 162         470 my $value = $self->[$i]->value;
254 162 100       265 if ( $value ){
255 105         289 $board_text .= sprintf( " %1d ", $self->[$i]->value );
256             } else {
257 57         63 $board_text .= " ";
258             }
259 162 100       331 if ( (( $i +1 ) % 3 ) == 0 ){
260 54         124 $board_text .= '|';
261             } else {
262 108         223 $board_text .= ':';
263             }
264             }
265              
266 2         9 $board_text .= "\n$big_row_mark\n";
267              
268 2         562 return $board_text;
269             }
270              
271             =item B
272              
273             This method displays the current state of the board including the possible
274             values for each unsolved square.
275              
276             =cut
277              
278              
279             sub show_board_detail {
280 0     0 1 0 my $self = shift;
281              
282 0         0 my $board_text;
283 0         0 my $reg_row_mark = '+.........' x 9 . '+';
284 0         0 my $big_row_mark = '+---------' x 9 . '+';
285              
286 0         0 for my $i ( 0 .. 80 ){
287 0 0       0 if ( ($i % 9) == 0 ) {
288 0 0       0 if ( ($i % 27 ) == 0 ){
289 0         0 $board_text .= "\n$big_row_mark\n|";
290             } else {
291 0         0 $board_text .= "\n$reg_row_mark\n|";
292             }
293             }
294 0         0 my $value = $self->[$i]->value;
295 0 0       0 if ( $value ){
296 0         0 $board_text .= sprintf( " %7d ", $self->[$i]->value );
297             } else {
298 0         0 $board_text .= sprintf("(%7s)", join('', @{ $self->[$i]->valid }));
  0         0  
299             }
300 0 0       0 if ( (( $i +1 ) % 3 ) == 0 ){
301 0         0 $board_text .= '|';
302             } else {
303 0         0 $board_text .= ':';
304             }
305             }
306              
307 0         0 $board_text .= "\n$big_row_mark\n";
308              
309 0         0 return $board_text;
310             }
311              
312              
313             =item B
314              
315             this method solve the board
316              
317             =cut
318              
319             sub run_board {
320 1     1 1 12 my $self = shift;
321              
322 1         2 my $new_values = 0;
323 1         2 my $values = @{$self->get_values};
  1         6  
324 1         3 my $passes = 0;
325 1         5 while ( $values != $new_values ){
326 3         11 $self->pass_one;
327 3         23 $self->pass_two;
328              
329 3         21 $passes++;
330 3         7 $values = $new_values;
331 3         7 $new_values = @{$self->get_values};
  3         13  
332             }
333 1         8 return $passes;
334             }
335              
336              
337             =item B
338              
339             the first pass looks for values and reduces valid_num arrays
340              
341             =cut
342              
343             sub pass_one {
344 17     17 1 26 my $self = shift;
345            
346 17         17 my $new_values = 0;
347 17         23 my $values = @{$self->get_values};
  17         50  
348 17         71 while ( $values != $new_values ){
349            
350 41         60 foreach my $element ( @{ $self } ){
  41         69  
351 3321 100       7921 next if $element->value;
352 1545         2843 my $ms = $self->get_element_membership( $element );
353            
354 1545         1661 foreach my $member ( @{ $ms } ){
  1545         2444  
355 4635         5054 foreach my $e ( @{ $member } ){
  4635         7194  
356 41715 100       94635 next unless $e->value;
357            
358 16517         37846 $element->valid_del( $e->value );
359 16517 50       17977 if ( @{ $element->valid } == 0 ){
  16517         32804  
360 0         0 warn $self->show_board_detail;
361 1     1   1325582 use Data::Dumper;
  1         9276  
  1         583  
362 0         0 warn Dumper $element;
363 0         0 warn Dumper $e;
364 0         0 warn Dumper $member;
365 0         0 die "Something very wrong here!!!!";
366             }
367             }
368             }
369              
370 1545         4063 my @valid = $element->valid;
371 1545 100       1792 if ( @{ $element->valid } == 1 ){
  1545         3567  
372 43         121 $element->value( $element->valid->[0] );
373             }
374             }
375              
376 41         71 $values = $new_values;
377 41         54 $new_values = @{$self->get_values};
  41         134  
378             }
379 17         33 return @{$self->get_values};
  17         107  
380             }
381              
382             =item B
383              
384             this pass looks for valid_num arrays which have a unique value
385             and therefore need to have that value assigned to them
386              
387             Example: Square 1 can be (1,2,3)
388             Square 2 can be (2,3,4,6)
389             Square 3 can be (2,3,4,5)
390             Square 4 can be (5,6)
391             Since square 1 is the only one with a '1', it needs to be 1
392              
393             =cut
394              
395             sub pass_two {
396 3     3 1 6 my $self = shift;
397            
398 3         6 foreach my $element ( @{ $self } ){
  3         8  
399 243 100       824 next if $element->value;
400              
401              
402             # get all the groups this square is an member of
403 86         109 foreach my $member ( @{$self->get_element_membership($element)}){
  86         178  
404 238         249 my %valid_nums;
405 238         240 map { $valid_nums{ $_ } = $_ } @{ $element->valid };
  758         6426  
  238         556  
406              
407 238         254 foreach my $e ( @{$member} ){
  238         351  
408             # don't work on the current element
409 2142 100       6483 next if ( $e->id == $element->id );
410 1904 100       4466 next if ( $e->value ); # skip element that is already processed
411              
412 1098         1152 foreach my $valid ( @{ $e->valid } ){
  1098         2332  
413 2403         2953 delete $valid_nums{ $valid };
414 2403 100       5416 last if (( keys %valid_nums ) == 0 );
415             }
416             }
417            
418 238 100       860 if ( (keys %valid_nums) == 1 ){
419 14         34 my @keys = keys %valid_nums;
420 14         101 $element->value( $keys[0] );
421 14         43 $self->pass_one;
422 14         171 last;
423             }
424             }
425             }
426            
427 3         9 return @{$self->get_values};
  3         13  
428             }
429              
430              
431             1;
432              
433              
434             =head1 AUTHOR
435              
436             Andrew Wyllie
437              
438             =head1 BUGS
439              
440             Please send any bugs to the author
441              
442             =head1 COPYRIGHT
443              
444             The Games::YASudoku moudule is free software and can be redistributed
445             and/or modified under the same terms as Perl itself.
446