第28回 Ruby/Rails勉強会@関西に参加

日本Rubyの会 公式Wiki - 第28回 Ruby/Rails勉強会@関西
http://jp.rubyist.net/?KansaiWorkshop28


自転車で京女の前の坂を汗だくになって登った上、ぎりぎりの到着でばたばたしてしまい、エラトステネスの嵐はちゃんと聴けなかった。

id:secondlifeさんのGitの解説は、これは使ってみたくなるなあ、という強力なもので、この日一気に信者を増やしたんじゃないだろうか。

branch切り替えとか異様に高速だったんですが、ファイルが大量にあってもやはり切り替え速いんだろうか。Subversionの場合、特にeclipseSubclipse使って作業したりすると逐一時間がかかって苛々しまくるのだが。それからTrac+Subversionの連携は便利だけど、Trac+Gitも良い感じに組み合わせられるのかな?とか、windowsで問題なく使えるようにGUIクライアントとかeclipseプラグインとか出ないと辛いな、とか、良く考えるとGitをメインに使うには色々なハードルが。

ちなみにGitは「ギット」なのか「ジット」なのかどっちなんだ?と思っていたんだけど、皆さん普通にギットと発音していたのでそうなのか?なんとなくジットかと思っていたのだけど。


初心者レッスンはirbの説明。irbをカスタマイズして使おうとか考えたことがなかったので、なるほどという感じで楽しめた。okkezさんのブログのエントリに設定ファイルが載っているので、とりあえずこれをそのまま頂いて使おう。

演習では、irbの結果表示を消す方法が分からずに苦労してしまった。一応conf.return_format = ""で消えたけど、こんなんでいいのか。あとはirb_context.echo = falseとか。

以下は計算100もどきの回答。なんかこの前の石取りゲームと似た感じになってしまうので、ステートはlambda使ってみたりした。rubyだったら、State/Strategyといったパターンは、クラス使う替わりにlambdaでやるのも良いかも。コード長いけど、表示とロジックは分離してないと気持ち悪くて仕方ないので、個人的にはこれで良し。include Observableするだけで一発でObserverが書けるrubyは最高。

require "observer"

QUESTION_NUMBER = 100

class UI
  def initialize(hundred_calc)
    @hundred_calc = hundred_calc
    @hundred_calc.add_observer(self)
    @response = Response.new(@hundred_calc)
    @hundred_calc.update(nil)
  end
  
  def update
    print @response.render
    exit if @hundred_calc.state.nil?
    @hundred_calc.update(gets.strip)
  end
end

class Response
  def initialize(hundred_calc)
    @calc = hundred_calc
  end

  def render
    send(@calc.result.to_s)
  end

  def greeting
    "計算 100 開始\n" + select_level
  end

  def select_level
    "難易度を選択してください。1:かんたん 2:むずかしい >"
  end

  def level
    (@calc.level == 1 ? "かんたん" : "むずかしい") +
      " を開始します。Press Enter >"
  end

  def first_question
    question
  end

  def correct_continue
    correct + question
  end

  def error_continue
    error + question
  end

  def correct_check_point
    correct + check_point + question
  end

  def error_check_point
    error + check_point + question
  end

  def correct_finish
    correct + summary
  end

  def error_finish
    error + summary
  end

  def correct
    "正解\n"
  end

  def error
    "不正解 (正解: #{@calc.answer(@calc.question_count - 1)})\n"
  end

  def question
    "#{@calc.question(@calc.question_count)} = "
  end

  def check_point
    "#{@calc.question_count} 問突破\n"
  end

  def summary
    m, s = (@calc.end_time.to_i - @calc.start_time.to_i).divmod(60)
    check_point +
      "#{QUESTION_NUMBER} 問終了しました\n" +
      "正解   : #{@calc.correct_count}\n" +
      "不正解 : #{@calc.error_count}\n" +
      "タイム : #{m}#{s}\n"
  end
end

class HundredCalc
  include Observable

  LEVELS = {1 => :easy, 2 => :difficult}

  attr_accessor :state, :input, :result, 
    :level, :correct_count, :error_count, :question_count, 
    :start_time, :end_time, :questions

  def initialize
    @level = nil
    @correct_count = 0
    @error_count = 0
    @question_count = 0
    @start_time = nil
    @end_time = nil
    @questions = []
    initialize_states
    @state = @initial_state
  end

  def update(input)
    @input = input
    @state.call
    changed
    notify_observers
  end

  def correct?(question_count, answer)
    @questions[question_count].correct?(answer)
  end
  
  def question(question_count)
    @questions[question_count].expression
  end

  def answer(question_count)
    @questions[question_count].answer
  end

private

  def create_question(question_count)
    @questions[question_count] = Question.new
  end

  def initialize_states
    @initial_state = lambda do
      @result = :greeting
      @state = @select_level_state
    end

    @select_level_state = lambda do
      if LEVELS.include?(@input.to_i)
        @level = @input.to_i
        Question.send(LEVELS[@level].to_s)
        @result = :level
        @state = @start_state
      else
        @result = :select_level
        @state = @select_level_state
      end
    end

    @start_state = lambda do
      (0...QUESTION_NUMBER).each {|i| create_question(i) }
      @question_count = 0
      @start_time = Time.now
      @result = :first_question
      @state = @question_state
    end

    @question_state = lambda do
      correct = @input =~ /\A-?\d+$/ && correct?(@question_count, @input.to_i)
      if correct
        @correct_count += 1
      else
        @error_count += 1
      end
      @question_count += 1
      if QUESTION_NUMBER <= @question_count
        @end_time = Time.now
        @result = correct ? :correct_finish : :error_finish
        @state = nil
      else
        if @question_count % 10 == 0
          @result = correct ? :correct_check_point : :error_check_point
        else
          @result = correct ? :correct_continue : :error_continue
        end
        @state = @question_state
      end
    end
  end
end

class Question
  @@expression_builder = nil

  def self.easy
    @@expression_builder = lambda do
      op = %w[+ - *][rand(3)]
      left = rand(10)
      right = op == "/" ? rand(9) + 1 : rand(10)
      "#{left} #{op} #{right}"
    end
  end

  def self.difficult
    @@expression_builder = lambda do
      op = %w[+ - * /][rand(4)]
      if op == "/"
        #除数が大きいとつまらないので
        right = rand < 0.9 ? rand(20) + 1 : rand(99) + 1
        left = right * (rand(100 / right) + 1)
      else
        right = rand(100)
        left = rand(100)
      end
      "#{left} #{op} #{right}"
    end
  end

  attr_reader :expression, :answer

  def initialize
    @expression = @@expression_builder.call
    @answer = eval(@expression)
  end

  def correct?(answer)
    @answer == answer
  end  
end

if $0 == __FILE__
  UI.new(HundredCalc.new)
end