import
unittest
class
Frame(
object
):
def
__init__(
self
, tenth
=
False
):
self
.rolls
=
[]
self
.tenth
=
tenth
def
full(
self
):
if
len
(
self
.rolls) >
=
self
.max_rolls():
return
True
if
self
.tenth:
has_special
=
all
([roll
in
(
'X'
,
'/'
)
for
roll
in
self
.rolls])
return
len
(
self
.rolls)
=
=
2
and
not
has_special
else
:
return
self
.strike()
def
roll(
self
, score):
if
self
.full():
raise
RuntimeError(
'attempted to record a roll on a full frame'
)
self
.rolls.append(score)
def
pins(
self
):
if
len
(
self
.rolls)
=
=
0
:
return
0
if
self
.strike()
or
self
.spare():
return
10
else
:
return
sum
([
int
(roll)
for
roll
in
self
.rolls])
def
first_roll_pins(
self
):
if
len
(
self
.rolls)
=
=
0
:
return
0
elif
self
.rolls[
0
]
=
=
'X'
:
return
10
else
:
return
int
(
self
.rolls[
0
])
def
score(
self
, subsequent_frames):
if
self
.tenth:
return
self
.tenth_frame_score()
score
=
self
.pins()
if
self
.strike():
score
+
=
sum
([frame.pins()
for
frame
in
subsequent_frames])
elif
self
.spare():
if
len
(subsequent_frames) >
0
:
score
+
=
subsequent_frames[
0
].first_roll_pins()
return
score
def
tenth_frame_score(
self
):
return
min
(Game(''.join(
self
.rolls)).score(),
40
)
def
strike(
self
):
return
len
(
self
.rolls) >
0
and
self
.rolls[
0
]
=
=
'X'
def
spare(
self
):
return
len
(
self
.rolls) >
0
and
self
.rolls[
-
1
]
=
=
'/'
def
max_rolls(
self
):
return
3
if
self
.tenth
else
2
class
Game(
object
):
def
__init__(
self
, roll_scores
=
''):
self
.frames
=
[]
for
score
in
roll_scores:
self
.roll(score)
def
roll(
self
, score):
if
len
(
self
.frames)
=
=
0
or
self
.frames[
-
1
].full():
tenth
=
len
(
self
.frames)
=
=
9
self
.frames.append(Frame(tenth))
self
.frames[
-
1
].roll(score)
def
score(
self
):
return
sum
(
self
.frame_scores())
def
frame_scores(
self
):
frame_scores
=
[]
for
frame_index
in
xrange
(
len
(
self
.frames)):
frame
=
self
.frames[frame_index]
subsequent_frames
=
[]
if
frame.strike()
or
frame.spare():
subsequent_frames
=
self
.frames[frame_index
+
1
:]
added_frames
=
1
if
frame.strike()
and
len
(subsequent_frames) >
0
:
added_frames
=
2
if
subsequent_frames[
0
].strike()
else
1
subsequent_frames
=
subsequent_frames[:added_frames]
frame_scores.append(frame.score(subsequent_frames))
return
frame_scores
class
GameTest(unittest.TestCase):
def
test_initial_strike(
self
):
self
.assertEqual(Game(
'X'
).score(),
10
)
def
test_two_strikes(
self
):
self
.assertEqual(Game(
'XX'
).score(),
20
+
10
)
def
test_three_strikes(
self
):
self
.assertEqual(Game(
'XXX'
).score(),
30
+
20
+
10
)
def
test_strike_spare_strike(
self
):
self
.assertEqual(Game(
'X9/X'
).score(),
20
+
20
+
10
)
def
test_strike_strike_spare(
self
):
self
.assertEqual(Game(
'XX9/'
).score(),
30
+
20
+
10
)
self
.assertEqual(Game(
'XX9/71'
).score(),
30
+
20
+
17
+
8
)
def
test_perfect_game(
self
):
game
=
Game(
'X'
*
12
)
self
.assertEqual(
len
(game.frames),
10
)
self
.assertEqual(game.score(),
300
)
def
test_made_up_game(
self
):
game
=
Game(
'X907/818/X70070/72'
)
self
.assertEqual(
len
(game.frames),
10
)
self
.assertEqual(game.score(),
132
)
if
__name__
=
=
'__main__'
:
unittest.main()