「アジャイルソフトウェア開発の奥義」のボウリングスコア集計


Haskell で Bowling Score - An Agile Way [ITmedia オルタナティブ・ブログ] で言及されている「アジャイルソフトウェア開発の奥義」のボウリングスコア集計の例を、SqueakSmalltalk で直訳気味に。

えーと。念のため、.cs ですが C# ではありません。Smalltalk の“チェンジセット”ファイルです。w


ファイルイン用のコードは読みづらいので、メタ情報を除いた加工コード(ファイルイン不可。ブラウザへのコピペが必要)も。

Object subclass: #Game
   instanceVariableNames: 'itsCurrentFrame itsScorer firstThrowInFrame'

Game >> initialize
   itsCurrentFrame := 1.
   firstThrowInFrame := true.
   itsScorer := Scorer new

Game >> score
   ^ self scoreForFrame: itsCurrentFrame

Game >> scoreForFrame: theFrame
   ^ itsScorer scoreForFrame: theFrame

Game >> add: pins
   itsScorer addThrow: pins.
   self adjustCurrentFrame: pins

Game >> adjustCurrentFrame: pins
   (self lastBallInFrame: pins)
      ifTrue: [self advanceFrame]
      ifFalse: [firstThrowInFrame := false]

Game >> advanceFrame
   itsCurrentFrame := 10 min: itsCurrentFrame + 1

Game >> lastBallInFrame: pins
   ^ (self strike: pins) or: [firstThrowInFrame not]

Game >> strike: pins
   ^ firstThrowInFrame and: [pins = 10]
Object subclass: #Scorer
   instanceVariableNames: 'itsThrows itsCurrentThrow ball'

Scorer >> initialize
   itsThrows := Array new: 21 withAll: 0.
   itsCurrentThrow := 1

Scorer >> scoreForFrame: theFrame
   | score |
   ball := 1.
   score := 0.
   1 to: theFrame do: [:currentFrame |
      self strike
         ifTrue: [
            score := score + 10 + self nextTwoBallsForStrike.
            ball := ball + 1]
         ifFalse: [
            self spare
               ifTrue: [
                  score := score + 10 + self nextBallForSpare.
                  ball := ball + 2]
               ifFalse: [
                  score := score + self twoBallsInFrame.
                  ball := ball + 2]]].
   ^ score

Scorer >> addThrow: pins
   itsThrows at: itsCurrentThrow put: pins.
   itsCurrentThrow := itsCurrentThrow + 1

Scorer >> nextBallForSpare
   ^ itsThrows at: ball + 2

Scorer >> nextTwoBallsForStrike
   ^ (itsThrows at: ball + 1) + (itsThrows at: ball + 2)

Scorer >> spare
   ^ (itsThrows at: ball) + (itsThrows at: ball + 1) = 10

Scorer >> strike
   ^ (itsThrows at: ball) = 10

Scorer >> twoBallsInFrame
   ^ (itsThrows at: ball) + (itsThrows at: ball + 1)


この「アジャイル…」の“スコアラー”の振る舞いの余計な部分をそぎ落としてゆくと、けっきょく“ゲーム”も不要になって、結果的に Haskell 版のようになるのかな…とふと感じました。


他方で、くだんのやりとりで否定された“フレーム”オブジェクトをリンクリスト(これまた同じく否定されている…)の要素として介在させて、ついでに平鍋さんが言及しておられる第10フレームの特殊な扱いについても取り込んだ例も作ってみました。

同じく、メタ情報を除いた加工コード。

LinkedList subclass: #BowlingScore

BowlingScore >> add: nPins
   (self isEmpty or: [self lastFrame isOver]) ifTrue: [
      self size = 10 ifTrue: [^ self  "error: 'the game is already over!!'"].
      super add: (self size < 9
         ifTrue: [BowlingScoreFrame] ifFalse: [BowlingScoreFinalFrame]) new].
   ^ self lastFrame add: nPins

BowlingScore >> lastFrame
   ^ lastLink

BowlingScore >> frameScoreAt: n
   ^ (self first: n) inject: 0 into: [:score :frame | score + (frame points ifNil: [^ nil])]

BowlingScore >> score
   ^ self inject: 0 into: [:score :frame | score + (frame points ifNil: [^ score])]
Link subclass: #BowlingScoreFrame
   instanceVariableNames: 'throws'

BowlingScoreFrame >> initialize
   throws := OrderedCollection new

BowlingScoreFrame >> nextFrame
   ^ nextLink

BowlingScoreFrame >> throws
   ^ throws

BowlingScoreFrame >> add: nPins
   throws add: nPins

BowlingScoreFrame >> isEmptyOrNil
   ^ throws isEmpty

BowlingScoreFrame >> isOver
   ^ self isStrike or: [throws size = 2]

BowlingScoreFrame >> isSpare
   ^ throws size = 2 and: [throws sum = 10]

BowlingScoreFrame >> isStrike
   ^ throws notEmpty and: [throws first = 10]

BowlingScoreFrame >> points
   throws isEmpty ifTrue: [^ nil].
   self isStrike ifTrue: [^ self pointsWithNextTwoThrows].
   self isSpare ifTrue: [^ self pointsWithNextThrow].
   ^ throws sum

BowlingScoreFrame >> pointsWithNextThrow
   self nextFrame isEmptyOrNil ifTrue: [^ nil].
   ^ self nextFrame throws first + 10

BowlingScoreFrame >> pointsWithNextTwoThrows
   self nextFrame isEmptyOrNil ifTrue: [^ nil].
   self nextFrame throws size = 1
      ifTrue: [^ self nextFrame pointsWithNextThrow ifNotNilDo: [:pt | pt + 10]].
   ^ (self nextFrame throws first: 2) sum + 10
BowlingScoreFrame subclass: #BowlingScoreFinalFrame

BowlingScoreFinalFrame >> isOver
   ^ throws size = 3 or: [throws size = 2 and: [throws sum < 10]]

BowlingScoreFinalFrame >> pointsWithNextThrow
   throws size < 3 ifTrue: [^ nil].
   ^ throws sum

BowlingScoreFinalFrame >> pointsWithNextTwoThrows
   throws size < 3 ifTrue: [^ nil].
   ^ throws sum

こういうのも嫌いじゃありません。むしろ、詰め込みすぎな感じのする“スコアラー”を介在させるよりは好みです。


追記
ここで用いている Smalltalk のリンクリスト(a LinkedList)は Java のリンクリスト(java.util.LinkedList)とは違い、要素がリンク(a Link)に限られる一方で、各リンクは次のリンク(つまり自分の次の要素)が誰だかを自分のホルダであるリンクリストを介さずに知ることができるという特殊な作りになっています。LISP の(CAR ではなく) CONS セルをそのまま要素として扱うような、そんな感じですね。この「次を知っている」という性質を、ストライクやスペア時のポイントの加算では旨味として利用できると考えました。というか、この旨味がなければリンクリストで実装しようとは思わなかったでしょう(^_^;)(ということで、「アジャイル…」でのリンクリスト否定の趣旨からはだいぶ離れしまっています。あしからず)。なお、この Smalltalk のリンクリストをそのまま Java で実装したものが、じゅん for Java で提供されています。