trackler 2.0.8.38 → 2.0.8.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/trackler/version.rb +1 -1
- data/tracks/csharp/exercises/list-ops/HINTS.md +1 -1
- data/tracks/csharp/exercises/nucleotide-count/HINTS.md +3 -0
- data/tracks/go/config.json +8 -0
- data/tracks/go/exercises/bowling/bowling_test.go +77 -0
- data/tracks/go/exercises/bowling/cases_test.go +225 -0
- data/tracks/go/exercises/bowling/example.go +133 -0
- data/tracks/go/exercises/bowling/example_gen.go +117 -0
- data/tracks/go/exercises/leap/cases_test.go +2 -1
- data/tracks/go/exercises/leap/example_gen.go +1 -3
- data/tracks/go/gen/gen.go +39 -6
- data/tracks/perl6/bin/README.md +29 -0
- data/tracks/perl6/bin/exercise-gen.pl6 +36 -0
- data/tracks/perl6/config.json +1 -0
- data/tracks/perl6/exercises/hello-world/example.yaml +19 -0
- data/tracks/perl6/templates/test.mustache +53 -0
- data/tracks/python/.travis.yml +1 -1
- data/tracks/python/README.md +2 -6
- data/tracks/python/config.json +9 -0
- data/tracks/python/exercises/anagram/anagram_test.py +52 -48
- data/tracks/python/exercises/largest-series-product/largest_series_product_test.py +43 -42
- data/tracks/python/exercises/nth-prime/example.py +3 -0
- data/tracks/python/exercises/nth-prime/nth_prime_test.py +15 -5
- data/tracks/python/exercises/rotational-cipher/example.py +14 -0
- data/tracks/python/exercises/rotational-cipher/rotational_cipher.py +2 -0
- data/tracks/python/exercises/rotational-cipher/rotational_cipher_test.py +53 -0
- data/tracks/python/exercises/word-search/example.py +9 -8
- metadata +57 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f16f5a4fa0fdde0642c976d3e020bdf84d359613
|
|
4
|
+
data.tar.gz: 940657bebb2d0ea237f01459d6beeab37766ee9b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f67845a691fb409f1c1453bca11cd939f37647ba716bac4a6ccd8d15cb7d0d50f43098c3448491adfc7724d5ef1c1922e96c6c98b1eefc60368e9786dd26fe87
|
|
7
|
+
data.tar.gz: 59000762c30e40be13816d4d7413ceff5ca4529b98fb912874a12a7a7a9ea272c003019bf7047769f16e73d19dde9e2912c91f15790735f2aa45099bf53630e3
|
data/lib/trackler/version.rb
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
## Hints
|
|
2
|
-
The `Foldl`
|
|
2
|
+
The `Foldl` and `Foldr` methods are "fold" functions, which is a concept well-known in the functional programming world, but less so in the object-oriented one. If you'd like more background information, check out this [fold](https://en.wikipedia.org/wiki/Fold_(higher-order_function)) page.
|
data/tracks/go/config.json
CHANGED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
package bowling
|
|
2
|
+
|
|
3
|
+
import "testing"
|
|
4
|
+
|
|
5
|
+
const targetTestVersion = 1
|
|
6
|
+
|
|
7
|
+
const previousRollErrorMessage = `FAIL: %s
|
|
8
|
+
Unexpected error occurred: %q
|
|
9
|
+
while applying the previous rolls for the
|
|
10
|
+
test case: %v
|
|
11
|
+
The error was returned from Roll(%d) for previousRolls[%d].`
|
|
12
|
+
|
|
13
|
+
func TestTestVersion(t *testing.T) {
|
|
14
|
+
if testVersion != targetTestVersion {
|
|
15
|
+
t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
func applyPreviousRolls(g *Game, rolls []int) (int, int, error) {
|
|
20
|
+
for index, pins := range rolls {
|
|
21
|
+
var err error
|
|
22
|
+
if err = g.Roll(pins); err != nil {
|
|
23
|
+
return index, pins, err
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return 0, 0, nil
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
func TestScore(t *testing.T) {
|
|
30
|
+
for _, tc := range scoreTestCases {
|
|
31
|
+
g := NewGame()
|
|
32
|
+
index, pins, err := applyPreviousRolls(g, tc.previousRolls)
|
|
33
|
+
if err != nil {
|
|
34
|
+
t.Fatalf(previousRollErrorMessage,
|
|
35
|
+
tc.description, err, tc.previousRolls, pins, index)
|
|
36
|
+
}
|
|
37
|
+
score, err := g.Score()
|
|
38
|
+
if tc.valid {
|
|
39
|
+
var _ error = err
|
|
40
|
+
if err != nil {
|
|
41
|
+
t.Fatalf("FAIL: %s : Score() after Previous Rolls: %#v expected %d, got error %s",
|
|
42
|
+
tc.description, tc.previousRolls, tc.score, err)
|
|
43
|
+
} else {
|
|
44
|
+
if score != tc.score {
|
|
45
|
+
t.Fatalf("%s : Score() after Previous Rolls: %#v expected %d, got %d",
|
|
46
|
+
tc.description, tc.previousRolls, tc.score, score)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
} else if err == nil {
|
|
50
|
+
t.Fatalf("FAIL: %s : Score() after Previous Rolls: %#v expected an error, got score %d\n\tExplanation: %s",
|
|
51
|
+
tc.description, tc.previousRolls, score, tc.explainText)
|
|
52
|
+
}
|
|
53
|
+
t.Logf("PASS: %s", tc.description)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
func TestRoll(t *testing.T) {
|
|
58
|
+
for _, tc := range rollTestCases {
|
|
59
|
+
g := NewGame()
|
|
60
|
+
index, pins, err := applyPreviousRolls(g, tc.previousRolls)
|
|
61
|
+
if err != nil {
|
|
62
|
+
t.Fatalf(previousRollErrorMessage,
|
|
63
|
+
tc.description, err, tc.previousRolls, pins, index)
|
|
64
|
+
}
|
|
65
|
+
err = g.Roll(tc.roll)
|
|
66
|
+
if tc.valid && err != nil {
|
|
67
|
+
var _ error = err
|
|
68
|
+
t.Fatalf("FAIL: %s : Roll(%d) after Previous Rolls: %#v got unexpected error \"%s\".",
|
|
69
|
+
tc.description, tc.roll, tc.previousRolls, err)
|
|
70
|
+
|
|
71
|
+
} else if !tc.valid && err == nil {
|
|
72
|
+
t.Fatalf("FAIL: %s : Roll(%d) after Previous Rolls: %#v expected an error.\n\tExplanation: %s",
|
|
73
|
+
tc.description, tc.roll, tc.previousRolls, tc.explainText)
|
|
74
|
+
}
|
|
75
|
+
t.Logf("PASS: %s", tc.description)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
package bowling
|
|
2
|
+
|
|
3
|
+
// Source: exercism/x-common
|
|
4
|
+
// Commit: 3cf5eb9 bowling: Make canonical-data.json compliant
|
|
5
|
+
// x-common version: 1.0.0
|
|
6
|
+
|
|
7
|
+
var scoreTestCases = []struct {
|
|
8
|
+
description string
|
|
9
|
+
previousRolls []int // bowling rolls to do before the Score() test
|
|
10
|
+
valid bool // true => no error, false => error expected
|
|
11
|
+
score int // when .valid == true, the expected score value
|
|
12
|
+
explainText string // when .valid == false, error explanation text
|
|
13
|
+
}{
|
|
14
|
+
{
|
|
15
|
+
"should be able to score a game with all zeros",
|
|
16
|
+
[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
|
17
|
+
true,
|
|
18
|
+
0,
|
|
19
|
+
"",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"should be able to score a game with no strikes or spares",
|
|
23
|
+
[]int{3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6},
|
|
24
|
+
true,
|
|
25
|
+
90,
|
|
26
|
+
"",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"a spare followed by zeros is worth ten points",
|
|
30
|
+
[]int{6, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
|
31
|
+
true,
|
|
32
|
+
10,
|
|
33
|
+
"",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"points scored in the roll after a spare are counted twice",
|
|
37
|
+
[]int{6, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
|
38
|
+
true,
|
|
39
|
+
16,
|
|
40
|
+
"",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"consecutive spares each get a one roll bonus",
|
|
44
|
+
[]int{5, 5, 3, 7, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
|
45
|
+
true,
|
|
46
|
+
31,
|
|
47
|
+
"",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"a spare in the last frame gets a one roll bonus that is counted once",
|
|
51
|
+
[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 7},
|
|
52
|
+
true,
|
|
53
|
+
17,
|
|
54
|
+
"",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"a strike earns ten points in a frame with a single roll",
|
|
58
|
+
[]int{10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
|
59
|
+
true,
|
|
60
|
+
10,
|
|
61
|
+
"",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"points scored in the two rolls after a strike are counted twice as a bonus",
|
|
65
|
+
[]int{10, 5, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
|
66
|
+
true,
|
|
67
|
+
26,
|
|
68
|
+
"",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"consecutive strikes each get the two roll bonus",
|
|
72
|
+
[]int{10, 10, 10, 5, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
|
73
|
+
true,
|
|
74
|
+
81,
|
|
75
|
+
"",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"a strike in the last frame gets a two roll bonus that is counted once",
|
|
79
|
+
[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 7, 1},
|
|
80
|
+
true,
|
|
81
|
+
18,
|
|
82
|
+
"",
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"rolling a spare with the two roll bonus does not get a bonus roll",
|
|
86
|
+
[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 7, 3},
|
|
87
|
+
true,
|
|
88
|
+
20,
|
|
89
|
+
"",
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"strikes with the two roll bonus do not get bonus rolls",
|
|
93
|
+
[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10},
|
|
94
|
+
true,
|
|
95
|
+
30,
|
|
96
|
+
"",
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"a strike with the one roll bonus after a spare in the last frame does not get a bonus",
|
|
100
|
+
[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 10},
|
|
101
|
+
true,
|
|
102
|
+
20,
|
|
103
|
+
"",
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"all strikes is a perfect game",
|
|
107
|
+
[]int{10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10},
|
|
108
|
+
true,
|
|
109
|
+
300,
|
|
110
|
+
"",
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
{
|
|
114
|
+
"two bonus rolls after a strike in the last frame can score more than 10 points if one is a strike",
|
|
115
|
+
[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 6},
|
|
116
|
+
true,
|
|
117
|
+
26,
|
|
118
|
+
"",
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
{
|
|
122
|
+
"an unstarted game can not be scored",
|
|
123
|
+
[]int{},
|
|
124
|
+
false,
|
|
125
|
+
0,
|
|
126
|
+
"Score cannot be taken until the end of the game",
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"an incomplete game can not be scored",
|
|
130
|
+
[]int{0, 0},
|
|
131
|
+
false,
|
|
132
|
+
0,
|
|
133
|
+
"Score cannot be taken until the end of the game",
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
{
|
|
137
|
+
"bonus rolls for a strike in the last frame must be rolled before score can be calculated",
|
|
138
|
+
[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10},
|
|
139
|
+
false,
|
|
140
|
+
0,
|
|
141
|
+
"Score cannot be taken until the end of the game",
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"both bonus rolls for a strike in the last frame must be rolled before score can be calculated",
|
|
145
|
+
[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10},
|
|
146
|
+
false,
|
|
147
|
+
0,
|
|
148
|
+
"Score cannot be taken until the end of the game",
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"bonus roll for a spare in the last frame must be rolled before score can be calculated",
|
|
152
|
+
[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3},
|
|
153
|
+
false,
|
|
154
|
+
0,
|
|
155
|
+
"Score cannot be taken until the end of the game",
|
|
156
|
+
},
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
var rollTestCases = []struct {
|
|
160
|
+
description string
|
|
161
|
+
previousRolls []int // bowling rolls to do before the Roll(roll) test
|
|
162
|
+
valid bool // true => no error, false => error expected
|
|
163
|
+
roll int // pin count for the test roll
|
|
164
|
+
explainText string // when .valid == false, error explanation text
|
|
165
|
+
}{
|
|
166
|
+
|
|
167
|
+
{
|
|
168
|
+
"rolls can not score negative points",
|
|
169
|
+
[]int{},
|
|
170
|
+
false,
|
|
171
|
+
-1,
|
|
172
|
+
"Negative roll is invalid",
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"a roll can not score more than 10 points",
|
|
176
|
+
[]int{},
|
|
177
|
+
false,
|
|
178
|
+
11,
|
|
179
|
+
"Pin count exceeds pins on the lane",
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"two rolls in a frame can not score more than 10 points",
|
|
183
|
+
[]int{5},
|
|
184
|
+
false,
|
|
185
|
+
6,
|
|
186
|
+
"Pin count exceeds pins on the lane",
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"bonus roll after a strike in the last frame can not score more than 10 points",
|
|
190
|
+
[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10},
|
|
191
|
+
false,
|
|
192
|
+
11,
|
|
193
|
+
"Pin count exceeds pins on the lane",
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
"two bonus rolls after a strike in the last frame can not score more than 10 points",
|
|
197
|
+
[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 5},
|
|
198
|
+
false,
|
|
199
|
+
6,
|
|
200
|
+
"Pin count exceeds pins on the lane",
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
{
|
|
204
|
+
"the second bonus rolls after a strike in the last frame can not be a strike if the first one is not a strike",
|
|
205
|
+
[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 6},
|
|
206
|
+
false,
|
|
207
|
+
10,
|
|
208
|
+
"Pin count exceeds pins on the lane",
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
"second bonus roll after a strike in the last frame can not score than 10 points",
|
|
212
|
+
[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10},
|
|
213
|
+
false,
|
|
214
|
+
11,
|
|
215
|
+
"Pin count exceeds pins on the lane",
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
{
|
|
219
|
+
"cannot roll if game already has ten frames",
|
|
220
|
+
[]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
|
221
|
+
false,
|
|
222
|
+
0,
|
|
223
|
+
"Cannot roll after game is over",
|
|
224
|
+
},
|
|
225
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// Package bowling implements scoring for the game of bowling.
|
|
2
|
+
package bowling
|
|
3
|
+
|
|
4
|
+
import "errors"
|
|
5
|
+
|
|
6
|
+
const testVersion = 1
|
|
7
|
+
|
|
8
|
+
var (
|
|
9
|
+
ErrNegativeRollIsInvalid = errors.New("Negative roll is invalid")
|
|
10
|
+
ErrPinCountExceedsPinsOnTheLane = errors.New("Pin count exceeds pins on the lane")
|
|
11
|
+
ErrPrematureScore = errors.New("Score cannot be taken until the end of the game")
|
|
12
|
+
ErrCannotRollAfterGameOver = errors.New("Cannot roll after game is over")
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
const (
|
|
16
|
+
pinsPerFrame = 10
|
|
17
|
+
framesPerGame = 10
|
|
18
|
+
maxRollsPerFrame = 2
|
|
19
|
+
maxRollsLastFrame = 3
|
|
20
|
+
maxRolls = (maxRollsPerFrame * (framesPerGame - 1)) + maxRollsLastFrame
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
// Game records the data to track a game's progress.
|
|
24
|
+
type Game struct {
|
|
25
|
+
rolls [maxRolls]int // storage for the rolls
|
|
26
|
+
nRolls int // counts the rolls accumulated.
|
|
27
|
+
nFrames int // counts completed frames, up to framesPerGame.
|
|
28
|
+
rFrameStart int // tracks the starting roll of each frame.
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// NewGame returns a fresh zero-valued game struct.
|
|
32
|
+
func NewGame() *Game {
|
|
33
|
+
return &Game{}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Roll records one roll for a bowling frame with 'pins' knocked down.
|
|
37
|
+
// An error is possible depending on pin value and previous rolls.
|
|
38
|
+
func (g *Game) Roll(pins int) error {
|
|
39
|
+
// Validate pin count on roll.
|
|
40
|
+
if pins > pinsPerFrame {
|
|
41
|
+
return ErrPinCountExceedsPinsOnTheLane
|
|
42
|
+
}
|
|
43
|
+
if pins < 0 {
|
|
44
|
+
return ErrNegativeRollIsInvalid
|
|
45
|
+
}
|
|
46
|
+
if g.completedFrames() == framesPerGame {
|
|
47
|
+
return ErrCannotRollAfterGameOver
|
|
48
|
+
}
|
|
49
|
+
// Record the roll.
|
|
50
|
+
g.rolls[g.nRolls] = pins
|
|
51
|
+
g.nRolls++
|
|
52
|
+
if pins == pinsPerFrame && g.completedFrames() < framesPerGame-1 {
|
|
53
|
+
// Frames before last one can be strikes with no problems.
|
|
54
|
+
g.completeTheFrame()
|
|
55
|
+
return nil
|
|
56
|
+
}
|
|
57
|
+
if g.rollsThisFrame() == maxRollsPerFrame {
|
|
58
|
+
// Have counted normal max rolls on a frame.
|
|
59
|
+
if g.rawFrameScore(g.rFrameStart) > pinsPerFrame {
|
|
60
|
+
// Unless we have completed all but last frame, cannot count > pinsPerFrame.
|
|
61
|
+
if g.completedFrames() != framesPerGame-1 || !g.isStrike(g.rFrameStart) {
|
|
62
|
+
return ErrPinCountExceedsPinsOnTheLane
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if g.completedFrames() < framesPerGame-1 {
|
|
66
|
+
// Completed frames before last one with maxRollsPerFrame.
|
|
67
|
+
g.completeTheFrame()
|
|
68
|
+
return nil
|
|
69
|
+
}
|
|
70
|
+
// For last frame, is it complete now ?
|
|
71
|
+
if g.rawFrameScore(g.rFrameStart) < pinsPerFrame {
|
|
72
|
+
// Yes, complete.
|
|
73
|
+
g.completeTheFrame()
|
|
74
|
+
}
|
|
75
|
+
} else if g.rollsThisFrame() == maxRollsLastFrame {
|
|
76
|
+
// Extra roll on the last frame.
|
|
77
|
+
if g.isStrike(g.rFrameStart) {
|
|
78
|
+
// First was a strike.
|
|
79
|
+
if !g.isStrike(g.rFrameStart + 1) {
|
|
80
|
+
// Second was NOT a strike, so last 2 rolls cannot exceed pinsPerFrame.
|
|
81
|
+
if g.strikeBonus(g.rFrameStart) > pinsPerFrame {
|
|
82
|
+
return ErrPinCountExceedsPinsOnTheLane
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if b := g.strikeBonus(g.rFrameStart); b > pinsPerFrame && b < 2*pinsPerFrame {
|
|
86
|
+
// Unless one of the bonuses was a strike, bonus frames too high.
|
|
87
|
+
if !g.isStrike(g.rFrameStart+1) && !g.isStrike(g.rFrameStart+2) {
|
|
88
|
+
return ErrPinCountExceedsPinsOnTheLane
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} else if !g.isSpare(g.rFrameStart) {
|
|
92
|
+
// Attempt to make extra roll in last frame without strike or spare.
|
|
93
|
+
return ErrCannotRollAfterGameOver
|
|
94
|
+
}
|
|
95
|
+
// Completed last frame.
|
|
96
|
+
g.completeTheFrame()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return nil
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Score returns the score of the game with a potential error.
|
|
103
|
+
func (g *Game) Score() (int, error) {
|
|
104
|
+
if g.completedFrames() != framesPerGame {
|
|
105
|
+
return 0, ErrPrematureScore
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
score := 0
|
|
109
|
+
frameStart := 0
|
|
110
|
+
|
|
111
|
+
for frame := 0; frame < framesPerGame; frame++ {
|
|
112
|
+
if g.isStrike(frameStart) {
|
|
113
|
+
score += pinsPerFrame + g.strikeBonus(frameStart)
|
|
114
|
+
frameStart++
|
|
115
|
+
} else if g.isSpare(frameStart) {
|
|
116
|
+
score += pinsPerFrame + g.spareBonus(frameStart)
|
|
117
|
+
frameStart += maxRollsPerFrame
|
|
118
|
+
} else {
|
|
119
|
+
score += g.rawFrameScore(frameStart)
|
|
120
|
+
frameStart += maxRollsPerFrame
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return score, nil
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
func (g *Game) rollsThisFrame() int { return g.nRolls - g.rFrameStart }
|
|
127
|
+
func (g *Game) completeTheFrame() { g.nFrames++; g.rFrameStart = g.nRolls }
|
|
128
|
+
func (g *Game) completedFrames() int { return g.nFrames }
|
|
129
|
+
func (g *Game) isStrike(f int) bool { return g.rolls[f] == pinsPerFrame }
|
|
130
|
+
func (g *Game) rawFrameScore(f int) int { return g.rolls[f] + g.rolls[f+1] }
|
|
131
|
+
func (g *Game) spareBonus(f int) int { return g.rolls[f+2] }
|
|
132
|
+
func (g *Game) strikeBonus(f int) int { return g.rolls[f+1] + g.rolls[f+2] }
|
|
133
|
+
func (g *Game) isSpare(f int) bool { return (g.rolls[f] + g.rolls[f+1]) == pinsPerFrame }
|