継続を試す
先日のRuby勉強会の演習のソースを少し修正しました。せっかく「irb の演習」がお題でしたので、calccによる「継続」の練習です。ホントは、Marshal使ってセーブまでしようと思ったのですが、ContinuationオブジェクトがMarshalできない、ということでしたので、そちらはやめました。たぶん、1.8/1.9の両方で動くはずです。
あと、NetBeansって、プロジェクトごとにエンコーディングを変えられるんですね。もちろん、windows-31j に変更です。
#! ruby -Ks # -*- encoding: Shift_JIS -*- require 'enumerator' require 'readline' require 'continuation' unless defined? Continuation class Array # ruby 1.8用 def choise_one self[rand(size)] end end module CalcHundred # 全問題数 MAX_N_QUESTIONS = 100 # 10問ずつ区切る N_FRACTION = 10 # かんたんモード MODE_EASY = 1 # むずかしいモード MODE_HARD = 2 # モードの一覧 MODES = [ MODE_EASY, MODE_HARD ] # モードの表示内容 MODE_NAMES = { MODE_EASY => "#{MODE_EASY}:かんたん", MODE_HARD => "#{MODE_HARD}:むずかしい" } # 演算子の集合 OPS ={ MODE_EASY => %w(+ - *), MODE_HARD => %w(+ - * /) } # オペランドの最大値 MAX_VALUE = { MODE_EASY => 10, MODE_HARD => 100 } class ExitException < StandardError; end class Controller def initialize(max_n_q = nil) @n_q = max_n_q || MAX_N_QUESTIONS end def setup @elapsed = 0 @n_correct = 0 @n_wrong = 0 end # mode を呼び出し時に設定することを可能にしています。 def select_mode(mode = nil) puts "計算 #{@n_q} 開始" @mode = mode ? mode : get_mode puts end def get_mode case Readline.readline "難易度を選択してください。 #{MODE_NAMES.values.sort.join(' ')} > " when /\A\s*([#{MODES}])\s*\Z/ $1.to_i when nil raise ExitException, "EOFが入力されました。" else get_mode end end def start setup Readline.readline("#{MODE_NAMES[@mode]} を開始します。 Press Enter > ") @time_start = Time.now srand(@time_start.to_i) (1..@n_q).each_slice(N_FRACTION) { |poses_q| poses_q.each do q = Question.new(@mode, OPS[@mode].choise_one) $DEBUG ? sample_answer(q) : answer_by_user(q) end puts "#{poses_q.last} 問突破" if poses_q.last.modulo(N_FRACTION) == 0 } @elapsed += Time.now - @time_start puts '', "#{@n_q} 問終了しました。" callcc { |cc| @cc = cc } end def sample_answer(q) print q sleep 0.2 rand(2) == 0 ? (puts q.answer; correct) : (puts; incorrect) end def answer_by_user(q) callcc { |cc| @cc = cc } ans = Readline.readline(q.to_s) suspend('中断します') if ans.nil? q.check_answer(ans) ? correct : incorrect end def correct puts "正解" @n_correct += 1 end def incorrect puts "不正解" @n_wrong += 1 end def summary s = [] s << "正解 : %d 問" % @n_correct s << "不正解: %d 問" % @n_wrong s << "タイム: %s" % elapsed_time puts s end def elapsed_time "%d 分 %d 秒" % @elapsed.divmod(60) end def suspend(mes) puts mes @elapsed += Time.now - @time_start summary if /y/i =~ Readline.readline("再開しますか? [yN]: ") resume else raise ExitException, "irbの場合、 ``_.resume'' で復帰できます" end end def resume @time_start = Time.now @cc.call end def exit(mes=nil) puts( mes ||= "終了します" ) end def main select_mode start summary rescue ExitException => e exit(e) ensure return self end end class Question def initialize(mode, op) @mode = mode @op = op mk_operands end def mk_operands @v1, @v2 = Array.new(2).map{ rand(MAX_VALUE[@mode]) } mk_operands if @op == '/' and (@v2 == 0 or @v1.modulo(@v2) != 0) end def to_s "%d %s %d = " % [@v1, @op, @v2] end def check_answer(ans) return false if /\A\s*\Z/ =~ ans.to_s ans.to_i == answer end def answer @ans ||= eval("#{@v1} #{@op} #{@v2}") end end def self.main(max = nil) max = nil if max.to_i <= 0 Controller.new(max).main end end if $0 == __FILE__ max = ARGV.shift.to_i CalcHundred.main(max) end
実行結果はこんな感じです。
irb(main):002:0> require'calc_hundred' => true irb(main):003:0> CalcHundred.main 15 <- 15問を選択 計算 15 開始 難易度を選択してください。 1:かんたん 2:むずかしい > 1 1:かんたん を開始します。 Press Enter > 9 + 9 = 18 正解 5 - 6 = 不正解 8 * 9 = 72 正解 5 + 3 = 不正解 9 * 1 = 9 正解 3 * 3 = 中断します <- C-d入力 正解 : 3 問 不正解: 2 問 タイム: 0 分 6 秒 再開しますか? [yN]: y <- 継続する 3 * 3 = 9 正解 1 + 0 = 1 正解 0 + 8 = 8 正解 4 + 8 = 12 正解 3 + 2 = 5 正解 10 問突破 2 + 1 = 3 正解 5 + 0 = 中断します 正解 : 9 問 不正解: 2 問 タイム: 0 分 15 秒 再開しますか? [yN]: n <- 継続しない irbの場合、 ``_.resume'' で復帰できます => #<CalcHundred::Controller:0x1b60278 @n_q=15, @mode=1, @elapsed=15.692, @n_correct=9, @n_wrong=2, @time_start=2008-08-03 15:48:18 +0900, @cc=#<Continuation:0x1bfddfc>> irb(main):004:0> _.resume <- 復旧する 5 + 0 = 5 正解 9 + 4 = 13 正解 0 + 8 = 不正解 1 + 4 = 5 正解 15 問終了しました。 正解 : 12 問 不正解: 3 問 タイム: 0 分 20 秒 => #<CalcHundred::Controller:0x1b60278 @n_q=15, @mode=1, @elapsed=20.036, @n_correct=12, @n_wrong=3, @time_start=2008-08-03 15:48:32 +0900, @cc=#<Continuation:0x1bfb494>>