One Saturday morning, I had the urge to learn the scoring rules for the game of bowling. This program resulted from that. Guido, here, talks about his bowling program that uses a simple data structure: list of lists to represent frames. I do the same.
# Sat March 22, 2008 (9am)
# bowling.py -- calculates scores, doesn't enforce rules
# create new instance for each player.
# Public API: knocked_down(npins)
# just invoke that -- will automatically deduce
# which frame you are in.
class BowlingGame(object):
def __init__(self):
self.f = [[None, None] for x in range(12)]
self.cf = 0 # current frame
def knocked_down(self, npins):
f = self.f[self.cf]
if f[0] is None:
f[0] = npins
else:
f[1] = npins
if npins == 10 or f[1] is not None:
self.cf += 1
def get_score(self):
total = 0
complete = True
for i, f in enumerate(self.f):
if i > 9:
break
elif f[0] is None: # hasn't bowled this frame
break
elif f[0] == 10: # strike
if self.f[i + 1][0] is None and \
(self.f[i + 1][1] is None or self.f[i + 2][0] is None):
complete = False
break
# strike -- next 2 bowls (both of which can be in the same frame!)
total += 10 + self.f[i + 1][0]
if self.f[i + 1][1] is None:
total += self.f[i + 2][0]
else:
total += self.f[i + 1][1]
elif f[1] is None: # hasn't bowled 2nd bowl
complete = False
break
elif f[0] + f[1] == 10: # spare
if self.f[i + 1][0] is None:
complete = False
break
total += 10 + self.f[i + 1][0]
else:
total += f[0] + f[1]
return total, complete
#
# Tests
#
def reporterror(actual, expected):
if actual != expected:
print 'actual: %s\nexpected: %s' % (actual, expected)
raise Exception
def test1():
game = BowlingGame()
game.knocked_down(7)
game.knocked_down(2)
reporterror(game.get_score()[0], 9)
def test2():
game = BowlingGame()
for i in range(12):
game.knocked_down(10)
reporterror(game.get_score()[0], 300)
def test3():
import random
for i in range(1000):
bowls = [random.randint(0, 4) for i in range(20)]
game = BowlingGame()
for b in bowls:
game.knocked_down(b)
reporterror(game.get_score(), (sum(bowls), True))
# wikipedia data:
# http://en.wikipedia.org/wiki/Ten-pin_bowling#Rules_of_play
def test4():
game = BowlingGame()
map(game.knocked_down, [10, 10, 4, 2])
reporterror(game.get_score(), (46, True))
def test5():
game = BowlingGame()
map(game.knocked_down, [7, 3, 4, 2])
reporterror(game.get_score(), (20, True))
def test6():
game = BowlingGame()
map(game.knocked_down, [10, 3, 6])
reporterror(game.get_score(), (28, True))
def test7():
game = BowlingGame()
for i in range(10):
game.knocked_down(10)
game.knocked_down(7)
game.knocked_down(1)
reporterror(game.get_score(), (285, True))
def t():
for i in range(100):
try:
name = "test%d" % (i+1)
fn = globals()[name]
print "testing", name
except KeyError:
break
else:
fn()
if __name__ == "__main__":
t()