File Coverage

blib/lib/Games/Bowling/Scorecard.pm
Criterion Covered Total %
statement 54 54 100.0
branch 14 14 100.0
condition 12 12 100.0
subroutine 12 12 100.0
pod 8 8 100.0
total 100 100 100.0


line stmt bran cond sub pod time code
1 3     3   2199 use v5.24.0;
  3         9  
2 3     3   16 use warnings;
  3         5  
  3         163  
3             package Games::Bowling::Scorecard 0.106;
4             # ABSTRACT: score your bowling game easily
5              
6             #pod =head1 SYNOPSIS
7             #pod
8             #pod use Games::Bowling::Scorecard;
9             #pod
10             #pod my $card = Games::Bowling::Scorecard->new;
11             #pod
12             #pod $card->record(6,1); # slow start
13             #pod $card->record(7,2); # getting better
14             #pod $card->record(10); # strike!
15             #pod $card->record(9,1); # picked up a spare
16             #pod $card->record(10) for 1 .. 3; # turkey!
17             #pod $card->record(0,0); # clearly distracted by something
18             #pod $card->record(8,2); # amazingly picked up 7-10 split
19             #pod $card->record(10, 9, 1); # pick up a bonus spare
20             #pod
21             #pod printf "total score: %u\n", $card->score; # total score: 156, lousy!
22             #pod
23             #pod =head1 DESCRIPTION
24             #pod
25             #pod Scoring ten-pin bowling can be confusing for new players. Frames can't always
26             #pod be scored until several frames later, and then there's that weird tenth frame.
27             #pod Modern bowling alleys incorporate computer scoring into the pin cleanup
28             #pod mechanism, so it's easy to just concentrate on throwing a perfect game and not
29             #pod on grease-pencilling the sheet for the overhead.
30             #pod
31             #pod What's one to do, though, when bowling cantaloupes at beer bottles in one's
32             #pod back yard? Now, with Games::Bowling::Scorecard, it's easy to improvise a
33             #pod scoring device -- maybe on a mobile phone running Symbian Perl.
34             #pod
35             #pod =cut
36              
37 3     3   1322 use Games::Bowling::Scorecard::Frame;
  3         8  
  3         1929  
