RubyのRSpecでリボルバーを作ってロシアンルーレットしようよ!
今までコードを書くとき
正直あまりまじめにテストをしてこなかったよ
でもいつまでもそういう訳にはいかないだろうから
以下の記事を参考にしてRSpecをやってみたんだ
RSpec の入門とその一歩先へ - t-wadaの日記
Rubyist Magazine - スはスペックのス 【第 1 回】 RSpec の概要と、RSpec on Rails (モデル編)
そうしたら次のようなことが分かったんだよ
でこの感覚をみんなと共有できればと思ったので
RSpec入門者の僕がRSpecを使って
ここで何か作ってみるよ
さて 何を作ろうか..
何か実用的で楽しいものがいいよね
そうだ折角だからなかなか手に入らないものがいいね
なかなか手に入らないものといったら..
もちろん輸入禁制品だよね!
そんなわけで..
RubyのRSpecを使ってけん銃を作るよ
で最高にイカしてるけん銃と言ったらリボルバーだから
リボルバーを作ることにするよ
銃が完成したら
君とロシアンルーレットを楽しみたいと思うんだ
最初に断っておくと
このポストは君がエディタとターミナルを開いて
僕と同じことをすることを期待したもの
つまりチュートリアルの形式になっているよ
だから相当長いポストになることを覚悟してほしいよ
結果だけみたい人は以下にコードを張ったから
それにざっと目を通してそれから
「ロシアンルーレットで遊ぶ」の項に飛んでくれるとうれしいよ
Revolver for Russian Roulette ― Gist
リボルバーの作り方
うれしいことにRubyには
ロシアンルーレットのための部品が既に用意されてるよ
Array#rotate! #shuffle #cycle は
まさにその目的のために作られたんだよ
これらのメソッドがあれば作業がかなり捗るよね
さて 簡単な方針だけ決めてRSpecを書いていくよ
けん銃にもいろいろあるからここでは
いきなりRevolverクラスを作るんじゃなくて
ベースとなるGunクラスを作って
Revolverクラスはそれを継承するようにしよう
#gun_spec.rb require "rspec" require_relative "gun" describe Gun do end describe Revolver do end
Gunクラス
けん銃は少なくとも弾を込めて
発砲できなくちゃいけないから
これをGunクラスの機能として持たせるよ
薬室(弾を込める場所) 装弾 発砲をそれぞれ
chamber set_cartridge triggerとするよ
弾はカートリッジ(cartridge)と言うよ
ちなみに銃に詳しくない人はここに目を通すといいよ
describe Gun do context "chamber" do end context "set_cartridge" do end context "trigger" do end end
ここで一度rspecを走らせてみるよ
% rspec -fs -c gun_spec.rb % gun_spec.rb:4:in `<top (required)>': uninitialized constant Object::Gun (NameError)
Gunクラスがないって文句を言われたからこれを作ろう
Revolverもね
#gun.rb class Gun end class Revolver < Gun end
さあもう一度テストするよ
% rspec -fs -c gun_spec.rb No examples found. Finished in 0.00005 seconds 0 examples, 0 failures
でchamberは最初は空でset_cartridgeすると
弾がそこに入って撃てるようになる
そういう設計に沿ってexampleを書くよ
require "rspec" require_relative "gun" describe Gun do before do @gun = Gun.new end context "chamber" do it "should be empty at default" do @gun.chamber.should be_empty end end context "set_cartridge" do it "should set a cartridge to the chamber" do @gun.set_cartridge @gun.chamber.should == [Cartridge.new] end end context "trigger" do end end describe Revolver do end
そしてテストするよ
% rspec -fs -c gun_spec.rb Gun chamber should be empty at default (FAILED - 1) set_cartridge should set a cartridge to the chamber (FAILED - 2) Failures: 1) Gun chamber should be empty at default Failure/Error: @gun.chamber.should be_empty NoMethodError: undefined method `chamber' for #<Gun:0x00000100a1e248> # ./gun_spec.rb:11:in `block (3 levels) in <top (required)>' 2) Gun set_cartridge should set a cartridge to the chamber Failure/Error: @gun.set_cartridge NoMethodError: undefined method `set_cartridge' for #<Gun:0x00000100a1cda8> # ./gun_spec.rb:18:in `block (3 levels) in <top (required)>' Finished in 0.00092 seconds 2 examples, 2 failures Failed examples: rspec ./gun_spec.rb:10 # Gun chamber should be empty at default rspec ./gun_spec.rb:17 # Gun set_cartridge should set a cartridge to the chamber
chamberメソッドもset_cartridgeメソッドも
無いって言われたから作るよ
class Gun attr_reader :chamber def initialize @chamber = [] end def set_cartridge @chamber << Cartridge.new end end
もう一度テストするよ
% rspec -fs -c gun_spec.rb Gun chamber should be empty at default set_cartridge should set a cartridge to the chamber (FAILED - 1) Failures: 1) Gun set_cartridge should set a cartridge to the chamber Failure/Error: @gun.set_cartridge NameError: uninitialized constant Gun::Cartridge # ./gun.rb:8:in `set_cartridge' # ./gun_spec.rb:18:in `block (3 levels) in <top (required)>' Finished in 0.00328 seconds 2 examples, 1 failure
1つ目はパスしたけど
2つ目はまだ弾を作ってなかったからフェイルしたよ
弾を作るよ
class Cartridge end
テストね
% rspec -fs -c gun_spec.rb Gun chamber should be empty at default set_cartridge should set a cartridge to the chamber (FAILED - 1) Failures: 1) Gun set_cartridge should set a cartridge to the chamber Failure/Error: @gun.chamber.should == [Cartridge.new] expected: [#<Cartridge:0x00000100a992b8>] got: [#<Cartridge:0x00000100a99420>] (using ==) Diff: @@ -1,2 +1,2 @@ -[#<Cartridge:0x00000100a992b8>] +[#<Cartridge:0x00000100a99420>] # ./gun_spec.rb:19:in `block (3 levels) in <top (required)>' Finished in 0.00145 seconds 2 examples, 1 failure
今度は弾が違うって言われたよ
弾の同値性をオブジェクトで判断するからだね
じゃあ同値性をクラスで判断して弾の個性を消すよ
class Cartridge include Comparable def <=>(other) self.class <=> other.class end end
さあもう一度
% rspec -fs -c gun_spec.rb Gun chamber should be empty at default set_cartridge should set a cartridge to the chamber Finished in 0.00088 seconds 2 examples, 0 failures
うまくいったよ!
さて次にchamberに弾が入ってたらもう詰められないから
そのときはエラーがでるようにしないと
exampleを書くよ
context "set_cartridge" do it "should set a cartridge to the chamber" do @gun.set_cartridge @gun.chamber.should == [Cartridge.new] end it "should be error when the chamber has a cartridge" do ->{ 2.times { @gun.set_cartridge } }.should raise_error end end
raise_errorを捕捉するにはset_cartridgeのリターンを
Procオブジェクト化しないといけないよ
ちょっとイケてないけどねー
テストするよ
% rspec -fs -c gun_spec.rb Gun chamber should be empty at default set_cartridge should set a cartridge to the chamber should be error when the chamber has a cartridge (FAILED - 1) Failures: 1) Gun set_cartridge should be error when the chamber has a cartridge Failure/Error: ->{ 2.times { @gun.set_cartridge } }.should raise_error expected Exception but nothing was raised # ./gun_spec.rb:23:in `block (3 levels) in <top (required)>' Finished in 0.00122 seconds 3 examples, 1 failure
エラーを期待したのに
エラーにならないって言われたので対応するよ
ErrorクラスはChamberErrorにしようか
class Gun class ChamberError < StandardError; end attr_reader :chamber def initialize @chamber = [] end def set_cartridge raise ChamberError, 'The chamber is full' unless @chamber.empty? @chamber << Cartridge.new end end
テストするよ
% rspec -fs -c gun_spec.rb Gun chamber should be empty at default set_cartridge should set a cartridge to the chamber should be error when the chamber has a cartridge Finished in 0.00137 seconds 3 examples, 0 failures
パスしたよ
さて次はtrigger周りを作ろう
もちろん発砲したら'Bang!'ってならなきゃ
context "trigger" do it "should return 'Bang!'" do @gun.trigger.should == 'Bang!' end end
実装もしちゃうよ
class Gun def trigger 'Bang!' end end
テストするよ
% rspec -fs -c gun_spec.rb Gun chamber should be empty at default set_cartridge should set a cartridge to the chamber should be error when the chamber has a cartridge trigger should return 'Bang!' Finished in 0.00148 seconds 4 examples, 0 failures
いいね!
さて今度は弾がないときの対応も取らないと
context "trigger" do it "should return 'Bang!'" do @gun.trigger.should == 'Bang!' end it "should be nil when the chamber is empty" do @gun.chamber.should be_empty @gun.trigger.should be_nil end end
テストするよ
% rspec -fs -c gun_spec.rb Gun chamber should be empty at default set_cartridge should set a cartridge to the chamber should be error when the chamber has a cartridge trigger should return 'Bang!' should be nil when the chamber is empty (FAILED - 1) Failures: 1) Gun trigger should be nil when the chamber is empty Failure/Error: @gun.trigger.should be_nil expected: nil got: "Bang!" # ./gun_spec.rb:34:in `block (3 levels) in <top (required)>' Finished in 0.00234 seconds 5 examples, 1 failure
弾がなくても発砲しちゃうって..
対応するよ
def trigger return nil if @chamber.empty? 'Bang!' end
テストするよ
% rspec -fs -c gun_spec.rb Gun chamber should be empty at default set_cartridge should set a cartridge to the chamber should be error when the chamber has a cartridge trigger should return 'Bang!' (FAILED - 1) should be nil when the chamber is empty Failures: 1) Gun trigger should return 'Bang!' Failure/Error: @gun.trigger.should == 'Bang!' expected: "Bang!" got: nil (using ==) # ./gun_spec.rb:29:in `block (3 levels) in <top (required)>' Finished in 0.00218 seconds 5 examples, 1 failure
今度はさっきパスしたexampleがフェイルしてる
そうだよ弾をセットしてないからね
テストが間違ってたんだ
テストを直してもう一度
context "trigger" do it "should return 'Bang!'" do @gun.set_cartridge @gun.trigger.should == 'Bang!' end it "should be nil when the chamber is empty" do @gun.chamber.should be_empty @gun.trigger.should be_nil end end
テストする
% rspec -fs -c gun_spec.rb Gun chamber should be empty at default set_cartridge should set a cartridge to the chamber should be error when the chamber has a cartridge trigger should return 'Bang!' should be nil when the chamber is empty Finished in 0.0023 seconds 5 examples, 0 failures
いいね!
さてそれから弾を撃ったら
chamberは空にならなきゃいけないね
追加しよう
context "chamber" do it "should be empty at default" do @gun.chamber.should be_empty end it "should be empty after triggering" do @gun.set_cartridge @gun.trigger @gun.chamber.should be_empty end end
テストするよ
% rspec -fs -c gun_spec.rb Gun chamber should be empty at default should be empty after triggering (FAILED - 1) set_cartridge should set a cartridge to the chamber should be error when the chamber has a cartridge trigger should return 'Bang!' should be nil when the chamber is empty Failures: 1) Gun chamber should be empty after triggering Failure/Error: @gun.chamber.should be_empty expected empty? to return true, got false # ./gun_spec.rb:17:in `block (3 levels) in <top (required)>' Finished in 0.00227 seconds 6 examples, 1 failure
フェイルするから実装するよ
def trigger return nil if @chamber.empty? @chamber.clear 'Bang!' end
もう一度テスト
% rspec -fs -c gun_spec.rb Gun chamber should be empty at default should be empty after triggering set_cartridge should set a cartridge to the chamber should be error when the chamber has a cartridge trigger should return 'Bang!' should be nil when the chamber is empty Finished in 0.00215 seconds 6 examples, 0 failures
いいね!
さてGunクラスは一応これで完成として
今度はRevolverクラスを作っていくよ
Revolverクラス
リボルバーは6弾程の弾を保持する
回転式シリンダーという独特の機構を持っていて
一発撃つ毎にシリンダーが一つ回転して
次の弾がチャンバーにアラインされて
発砲の準備が整うよ
早速Revolverクラスを設計しよう
Gunクラスでは弾を直接chamberにセットしたけど
Revolverではcylinderにセットすることになるよね
だからset_cartridgeとchamberの再設計が必要だよ
ここではcylinderの0位置に弾がセットされたら
chamberにも弾があることにするよ
describe Revolver do before do @rev = Revolver.new end context "set_cartridge" do it "should set a cartridge to the cylinder pos 0" do @rev.set_cartridge @rev.cylinder[0].should == Cartridge.new end end end
テストしてみるよ
% rspec -fs -c gun_spec.rb Revolver set_cartridge should set a cartridge to the cylinder pos 0 (FAILED - 1) Failures: 1) Revolver set_cartridge should set a cartridge to the cylinder pos 0 Failure/Error: @rev.cylinder[0].should == Cartridge.new NoMethodError: undefined method `cylinder' for #<Revolver:0x00000100a5af90> # ./gun_spec.rb:53:in `block (3 levels) in <top (required)>' Finished in 0.00296 seconds 7 examples, 1 failure
cylinderがないって言われたので作るよ
class Revolver < Gun CYLINDER_SIZE = 6 attr_reader :cylinder def initialize @cylinder = Array.new(CYLINDER_SIZE) super end end
再テストするよ
% rspec -fs -c gun_spec.rb Revolver set_cartridge should set a cartridge to the cylinder pos 0 (FAILED - 1) Failures: 1) Revolver set_cartridge should set a cartridge to the cylinder pos 0 Failure/Error: @rev.cylinder[0].should == Cartridge.new expected: #<Cartridge:0x00000100a6a3a0> got: nil (using ==) # ./gun_spec.rb:53:in `block (3 levels) in <top (required)>' Finished in 0.00281 seconds 7 examples, 1 failure
今度は設計と違う結果(nil)が返ってきたよ
Revolver用のset_cartridgeを実装しないとね
最初は0ポジション空いてなければ隣に装弾する方式にしよう
先を急ぐから例外処理もここに書いちゃうよ
class Revolver < Gun class CylinderError < StandardError; end CYLINDER_SIZE = 6 attr_reader :cylinder def initialize @cylinder = Array.new(CYLINDER_SIZE) super end def set_cartridge pos = @cylinder.index(nil) raise CylinderError, 'Cylinder is full' unless pos @cylinder[pos] = Cartridge.new end end
もう2つほどexampleを追加してからテストしてみよう
context "set_cartridge" do it "should set a cartridge to the cylinder pos 0" do @rev.set_cartridge @rev.cylinder[0].should == Cartridge.new end it "should set 3 cartridges to the cylinder pos 0-3" do 3.times { @rev.set_cartridge } @rev.cylinder.should == [Cartridge.new, Cartridge.new, Cartridge.new, nil, nil, nil] end it "should be error when it called more than the cylinder rooms" do Revolver::CYLINDER_SIZE.times { @rev.set_cartridge } ->{ @rev.set_cartridge }.should raise_error(Revolver::CylinderError) end end
テストだよ
% rspec -fs -c gun_spec.rb Revolver set_cartridge should set a cartridge to the cylinder pos 0 should set 3 cartridges to the cylinder pos 0-3 should be error when it called more than the cylinder rooms Finished in 0.00315 seconds 9 examples, 0 failures
OKだね!
ほんとうはcylinderの自由な位置に
カートリッジを装填できるべきだけど
ここでは割愛するよ
さてtriggerにいこう
まずはちゃんとBangするように
context "trigger" do it "should return 'Bang!'" do @rev.set_cartridge @rev.trigger.should == 'Bang!' end end
% rspec -fs -c gun_spec.rb Revolver set_cartridge should set a cartridge to the cylinder pos 0 should set 3 cartridges to the cylinder pos 0-3 should be error when it called more than the cylinder rooms trigger should return 'Bang!' (FAILED - 1) Failures: 1) Revolver trigger should return 'Bang!' Failure/Error: @rev.trigger.should == 'Bang!' expected: "Bang!" got: nil (using ==) # ./gun_spec.rb:70:in `block (3 levels) in <top (required)>' Finished in 0.00469 seconds 10 examples, 1 failure
フェイルしちゃったよ
あーchamberに弾が無いからね
cylinder[0]に弾があれば
chamberにも弾があるって設計だったよね
def chamber [ @cylinder[0] ].compact end
% rspec -fs -c gun_spec.rb Revolver set_cartridge should set a cartridge to the cylinder pos 0 should set 3 cartridges to the cylinder pos 0-3 should be error when it called more than the cylinder rooms trigger should return 'Bang!' (FAILED - 1) Failures: 1) Revolver trigger should return 'Bang!' Failure/Error: @rev.trigger.should == 'Bang!' expected: "Bang!" got: nil (using ==) # ./gun_spec.rb:70:in `block (3 levels) in <top (required)>' Finished in 0.00338 seconds 10 examples, 1 failure
まだフェイルする..
そうかこれはGun#triggerで
直接@chamberを参照してるから起きてるんだ
ここを直そう
class Gun def trigger - return nil if @chamber.empty? + return nil if chamber.empty? - @chamber.clear + reset_chamber 'Bang!' end + private + def reset_chamber + @chamber.clear + end end class Revolver + def reset_chamber + @cylinder[0] = nil + end + private :reset_chamber end
テストだよ
% rspec -fs -c gun_spec.rb Gun chamber should be empty at default should be empty after triggering set_cartridge should set a cartridge to the chamber should be error when the chamber has a cartridge trigger should return 'Bang!' should be nil when the chamber is empty Revolver set_cartridge should set a cartridge to the cylinder pos 0 should set 3 cartridges to the cylinder pos 0-3 should be error when it called more than the cylinder rooms trigger should return 'Bang!' Finished in 0.00373 seconds 10 examples, 0 failures
OKだね
考えてみたらロシアンルーレットってみんな
毎回ハンマー(撃鉄)を手で起こしてるよね
このタイプのリボルバーはシングルアクションというそうだよ
是非ともこの機構(cocking)を実装したいよ
cockingしないと発砲できないようにしよう
それから発砲した後はhammerが戻るようにしよう
context "trigger" do it "should return 'Bang!'" do @rev.set_cartridge @rev.cocking @rev.trigger.should == 'Bang!' end it "should be nil without cocking" do @rev.set_cartridge @rev.trigger.should be_nil end end context "hammer" do it "should be false after triggering" do @rev.set_cartridge @rev.cocking @rev.trigger @rev.hammer.should be_false end end
当然hammerやcockingが無いって怒られるから
それを確認した上で実装しよう
class Revolver < Gun attr_reader :cylinder, :hammer def initialize @cylinder = Array.new(CYLINDER_SIZE) @hammer = false super end def cocking @hammer = true end def trigger return nil unless @hammer @hammer = false super end end
テストするよ
% rspec -fs -c gun_spec.rb Revolver set_cartridge should set a cartridge to the cylinder pos 0 should set 3 cartridges to the cylinder pos 0-3 should be error when it called more than the cylinder rooms trigger should return 'Bang!' should be nil without cocking hammer should be false after triggering Finished in 0.0042 seconds 12 examples, 0 failures
いいみたいだね
あ それとcockingしたら
cylinderが一つ回転するようにしないといけなかったよ
回転しないと連続発砲ができないからね
context "cylinder" do it "should rotate for next when cocking" do @rev.set_cartridge @rev.cocking @rev.cylinder.should == [nil, nil, nil, nil, nil, Cartridge.new] end end
テストするよ
% rspec -fs -c gun_spec.rb Revolver set_cartridge should set a cartridge to the cylinder pos 0 should set 3 cartridges to the cylinder pos 0-3 should be error when it called more than the cylinder rooms cylinder should rotate for next when cocking (FAILED - 1) trigger should return 'Bang!' should be nil without cocking hammer should be false after triggering Failures: 1) Revolver cylinder should rotate for next when cocking Failure/Error: @rev.cylinder.should == [nil, Cartridge.new, nil, nil, nil, nil] expected: [nil, #<Cartridge:0x000001009cc1c8>, nil, nil, nil, nil] got: [#<Cartridge:0x000001009cc330>, nil, nil, nil, nil, nil] (using ==) Diff: @@ -1,2 +1,2 @@ -[nil, #<Cartridge:0x000001009cc1c8>, nil, nil, nil, nil] +[#<Cartridge:0x000001009cc330>, nil, nil, nil, nil, nil] # ./gun_spec.rb:71:in `block (3 levels) in <top (required)>' Finished in 0.00549 seconds 13 examples, 1 failure
フェイルするから直すよ
def cocking @cylinder.rotate! @hammer = true end
テストだよ
% rspec -fs -c gun_spec.rb Revolver set_cartridge should set a cartridge to the cylinder pos 0 should set 3 cartridges to the cylinder pos 0-3 should be error when it called more than the cylinder rooms cylinder should rotate for next when cocking trigger should return 'Bang!' (FAILED - 1) should be nil without cocking hammer should be false after triggering Failures: 1) Revolver trigger should return 'Bang!' Failure/Error: @rev.trigger.should == 'Bang!' expected: "Bang!" got: nil (using ==) # ./gun_spec.rb:79:in `block (3 levels) in <top (required)>' Finished in 0.00466 seconds 13 examples, 1 failure
今度はtriggerのexampleでエラーが出たよ
cockingで弾がpos 0にいなくなったからだね
じゃあset_cartridgeでpos 0じゃなくpos 1に
弾をセットするように変えて対応するよ
context "set_cartridge" do - it "should set a cartridge to the cylinder pos 0" do + it "should set a cartridge to the cylinder pos 1" do @rev.set_cartridge - @rev.cylinder[0].should == Cartridge.new + @rev.cylinder[1].should == Cartridge.new end - it "should set 3 cartridges to the cylinder pos 0-3" do + it "should set 3 cartridges to the cylinder pos 1-4" do 3.times { @rev.set_cartridge } - @rev.cylinder.should == [Cartridge.new, Cartridge.new, Cartridge.new, nil, nil, nil] + @rev.cylinder.should == [nil, Cartridge.new, Cartridge.new, Cartridge.new, nil, nil] end it "should be error when it called more than the cylinder rooms" do Revolver::CYLINDER_SIZE.times { @rev.set_cartridge } ->{ @rev.set_cartridge }.should raise_error(Revolver::CylinderError) end end context "cylinder" do it "should rotate for next when cocking" do @rev.set_cartridge @rev.cocking - @rev.cylinder.should == [nil, nil, nil, nil, nil, Cartridge.new] + @rev.cylinder.should == [Cartridge.new, nil, nil, nil, nil, nil] end end
フェイルするから実装するよ
def set_cartridge pos = @cylinder.rotate.index(nil) raise CylinderError, 'Cylinder is full' unless pos pos = (pos + 1) % CYLINDER_SIZE @cylinder[pos] = Cartridge.new end
テストだよ
% rspec -fs -c gun_spec.rb Revolver set_cartridge should set a cartridge to the cylinder pos 1 should set 3 cartridges to the cylinder pos 1-4 should be error when it called more than the cylinder rooms cylinder should rotate for next when cocking trigger should return 'Bang!' should be nil without cocking hammer should be false after triggering Finished in 0.00461 seconds 13 examples, 0 failures
うまくいったよ
じゃあ連続発砲ができるか試すよ
context "trigger" do it "should work sequentially" do 6.times { @rev.set_cartridge } @rev.cocking 8.times.map { @rev.trigger.tap{ @rev.cocking } }.should == ["Bang!", "Bang!", "Bang!", "Bang!", "Bang!", "Bang!", nil, nil] end end
テストするよ
% rspec -fs -c gun_spec.rb Revolver set_cartridge should set a cartridge to the cylinder pos 1 should set 3 cartridges to the cylinder pos 1-4 should be error when it called more than the cylinder rooms cylinder should rotate for next when cocking trigger should return 'Bang!' should be nil without cocking should work sequentially hammer should be false after triggering Finished in 0.00705 seconds 14 examples, 0 failures
うまくいってるようだね
さて最後に
ロシアンルーレットに必須の機能とも言うべき
spin_cylinderを実装しよう
context "spin_cylinder" do it "should rotate the cylinder line randomly(fail sometimes)" do @rev.set_cartridge before_spin = @rev.cylinder.dup @rev.spin_cylinder @rev.cylinder.should_not == before_spin end end
あまり良いテストじゃないよね..
こういうランダムな結果をテストするのは
どうすればいいんだろうね
わからないから今回はこれでよしとして先に進むよ
さてもちろんテストに通らないので
spin_cylinderを実装するよ
def spin_cylinder @cylinder.rotate!(rand CYLINDER_SIZE) end
さあテストだよ
% rspec -fs -c gun_spec.rb Gun chamber should be empty at default should be empty after triggering set_cartridge should set a cartridge to the chamber should be error when the chamber has a cartridge trigger should return 'Bang!' should be nil when the chamber is empty Revolver set_cartridge should set a cartridge to the cylinder pos 1 should set 3 cartridges to the cylinder pos 1-4 should be error when it called more than the cylinder rooms cylinder should rotate for next when cocking trigger should return 'Bang!' should be nil without cocking should work sequentially hammer should be false after triggering spin_cylinder should rotate the cylinder line randomly(fail sometimes) Finished in 0.00705 seconds 15 examples, 0 failures
いいね!
さあこれでようやくRevolverが完成したよ!
ロシアンルーレットで遊ぶ
さあ銃が用意できたから
早速ロシアンルーレットできるか試してみるよ
まずは舞台(russian_roulette.rb)を作ろう
require "term/ansicolor" require_relative "gun" String.send(:include, Term::ANSIColor) def russian_roulette(fighters) print "--- Welcome to Russian Roulette ---\n".green print "Today's fighters are: " print fighters.map { |f| f.magenta.underline }.join(", ") print "\nLet's go!\n\n" rev = Revolver.new rev.set_cartridge sleep 2 fighters.shuffle.cycle do |fighter| print "#{fighter}'s turn:\n".cyan rev.cocking rev.spin_cylinder sleep 2 unless result = rev.trigger print " Nothing happened..\n\n".yellow sleep 1 else print " #{result} ".yellow.on_red.blink print " #{fighter} is dead.\n".blue print "\n--- Game is over ---\n".green exit end end end russian_roulette(ARGV)
term-ansicolorで色を付けたから
gem install term-ansicolorしてね
さあ試してみるよ..
% ruby russian_roulette.rb Charlie Fox Henry --- Welcome to Russian Roulette --- Today's fighters are: Charlie, Fox, Henry Let's go! Fox's turn: Nothing happened.. Charlie's turn: Nothing happened.. Henry's turn: Nothing happened.. Fox's turn: Nothing happened.. Charlie's turn: Bang! Charlie is dead. --- Game is over ---
あー
チャーリーが死んじゃった..
じゃあ今度は君と僕とで勝負しようよ!
まずは僕から
% ruby russian_roulette.rb me you --- Welcome to Russian Roulette --- Today's fighters are: you, me Let's go! me's turn: Nothing happened..
ふー セーフだったよ!
じゃあ今度は君
you's turn: Nothing happened..
おー よかったね!
次は僕の番
me's turn: Bang! me is dead. --- Game is over ---