File Coverage

blib/lib/Games/Mines.pm
Criterion Covered Total %
statement 152 163 93.2
branch 57 68 83.8
condition 12 15 80.0
subroutine 26 27 96.3
pod 16 16 100.0
total 263 289 91.0


line stmt bran cond sub pod time code
1             package Games::Mines;
2              
3             require 5.005_62;
4 50     50   127796 use strict;
  50         103  
  50         2034  
5 50     50   257 use warnings;
  50         160  
  50         1839  
6 50     50   293 use Carp qw(verbose);
  50         95  
  50         8186  
7              
8 50     50   5882 use Data::Dumper;
  50         59537  
  50         111995  
9             our $VERSION = sprintf("%01d.%02d", 0,q$Revision: 2.2 $ =~ /(\d+)\.(\d+)/);
10              
11             =head1 NAME
12              
13             Games::Mines - a mine finding game
14              
15             =head1 SYNOPSIS
16              
17             require Games::Mines;
18              
19             # get new 30x40 mine field with 50 mines
20             my($game) = Games::Mines->new(30,40,50);
21              
22             # fill with mines, except at the four corners
23             $game->fill_mines([0,0],[0,40],[30,0],[30,40]);
24              
25             =head1 DESCRIPTION
26              
27             This module is the basis for mine finding game. It contains the basic
28             methods necessary for a game.
29              
30             =cut
31              
32             # Preloaded methods go here.
33              
34             # internal:
35             # - nothing
36             # 1-8 - number of mines around that square
37             # * - mine (steped on )
38             # visible:
39             # . - unsteped
40             # F - unstepped and flaged
41             # - stepped
42              
43             # 'unstepped' => '.',
44             # 'flagged' => 'F',
45             # 'mine' => '*',
46             # 'wrong' => 'X',
47             # 'blank' => ' ',
48              
49              
50             =head2 Class and object Methods
51              
52              
53             =over 5
54              
55             =item $Class->new;
56              
57             The new method creates a new mine field object. It takes three
58             arguments: The width of the field, the height of the field, and the
59             number of mines.
60              
61             =cut
62              
63             sub new {
64 73     73 1 9426 my($base) = shift;
65            
66             # Get class or object ref and construct object
67 73   66     1218 my($class) = ref($base) || $base;
68            
69 73         259 my($width,$height,$count,) = @_;
70            
71 73 100       441 if( $count > $width*$height ) {
72 2         7 return;
73             }
74            
75 71         3611 my($mine_field) = {
76             'on' => 0,
77             'field' => undef(),
78              
79             # mine count
80             'count' => $count,
81             'flags' => 0,
82             'unknown' => 0,
83            
84             # game information text
85             'why' => 'not started',
86             'pre-start-text' => 'not started',
87             'running-text' => 'Running',
88             'win-text' => 'You Win!!!',
89             'lose-text' => 'KABOOOOOM!!!',
90              
91             # extra field to hold other field information
92             'extra'=>{}
93             };
94            
95 71         465 bless $mine_field, $class;
96            
97 71         628 $mine_field->_reset($width,$height);
98            
99 71         289 return $mine_field;
100             }
101              
102             =item $obj->width
103              
104             Returns the width of a mine field.
105              
106             =cut
107              
108             sub width {
109 5395     5395 1 6202 my($mine_field) = shift;
110 5395         5553 return $#{$mine_field->{field} };
  5395         15525  
111             }
112              
113             =item $obj->height
114              
115             Returns the height of the mine field.
116              
117             =cut
118              
119             sub height {
120 6757     6757 1 7906 my($mine_field) = shift;
121 6757         7581 return $#{$mine_field->{field}[0]};
  6757         21722  
122             }
123              
124             =item $obj->count
125              
126             Returns the total number of mines within the field.
127              
128             =cut
129              
130             sub count {
131 2     2 1 5 my($mine_field) = shift;
132 2         238 return $mine_field->{count};
133             }
134              
135             =item $obj->running
136              
137             Returns a boolean that says if game play is still possible. Returns
138             false after field is create, but before fill_mines is called. Also
139             returns false if the whole field has been solved, or a mine has
140             been stepped on.
141              
142             =cut
143              
144             sub running {
145 11     11 1 50 my($mine_field) = shift;
146 11         14 my($test);
147 11         14 my($w,$h);
148            
149 11 50 66     29 if($mine_field->found_all && $mine_field->{on}) {
150 0         0 $mine_field->{on}=0;
151 0         0 $mine_field->{why} = $mine_field->{'win-text'};
152             }
153 11         57 return $mine_field->{on};
154             }
155              
156             =item $obj->why
157              
158             Returns a human readable status of the current game. Mostly useful
159             after a game has ended to say why it has ended.
160              
161             =cut
162              
163             sub why {
164 7     7 1 18 my($mine_field) = shift;
165              
166 7         35 return $mine_field->{why};
167             }
168              
169             =item $obj->fill_mines
170              
171             Randomly fills the field with mines. It takes any number of arguments,
172             which should be array references to a pair of coordinates of where
173             I to put a mine.
174              
175             =cut
176              
177             sub fill_mines {
178 55     55 1 153 my($mine_field) = shift;
179 55         123 my(@exclude) = @_;
180 55         110 my($i,$w,$h);
181            
182 55         191 $mine_field->{why} = $mine_field->{'running-text'};
183 55         142 $mine_field->{on} = 1;
184            
185             {
186 55         84 for($i = 1; $i<=$mine_field->{count}; $i++) {
  55         266  
187 312         703 $w = int( rand( $mine_field->width() +1 ) );
188 312         801 $h = int( rand( $mine_field->height() +1 ) );
189 312 100       971 redo if( $mine_field->_at($w,$h) eq '*');
190            
191 310 0       1031 redo if( grep { ($_->[0] == $w) && ($_->[1] == $h)} @exclude);
  0 50       0  
192 310 50       820 redo unless( $mine_field->_check_mine_placement($w,$h));
193            
194 310         638 $mine_field->{field}[$w][$h]{contains} = '*';
195 310         676 $mine_field->_fill_count($w,$h);
196             }
197 55 50       201 unless( $mine_field->_check_mine_field ) {
198 0         0 $mine_field->_clear_mines;
199 0         0 redo;
200             }
201             }
202             }
203              
204             =item $obj->at($col,$row)
205              
206             Returns what is visible at the coordinates given. Takes two arguments:
207             the col and the row coordinates.
208              
209             =cut
210              
211             sub at {
212 165     165 1 247 my($mine_field) = shift;
213 165         336 my($w,$h) = $mine_field->_limit(@_);
214            
215 165 100       365 if($mine_field->shown($w,$h)) {
216 93         235 return $mine_field->_at($w,$h);
217             }
218 72         395 return $mine_field->{field}[$w][$h]{visibility};
219             }
220              
221             =item $obj->hidden($col,$row)
222              
223             Returns a boolean saying if the position has not been stepped on and
224             exposed. Takes two arguments: the col and the row coordinates.
225              
226             =cut
227              
228             sub hidden {
229 16     16 1 30 my($mine_field) = shift;
230 16         43 my($w,$h) = $mine_field->_limit(@_);
231 16         91 return $mine_field->{field}[$w][$h]{visibility};
232             }
233              
234             =item $obj->shown($col,$row)
235              
236             Returns a boolean saying if the position has been stepped on and
237             exposed. Takes two arguments: the col and the row coordinates.
238              
239             =cut
240              
241             sub shown {
242 341     341 1 453 my($mine_field) = shift;
243 341         684 my($w,$h) = $mine_field->_limit(@_);
244             #print STDERR "getting value w,h: ", $w,", ",$h,"\n";
245 341         1423 return not($mine_field->{field}[$w][$h]{visibility});
246             }
247              
248              
249             =item $obj->step($col,$row)
250              
251             Steps on a particular square, exposing what was underneath. Takes
252             two arguments: the col and the row coordinates. Note that if the
253             particular field is blank, indicating it has no mines in any of
254             the surrounding squares, it will also automatically step on those
255             squares as well. Returns false if already stepped on that square,
256             or if a mine is under it. Returns true otherwise.
257              
258             =cut
259              
260             sub step {
261 14     14 1 34 my($mine_field) = shift;
262            
263 14         43 my(@stepping) = ( [ $mine_field->_limit(@_) ] );
264              
265 14         52 while(@stepping) {
266 52         81 my($w,$h) = @{ shift @stepping };
  52         86  
267            
268 52 100       143 next if( $mine_field->shown($w,$h) );
269 40         106 $mine_field->{field}[$w][$h]{visibility} = '';
270 40         74 $mine_field->{unknown}--;
271            
272 40 100       94 if($mine_field->_at($w,$h) eq '*' ) {
273 1         4 $mine_field->{field}[$w][$h]{visibility} = 'X';
274 1         1 $mine_field->{on} = 0;
275 1         4 $mine_field->{why}= $mine_field->{'lose-text'};
276 1         3 return;
277             }
278            
279 39 100       108 if( $mine_field->at($w,$h) eq ' ') {
280 15         113 foreach my $dw (-1..1) {
281 45 100       114 next if( $w+$dw <0);
282 36 100       79 next if( $w+$dw > $mine_field->width());
283            
284 33         83 foreach my $dh (-1 ..1) {
285 99 100 100     357 next if($dw ==0 && $dh==0);
286 84 100       182 next if( $h+$dh <0);
287 65 100       130 next if( $h+$dh > $mine_field->height());
288            
289 58 100       157 next if( $mine_field->shown($w+$dw,$h+$dh) );
290 38         163 push @stepping, [$w+$dw, $h+$dh];
291             }
292             }
293             }
294             }
295 13         42 return 1;
296             }
297              
298             =item $obj->flag($col,$row)
299              
300             Place a flag on a particular unexposed square. Takes two arguments:
301             the col and the row coordinates. Returns true if square can and has
302             been flagged.
303              
304             =cut
305              
306             sub flag {
307 10     10 1 26 my($mine_field) = shift;
308            
309 10         36 my($w,$h) = $mine_field->_limit(@_);
310            
311 10 50       32 return if( $mine_field->shown($w,$h) );
312 10 50       45 return if( $mine_field->flagged($w,$h) );
313 10         72 $mine_field->{field}[$w][$h]{visibility} = 'F';
314 10         41 $mine_field->{flags}++;
315 10         43 $mine_field->{unknown}--;
316 10         63 return 1;
317             }
318              
319             =item $obj->unflag($col,$row)
320              
321             Removes a flag from a particular unexposed square. Takes two
322             arguments: the col and the row coordinates. Returns true if
323             square can and has been unflagged.
324              
325             =cut
326              
327             sub unflag {
328 2     2 1 6 my($mine_field) = shift;
329            
330 2         7 my($w,$h) = $mine_field->_limit(@_);
331            
332 2 50       9 return if( $mine_field->shown($w,$h) );
333 2 50       7 return if( not $mine_field->flagged($w,$h) );
334 2         5 $mine_field->{field}[$w][$h]{visibility} = '.';
335 2         5 $mine_field->{flags}--;
336 2         4 $mine_field->{unknown}++;
337 2         4 return 1;
338             }
339              
340              
341             =item $obj->flagged($col,$row)
342              
343             Returners a boolean based on whether a flag has been placed on a
344             particular square. Takes two arguments: the col and the row
345             coordinates.
346              
347             =cut
348              
349             sub flagged {
350 29     29 1 51 my($mine_field) = shift;
351            
352 29         81 my($w,$h) = $mine_field->_limit(@_);
353            
354 29 100       72 return if( $mine_field->shown($w,$h) );
355             #print STDERR Dumper($mine_field->{field}[$w][$h]{visibility}, $h,$w);
356 26         146 return $mine_field->{field}[$w][$h]{visibility} eq 'F';
357             }
358              
359              
360             =item $obj->flags
361              
362             Return the total number of flags throughout the whole field.
363              
364             =cut
365              
366             sub flags {
367 6     6 1 14 my($mine_field) = shift;
368 6         40 return $mine_field->{flags};
369             }
370              
371             =item $obj->found_all
372              
373             Returners a boolean saying whether all mines have been found or not.
374              
375             =cut
376              
377             sub found_all {
378 23     23 1 40 my($mine_field) = shift;
379            
380 23         282 my($w,$h);
381            
382 23 100       88 if( $mine_field->{flags}+$mine_field->{unknown}
383             == $mine_field->{count} ) {
384            
385 4         12 for($w = 0; $w <= $mine_field->width(); $w++) {
386 12         64 for($h = 0; $h<= $mine_field->height(); $h++) {
387 36 50 66     60 if( $mine_field->at($w,$h) eq 'F' &&
388             not ($mine_field->_at($w,$h) eq '*')){
389 0         0 return;
390             }
391             }
392             }
393 4         8 $mine_field->{why} = $mine_field->{'win-text'};
394 4         7 $mine_field->{on} = 0;
395            
396 4         23 return 1;
397             }
398            
399 19         78 return;
400             }
401              
402              
403             =begin for developers
404              
405             =item $obj->_limit($col,$row)
406              
407             An internal check to make sure the coordinates given are actually on
408             the field itself. Will truncate to the field limits and values
409             that are no.
410              
411             =cut
412              
413             sub _limit {
414 3493     3493   6700 my($mine_field) = shift;
415 3493         4533 my($w,$h,@rest)=@_;
416              
417 3493 100       8612 if( $w<0) {
    100          
418 2         4 $w =0;
419             }
420             elsif( $w >= $mine_field->width() ) {
421 544         947 $w = $mine_field->width();
422             }
423            
424 3493 100       9017 if($h<0) {
    100          
425 2         5 $h=0;
426             }
427             elsif( $h >= $mine_field->height() ) {
428 510         899 $h = $mine_field->height();
429             }
430              
431 3493         7242 return ($w,$h,@rest);
432             }
433              
434             =item $obj->_reset($width,$height)
435            
436             This is the method that actually sets up the whole data structure that
437             represents the field, and fills it with the default values. Takes
438             two arguments: The width of the column and row of the
439             coordinates.
440              
441             =cut
442              
443             sub _reset {
444 74     74   4257 my($mine_field) = shift;
445            
446 74         1870 my($width,$height) = @_;
447 74         168 my($w,$h);
448            
449 74         11785 $mine_field->{field} = [ undef() x $width ];
450 74         1082 for( $w = 0; $w <= $width-1; $w++) {
451 712         59828 $mine_field->{field}[$w] = [ undef() x $height ];
452            
453 712         2759 for( $h = 0; $h<= $height-1; $h++) {
454 43012         223439 $mine_field->{field}[$w][$h] = {
455             contains => " ",
456             visibility => '.',
457             extra =>{},
458             };
459             }
460             }
461 74         330 $mine_field->{unknown} = $w * $h;
462             }
463              
464              
465             =item $obj->_fill_count($col,$row)
466              
467             Used to add to the numbers surrounding a mine. Normally called from
468             fill_mines to fill the field with the mine counts. Takes two
469             coordinates, the $col and $row coordinates. Assumes there is a
470             mine at the center.
471              
472             =cut
473              
474             sub _fill_count {
475 316     316   438 my($mine_field) = shift;
476            
477 316         559 my($w,$h)=$mine_field->_limit(@_);
478            
479 316         714 foreach my $dw (-1..1) {
480 948 100       1980 next if( $w+$dw <0);
481 947 100       1820 next if( $w+$dw > $mine_field->width());
482            
483 889         1411 foreach my $dh (-1 ..1) {
484 2667 100 100     7904 next if($dw ==0 && $dh==0);
485 2351 100       4720 next if( $h+$dh <0);
486 2236 100       5317 next if( $h+$dh > $mine_field->height());
487            
488 2060 100       4824 next if( $mine_field->_at($w+$dw, $h+$dh) eq '*');
489            
490 2053         7443 $mine_field->{field}[ $w+$dw ][ $h+$dh ]{contains}++;
491             }
492             }
493             }
494              
495             =item $obj->_clear_mines
496              
497             Clears mine field of all bombs, and resets the field to a pre-start
498             state.
499              
500             =cut
501              
502             sub _clear_mines {
503 0     0   0 my($mine_field) = shift;
504 0         0 my($i);
505 0         0 my($w,$h) = ($mine_field->width(),$mine_field->height() );
506 0         0 $mine_field->{'why'} = $mine_field->{'pre-start-text'};
507 0         0 $mine_field->_reset($w,$h);
508             }
509              
510             =item $obj->_check_mine_placement($col,$row)
511              
512             It checks to see if a mine should be placed at the the coordinates given.
513             Returns true if it's an acceptable position.
514              
515             This is a placeholder method for modules that inherit from this one
516             to over ride. Always returns true by default.
517              
518             =cut
519              
520             sub _check_mine_placement {
521 311     311   721 return 1;
522             }
523              
524             =item $obj->_check_mine_field
525              
526             It checks to see if a mine field has an acceptable layout.
527             Returns true if it's an acceptable field.
528              
529             This is a placeholder method for modules that inherit from this one
530             to over ride. Always returns true by default.
531              
532             =cut
533              
534             sub _check_mine_field {
535 56     56   357 return 1;
536             }
537              
538             =item $obj->_at($col,$row)
539              
540             Returns what is underneath at the coordinates given, regardless of
541             weather it is uncovered or not. Takes two arguments: the col and
542             the row coordinates.
543              
544             =cut
545              
546             sub _at {
547 2594     2594   3060 my($mine_field) = shift;
548 2594         6873 my($w,$h) = $mine_field->_limit(@_);
549 2594         12283 return $mine_field->{field}[$w][$h]{contains};
550             }
551              
552              
553              
554             =end for developers
555              
556             =back
557              
558             =head1 AUTHOR
559              
560             Martyn W. Peck
561              
562             =head1 BUGS
563              
564             None known. But if you find any, let me know.
565              
566             =head1 COPYRIGHT
567              
568             Copyright 2003, Martyn Peck. All Rights Reserves.
569              
570             This program is free software. You may copy or redistribute
571             it under the same terms as Perl itself.
572              
573             =cut
574              
575             1;
576