bowling‎ > ‎

bowling-kata1

2009-05-24 チャレンジ開始



前提といいわけ
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

$ spec -cfs test/bowling_spec.rb

全部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"))
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

    #describe "ストライクの場合" do
    #  before do
    #  end
    #
    #end

    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)
'ボウリングゲーム ストライクの場合 スコアは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
  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

ストライクの際の後ろ2投加算というのはストライクのボーナスなので意味で切り出して
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"))
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

bowling/lib/bowling.rb
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
色つけたりして上記コードを見やすくする
Comments