2009-05-24 チャレンジ開始 $ spec -cfs test/bowling_spec.rb前提といいわけ rubyほぼさわったことない railsのチュートリアル試した程度 意味分からずにコピペしている部分多数 autetestなどなどは全部あとまわしでまずは空気を感じることにする ファイル構成 bowling/ lib/ bowling.rb test/ bowling_spec.rb bowling/test/bowling_spec.rb $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib")) require 'rubygems' require 'spec' require 'bowling' describe Bowling do it "should score 0 for gutter game" do bowling = Bowling.new 20.times { bowling.hit(0) } bowling.score.should == 0 end end 上のほうはみようみまねのコピペ たぶん、このかき方でlib以下のファイルを読み込んで、require 'bowling'でクラスが読み込まれるんじゃないかな? よく分からないけどこれでうまく行くっぽい。 下のほうはrspec.infoからのコピペ bowling/lib/bowling.rb class Bowling end $ spec -cfs test/bowling_spec.rb hitがないと怒られたのでhitを作る bowling/lib/bowling.rb class Bowling def hit(pins) end end $ spec -cfs test/bowling_spec.rb scoreがないと怒られたのでscoreを作る bowling/lib/bowling.rb class Bowling
def hit(pins) end def score 0 end end $ spec -cfs test/bowling_spec.rb Bowling - should score 0 for gutter game Finished in 0.006862 seconds 1 example, 0 failures よし! bowling/test/bowling_spec.rb $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib")) require 'rubygems' require 'spec' require 'bowling' describe "全部0ピンの場合" do before do @bowling = Bowling.new 20.times { @bowling.hit(0) } end it "スコアは0点であること" do @bowling.score.should == 0 end end describe "全部1ピンの場合" do before do @bowling = Bowling.new 20.times { @bowling.hit(1) } end it "スコアは20点であること" do @bowling.score.should == 20 end end 全部0ピンの場合 - スコアは0点であること 全部1ピンの場合 - スコアは20点であること (FAILED - 1) 1) '全部1ピンの場合 スコアは20点であること' FAILED expected: 20, got: 0 (using ==) ./test/bowling_spec.rb:24: Finished in 0.00901 seconds 2 examples, 1 failure bowling/lib/bowling.rb class Bowling def initialize @score = 0 end def hit(pins) @score += pins end def score @score end end $ spec -cfs test/bowling_spec.rb 全部0ピンの場合 - スコアは0点であること 全部1ピンの場合 - スコアは20点であること Finished in 0.006942 seconds 2 examples, 0 failures ここまでで何もないときの集計は出来た。 テストがグリーンのままでリファクタリング bowling/test/bowling_spec.rb $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib")) bowling/test/bowling_spec.rbrequire 'rubygems' require 'spec' require 'bowling' describe "ボウリングゲーム" do before do @bowling = Bowling.new end describe "全部0ピンの場合" do before do 20.times { @bowling.hit(0) } end it "スコアは0点であること" do @bowling.score.should == 0 end end describe "全部1ピンの場合" do before do 20.times { @bowling.hit(1) } end it "スコアは20点であること" do @bowling.score.should == 20 end #describe "ストライクの場合" do # before do # end # #end end end $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib")) $ spec -cfs test/bowling_spec.rbrequire 'rubygems' require 'spec' require 'bowling' describe "ボウリングゲーム" do before do @bowling = Bowling.new end describe "全部0ピンの場合" do before do 20.times { @bowling.hit(0) } end it "スコアは0点であること" do @bowling.score.should == 0 end end describe "全部1ピンの場合" do before do 20.times { @bowling.hit(1) } end it "スコアは20点であること" do @bowling.score.should == 20 end end describe "ストライクの場合" do before do @bowling.hit(10) @bowling.hit(6) @bowling.hit(3) # >< | 6 3 # 19 9 -> 28 16.times {@bowling.hit(0) } end it "スコアは28点であること" do @bowling.score.should == 28 end end end ボウリングゲーム 全部0ピンの場合 - スコアは0点であること ボウリングゲーム 全部1ピンの場合 - スコアは20点であること ボウリングゲーム ストライクの場合 - スコアは28点であること (FAILED - 1) 1) 'ボウリングゲーム ストライクの場合 スコアは28点であること' FAILED expected: 28, got: 19 (using ==) ./test/bowling_spec.rb:42: Finished in 0.004323 seconds 3 examples, 1 failure bowling/lib/bowling.rb class Bowling def initialize @score = 0 @hits = [] end def hit(pins) @hits << pins # @score += pins end def score score = 0 @hits.each do |pins| score += pins end score end end $ spec -cfs test/bowling_spec.rb ボウリングゲーム 全部0ピンの場合 - スコアは0点であること ボウリングゲーム 全部1ピンの場合 - スコアは20点であること Finished in 0.007376 seconds 2 examples, 0 failures グリーンなので、リファクタリングもいける bowling/lib/bowling.rb class Bowling def initialize @hits = [] end def hit(pins) @hits << pins end def score @hits.inject(0) { |score, pins| score + pins } end end injectだと順番に足し算なので、10回フレームを繰り返す考え方にする bowling/lib/bowling.rb class Bowling def initialize @hits = [] end def hit(pins) @hits << pins end def score score = 0 hit_index = 0 10.times do score += @hits[hit_index] + @hits[hit_index + 1] hit_index += 2 end score end end ストライクのテストを復活 bowling/test/bowling_spec.rb $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib")) require 'rubygems' require 'spec' require 'bowling' describe "ボウリングゲーム" do before do @bowling = Bowling.new end describe "全部0ピンの場合" do before do 20.times { @bowling.hit(0) } end it "スコアは0点であること" do @bowling.score.should == 0 end end describe "全部1ピンの場合" do before do 20.times { @bowling.hit(1) } end it "スコアは20点であること" do @bowling.score.should == 20 end end describe "ストライクの場合" do before do @bowling.hit(10) @bowling.hit(6) @bowling.hit(3) # >< | 6 3 # 19 9 -> 28 16.times {@bowling.hit(0) } end it "スコアは28点であること" do @bowling.score.should == 28 end end end $ spec -cfs test/bowling_spec.rb ボウリングゲーム 全部0ピンの場合 - スコアは0点であること ボウリングゲーム 全部1ピンの場合 - スコアは20点であること ボウリングゲーム ストライクの場合 - スコアは28点であること (FAILED - 1) 1) TypeError in 'ボウリングゲーム ストライクの場合 スコアは28点であること' nil can't be coerced into Fixnum /home/sane/studyruby/bowling/lib/bowling.rb:14:in `+' /home/sane/studyruby/bowling/lib/bowling.rb:14:in `score' /home/sane/studyruby/bowling/lib/bowling.rb:13:in `times' /home/sane/studyruby/bowling/lib/bowling.rb:13:in `score' ./test/bowling_spec.rb:42: Finished in 0.010926 seconds 3 examples, 1 failure よく分からないエラーが出る 今度は、小手先の実装ではなくて、まじめに実装する bowling/lib/bowling.rb class Bowling def initialize @hits = [] end def hit(pins) @hits << pins end def score score = 0 hit_index = 0 10.times do if @hits[hit_index] == 10 score += 10 + @hits[hit_index + 1] + @hits[hit_index + 2] hit_index += 1 else score += @hits[hit_index] + @hits[hit_index + 1] hit_index += 2 end end score end end よく分からないが写経。 @hitsが投球を記録した配列。hit_indexは、フレームごとの基準(1投目)となるポインタ。10フレーム繰り返しの順番に見て行くところ。「ポインタ」って言葉の使い方があっているかは不明。指し示す場所。 なので、普通のフレームの場合、基準地点は次のフレームに進むたびに2進む。 ストライクのフレームの場合、基準地点は次のフレームに進むのに1進む。 グリーンのままリファクタリング。ストライクかどうかをメソッドに bowling/lib/bowling.rb class Bowling ストライクの際の後ろ2投加算というのはストライクのボーナスなので意味で切り出してdef initialize @hits = [] end def hit(pins) @hits << pins end def score score = 0 hit_index = 0 10.times do if strike?(hit_index) score += 10 + @hits[hit_index + 1] + @hits[hit_index + 2] hit_index += 1 else score += @hits[hit_index] + @hits[hit_index + 1] hit_index += 2 end end score end private def strike?(hit_index) @hits[hit_index] == 10 end end bowling/lib/bowling.rb class Bowling def initialize @hits = [] end def hit(pins) @hits << pins end def score score = 0 hit_index = 0 10.times do if strike?(hit_index) score += 10 + strike_bonus(hit_index) hit_index += 1 else score += @hits[hit_index] + @hits[hit_index + 1] hit_index += 2 end end score end private def strike?(hit_index) @hits[hit_index] == 10 end def strike_bonus(hit_index) @hits[hit_index + 1] + @hits[hit_index + 2] end end 通常の加算も切り出して bowling/lib/bowling.rb class Bowling def initialize @hits = [] end def hit(pins) @hits << pins end def score score = 0 hit_index = 0 10.times do if strike?(hit_index) score += 10 + strike_bonus(hit_index) hit_index += 1 else score += score_of_regular_frame(hit_index) hit_index += 2 end end score end private def strike?(hit_index) @hits[hit_index] == 10 end def strike_bonus(hit_index) @hits[hit_index + 1] + @hits[hit_index + 2] end def score_of_regular_frame(hit_index) @hits[hit_index] + @hits[hit_index + 1] end end 全部ストライクの場合 パーフェクトゲーム300点のexampleをかく bowling/test/bowling_spec.rb $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib")) require 'rubygems' require 'spec' require 'bowling' describe "ボウリングゲーム" do before do @bowling = Bowling.new end describe "全部0ピンの場合" do before do 20.times { @bowling.hit(0) } end it "スコアは0点であること" do @bowling.score.should == 0 end end describe "全部1ピンの場合" do before do 20.times { @bowling.hit(1) } end it "スコアは20点であること" do @bowling.score.should == 20 end end describe "ストライクの場合" do before do @bowling.hit(10) @bowling.hit(6) @bowling.hit(3) # >< | 6 3 # 19 9 -> 28 16.times {@bowling.hit(0) } end it "スコアは28点であること" do @bowling.score.should == 28 end end describe "パーフェクトゲームの場合" do before do 12.times { @bowling.hit(10) } end it "スコアは300点であること" do @bowling.score.should == 300 end end end $ spec -cfs test/bowling_spec.rb ボウリングゲーム 全部0ピンの場合 - スコアは0点であること ボウリングゲーム 全部1ピンの場合 - スコアは20点であること ボウリングゲーム ストライクの場合 - スコアは28点であること ボウリングゲーム パーフェクトゲームの場合 - スコアは300点であること Finished in 0.004141 seconds 4 examples, 0 failures いきなりグリーン!ひゃっほう! 今度はスペアの場合 bowling/test/bowling_spec.rb $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib")) require 'rubygems' require 'spec' require 'bowling' describe "ボウリングゲーム" do def hit_gutter @bowling.hit(0) end def hit_strike @bowling.hit(10) end before do @bowling = Bowling.new end describe "全部0ピンの場合" do before do 20.times { hit_gutter } end it "スコアは0点であること" do @bowling.score.should == 0 end end describe "全部1ピンの場合" do before do 20.times { @bowling.hit(1) } end it "スコアは20点であること" do @bowling.score.should == 20 end end describe "ストライクの場合" do before do hit_strike @bowling.hit(6) @bowling.hit(3) # >< | 6 3 # 19 9 -> 28 16.times { hit_gutter } end it "スコアは28点であること" do @bowling.score.should == 28 end end describe "パーフェクトゲームの場合" do before do 12.times { hit_strike } end it "スコアは300点であること" do @bowling.score.should == 300 end end describe "スペアの場合" do before do @bowling.hit(6); @bowling.hit(4) #spare @bowling.hit(7); @bowling.hit(1) # 6 / | 7 1 | # 17 25 16.times { hit_gutter } end it "スコアは25点であること" do @bowling.score.should == 25 end end end $ spec -cfs test/bowling_spec.rb ボウリングゲーム 全部0ピンの場合 - スコアは0点であること ボウリングゲーム 全部1ピンの場合 - スコアは20点であること ボウリングゲーム ストライクの場合 - スコアは28点であること ボウリングゲーム パーフェクトゲームの場合 - スコアは300点であること ボウリングゲーム スペアの場合 - スコアは25点であること (FAILED - 1) 1) 'ボウリングゲーム スペアの場合 スコアは25点であること' FAILED expected: 25, got: 18 (using ==) ./test/bowling_spec.rb:75: Finished in 0.014273 seconds 5 examples, 1 failure ダメー bowling/lib/bowling.rb class Bowling def initialize @hits = [] end def hit(pins) @hits << pins end def score score = 0 hit_index = 0 10.times do if strike?(hit_index) score += 10 + strike_bonus(hit_index) hit_index += 1 elsif spare?(hit_index) score += 10 + spare_bonus(hit_index) hit_index += 2 else score += score_of_regular_frame(hit_index) hit_index += 2 end end score end private def strike?(hit_index) @hits[hit_index] == 10 end def spare?(hit_index) @hits[hit_index] + @hits[hit_index + 1] == 10 end def spare_bonus(hit_index) @hits[hit_index + 2] end def strike_bonus(hit_index) @hits[hit_index + 1] + @hits[hit_index + 2] end def score_of_regular_frame(hit_index) @hits[hit_index] + @hits[hit_index + 1] end end $ spec -cfs test/bowling_spec.rb ボウリングゲーム 全部0ピンの場合 - スコアは0点であること ボウリングゲーム 全部1ピンの場合 - スコアは20点であること ボウリングゲーム ストライクの場合 - スコアは28点であること ボウリングゲーム パーフェクトゲームの場合 - スコアは300点であること ボウリングゲーム スペアの場合 - スコアは25点であること Finished in 0.012842 seconds 5 examples, 0 failures スペアも出来たー 受け入れケースのゲームの場合 いよいよ bowling/test/bowling_spec.rb $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib")) require 'rubygems' require 'spec' require 'bowling' describe "ボウリングゲーム" do def hit_gutter @bowling.hit(0) end def hit_strike @bowling.hit(10) end before do @bowling = Bowling.new end describe "全部0ピンの場合" do before do 20.times { hit_gutter } end it "スコアは0点であること" do @bowling.score.should == 0 end end describe "全部1ピンの場合" do before do 20.times { @bowling.hit(1) } end it "スコアは20点であること" do @bowling.score.should == 20 end end describe "ストライクの場合" do before do hit_strike @bowling.hit(6) @bowling.hit(3) # >< | 6 3 # 19 9 -> 28 16.times { hit_gutter } end it "スコアは28点であること" do @bowling.score.should == 28 end end describe "パーフェクトゲームの場合" do before do 12.times { hit_strike } end it "スコアは300点であること" do @bowling.score.should == 300 end end describe "スペアの場合" do before do @bowling.hit(6); @bowling.hit(4) #spare @bowling.hit(7); @bowling.hit(1) # 6 / | 7 1 | # 17 25 16.times { hit_gutter } end it "スコアは25点であること" do @bowling.score.should == 25 end end describe "受け入れケースのゲームの場合" do before do [1, 4, 4, 5, 6, 4, 5, 5, 10, 0, 1, 7, 3, 6, 4, 10, 2, 8, 6].each do |pins| @bowling.hit(pins) end end it "スコアは133点であること" do @bowling.score.should == 133 end end end $ spec -cfs test/bowling_spec.rb ボウリングゲーム 全部0ピンの場合 - スコアは0点であること ボウリングゲーム 全部1ピンの場合 - スコアは20点であること ボウリングゲーム ストライクの場合 - スコアは28点であること ボウリングゲーム パーフェクトゲームの場合 - スコアは300点であること ボウリングゲーム スペアの場合 - スコアは25点であること ボウリングゲーム 受け入れケースのゲームの場合 - スコアは133点であること Finished in 0.023624 seconds 6 examples, 0 failures くりやーーーーーーー 最終形 bowling/test/bowling_spec.rb $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib")) bowling/lib/bowling.rbrequire 'rubygems' require 'spec' require 'bowling' describe "ボウリングゲーム" do def hit_gutter @bowling.hit(0) end def hit_strike @bowling.hit(10) end before do @bowling = Bowling.new end describe "全部0ピンの場合" do before do 20.times { hit_gutter } end it "スコアは0点であること" do @bowling.score.should == 0 end end describe "全部1ピンの場合" do before do 20.times { @bowling.hit(1) } end it "スコアは20点であること" do @bowling.score.should == 20 end end describe "ストライクの場合" do before do hit_strike @bowling.hit(6) @bowling.hit(3) # >< | 6 3 # 19 9 -> 28 16.times { hit_gutter } end it "スコアは28点であること" do @bowling.score.should == 28 end end describe "パーフェクトゲームの場合" do before do 12.times { hit_strike } end it "スコアは300点であること" do @bowling.score.should == 300 end end describe "スペアの場合" do before do @bowling.hit(6); @bowling.hit(4) #spare @bowling.hit(7); @bowling.hit(1) # 6 / | 7 1 | # 17 25 16.times { hit_gutter } end it "スコアは25点であること" do @bowling.score.should == 25 end end describe "受け入れケースのゲームの場合" do before do [1, 4, 4, 5, 6, 4, 5, 5, 10, 0, 1, 7, 3, 6, 4, 10, 2, 8, 6].each do |pins| @bowling.hit(pins) end end it "スコアは133点であること" do @bowling.score.should == 133 end end end class Bowling FRAMES_OF_A_GAME = 10 def initialize @hits = [] end def hit(pins) @hits << pins end def score score = 0 hit_index = 0 FRAMES_OF_A_GAME.times do if strike?(hit_index) score += 10 + strike_bonus(hit_index) hit_index += 1 elsif spare?(hit_index) score += 10 + spare_bonus(hit_index) hit_index += 2 else score += score_of_regular_frame(hit_index) hit_index += 2 end end score end private def strike?(hit_index) @hits[hit_index] == 10 end def spare?(hit_index) @hits[hit_index] + @hits[hit_index + 1] == 10 end def spare_bonus(hit_index) @hits[hit_index + 2] end def strike_bonus(hit_index) @hits[hit_index + 1] + @hits[hit_index + 2] end def score_of_regular_frame(hit_index) @hits[hit_index] + @hits[hit_index + 1] end end 反省と課題 TDDの写経といいながらも、ぼくにとってはそれ以上にvimの練習教材だったようにも思える。 エディタの使い方を調べたり、コード補完されないことで作業量が増えて直感的でなくなっている。 複数行のコメントアウトやアンドゥ、リドゥ、オートインデントでもたもたしている。 いい加減どの環境でどういうコーディングをするかを決めたい。 今回実施した環境 ホストwindowsゲストcentos上のvim rails.vimだけ入っている autotest使いたいのと、できればlinux使いたいのと、あとgit、svnなどデプロイまわりとで、はやいところ固めたい。 windows上でNetBeans使うのが一番しっくり来るのかもなあ。 windows上で秀丸でもいいっちゃいいんだよね。 それともlinuxのX環境かなあ。macでもいいけど… 残todo 色つけたりして上記コードを見やすくする |
bowling >