File Coverage

blib/lib/Games/Mines/Play.pm
Criterion Covered Total %
statement 191 215 88.8
branch 75 104 72.1
condition 16 29 55.1
subroutine 26 26 100.0
pod 8 8 100.0
total 316 382 82.7


line stmt bran cond sub pod time code
1             package Games::Mines::Play;
2              
3             require 5.005_62;
4 45     45   1245724 use strict;
  45         128  
  45         1671  
5 45     45   270 use warnings;
  45         89  
  45         1393  
6              
7 45     45   247 use Carp;
  45         94  
  45         5020  
8 45     45   237 use vars qw($AUTOLOAD);
  45         117  
  45         2909  
9              
10 45     45   53048 use Data::Dumper;
  45         567748  
  45         4479  
11              
12 45     45   30209 use Games::Mines;
  45         130  
  45         32754  
13             our $VERSION = sprintf("%01d.%02d.%02d", 0,q$Revision: 1.11 $ =~ /(\d+)\.(\d+)/);
14              
15             =head1 NAME
16              
17             Games::Mines::Play;
18              
19             =head1 SYNOPSIS
20              
21             require Games::Mines::Play;
22              
23             # get new 30x40 mine field with 50 mines
24             my($game) = Games::Mines->new(30,40,50);
25              
26             # use color text
27             $game->set_ANSI_Color;
28              
29             # fill with mines, except at the four corners
30             $game->fill_mines([0,0],[0,40],[30,0],[30,40]);
31              
32             # print out playing field
33             $game->print_out("field");
34             $game->print_status_line;
35              
36              
37             =head1 DESCRIPTION
38              
39             This module is he basis for mine finding game. It builds on the
40             Games::Mines base class to with all the various methods needed to play
41             a text version of the game.
42              
43             =head2 Class and object Methods
44              
45              
46             =over 5
47              
48             =item $Class->new;
49              
50             The new method creates a new mine field object. It takes three
51             arguments: The width of the field, the height of the field, and the
52             number of mines.
53              
54             =cut
55              
56             sub new {
57 64     64 1 153442902 my($base) = shift;
58            
59             # Get class or object ref and construct object
60 64   66     1821 my($class) = ref($base) || $base;
61            
62 64         458 my($game) = 'Games::Mines';
63            
64 64         296 my($w,$h,$m) = (shift,shift,shift);
65            
66 64 100       512 if(@_) {
67 2         5 $game=shift;
68             }
69            
70 64         1035 my($new_game) = $game->new($w,$h,$m);
71 64 100       4971 return unless defined( $new_game);
72            
73 63         738 my($mine_field) = {
74             'GAME' => $new_game,
75             'map' =>'',
76             'game number' => 1,
77             };
78            
79 63         224 bless $mine_field, $class;
80 63         501 $mine_field->set_ASCII;
81 63         282 return $mine_field;
82             }
83              
84             # class methodes
85              
86             =item $class->default(%opts)
87              
88             Returns an array of width, height, and number of mines, based on some
89             common arguments. It takes a has with the some combination of six
90             keys. The first three are B, B, and B. These
91             are boolean keys, who's value are only checked to see if they
92             contain a true value. The small field is 8x8 with 10 mines, the medium
93             field is 16x16 with 40 mines, and the large field is 16x30 with 99
94             mines. The other three are B, B, and B, which
95             sets the corresponding term. Note that this is designed to work with
96             Getopt::Long, so any other keys are ignored. The default is to return
97             a large field.
98              
99             =cut
100              
101             sub default {
102 6     6 1 4732 my($class) = shift; # don't really do anything with this.
103 6         18 my(%opt) = @_;
104            
105 6         11 my (@defs) = (16,30,99);
106            
107 6 100 66     50 if( exists ( $opt{small} ) && $opt{small} ) {
    100 66        
    50 33        
108 2         6 @defs = (8,8,10);
109             }
110             elsif( exists ( $opt{medium} )&& $opt{medium}) {
111 2         5 @defs = (16,16,40);
112             }
113             elsif( exists ( $opt{large} ) && $opt{large} ) {
114 2         5 @defs = (16,30,99);
115             }
116            
117 6 100 66     26 if( defined( $opt{ height }) && ($opt{ height }>1) ) {
118 1         2 $defs[0] = $opt{height};
119             }
120 6 100 66     22 if( defined( $opt{ width }) && ($opt{ width }>1) ) {
121 1         2 $defs[1] = $opt{width};
122             }
123 6 100 66     24 if( defined( $opt{ mines }) && ($opt{ mines }>0) ) {
124 1         3 $defs[2] = $opt{mines};
125             }
126 6         27 return @defs;
127             }
128              
129             =back
130              
131             =head2 Object Methods
132              
133             =over 5
134              
135             =item $obj->print_out($arg)
136              
137             Prints out the game field. It takes one argument, saying what to
138             print. The "field" argument prints out the current visible
139             field. The "solution" argument prints out the actual location of
140             the mines. The "check" argument prints out the field, marking any
141             mistakes that where made. Default is is to print a "field".
142              
143             =cut
144              
145             sub print_out {
146 3     3 1 35 my($mine_field) = shift;
147            
148 3   50     50 my($type) = shift ||"field";
149 3         8 my($w,$h);
150            
151 3         61 $mine_field->_start_field;
152            
153 3         30 for($w = 0; $w <= $mine_field->width(); $w++) {
154 9         63 $mine_field->_start_line($w);
155            
156 9         65 for($h = 0; $h<= $mine_field->height(); $h++) {
157 27         72 $mine_field->_start_cell($w,$h);
158 27 100       243 if($type eq "field") {
    100          
    50          
159 9         45 $mine_field->_map( $mine_field->at($w,$h),$w,$h );
160             }
161             elsif($type eq "check") {
162 9         26 $mine_field->_map( $mine_field->_diff($w,$h),$w,$h );
163             }
164             elsif($type eq "solution") {
165 9         34 $mine_field->_map( $mine_field->_at($w,$h),$w,$h );
166             }
167 27         109 $mine_field->_end_cell($w,$h);
168             }
169 9         27 $mine_field->_end_line($w);
170             }
171 3         15 $mine_field->_end_field;
172             }
173              
174             =item $obj->print_status_line
175              
176             Prints out a status line of how many mines have been located. If the
177             game has ended, it also prints out the ending text saying why.
178              
179             =cut
180              
181             sub print_status_line {
182 1     1 1 3 my($mine_field) = shift;
183 1         6 print "mines: ",$mine_field->flags," of ",
184             $mine_field->count,"\n";
185 1 50       26 unless($mine_field->running) {
186 0         0 print $mine_field->why,"\n";
187             }
188             }
189              
190             =item $obj->set_ASCII
191              
192             Set the default mapping of the internal representation to the actual
193             characters printed out, to a plain ascii characters.
194              
195             =cut
196              
197             sub set_ASCII {
198 93     93 1 220 my($mine_field) = shift;
199 93         2266 $mine_field->{'map'} = {
200             '*' => '*',
201             '.' => '.',
202             'F' => 'F',
203             'f' => 'f',
204             ' ' => ' ',
205             '1' => '1',
206             '2' => '2',
207             '3' => '3',
208             '4' => '4',
209             '5' => '5',
210             '6' => '6',
211             '7' => '7',
212             '8' => '8',
213             'X' => 'X',
214             };
215             }
216              
217              
218             =item $obj->set_ANSI_Color
219              
220             Set the default mapping of the internal representation to the actual
221             characters printed out, to ascii characters with ANSI colors. If
222             Term::ANSIColor is not installed on your machine, this will quietly
223             fail.
224              
225             =cut
226              
227 45     45   332 use vars q($loaded_ansi_color);
  45         108  
  45         2910  
