Begin forwarded message:

> From: "Glen F. Pankow" <Glen.F.Pankow / noaa.gov>
> Date: July 31, 2006 12:15:35 PM CDT
> To: submission / rubyquiz.com
> Subject: Please Forward: Ruby Quiz Submission
>
> Dear sir:
>
> I thought that since this week's Ruby Quiz was very simple,
> I'd give it a go.  If reasonable, please forward this as a
> solution.
>
> I don't know much about the Quiz culture, so please let me
> know if I'm doing something verboten.  For one thing, it's
> over 500 lines long, much of it due to my verbose commenting
> style (actually, not as verbose as I would normally write it).
> The comments are plain -- I could try to rdoc them if that's
> preferred.  Please let me know if there's anything I can do
> to make it more appropriate for the Ruby community.  Thanks.
>
> Glen Pankow
> #! /usr/bin/ruby
> #
> #  chip8  --  framework for running a [limited] CHIP-8 program.
> #
> #  Usage:  chip8 [ -d ] [ <infile> ]
> #
> #  Run the program for the CHIP-8 raw-nibbles file <infile>.  If not
> #  specified, 'Chip8Text.txt' is used for it.  If the file doesn't  
> exist, the
> #  Ruby Quiz 88 test raw-nibbles are fudged for it.
> #
> #  'limited' here means the requirements for Ruby Quiz 88 are  
> supported.
> #
> #  Glen Pankow      07/29/06        Original version.
> #
>
>
> #
> # Register  --  class to embody the state of a [limited] CHIP-8  
> data register.
> #
> # This class method is supported:
> #
> #    register = Register.new(hexchar)  --  create a new Register  
> object for a
> #       hex character '0'..'9' or 'A'..'F'.
> #
> # These instance methods are supported:
> #
> #    name  --  [String] the name of the register (e.g., 'V3').
> #
> #    hexchar  --  [String] the hex character portion of the name  
> (e.g., '3').
> #
> #    value/value=  --  [Fixnum in range 0..255] the value held by  
> the register.
> #
> #    dump  --  print a representation of the register to the  
> standard output
> #       device.
> #
> class Register
>
>     attr_reader     :name, :hexchar
>     attr_accessor   :value
>
>     def initialize(hexchar)
>         @name, @hexchar, @value = 'V' + hexchar, hexchar, 0
> # @value = rand(256)
>     end
>
>     def dump
>         printf "register %s: %08b (%02x %3d)\n", @name, @value,  
> @value, @value
>     end
> end
>
>
> #
> # Instruction  --  classes to embody a disassembled [limited] CHIP-8
> #    instruction.
> #
> # This class method is supported:
> #
> #    instruction = Instruction.new(trace, proc, *args)  --  create  
> a new
> #       Instruction object.  trace is a human-readable form of the  
> original
> #       instruction nibbles, proc is a process that modifies its  
> master's
> #       state during execution, and args are any extra parameters  
> to be passed
> #       to proc.
> #
> # These instance methods are supported:
> #
> #    here/here=  --  [TrueClass/FalseClass] whether this is the next
> #       instruction to be executed (in debug mode).
> #
> #    execute  --  call the instruction's process on the configured  
> arguments.
> #
> #    dump  --  print a representation of the instruction to the  
> standard output
> #       device.
> #
> class Instruction
>
>     attr_accessor   :here
>
>     def initialize(trace, proc, *args)
>         @trace, @proc, @args, @here = trace, proc, args, false
>     end
>
>     def execute
>         @proc.call(*@args)
>     end
>
>     def dump
>         print((@here ? '--> ' : '    '), @trace, "\n")
>     end
> end
>
>
>
> #
> # Program  --  class to embody the construction and running of a  
> [limited]
> #    CHIP-8 program.
> #
> # This class method is supported:
> #
> #    program = new(infileName)  --  create a new Program object  
> from a raw-
> #       nibbles program file (or Ruby Quiz 88 data).
> #
> # These instance methods are supported:
> #
> #    run  --  step through the program from beginning to end.
> #
> #    debug  --  step through the program incrementally.
> #
> #    reset  --  explicitly set the state of the program to the  
> beginning
> #       (sort of  --  register contents are not reset).  You only  
> need call
> #       this if you're doing manualy stepping through the code.
> #
> #    step(print_trace = true)  --  execute the next instruction of  
> the program.
> #       An automatic call to reset is made if an exit instruction  
> was last
> #       executed.
> #
> #    running  --  [TrueClass/FalseClass] whether the program is  
> currently being
> #       stepped through (see the documentation to step() below).
> #
> #    dump(full = true)  --  print a representation of the program  
> to the
> #       standard output device.  If full = false, only the  
> registers are dumped.
> #
> class Program
>
>     attr_accessor :running, :program_counter
>     attr_reader :registers
>
>     def initialize(infileName)
>
>         #
>         # Set up the program registers.
>         #
>         @registers = [ ]            # the program registers
>         # (0..15).each { |i| @registers << Register.new(sprintf('% 
> X', i)) }
>         ('0'..'9').each { |hexchar| @registers << Register.new 
> (hexchar) }
>         ('A'..'F').each { |hexchar| @registers << Register.new 
> (hexchar) }
>
>         #
>         # Read in all the nibbles of the program.
>         #
>         # Fudge some input for Ruby Quiz 88 testing if no other  
> input is found.
>         #
>         @nibbles = [ ]          # the program stack (nibbles version)
>         @hexchars = [ ]         # the program stack (nibbles  
> hexchar version)
>         if (File.exists?(infileName))
>             infile = File.open(infileName)
>         else
>             require 'stringio'
>             infile = StringIO.new( \
>               "\x61\x77\x62\x45\x71\x01\x83\x20\x81\x21\x81\x22" \
>               "\x82\x33\x81\x34\x82\x35\x81\x06\x83\x27\x83\x0e" \
>               "\x64\xff\xc4\x11\x32\xbb\x10\x00\x00\x00")
>         end
>         @hexchars = infile.readlines.join.unpack('H*')[0].split(//)
>         @nibbles = @hexchars.collect { |hexchar| hexchar.hex }
>         infile.close
>
>         #
>         # Disassemble the nibbles into instructions.
>         #
>         @instructions = [ ]
>         raw_program_counter = 0
>         while (raw_program_counter < @nibbles.size)
>             @instructions << disassemble(raw_program_counter)
>             3.times { @instructions << nil }        # pad (see note  
> below)
>             raw_program_counter += 4
>         end
>         #
>         # Note:  for simplicity, we'll keep our instruction array  
> aligned with
>         # our raw bytes arrays, since the program counter is really  
> just an
>         # offsets in our arrays.
>         #
>
>         #
>         # Leave the freshly-disassembled program in a ready-to-run  
> state.
>         #
>         reset
>     end
>
>
>     #
>     # instruction = disassemble(raw_program_counter)
>     #
>     # Parse the next four raw nibbles and their character  
> equivalents from
>     # @nibbles and @hexchars offset by raw_program_counter; convert  
> them into
>     # a new Instruction object.  An exception is raised on any  
> parsing error.
>     #
>     # We call it raw_program_counter because we're not emulating  
> any run-time
>     # activity here (we're just stepping through it assuming a strict
>     # correlation between four raw nibbles and one instruction),  
> and we don't
>     # want to confuse this with the accessors to the run-time  
> program counter
>     # @program_counter.
>     #
>     def disassemble(raw_program_counter)
>
>         case @hexchars[raw_program_counter]
>         when nil
>             trace = sprintf("%03x:  0000 Abnormal exit!",  
> raw_program_counter)
>             proc = lambda { @running = false }
>             Instruction.new(trace, proc)
>         when '0'
>             trace = sprintf("%03x:  0000 Exit", raw_program_counter)
>             proc = lambda { @running = false }
>             Instruction.new(trace, proc)
>         when '1'
>             address = get__NNN(raw_program_counter)
>             trace = sprintf("%03x:  1%03x Jump to the address %03x  
> of the file",
>               raw_program_counter, address, address)
>             proc = lambda { |address| @program_counter = _address }
>             Instruction.new(trace, proc, address)
>         when '3'
>             register, value = get__XKK(raw_program_counter)
>             trace = sprintf( \
>               "%03x:  3%s%02x Skip next instruction if V%s == %02x",
>               raw_program_counter, register.hexchar, value,
>               register.hexchar, value)
>             proc = lambda do |_register, _value|
>                 @program_counter += 4
>                 @program_counter += 4 if (_register.value == _value)
>             end
>             Instruction.new(trace, proc, register, value)
>         when '6'
>             register, value = get__XKK(raw_program_counter)
>             trace = sprintf("%03x:  6%s%02x V%s = %02x",  
> raw_program_counter,
>               register.hexchar, value, register.hexchar, value)
>             proc = lambda do |_register, _value|
>                 _register.value = _value
>                 @program_counter += 4
>             end
>             Instruction.new(trace, proc, register, value)
>         when '7'
>             register, value = get__XKK(raw_program_counter)
>             trace = sprintf("%03x:  7%s%02x V%s = V%s + %02x",
>               raw_program_counter, register.hexchar, value,  
> register.hexchar,
>               register.hexchar, value)
>             proc = lambda do |_register, _value|
>                 newValue = _register.value + _value
>                 if (newValue > 0x00ff)
>                     _register.value = newValue & 0x00ff
>                     @registers[15].value = 1
>                 else
>                     _register.value = newValue
>                     @registers[15].value = 0
>                 end
>                 @program_counter += 4
>             end
>             Instruction.new(trace, proc, register, value)
>         when '8'
>             register1, register2, type = get__XYn(raw_program_counter)
>             case type
>             when '0'
>                 trace = sprintf("%03x:  8%s%s0 V%s = V%s",  
> raw_program_counter,
>                   register1.hexchar, register2.hexchar,
>                   register1.hexchar, register2.hexchar)
>                 proc = lambda do |_register1, _register2|
>                     _register1.value = _register2.value
>                     @program_counter += 4
>                 end
>                 Instruction.new(trace, proc, register1, register2)
>             when '1'
>                 trace = sprintf("%03x:  8%s%s1 V%s = V%s OR V%s",
>                   raw_program_counter, register1.hexchar,  
> register2.hexchar,
>                   register1.hexchar, register1.hexchar,  
> register2.hexchar)
>                 proc = lambda do |_register1, _register2|
>                     _register1.value |= _register2.value
>                     @program_counter += 4
>                 end
>                 Instruction.new(trace, proc, register1, register2)
>             when '2'
>                 trace = sprintf("%03x:  8%s%s2 V%s = V%s AND V%s",
>                   raw_program_counter, register1.hexchar,  
> register2.hexchar,
>                   register1.hexchar, register1.hexchar,  
> register2.hexchar)
>                 proc = lambda do |_register1, _register2|
>                     _register1.value &= _register2.value
>                     @program_counter += 4
>                 end
>                 Instruction.new(trace, proc, register1, register2)
>             when '3'
>                 trace = sprintf("%03x:  8%s%s3 V%s = V%s XOR V%s",
>                   raw_program_counter, register1.hexchar,  
> register2.hexchar,
>                   register1.hexchar, register1.hexchar,  
> register2.hexchar)
>                 proc = lambda do |_register1, _register2|
>                     _register1.value ^= _register2.value
>                     @program_counter += 4
>                 end
>                 Instruction.new(trace, proc, register1, register2)
>             when '4'
>                 trace = sprintf("%03x:  8%s%s4 V%s = V%s + V%s",
>                   raw_program_counter, register1.hexchar,  
> register2.hexchar,
>                   register1.hexchar, register1.hexchar,  
> register2.hexchar)
>                 proc = lambda do |_register1, _register2|
>                     newValue = _register1.value + _register2.value
>                     if (newValue > 0x00ff)
>                         _register1.value = newValue & 0x00ff
>                         @registers[15].value = 1
>                     else
>                         _register1.value = newValue
>                         @registers[15].value = 0
>                     end
>                     @program_counter += 4
>                 end
>                 Instruction.new(trace, proc, register1, register2)
>             when '5'
>                 trace = sprintf("%03x:  8%s%s5 V%s = V%s - V%s",
>                   raw_program_counter, register1.hexchar,  
> register2.hexchar,
>                   register1.hexchar, register1.hexchar,  
> register2.hexchar)
>                 proc = lambda do |_register1, _register2|
>                     if (_register1.value >= _register2.value)
>                         _register1.value -= _register2.value
>                         @registers[15].value = 1
>                     else
>                         _register1.value += 0x0100 - _register2.value
>                         @registers[15].value = 0
>                     end
>                     @program_counter += 4
>                 end
>                 Instruction.new(trace, proc, register1, register2)
>             when '6'
>                 trace = sprintf("%03x:  8%s06 V%s = V%s SHIFT RIGHT  
> 1",
>                   raw_program_counter,
>                   register1.hexchar, register1.hexchar,  
> register1.hexchar)
>                 proc = lambda do |_register|
>                     @registers[15].value = _register.value & 0x0001
>                     _register.value >>= 1
>                     @program_counter += 4
>                 end
>                 Instruction.new(trace, proc, register1)
>             when '7'
>                 trace = sprintf("%03x:  8%s%s7 V%s = V%s - V%s",
>                   raw_program_counter, register1.hexchar,  
> register2.hexchar,
>                   register1.hexchar, register2.hexchar,  
> register1.hexchar)
>                 proc = lambda do |_register1, _register2|
>                     if (_register2.value >= _register1.value)
>                         _register1.value = _register2.value -  
> _register1.value
>                         @registers[15].value = 1
>                     else
>                         _register1.value \
>                           = _register2.value + 0x0100 -  
> _register1.value
>                         @registers[15].value = 0
>                     end
>                     @program_counter += 4
>                 end
>                 Instruction.new(trace, proc, register1, register2)
>             when 'e'
>                 trace = sprintf("%03x:  8%s0e V%s = V%s SHIFT LEFT 1",
>                   raw_program_counter,
>                   register1.hexchar, register1.hexchar,  
> register1.hexchar)
>                 proc = lambda do |_register|
>                     @registers[15].value = _register.value & 0x0080
>                     _register.value <<= 1
>                     _register.value &= 0x00ff
>                     @program_counter += 4
>                 end
>                 Instruction.new(trace, proc, register1)
>             else
>                 raise ArgumentError,
>                   sprintf("Invalid instruction 8%s%s%s at %03x",
>                     @hexchars[raw_program_counter + 1],
>                     @hexchars[raw_program_counter + 2],
>                     @hexchars[raw_program_counter + 3],  
> raw_program_counter)
>             end
>         when 'c'
>             register, value = get__XKK(raw_program_counter)
>             trace = sprintf("%03x:  C%s%02x V%s = Random number AND  
> %02x",
>               raw_program_counter,
>               register.hexchar, value, register.hexchar, value)
>             proc = lambda do |_register, _value|
>                 _register.value = rand(256) & value
>                 @program_counter += 4
>             end
>             Instruction.new(trace, proc, register, value)
>         else
>             raise ArgumentError,
>               sprintf("Invalid instruction %s%s%s%s at %03x",
>                 @hexchars[raw_program_counter],
>                 @hexchars[raw_program_counter + 1],
>                 @hexchars[raw_program_counter + 2],
>                 @hexchars[raw_program_counter + 3],  
> raw_program_counter)
>         end
>     end
>     protected :disassemble
>
>     #
>     # address = get_NNN(program_counter)  --  return the 3-nibble  
> address
>     #    literal value starting at program_counter + 1
>     #
>     def get__NNN(program_counter)
>         @nibbles[program_counter + 1] << 8 \
>           | @nibbles[program_counter + 2] << 4 \
>           | @nibbles[program_counter + 3]
>     end
>
>     #
>     # register, value = get__XKK(program_counter)  -- return the  
> register and
>     #    2-nibble literal value starting at program_counter + 1
>     #
>     def get__XKK(program_counter)
>         return get__X__(program_counter), get___KK(program_counter)
>     end
>
>     #
>     # register = get__X__(program_counter)  --  return the register at
>     #    program_counter + 1
>     #
>     def get__X__(program_counter)
>         @registers[@nibbles[program_counter + 1]]
>     end
>
>     #
>     # value = get___KK(program_counter)  --  return the two-nibble  
> literal
>     #    value starting at program_counter + 2
>     #
>     def get___KK(program_counter)
>         @nibbles[program_counter + 2] << 4 | @nibbles 
> [program_counter + 3]
>     end
>
>     #
>     # register1, register2, type = get__XYn(program_counter)  --   
> return the
>     #    registers and selector value starting at program_counter + 1
>     #
>     def get__XYn(program_counter)
>         return @registers[@nibbles[program_counter + 1]],
>           @registers[@nibbles[program_counter + 2]],
>           @hexchars[program_counter + 3]
>     end
>     protected :get__NNN, :get__XKK, :get__X__, :get___KK, :get__XYn
>
>
>     #
>     # Reset the program so that the next execution step occurs at  
> the start of
>     # the program.  Note that the registers are not cleared.
>     #
>     def reset
>         @program_counter = 0
>         @running = true
>     end
>
>
>     #
>     # run  --  run the program from beginning to end.
>     #
>     def run
>         reset
>         while (@running)
>             step
>         end
>     end
>
>
>     #
>     # debug  --  step through the program incrementally (also from  
> beginning to
>     #    end).
>     #
>     def debug
>         reset
>         while (@running)
>             instruction = @instructions[@program_counter]
>             instruction.here = true
>             @registers.each do |register|
> next unless (((register.hexchar >= '1') && (register.hexchar <=  
> '4')) \
>   || (register.hexchar == 'F'))
> # ignore V5..VE for the quiz
>                 register.dump
>             end
>             @instructions.each do |inst|
>                 next if (inst.nil?)     # skip padding
>                 inst.dump
>             end
>             print "Hit <enter> to execute the next instruction: "
>             $stdout.flush
>             dummy = $stdin.gets
>             instruction.here = false
>             puts
>             step
>         end
>     end
>
>
>     #
>     # step(print_trace = true)
>     #
>     # Execute the next instruction (the instruction at the current  
> program
>     # counter, which is moved to the next instruction on exit).  If  
> print_trace
>     # is true, a human-readable form of the instruction is printed.
>     #
>     # Also, running past the end of the instructions is treated  
> like an exit.
>     #
>     def step(print_trace = true)
>         reset unless (@running)
>         instruction = @instructions[@program_counter]
>         if (instruction.nil?)
>             printf("%03x:  ---- Premature EOF -- exit!\n",  
> @program_counter) \
>               if (print_trace)
>             @running = false
>             return
>         end
>         instruction.dump if (print_trace)
>         instruction.execute
>     end
>
>
>     def dump(full = true)
>         @registers.each do |register|
> next unless (((register.hexchar >= '1') && (register.hexchar <=  
> '4')) \
>   || (register.hexchar == 'F'))
> # ignore V5..VE for the quiz
>             register.dump
>         end
>         return unless (full)
>         printf "program_counter: %03x\n", @program_counter
>         (0... / nibbles.size).each do |i|
>             printf "%03x:  nibble = 0x%02x, hexchar = '%s'\n",
>               i, @nibbles[i], @hexchars[i]
>         end
>     end
> end
>
>
> #
> # Go for it!
> #
> do_debug = false
> infile = 'Chip8Text.txt'
> ARGV.each do |arg|
>     if (arg == '-d')
>         do_debug = true
>     else
>         infile = arg
>     end
> end
> program = Program.new(infile)
> if (do_debug)
>     program.debug
> else
>     program.run
>     program.dump(false)
> end