From: James Edward Gray II Date: 2006-08-01T06:02:50+09:00 Subject: Re: [QUIZ SOLUTION] #88 Chip-8 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 Gray 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