On Sep 25, 2005, at 2:55 PM, Gavin Kistner wrote:

> My solution follows.

My solution is below.  Here are my random thoughts about it:

1.  My code requires Glenn Parker's Ruby Quiz #25 solution.  I did  
make one minor change to it, which was to wrap the quiz specific code  
in a:

if __FILE__ == $0
     # ...
end

That allowed me to use it as a library.
2.  I did the first two extra credits, but couldn't think of good  
systems for the last two.
3.  I'm in a "Write less code" kick this week, so I really tried to  
pack a lot of punch into minimal code.  I'm happy with the results.

Here's the code:

#!/usr/local/bin/ruby -w

require "erb"

# Glenn Parker's code from Ruby Quiz 25...
require "english_numerals"
class Integer
     alias_method :to_en, :to_english
end

class Array
     def insert_at_nil( obj )
         if i = index(nil)
             self[i] = obj
             i
         else
             self << obj
             size - 1
         end
     end
end

module MathCaptcha
     @@captchas = Array.new
     @@answers  = Array.new

     def self.add_captcha( template, &validator )
         @@captchas << Array[template, validator]
     end

     def self.create_question
         raise "No captchas loaded." if @@captchas.empty?

         captcha = @@captchas[rand(@@captchas.size)]

         args = Array.new
         class << args
             def arg( value )
                 push(value)
                 value
             end

             def resolve( template )
                 ERB.new(template).result(binding)
             end
         end
         question = args.resolve(captcha.first)
         index    = @@answers.insert_at_nil(Array[captcha.first, *args])

         Hash[:question  => question, :answer_id => index]
     end

     def self.check_answer( answer )
         raise "Answer id required." unless answer.include? :answer_id

         template, *args = @@answers[answer[:answer_id]]
         raise "Answer not found." if template.nil?

         validator = @@captchas.assoc(template).last
         raise "Unable to match captcha." if validator.nil?

         if validator[answer[:answer], *args]
             @@answers[answer[:answer_id]] = nil
             true
         else
             false
         end
     end

     def self.load_answers( file )
         @@answers = File.open(file) { |answers| Marshal.load(answers) }
     end

     def self.load_captchas( file )
         code = File.read(file)
         eval(code, binding)
     end

     def self.save_answers( file )
         File.open(file, "w") { |answers| Marshal.dump(@@answers,  
answers) }
     end
end

if __FILE__ == $0
     captchas = File.join(ENV["HOME"], ".math_captchas")
     unless File.exists? captchas
         File.open(captchas, "w") { |file| file << DATA.read }
     end
     MathCaptcha.load_captchas(captchas)

     answers = File.join(ENV["HOME"], ".math_captcha_answers")
     MathCaptcha.load_answers(answers) if File.exists? answers

     if ARGV.empty?
         question = MathCaptcha.create_question
         puts "#{question[:answer_id]} : #{question[:question]}"
     else
         args = Hash.new
         while ARGV.size >= 2 and ARGV.first =~ /^--\w+$/
             key       = ARGV.shift[2..-1].to_sym
             value     = ARGV.first =~ /^\d+$/ ? ARGV.shift.to_i :  
ARGV.shift
             args[key] = value
         end

         answer = MathCaptcha.check_answer(args)
         puts answer
     end

     END { MathCaptcha.save_answers(answers) }
end

__END__
add_captcha(
     "<%= arg(rand(10)).to_en.capitalize %> plus <%= arg(2).to_en %>?"
) do |answer, *opers|
     if answer.is_a?(String) and answer =~ /^\d+$/
         answer = answer.to_i.to_en
     elsif answer.is_a?(Integer)
         answer = answer.to_en
     end
     answer == opers.inject { |sum, var| sum + var }.to_en
end

__END__

James Edward Gray II