2018/08/19に、「TDD+モブプログラミングでワイワイする会 その14」に参加してきました。その13に続き、2回目の参加となります。
こちらのイベントでは、文字通り「TDD ( Test Driven Development : テスト駆動開発)」と「モブプログラミング」の両方を体験する事ができます。主催者さん達も交え、4-5人でチームを作り、各チームで何をしていくかを決めていくというスタイルのようです。
今回は、チームメンバが全員2回目以上の経験者だったと言うこともあり、cyber-dojo ( http://www.cyber-dojo.org/ )上で「rubyによるボウリングのスコア計算」を実施することになりました。
余談ですが、別チームは「Chapel」と言う言語でFizzBuzzを書いたそうです。それはそれで面白そうでした。
なお、最初にお断りしておくと、完成には至りませんでした。そのため、最終的な答えだけを知りたい方にはお役に立たないかと思います。
ただし、それなりのステップは踏んで実装していったので、実施していく上での設計方針としては、失敗事例としてでも少しはお役に立てるかもしれません。
仕様
ボウリングの点数計算の方法を書くと長いので、簡単にポイントだけを記載します。
スコアデータが以下のような文字列として構成されています。
例) 1-|2-|3-|4-|5-|6-|7-|8-|9-|5-||
これを引数として渡して点数を返すメソッドを作成します。
なお、ストライクは「X」、スペアは「/」、ガターは「-」で表現します。
また、各フレームは「|」で区切り、「||」の後ろに10フレームでのボーナス点が付与されています。
以下、10フレーム目でストライク、スペア、両方なしの場合の表記になります。
X|X|X|X|X|X|X|X|X|X||XX :全てストライクの場合
5/|5/|5/|5/|5/|5/|5/|5/|5/|5/||5 :10フレーム目でスペアだった場合
9-|9-|9-|9-|9-|9-|9-|9-|9-|72|| :10フレーム目がストライクやスペアでない場合
上記の仕様に従い、実装していきました。
通常の点数計算
まず、簡単なところからと言うことで、通常の計算(スペアやストライクがなかった場合)から実装することにしました。
この場合、単純に各フレーム内の数値を加算していけばよいことになります。よって、フレーム内の文字を数値(int)に変換して加算する方式で考えました。なお、ガターである「-」は記号であるため、to_iすれば「0」になるので特殊な対応はしません。
結果として、以下のような一見シンプルな構成になりました。
def score(s) frames = s.split("||").first.split("|") frames.map{|frame| frame.chars.first.to_i + frame.chars.last.to_i}.sum end
ここからスペアやストライクに対応させようとしました。
しかし、結果としてフレーム毎に分割して計算を実施していくのは、あまり良くない手法だったようです。
スペアを含んだ点数計算
次に、スペアを含む点数計算となります。ここでは点数計算のルールが関係してきます。スペアは、スペア後の一投目の点数が加算されます。例えば、スペア後の一投目で6本倒せば、前フレームの点数は「10 + 6」で16点となります。このため、現在のフレームの点数と、次フレームの一投目の点数を必要とします。
この時は、各フレームの二投目がスペア「/」かどうかで、フレーム内の合計点数を調整しました。例えば、一投目が3の後でスペアをとった場合には、10点から一投目の3を引いた7点を加算して、現在のフレームでの点数は10点としました。
では、スペアによる追加点はどうするかと言うと、スペアである事がわかった時点でフラグを立て、次フレームでの一投目の点数を2倍にして加算していました。
すなわち、入力が「3/|6-|(以降省略)」の場合、以下になります。
- 一投目の「3」で+3
- 二投目で10になるように計算して「10-3(一投目)」で+7。この時、次フレームの一投目を倍にするフラグをONにする。
- そして次フレームで一投目の「6」を2倍して+12。その後、倍にするフラグをOFFにする。
結果として、本来は「16+6->22」となる箇所を「10+12->22」とした形になります。違和感はありましたが、算出するのは最終的な点数であるため良しとしました。
結果として、ソースコードではmapで実施していた箇所はeachに置き換えて、一投目、二投目といった処理を実施することになりました。
少し雲行きが怪しくなってきました。このあたりからチームメンバの表情には陰りが出ていました。
ストライクを含んだ点数計算
続いて、ストライクを含む点数計算となります。ストライクは、スペア後の一投目と二投目の点数が加算されます。
例えば、ストライク後の一投目で6本で二投目で3本倒せば前フレームの点数は「10 + 6 + 3」で19点となります。また、ストライク後も二投連続でストライク(つまりターキー)となった場合には、「10 + 10 + 10」で30点となります。
このため、現在のフレームの点数と、その後の一投目と二投目の点数を必要とします。また、ストライクだと現在のフレームにおける二投目はありません。
よって、ストライクだった場合には、次のフレーム以降での二投分の点数を加算するためのフラグを設定する+現フレームでの二投目計算をスキップする処理を入れて対応していきました。
そして、最終的に出来上がったコードは以下となります。
def score(game_score) frames, bonus = game_score.split('||') score = 0 double = 0 frames.split('|').each do |frame| first, second = frame.chars if first == 'X' score += 10 double = 2 next end if double > 0 score += first.to_i double -= 1 end score += first.to_i if double > 0 score += second == '/' ? 10 - first.to_i : second.to_i double -= 1 end if second == '/' score += 10 - first.to_i double = 1 else score += second.to_i end end score + bonus.to_i end
一応、この時点までに挙げたテストケース(ゲームスコア値)には対応できています。
しかし、10フレームでのボーナス部分である「||」以降の部分はサボって最後にto_iしたままですので、全てストライクを出した場合のテストケースでは300点となれません。また、ストライクの時はスキップしているわけですから、全てストライクの場合ではストライク分の加算処理である10点 x 10で終わってしまいます。
といった所で、時間切れで終了となりました。
実装中に出た議論では、フレーム単位で扱うのではなく「|」を取り除いた「文字の配列」で扱って、インデックスの一つ先や二つ先を取り出して計算した方が良かったのでは、といった話も出てきました。もっとも、それはそれでインデックスの管理をしたりと少し手間な部分は出てくると思います。
モブプログラムでの実装は一旦終わってしまいましたが、元のソースからforkしたセッションを作りました。時間を見て、一人でチャレンジしてみようと思います。