File Coverage

blib/lib/Games/Tetris.pm
Criterion Covered Total %
statement 57 59 96.6
branch 12 14 85.7
condition 15 19 78.9
subroutine 10 10 100.0
pod 5 8 62.5
total 99 110 90.0


line stmt bran cond sub pod time code
1             package Games::Tetris;
2 1     1   737 use strict;
  1         1  
  1         32  
3 1     1   514 use Games::Tetris::Shape;
  1         4  
  1         8  
4             our $VERSION = '0.01';
5              
6             =head1 NAME
7              
8             Games::Tetris - representation of a tetris game state
9              
10             =head1 SYNOPSIS
11              
12             use Games::Tetris;
13             my $well = Games::Tetris->new;
14             my $ess = $well->new_shape(' +',
15             '++',
16             '+ ');
17             $well->drop( $ess, 3, 1 );
18             $well->print;
19              
20             =head1 DESCRIPTION
21              
22             This module can be used as the rules engine for the game of tetris.
23             It allows you to create a well and drop pieces in it. The well tracks
24             the status its contents and handles completed line removal.
25              
26             =head1 METHODS
27              
28             =head2 new
29              
30             Creates a new gamestate
31              
32             Takes the following optional parameters:
33              
34             C an initial well, an array of arrays. use undef to indicate an
35             empty cell, any other value is considered occupied
36              
37             or
38              
39             C, C dimensions of a new well (defaults to 15 x 20)
40              
41             =cut
42              
43             sub new {
44 2     2 1 429 my $referent = shift;
45 2         6 my %args = @_;
46 2   33     14 my $class = ref $referent || $referent;
47              
48 2         8 my $self = bless {}, $class;
49              
50 2         7 my ($w, $d) = delete @args{ qw{ width depth } };
51 2 50       11 if ($self->{_well} = delete $args{well}) {
52             # figure out width and depth
53 0         0 die "I be slack";
54             }
55             else {
56             # make a new well
57 2   50     8 $self->{_width} = $w || 15;
58 2   50     6 $self->{_depth} = $d || 20;
59              
60 10         15 $self->{_well} = [ map {
61 2         5 [ (undef) x $self->width ]
62             } 1 .. $self->depth ];
63             }
64              
65 2 50       450 die "leftover arguments:". join (', ', map {"'$_'"} keys %args)
  0         0  
66             if keys %args;
67 2         9 return $self;
68             }
69              
70 273     273 0 2220 sub width { $_[0]->{_width} }
71 292     292 0 1423 sub depth { $_[0]->{_depth} }
72 285     285 0 1850 sub well { $_[0]->{_well} }
73              
74             =head2 new_shape
75              
76             delegates to Games::Tetris::Shape->new
77              
78             =cut
79              
80             sub new_shape {
81 2     2 1 3 my $self = shift;
82 2         10 Games::Tetris::Shape->new(@_);
83             }
84              
85             =head2 print
86              
87             used by the testsuite. prrints the current state of the well
88              
89             =cut
90              
91             sub print {
92 9     9 1 17 my $self = shift;
93 9         26 print "# /", ('-') x $self->width, "\\\n";
94 9 100       40 print "# |", join( '', map { $_ ? $_ : ' ' } @$_ ), "|\n"
  450         1666  
95 9         15 for @{ $self->well };
96 9         30 print "# \\", ('-') x $self->width, "/\n";
97             }
98              
99             =head2 ->fits( $shape, $x, $y )
100              
101             returns a true value if the given shape would fit in the well at the
102             location C<$x, $y>
103              
104             =cut
105              
106             sub fits {
107 55     55 1 75 my $self = shift;
108 55         70 my ($shape, $at_x, $at_y) = @_;
109              
110 55         162 for ($shape->covers($at_x, $at_y)) {
111 200         279 my ($x, $y) = @$_;
112 200 100 100     1056 return if ($x < 0 ||
      100        
      100        
      100        
113             $y < 0 ||
114             $x >= $self->width ||
115             $y >= $self->depth ||
116             $self->well->[ $y ][ $x ]);
117             }
118 41         179 return 1;
119             }
120              
121             =head2 ->drop( $shape, $x, $y )
122              
123             returns false if the shape will not fit at the location indicated by
124             C<$x, $y>
125              
126             if the shape can be dropped it will be advanced to the bottom of the
127             well and the return value will be the rows removed by the dropping
128             operation, if any, as an array reference
129              
130             =cut
131              
132             sub drop {
133 10     10 1 19 my $self = shift;
134 10         18 my ($shape, $at_x, $at_y) = @_;
135              
136 10 100       27 return unless $self->fits(@_);
137 9         12 my $max_y = $at_y;
138 9         23 for (my $y = $at_y; $y <= $self->depth; $y++) {
139 39 100       76 last if !$self->fits( $shape, $at_x, $y );
140 30         82 $max_y = $y;
141             }
142 9         50 for ($shape->covers($at_x, $max_y)) {
143 36         61 my ($x, $y, $val) = @$_;
144 36         75 $self->well->[ $y ][ $x ] = $val;
145             }
146              
147 9         24 my @removed;
148 9         24 for (my $y = 0; $y < $self->depth; $y++) {
149 45         53 my $inrow = grep { $_ } @{ $self->well->[$y] };
  450         2320  
  45         84  
150 45 100       129 next if $inrow != $self->width;
151 2         5 push @removed, $y;
152             }
153              
154 2         4 splice @{ $self->well }, $_, 1
155 9         32 for reverse @removed;
156 2         5 unshift @{ $self->well }, [(undef) x $self->width]
157 9         29 for @removed;
158 9         73 return \@removed;
159             }
160              
161             1;
162              
163             __END__