Let me offer these two additions to Hans' interface: a CharPhonePad that 
uses JEGII's raw character input method, and removal of the input handling 
into an object (in this simple case, a class, but it could easily be an 
instance of a class).

Cheers,
Dave


# include everything above "if $0 == __FILE__" from Hans' file given in the 
quiz question.

class CharPhonePad < PhonePad
  def initialize
    super
    Thread.new do
      loop do
        case c = self.class.read_char
        when 3, 27  # Break on Escape and Ctrl+C
          break
        when 43  # convert '+' to '*' for ease of typing on numpad
          c = 42
        when 45, 46, 47  # convert '-', '.', '/' to '#' for ease of typing
          c = 35
        end
        notify_observers(c.chr)
      end
    end
  end
  def set_text(text, cursor)
    super(text, cursor)
    puts to_s
  end
  def to_s
    @text.dup.insert(@cursor, '_')
  end

  begin
    require "Win32API"
    def self.read_char
      c = Win32API.new("crtdll", "_getch", [], "L").Call
    end
  rescue LoadError
    def self.read_char
      system "stty raw -echo"
      STDIN.getc
    ensure
      system "stty -raw echo"
    end
  end
end

class NoOpTapHandler
  def process_event(ev)
    puts "#{ev} at #{ev.when}"
    p.set_text(p.text+ev.digit, p.cursor+1)
  end
end

if $0 == __FILE__
  p = CharPhonePad.new
  tap_handler = NoOpKeyProcessor

  p.register do |ev|
    tap_handler.process_event(ev)
  end

  Thread.list[0].join
end