はじめに
先日、ペアプロワークショップで出されたボーリングプログラムのRubyバージョンを書いてみたのですが、どう考えてもRubyらしくない。
なにがRubyらしいのかというのはいろんな人の思いがあると思うのですが、個人的には、
ループ処理でインデックスを使わなくて書くこと
がRubyらしいかなと思っています。
いろいろ考えてみた
で、インデックスを使わないように考えてみたのですが、そこはへっぽこプログラマ。結構考えたのですが、今回はGoogle先生に頼ることにしました。^^;
「Ruby」「Bowling」で検索すると引っかかったこのページにそのままズバリのコードが書いてありました。
正確には、このページの後ろの方にあるコードが正しいプログラム。このページにはちゃんとテストコードも書かれているという素晴らしさ。いやぁ、こういうコードを書けるようになりたいなぁ。
考え方を見てみる
これだけだとあれなので、ちょっとソースに書かれている考え方を勝手に解説してみよう。
まず、ボーリングのスコアを格納しているのが、インスタンス変数の@rolls。データの格納はrecordメソッドでやってんだけど、面白いのは、
def record value @rolls << value @rolls << 0 if first_throw_is_strike? end
のようにストライクの時には0を追加でいれているところ。これは今回のペアプロで出された例と同じ形。これで、ストライクだろうが、それ以外だろうが、1フレームで投げる回数が2回と一般化できる。
おっと、忘れてた。第10フレームがある。第10フレームはストライクやスペアをとると1フレーム3回投げられるのだ。これをどうやっているかというと、should_allow_roll?にそのヒントがある。
def should_allow_roll? max = 20 max = 21 if is_spare_at? 10 max = 22 if is_strike_at? 10 max = 23 if is_strike_at? 11 @rolls.length < max end
パッとみたところ何しているのか分からなかったんだけど、
- 第10フレームでも2投しか投げないことを前提とする。(max=20のところ)
- 第10フレームでスペアが発生した場合は、投げれる回数を増やす。(max=21のところ)
- 第10フレームでストライクが発生した場合は、投げれる回数を増やす。(max=22のところ)
- 第10フレームでも2投しか投げないことになっているので、ストライクやスペアが発生したときには、回数が増えてしまう。それをif文でこねくり回すわけではなく、11フレームとして取り扱う。(max = 23のところ)
ということをしている。
頭の中で「ボーリングは10フレームしかない」と思い込んでいて、それに引きづられて10フレームを前提としたデータ設計を考えていたんだけど、11フレームというのを導入したら話は簡単。これでスコアの計算も
def score (1..10).inject(0) { |score, frame| score += score_for frame } end
というふうに綺麗にかける。す、素晴らしい・・・。
まとめ
今回の結論として、
- 一般化することが重要。出来る限り一般化。
- 現実世界の制約とプログラムの制約とは異なる。
- 考えぬかれたプログラムは綺麗で素晴らしい。
という結果を得られました。いやぁ、奥が深いなぁ。