228              
229             BEGIN {
230 45     45   3473 eval 'use Term::ANSIColor; $loaded_ansi_color=1';
  45     45   54611  
  45         577405  
  45         4792  
231             }
232              
233             sub set_ANSI_Color {
234 17     17 1 15027 my($mine_field) = shift;
235 17 100       166 return unless( $loaded_ansi_color );
236 16         322 $mine_field->{'map'} = {
237             '*' => colored('*',"black","on_white"),
238             '.' => colored('L',"black","on_blue","bold"),
239             'F' => colored('F',"red","on_blue","bold"),
240             'f' => colored('f',"black","on_red"),
241             ' ' => colored(' ',"on_white"),
242             '1' => colored('1',"blue","on_white"),
243             '2' => colored('2',"green","on_white"),
244             '3' => colored('3',"red","on_white"),
245             '4' => colored('4',"black","on_white"),
246             '5' => colored('5',"magenta","on_white"),
247             '6' => colored('6',"cyan","on_white"),
248             '7' => colored('7',"yellow","on_white"),
249             '8' => colored('8',"black","on_white"),
250             'X' => colored('*',"black","on_red","blink"),
251             };
252             }
253              
254              
255             =item $obj->save_game($filename,$number)
256              
257             Saves the current game. Takes two arguments: The filename to save it
258             to, and the game number to save it under. Note that if you give it
259             a game number that already exists within that file, that game will
260             get over written by this one. If no such game number exists, then
261             it is simply added to the end.
262              
263             =cut
264              
265             sub save_game {
266 1     1 1 3 my($field) = shift;
267 1         6 my($file,$game) = @_;
268 1         3 my($reading)=1;
269            
270 1         3 my($mine_field) = $field->{'GAME'};
271            
272 1   33     5 $game ||=$mine_field->{'game number'};
273            
274 1 50       97 unless( open(FILE, "$file") ){
275 0         0 $reading=0;
276             }
277 1 50       133 unless( open(FILE_TO, "> $file.working") ){
278 0         0 warn("can't open file $file.working for temporary file: $!");
279 0         0 return;
280             }
281            
282 1         4 my($line)="\n";
283             # skip games untill we find the right one
284 1 50       6 if($reading) {
285 1         60 while($line=) {
286 45 100       188 last if( $line =~/Game\s+$game\s*$/);
287 44         161 print FILE_TO $line;
288             }
289             }
290            
291 1         7 print FILE_TO "Game $game\n";
292 1         7 print FILE_TO $mine_field->width+1,"x",$mine_field->height+1,"\n";
293              
294 1         41 my($w,$h);
295 1         7 for($w = 0; $w <= $mine_field->width(); $w++) {
296 3         12 for($h = 0; $h<= $mine_field->height(); $h++) {
297 9 100       229 if($mine_field->at($w,$h) eq ' ') {
    100          
    50          
    100          
    50          
298 1         6 print FILE_TO ' ';
299             }
300             elsif($mine_field->at($w,$h) =~/\d/) {
301 3         15 print FILE_TO ' ';
302             }
303             elsif($mine_field->at($w,$h) eq '*') {
304 0         0 print FILE_TO '*';
305             }
306             elsif($mine_field->at($w,$h) eq '.') {
307 3 50       9 if($mine_field->_at($w,$h) eq ' ') {
    100          
    50          
308 0         0 print FILE_TO '.';
309             }
310             elsif($mine_field->_at($w,$h) =~/\d/) {
311 2         10 print FILE_TO '.';
312             }
313             elsif($mine_field->_at($w,$h) eq '*') {
314 1         5 print FILE_TO ':';
315             }
316             }
317             elsif($mine_field->at($w,$h) eq 'F') {
318 2 50       6 if($mine_field->_at($w,$h) eq ' ') {
    100          
    50          
319 0         0 print FILE_TO 'f';
320             }
321             elsif($mine_field->_at($w,$h) =~/\d/) {
322 1         6 print FILE_TO 'f';
323             }
324             elsif($mine_field->_at($w,$h) eq '*') {
325 1         7 print FILE_TO 'F';
326             }
327             }
328             }
329 3         12 print FILE_TO "\n";
330             }
331 1         2 print FILE_TO"\n";
332            
333 1 50       4 if($reading) {
334 1         5 while(not eof(FILE)) {
335             # dump old game number
336 1         7 while($line=) {
337 19 100       44 last if( $line =~/Game/);
338             }
339 1 50       3 last if( eof(FILE));
340             # copy rest of games
341 1         2 print FILE_TO $line;
342 1         4 while($line=) {
343 18         56 print FILE_TO $line;
344             }
345             }
346             }
347            
348 1         81 close(FILE_TO);
349 1 50       5 if($reading) {
350 1         18 close(FILE);
351             }
352 1 50       5 if($reading) {
353 1 50       136 rename($file,"$file.bak") || die("Can't move $file to backup: $!");
354             }
355            
356 1 50       42 rename("$file.working",$file) ||
357             die("Can't rename temporary file $file.working to $file: $!");
358             }
359              
360             =item $obj->load_game($filename,$number)
361              
362             Loads a previously saved game to replace the current game. It takes
363             two arguments: the file name to get the game from and the game
364             number to load. If it can't open the file or find the given game
365             number will leave the current game unchanged, and return undefined.
366              
367             =cut
368              
369             sub load_game {
370 2     2 1 277689 my($field) = shift;
371 2         11 my($file,$game) = @_;
372            
373 2         7 my($old_field) = $field->{'GAME'};
374            
375 2   33     13 $game ||=$field->{'game number'};
376 2 50       167 unless( open(FILE, $file) ){
377 0         0 warn("can't open save file $file: $!");
378 0         0 return;
379             }
380            
381 2         6 my($line);
382             # skip games untill we find the right one
383 2         78 while($line=) {
384 65 100       402 last if( $line =~/Game $game\s*$/);
385             }
386            
387 2 50       11 return if(eof(FILE));
388            
389             # get the width and height and make new field
390 2         8 $line=;
391 2         16 $line=~/\s*(\d+)x(\d+)/;
392 2         17 my($width,$height) = ($1,$2);
393            
394 2         21 my($mine_field) = $old_field->new($width,$height,0);
395            
396 2         5 my($w,$h);
397 2         5 my($error)=0;
398             # fill in playing field
399 2         12 for($w =0;$w<=$mine_field->width;$w++) {
400            
401 6         19 $line=;
402            
403 6         33 my(@sq) = split('',$line);
404            
405 6         12 my($cont,$vis);
406 6         40 for($h=0; $h<=$mine_field->height;$h++) {
407 18 100       113 if($sq[$h] eq '.') { #no mine/unstepped
    100          
    100          
    50          
    100          
    50          
    0          
408 4         18 $mine_field->{field}[$w][$h]{visibility} = '.';
409             }
410             elsif($sq[$h] eq 'f') { #no mine/flagged
411 2         7 $mine_field->{field}[$w][$h]{visibility} = 'F';
412 2         4 $mine_field->{flags}++;
413 2         10 $mine_field->{unknown}--;
414             }
415             elsif($sq[$h] eq ' ') { #no mine/stepped
416 8         110 $mine_field->{field}[$w][$h]{visibility} = '',;
417 8         37 $mine_field->{unknown}--;
418             }
419             elsif($sq[$h] =~/\d/) { #no mine/stepped
420 0         0 $mine_field->{field}[$w][$h]{visibility} = '',;
421 0         0 $mine_field->{unknown}--;
422             }
423             elsif($sq[$h] eq ':') { # mine/unstepped
424 2         16 $mine_field->{field}[$w][$h] = {
425             contains => '*',
426             visibility => '.',
427             };
428 2         9 $mine_field->{count}++;
429 2         19 $mine_field->_fill_count($w,$h);
430             }
431             elsif($sq[$h] eq 'F') { #mine/flagged
432 2         14 $mine_field->{field}[$w][$h] = {
433             contains => '*',
434             visibility => 'F',
435             };
436 2         13 $mine_field->_fill_count($w,$h);
437 2         5 $mine_field->{flags}++;
438 2         7 $mine_field->{unknown}--;
439 2         7 $mine_field->{count}++;
440             }
441             elsif($sq[$h] eq 'X') { #mine/stepped : shouldn't happen
442 0         0 $mine_field->{field}[$w][$h] = {
443             contains => '*',
444             visibility => '',
445             };
446 0         0 $mine_field->_fill_count($w,$h);
447 0         0 $mine_field->{flags}++;
448 0         0 $mine_field->{unknown}--;
449 0         0 $mine_field->{count}++;
450             }
451             else { #got something totaly unknown
452 0         0 die("Don't know how to interpret $sq[$h] in Game $game at line $.\n");
453 0         0 $error=1;
454 0         0 last;
455             }
456             }
457             }
458            
459 2 50       9 if($error) {
460 0         0 $field->{'GAME'} = $old_field;
461 0         0 return;
462             }
463            
464 2         5 $mine_field->{on} = 1;
465 2         5 $field->{'GAME'} = $mine_field;
466            
467 2         1235 return 1;
468             }
469              
470             =begin for developers
471              
472             =cut
473              
474             sub _start_field {
475 4     4   30 my($mine_field) = shift;
476 4         12 my($w,$h);
477            
478 4         58 my $cars= length($mine_field->height());
479 4         49 my($format) = "%0". length($mine_field->height()). "u";
480            
481 4         88 foreach my $line (0.. $cars-1 ) {
482 4         63 print " "x(length($mine_field->width())+1);
483            
484 4         39 foreach my $h (0.. $mine_field->height()) {
485 12         296 print substr(sprintf($format,$h),$line,1);
486             }
487 4         76 print "\n";
488             }
489            
490             print
491 4         35 " "x(length($mine_field->width())),
492             "+",
493             "-"x($mine_field->height()+1),
494             "+\n";
495             }
496              
497             sub _end_field {
498 4     4   23 my($mine_field) = shift;
499 4         37 print
500             " "x(length($mine_field->width())),
501             "+",
502             "-"x($mine_field->height()+1),
503             "+\n";
504             }
505              
506             sub _start_line {
507 10     10   27 my($mine_field) = shift;
508 10         17 my($line) = shift;
509 10         70 my($format) = "%0". length($mine_field->width()). "u|";
510 10         1110 printf($format,$line);
511             }
512              
513             sub _end_line {
514 10     10   202 print "|\n";
515             }
516              
517             sub _map {
518 55     55   176080719 my($mine_field) = shift;
519            
520 55         1717 my($value) = shift;
521 55         5806 print $mine_field->{map}->{ $value };
522             }
523              
524 27     27   37 sub _start_cell {1;}
525              
526 27     27   150 sub _end_cell {1;}
527              
528             =item $obj->_diff
529              
530             Internal method used to print out the end game results, indicating any
531             wrongly marked or stepped fields.
532              
533             =cut
534              
535             sub _diff {
536 9     9   16 my($field) = shift;
537 9         18 my($mine_field) = $field->{'GAME'};
538 9         13 my($w,$h) = @_;
539            
540 9 100       29 if($mine_field->shown($w,$h)) {
    50          
    100          
541 4         16 return $mine_field->_at($w,$h);
542             }
543             elsif($mine_field->at($w,$h) eq 'X') {
544 0         0 return 'X';
545             }
546             elsif($mine_field->at($w,$h) eq 'F') {
547 1 50       5 if($mine_field->_at($w,$h) eq '*'){
548 0         0 return $mine_field->_at($w,$h);
549             }
550             else {
551 1         13 return 'f';
552             }
553             }
554 4         39 return $mine_field->_at($w,$h);
555             }
556              
557             =item AUTOLOAD
558              
559             Pass any unknown method calls to the contined object.
560              
561             =cut
562              
563             sub AUTOLOAD {
564 263     263   9375 my($game) = shift;
565            
566             # DESTROY messages should never be propagated.
567 263 50       1179 return if $AUTOLOAD =~ /::DESTROY$/;
568            
569 263         468 my($attr) = $AUTOLOAD;
570 263         2376 $attr =~ s/^.*:://;
571              
572 263 50       1789 unless ( $game->{GAME}->can($attr)) {
573 0         0 croak "Don't have a method of type $attr";
574             }
575            
576 263         1213 $game->{GAME}->$attr(@_);
577             }
578              
579             =end for developers
580              
581             =back
582              
583             =head1 SEE ALSO
584              
585             Games::Mines for more details of the base class.
586              
587             =head1 AUTHOR
588              
589             Martyn W. Peck
590              
591             =head1 BUGS
592              
593             None known. But if you find any, let me know.
594              
595             =head1 COPYRIGHT
596              
597             Copyright 2003, Martyn Peck. All Rights Reserves.
598              
599             This program is free software. You may copy or redistribute
600             it under the same terms as Perl itself.
601              
602             =cut
603              
604             1;