File Coverage

blib/lib/Games/Bowling/Scorecard/Frame.pm
Criterion Covered Total %
statement 57 59 96.6
branch 30 32 93.7
condition 11 12 91.6
subroutine 14 14 100.0
pod 7 8 87.5
total 119 125 95.2


line stmt bran cond sub pod time code
1 5     5   1227 use v5.24.0;
  5         17  
2 5     5   27 use warnings;
  5         11  
  5         195  
3              
4             package Games::Bowling::Scorecard::Frame 0.106;
5             # ABSTRACT: one frame on a scorecard
6              
7             #pod =head1 DESCRIPTION
8             #pod
9             #pod A frame is one attempt to knock down all ten pins -- unless it's the tenth
10             #pod frame, in which case it's so goofy that you need to use a different class,
11             #pod L. A frame is done when you've
12             #pod bowled twice or knocked down all the pins, and it's pending until its score can
13             #pod be definitively be stated.
14             #pod
15             #pod =cut
16              
17 5     5   27 use Carp ();
  5         9  
  5         3744  
18              
19             #pod =method new
20             #pod
21             #pod This method returns a new frame object.
22             #pod
23             #pod =cut
24              
25             sub new {
26 115     115 1 2799 my ($class) = @_;
27              
28 115         440 bless {
29             balls => [],
30             score => 0,
31              
32             done => 0,
33             pending => 0,
34             } => $class;
35             }
36              
37             #pod =method record
38             #pod
39             #pod $frame->record($ball, \%arg);
40             #pod
41             #pod This method records a single ball against the frame. This method is used for
42             #pod both the current frame and for pending frames. It updates the frame's score
43             #pod and whether the frame is done or pending.
44             #pod
45             #pod The only valid argument in C<%arg> is C. If true, it indicates the pins
46             #pod are split. This can only be passed on the first ball of a frame.
47             #pod
48             #pod =cut
49              
50             sub _assert_split_ok {
51 4     4   6 my ($self, $ball) = @_;
52              
53 4 50       10 if ($self->{balls}->@*) {
54 0         0 Carp::croak "can't record a split on second ball in a frame";
55             }
56              
57 4 50       14 if ($ball >= 9) {
58 0         0 Carp::croak "you can't split if you knocked down $ball pins!";
59             }
60              
61 4         8 return;
62             }
63              
64             sub record { ## no critic Ambiguous
65 268     268 1 2240 my ($self, $ball, $arg) = @_;
66              
67 268 100       590 if ($arg->{split}) {
68 4         12 $self->_assert_split_ok($ball);
69              
70 4         6 $self->{split} = 1;
71             }
72              
73 268 100       490 if ($self->is_done) {
74 53 100       95 if ($self->is_pending) {
75 52         82 $self->{pending}--;
76 52         80 $self->{score} += $ball;
77 52         122 return;
78             } else {
79 1         71 Carp::croak "two balls already recorded for frame";
80             }
81             }
82              
83 215         598 $self->roll_ok($ball);
84              
85 215         308 push @{ $self->{balls} }, $ball;
  215         406  
86 215         332 $self->{score} += $ball;
87              
88 215         511 $self->_check_done;
89 215         423 $self->_check_pending;
90             }
91              
92             sub was_split {
93 34 100   34 0 88 return $_[0]->{split} ? 1 : 0;
94             }
95              
96             sub _check_pending {
97 215     215   410 my ($self) = @_;
98 215 100       390 return unless $self->is_done;
99              
100 111         200 my @balls = $self->balls;
101              
102 111 100 66     317 return $self->{pending} = 2 if @balls == 1 and $balls[0] == 10;
103 91 100 100     462 return $self->{pending} = 1 if @balls == 2 and $balls[0] + $balls[1] == 10;
104             }
105              
106             sub _check_done {
107 215     215   338 my ($self) = @_;
108              
109 215         352 my @balls = $self->balls;
110              
111 215 100 100     990 $self->{done} = 1 if (@balls == 1 and $balls[0] == 10) or @balls == 2;
      100        
112             }
113              
114             #pod =method roll_ok
115             #pod
116             #pod $frame->roll_ok($ball);
117             #pod
118             #pod This method asserts that given value is an acceptable number to score next in
119             #pod this frame. It checks that:
120             #pod
121             #pod * the frame is not already done
122             #pod * $ball is defined, an integer, and between 0 and 10
123             #pod * $ball would not bring the total number of pins downed above 10
124             #pod
125             #pod =cut
126              
127             sub roll_ok {
128 227     227 1 4859 my ($self, $ball) = @_;
129              
130 227 100       401 Carp::croak "the frame is done" if $self->is_done;
131 226 100       570 Carp::croak "you can't bowl an undefined number of pins!" if !defined $ball;
132 225 100       625 Carp::croak "you can't bowl more than 10 on a single ball" if $ball > 10;
133 223 100       580 Carp::croak "you can't bowl less than 0 on a single ball" if $ball < 0;
134 222 100       489 Carp::croak "you can't knock down a partial pin" if $ball != int($ball);
135              
136 221         338 my $i = 0;
137 221         403 $i += $_ for $self->balls, $ball;
138              
139 221 100       2263 Carp::croak "bowling a $ball would bring the frame above 10" if $i > 10;
140             }
141              
142             #pod =method score
143             #pod
144             #pod This method returns the current score for the frame, even if the frame is not
145             #pod done or is pending further balls.
146             #pod
147             #pod =cut
148              
149             sub score {
150 468     468 1 4044 my ($self) = @_;
151 468         1082 return $self->{score};
152             }
153              
154             #pod =method is_pending
155             #pod
156             #pod This method returns true if the frame is pending more balls -- that is, it
157             #pod returns true for strikes or spares which have not yet recorded the results of
158             #pod subsequent balls.
159             #pod
160             #pod =cut
161              
162             sub is_pending {
163 1401     1401 1 2146 my ($self) = @_;
164 1401         3224 return $self->{pending};
165             }
166              
167             #pod =method is_done
168             #pod
169             #pod This method returns true if the frame is done.
170             #pod
171             #pod =cut
172              
173             sub is_done {
174 1163     1163 1 1792 my ($self) = @_;
175 1163         3013 return $self->{done};
176             }
177              
178             #pod =method balls
179             #pod
180             #pod This method returns the balls recorded against the frame, each ball returned as
181             #pod the number of pins it knocked down. In scalar context, it returns the number
182             #pod of balls recoded against the frame.
183             #pod
184             #pod =cut
185              
186             sub balls {
187 802     802 1 1300 my ($self) = @_;
188 802         1067 return @{ $self->{balls} };
  802         1828  
189             }
190              
191             300;
192              
193             __END__