Haskell で Bowling Score - An Agile Way [ITmedia オルタナティブ・ブログ] で言及されている「アジャイルソフトウェア開発の奥義」のボウリングスコア集計の例を、Squeak の Smalltalk で直訳気味に。
えーと。念のため、.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