38              
39             #pod =method new
40             #pod
41             #pod This returns a new scorecard object. It does not take any arguments.
42             #pod
43             #pod =cut
44              
45             sub new {
46 12     12 1 6430 my ($class) = @_;
47              
48 12         39 my $self = bless { frames => [ ] } => $class;
49              
50 12         33 return $self;
51             }
52              
53             #pod =method frames
54             #pod
55             #pod my @frames = $card->frames;
56             #pod
57             #pod This method returns all of the frames for the game. This will return all
58             #pod frames in which scores have been recorded, and possibly one final frame with no
59             #pod recorded balls. It will never return any frames after that.
60             #pod
61             #pod Frames are returned as Games::Bowling::Scorecard::Frame objects.
62             #pod
63             #pod =cut
64              
65             sub frames {
66 1011     1011 1 1541 my ($self) = @_;
67              
68 1011         1338 return @{ $self->{frames} };
  1011         2163  
69             }
70              
71             #pod =method current_frame
72             #pod
73             #pod The current frame is the frame into which the next ball will be recorded. If
74             #pod the card is done, this method returns false.
75             #pod
76             #pod =cut
77              
78             sub current_frame {
79 202     202 1 1220 my ($self) = @_;
80              
81 202 100       362 return if $self->is_done;
82              
83 201         413 my @frames = $self->frames;
84              
85 201         306 my $frame = pop @frames;
86              
87 201 100 100     549 return $self->_next_frame if !$frame || $frame->is_done;
88              
89 93         220 return $frame;
90             }
91              
92             sub _next_frame {
93 108     108   177 my ($self) = @_;
94              
95             my $frame = $self->frames == 9
96 108 100       198 ? do {
97 10         1548 require Games::Bowling::Scorecard::Frame::TenPinTenth;
98 10         52 Games::Bowling::Scorecard::Frame::TenPinTenth->new;
99             }
100             : Games::Bowling::Scorecard::Frame->new;
101              
102 108         177 push @{ $self->{frames} }, $frame;
  108         239  
103              
104 108         297 return $frame;
105             }
106              
107             #pod =method pending_frames
108             #pod
109             #pod This method returns any completed frames the score of which has not yet been
110             #pod finalized. This includes spares and strikes, before the next ball or balls
111             #pod have been recorded.
112             #pod
113             #pod =cut
114              
115             sub pending_frames {
116 199     199 1 312 my ($self) = @_;
117              
118 199         333 my @pending_frames = grep { $_->is_pending } $self->frames;
  971         1752  
119             }
120              
121             #pod =method record
122             #pod
123             #pod $card->record(@balls);
124             #pod
125             #pod This method makes a record of a ball or balls. It is passed a list of bowling
126             #pod results, each being a number of pins knocked down by the ball.
127             #pod
128             #pod For example:
129             #pod
130             #pod $card->record(0, 0); # two gutter balls
131             #pod
132             #pod $card->record(6, 4); # a spare
133             #pod
134             #pod $card->record( (0, 0) x 10); # the worst game you could play
135             #pod
136             #pod $card->record( (10) x 12 ); # a perfect game
137             #pod
138             #pod An exception will be raised if this method is called on a scorecard that's
139             #pod done.
140             #pod
141             #pod If you need to record a ball with more arguments, you can pass them together in
142             #pod an array reference. For example, to pick up an incredible 7-10 split, you
143             #pod might call:
144             #pod
145             #pod $card->record([ 8, { split => 1 } ], 2);
146             #pod
147             #pod The first ball records that it's a split, and the second ball just gets two
148             #pod pins.
149             #pod
150             #pod =cut
151              
152             sub record { ## no critic Ambiguous
153 115     115 1 1706 my $self = shift;
154 115         210 my @balls = @_;
155              
156 115         256 for my $i (0 .. $#balls) {
157 203 100       365 Carp::croak "can't record more balls on a completed scorecard"
158             if $self->is_done;
159              
160 199 100       508 my ($ball, $arg) = ref $balls[$i]
161             ? ($balls[$i][0], $balls[$i][1])
162             : ($balls[$i]);
163              
164 199         362 for my $pending ($self->pending_frames) {
165 49         146 $pending->record($ball);
166             }
167              
168 199         377 $self->current_frame->record($ball, $arg);
169             }
170             }
171              
172             #pod =method score
173             #pod
174             #pod This method returns the current score. It will include the tentative score for
175             #pod all pending frames.
176             #pod
177             #pod =cut
178              
179             sub score {
180 6     6 1 27 my ($self) = @_;
181              
182 6         9 my $score = 0;
183 6         12 $score += $_->score for $self->frames;
184              
185 6         33 return $score;
186             }
187              
188             #pod =method score_through
189             #pod
190             #pod my $score = $card->score_through($n)
191             #pod
192             #pod This method returns the score as of the end of the Ith frame. If that
193             #pod frame's cannot be definitively stated, because it is pending or not done, undef
194             #pod is returned.
195             #pod
196             #pod =cut
197              
198             sub score_through {
199 78     78 1 4807 my ($card, $n) = @_;
200              
201 78 100 100     442 Carp::croak "frame out of range" unless $n >= 1 and $n <= 10;
202              
203 76         157 my @frames = $card->frames;
204 76         115 my $score = 0;
205              
206 76         149 INDEX: for my $idx (0 .. $n - 1) {
207 384         579 my $frame = $frames[ $idx ];
208 384 100 100     685 return undef if $frame->is_pending or not $frame->is_done;
209              
210 378         734 $score += $frame->score;
211             }
212              
213 70         171 return $score;
214             }
215              
216             #pod =method is_done
217             #pod
218             #pod This returns true if the scorecard is done. The scorecard is done if its
219             #pod contents indicate that the player's game is over.
220             #pod
221             #pod =cut
222              
223             sub is_done {
224 411     411 1 1176 my ($self) = @_;
225              
226 411         670 my @frames = $self->frames;
227              
228 411   100     1301 return (@frames == 10 and $frames[9]->is_done);
229             }
230              
231             #pod =head1 TODO
232             #pod
233             #pod =for :list
234             #pod * maybe a way to indicate a split
235             #pod
236             #pod =head1 SECRET ORIGINS
237             #pod
238             #pod In late 2006, I hadn't bowled in something like ten years. I got a Wii, and
239             #pod while I recognized the little triangle and X marks on the Wii Sports Bowling
240             #pod scorecard, I couldn't remember how on earth scoring worked. Once I thought I
241             #pod had a handle on it, I thought writing this would be a good way to cement it in
242             #pod my mind.
243             #pod
244             #pod =cut
245              
246             300;
247              
248             __END__