There have been much better solutions, but for the sake of
completeness, here’s what I came up with while considering the quiz.
James Edward G. II
#!/usr/local/bin/ruby -w
require “enumerator”
require “forwardable”
module Kernel
module_function
def NNN(digits)
Integer(“0x#{digits.map { |d| d.to_s(16) }.join}”)
end
alias_method :KK, :NNN
end
class Chip8
extend Forwardable
MAX_REGISTER = 0b1111_1111
DEFAULT_HANDLER = [ Array.new,
lambda { |em, op| raise “Unknown Op: #
{op.inspect}.”} ]
def self.handlers
@@handlers ||= Array.new
end
def self.handle(*pattern, &handler)
handlers << [pattern, handler]
end
handle(1) { |em, op| em.head = NNN(op[-3…-1]) }
handle(3) { |em, op| em.skip if em[op[1]] == KK(op[-2…-1]) }
handle(6) { |em, op| em[op[1]] = KK(op[-2…-1]) }
handle(7) { |em, op| em[op[1]] += KK(op[-2…-1]) }
handle(8, nil, nil, 0) { |em, op| em[op[1]] = em[op[2]] }
handle(8, nil, nil, 1) { |em, op| em[op[1]] |= em[op[2]] }
handle(8, nil, nil, 2) { |em, op| em[op[1]] &= em[op[2]] }
handle(8, nil, nil, 3) { |em, op| em[op[1]] ^= em[op[2]] }
handle(8, nil, nil, 4) do |em, op|
em[op[1]] += em[op[2]]
em[op[1]], em[15] = em[op[1]] - MAX_REGISTER, 1 if em[op[1]] >
MAX_REGISTER
end
handle(8, nil, nil, 5) do |em, op|
em[op[1]] -= em[op[2]]
em[op[1]], em[15] = MAX_REGISTER + 1 + em[op[1]], 1 if em[op[1]]
< 0
end
handle(8, nil, nil, 6) do |em, op|
em[15], em[op[1]] = em[op[1]][0], em[op[1]] >> 1
end
handle(8, nil, nil, 7) do |em, op|
em[op[1]] = em[op[2]] - em[op[1]]
em[op[1]], em[15] = MAX_REGISTER + 1 + em[op[1]], 1 if em[op[1]]
< 0
end
handle(8, nil, 0, 14) do |em, op|
em[15], em[op[1]] = em[op[1]][7], em[op[1]] << 1
end
handle(12) { |em, op| em[op[1]] = rand(MAX_REGISTER) & KK(op
[-2…-1]) }
handle(0, 0, 0, 0) { exit }
def initialize(file)
@addresses = File.read(file).scan(/…/m)
@registers = Hash.new
@head = 0
end
attr_accessor :head
def_delegators :@registers, :[], :[]=
def run
while op_code = read_one_op_code
find_handler(op_code).call(self, op_code)
trim_registers
end
end
def read_one_op_code
if @head >= @addresses.size
nil
else
@head += 1
@addresses[@head - 1].unpack(“HXhHXh”).map { |nib| Integer(“0x#
{nib}”) }
end
end
alias_method :skip, :read_one_op_code
def to_s
@registers.inject(String.new) do |output, (key, value)|
output + “V#{key.to_s(16).upcase}:%08b\n” % value
end
end
private
def find_handler(op_code)
(self.class.handlers + [DEFAULT_HANDLER]).find do |pattern,
handler|
pattern.enum_for(:each_with_index).all? do |match, index|
match.nil? or match == op_code[index]
end
end.last
end
def trim_registers
@registers.each { |name, bits| @registers[name] = MAX_REGISTER &
bits }
end
end
if FILE == $PROGRAM_NAME
emulator = Chip8.new(“Chip8Test”)
at_exit { puts emulator }
emulator.run
end