Skip to content

Instantly share code, notes, and snippets.

@ngsw-taro
Last active December 11, 2015 08:08
Show Gist options
  • Save ngsw-taro/4570923 to your computer and use it in GitHub Desktop.
Save ngsw-taro/4570923 to your computer and use it in GitHub Desktop.
Rubyの学習のため、ヒット&ブローを作ってみた。 Rubyよくわからないので、指摘しまくってください。
# ヒット&ブローを表すクラス。
# このクラスのオブジェクトはイミュータブル。
class HitAndBlow
NUMBERS_LENGTH = 4
NUMBER_RANGE = (1..9)
# 指定された配列が、正解の数の形式として妥当か判定する。
# 数の重複がなく、所定の範囲ないの値を持ち、長さがNUMBERS_LENGTHの配列ならばtrueを、それ以外ならfalseを返す。
def self.valid_numbers? submitted_numbers
submitted_numbers.uniq.size == NUMBERS_LENGTH &&
submitted_numbers.all? do |i|
NUMBER_RANGE === i
end
end
# 引数に指定された配列を、正解の数とする。
# 引数が省略された場合、ランダムな数を設定する。
# 正解の数として妥当でない値が指定された場合、ArgumentErrorを起こす。
def initialize(numbers = NUMBER_RANGE.to_a.shuffle.shuffle.take(NUMBERS_LENGTH))
raise ArgumentError if not HitAndBlow.valid_numbers? numbers
@numbers = numbers
@count = 0
end
# 回答の試行回数を表す
attr_reader :count
# 正解の数を表す
def numbers
@numbers.dup
end
# 回答をする。
# 引数に指定した配列と、正解の数を比較し、ヒントとして
# ヒット数とブロー数をハッシュで返す。
# ヒット数のキーは :hit, ブロー数のキーは :blow である。
# 回答として指定された配列の形式が妥当でない場合、ArgumentErrorを起こす。
def submit(submitted_numbers)
raise ArgumentError if not HitAndBlow.valid_numbers? submitted_numbers
@count += 1
hit = count_hit submitted_numbers
blow = count_blow submitted_numbers
{:hit => hit, :blow => blow}
end
private
# 指定された配列について、ヒット数を返す。
def count_hit(submitted_numbers)
submitted_numbers.zip(@numbers).select do |num1, num2|
num1 == num2
end.count
end
# 指定された配列について、ブロー数を返す。
def count_blow(submitted_numbers)
(submitted_numbers & @numbers).count - count_hit(submitted_numbers)
end
end
require "../src/hit_and_blow"
require "rspec"
describe HitAndBlow do
describe "初期化する場合" do
it "4桁以外の配列を引数に渡すと例外を投げること" do
proc do
HitAndBlow.new [1, 2, 3]
end.should raise_error(ArgumentError)
proc do
HitAndBlow.new [1, 2, 3, 4, 5]
end.should raise_error(ArgumentError)
end
it "重複する数が含まれる配列を引数に渡すと例外を投げること" do
proc do
HitAndBlow.new [1, 1, 2, 3]
end.should raise_error(ArgumentError)
proc do
HitAndBlow.new [9, 8, 7, 9]
end.should raise_error(ArgumentError)
end
it "範囲外の数を含む配列を引数に渡すと例外を投げること" do
proc do
HitAndBlow.new [0, 1, 2, 3]
end.should raise_error(ArgumentError)
proc do
HitAndBlow.new [7, 8, 9, 10]
end.should raise_error(ArgumentError)
end
it "引数に渡した配列が正解の数となること" do
HitAndBlow.new([3, 2, 5, 6]).numbers.should == [3, 2, 5, 6]
end
it "newに引数を指定しなくても_妥当な正解の数が設定されること" do
HitAndBlow.new.numbers.uniq.should have(4).item
end
end
describe "正しく初期化された場合" do
# スコープを絞るため
hab = nil
before do
hab = HitAndBlow.new [1, 2, 3, 4]
end
it "正解の数は変更不可であること" do
hab.numbers[0] = 9
hab.numbers[0].should == 1
end
it "直後の回答回数は0回であること" do
hab.count.should == 0
end
end
describe "回答する場合" do
hab = HitAndBlow.new
it "4桁以外の配列で回答すると例外を投げること" do
proc do
hab.submit [1, 2, 3]
end.should raise_error ArgumentError
proc do
hab.submit [1, 2, 3, 4, 5]
end.should raise_error ArgumentError
end
it "重複する数が含まれる配列で回答すると例外を投げること" do
proc do
hab.submit [6, 4, 2, 4]
end.should raise_error ArgumentError
proc do
hab.submit [1, 9, 1, 1]
end.should raise_error ArgumentError
end
it "指定外の範囲を含む配列で回答すると例外を投げること" do
proc do
hab.submit [0, 1, 2, 3]
end.should raise_error ArgumentError
proc do
hab.submit [7, 8, 9, 10]
end.should raise_error ArgumentError
end
it "4桁の重複しない、所定の範囲内の値をもった配列で回答するとヒントを返すこと" do
hab.submit([1, 2, 3, 4]).should be_an_instance_of Hash
end
end
describe "正しく回答した場合" do
it "ヒントとして正しいヒット数を返すこと" do
hab = HitAndBlow.new [1, 2, 3, 4]
hab.submit([4, 3, 2, 1])[:hit].should == 0
hab.submit([9, 8, 7, 6])[:hit].should == 0
hab.submit([1, 8, 5, 7])[:hit].should == 1
hab.submit([6, 2, 3, 5])[:hit].should == 2
hab.submit([9, 2, 3, 4])[:hit].should == 3
hab.submit([1, 2, 3, 4])[:hit].should == 4
end
it "ヒントとして正しいブロー数を返すこと" do
hab = HitAndBlow.new [1, 2, 3, 4]
hab.submit([4, 3, 2, 1])[:blow].should == 4
hab.submit([9, 8, 7, 6])[:blow].should == 0
hab.submit([1, 8, 5, 7])[:blow].should == 0
hab.submit([6, 2, 3, 5])[:blow].should == 0
hab.submit([9, 2, 3, 4])[:blow].should == 0
hab.submit([1, 2, 3, 4])[:blow].should == 0
hab.submit([2, 3, 4, 5])[:blow].should == 3
hab.submit([3, 8, 6, 9])[:blow].should == 1
end
it "正しい回答回数を取得できること" do
hab = HitAndBlow.new
hab.submit [1, 2, 3, 4]
hab.count.should == 1
hab.submit [1, 2, 3, 4]
hab.count.should == 2
hab.submit [1, 2, 3, 4]
hab.count.should == 3
end
end
describe "クラスメソッド valid_number?" do
it "4桁以外の配列はinvalidであること" do
HitAndBlow.valid_numbers?([1, 2, 3]).should be_false
HitAndBlow.valid_numbers?([1, 2, 3, 4, 5]).should be_false
end
it "重複する数が含まれる配列はinvalidであること" do
HitAndBlow.valid_numbers?([6, 4, 2, 4]).should be_false
HitAndBlow.valid_numbers?([1, 9, 1, 1]).should be_false
end
it "指定外の範囲を含む配列はinvalidであること" do
HitAndBlow.valid_numbers?([0, 1, 2, 3]).should be_false
HitAndBlow.valid_numbers?([7, 8, 9, 10]).should be_false
end
it "4桁の重複しない、所定の範囲内の値をもった配列はvalidであること" do
HitAndBlow.valid_numbers?([1, 2, 3, 4]).should be_true
end
end
end
# ヒット&ブローをCUIで遊べるプログラム
require "hit_and_blow"
def get_count(count)
case count
when 1 then "once"
when 2 then "twice"
else "#{count} times"
end
end
HIT_AND_BLOW = HitAndBlow.new
loop do
print "> "
answer = STDIN.gets.strip.split(//).map do |c|
c.to_i
end
if not HitAndBlow.valid_numbers? answer
puts "Please input 4 digit number. It should not contain 0."
redo
end
hint = HIT_AND_BLOW.submit answer
if hint[:hit] == 4
puts "Congratulations!! That is correct!"
puts "You answered #{get_count HIT_AND_BLOW.count}."
break
end
puts "HIT: #{hint[:hit]}, BLOW: #{hint[:blow]}"
end
@daneko
Copy link

daneko commented Jan 21, 2013

eachの中でeachをあまりしたくないので、俺ならこう書くかな〜

# 指定された配列について、ブロー数を返す。
# ブローは積集合から正解個数を引いたものとなる
def count_blow(submitted_numbers)
  (submitted_numbers & @numbers).count - count_hit(submitted_numbers)
end

あと好みで…w

# 指定された配列について、ヒット数を返す。
def count_hit(submitted_numbers)
  submitted_numbers.zip(@numbers).select{|num1,num2| num1 == num2}.count
end

@anekos
Copy link

anekos commented Jan 24, 2013

気になったところが同じだったので、細かすぎるところを…

def HitAndBlow.valid_numbers? 

def self.valid_numbers?

って書きますね。自分のばやい。

あと、case と when のインデント位置は揃えるのが一般的な気がします。
そういう自分は、ブロック引数の書き方が一般的じゃないです。

@daneko
Copy link

daneko commented Jan 24, 2013

L47とか

before {@hab = HitAndBlow.new [1, 2, 3, 4]}

とか?

まず無いだろうけど L54 の前に別のテストとかで hab.submit ... って呼ぶとあわわわ


以下こんな書き方あるとか
HitAndBlow.new.numbers.uniq.should have(4).items // L43

hab.submit([1, 2, 3, 4]).should be_an_instance_of(Hash) // L93

かきかたいぱーい

自分も正直そんな詳しくないので、「こう書け!!」とはいえないけどw

@ngsw-taro
Copy link
Author

ありがとうございます!
アドバイス通りに修正してみました!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment