# \#88 Chip-8

**URL:** <https://www.ruby-forum.com/t/88-chip-8/100601>\
**Category:** Ruby\
**Created:** [July 30, 2006, 7:17pm UTC](https://www.ruby-forum.com/t/88-chip-8/100601 "2006-07-30T19:17:49Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![tomrobinson](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/d78d45/32.png) [@tomrobinson](https://www.ruby-forum.com/u/tomrobinson)\
**Post date:** [July 30, 2006, 7:17pm UTC](https://www.ruby-forum.com/t/88-chip-8/100601/1 "2006-07-30T19:17:49Z")

</div>

Hi,

Here is my solution to the current quiz. It has a few flaws, and it  
uses a method call for each statement (which is probably quite slow).  
The nice thing is that you can dump a program and view it in an  
assembler-like way.  
The implementation is quite simple, I don’t think i need to explain it  
in detail. I hope i haven’t screwed up somewhere 🙂

Tom

---

<div class="post-metadata">

**Author:** ![tomrobinson](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/d78d45/32.png) [@tomrobinson](https://www.ruby-forum.com/u/tomrobinson)\
**Post date:** [July 31, 2006, 12:35am UTC](https://www.ruby-forum.com/t/88-chip-8/100601/2 "2006-07-31T00:35:54Z")

</div>

Tom R. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) writes:

> Hi,
> 
> Here is my solution to the current quiz. It has a few flaws, and it  
> uses a method call for each statement (which is probably quite slow).  
> The nice thing is that you can dump a program and view it in an  
> assembler-like way.  
> The implementation is quite simple, I don’t think i need to explain it  
> in detail. I hope i haven’t screwed up somewhere 🙂

Well, I just read my code again, and noticed that I did screw up. An  
instruction is  
2 byte, so  
def jmp addr  
@pc=addr/4-1 #each instruction has 4 byte. -1, because @pc gets  
incremented in main loop  
end  
should be  
def jmp addr  
@pc=addr/2-1 #each instruction has 2 byte. -1, because @pc gets  
incremented in main loop  
end

I hope next time I’ll notice such things earlier 🙂

Tom

---

<div class="post-metadata">

**Author:** ![tomrobinson](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/d78d45/32.png) [@tomrobinson](https://www.ruby-forum.com/u/tomrobinson)\
**Post date:** [July 31, 2006, 4:02pm UTC](https://www.ruby-forum.com/t/88-chip-8/100601/3 "2006-07-31T16:02:34Z")

</div>

On Jul 30, 2006, at 12:17 PM, Tom R. wrote:

> Hi,
> 
> Here is my solution to the current quiz. It has a few flaws, and it  
> uses a method call for each statement (which is probably quite slow).  
> The nice thing is that you can dump a program and view it in an  
> assembler-like way.  
> The implementation is quite simple, I don’t think i need to explain it  
> in detail. I hope i haven’t screwed up somewhere 🙂

Sure looks good to me. Here’s one trivial suggestion though:

> print “Program read: \n”  
> print “============= \n”  
> emu.dump\_prog  
> print “\nRunning… \n\n”  
> emu.run  
> print “Done! Registers: \n”  
> print “================ \n”  
> emu.dump

puts() will add a newline character, if it’s not already at the end  
of the String you pass to it, so you could lose a lot of \n’s above  
if you like.

James Edward G. II

---

<div class="post-metadata">

**Author:** ![tomrobinson](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/d78d45/32.png) [@tomrobinson](https://www.ruby-forum.com/u/tomrobinson)\
**Post date:** [July 31, 2006, 10:02pm UTC](https://www.ruby-forum.com/t/88-chip-8/100601/4 "2006-07-31T22:02:11Z")

</div>

James Edward G. II [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) writes:

> Sure looks good to me. Here’s one trivial suggestion though:

What bugs me a little bit is that the jmp-code isn’t quite right. If i  
read the spec right, it would be possible to jump to any byte in the  
source-file, even if it’s in the middle of an opcode. With my solution  
this is not possible. But I realized that after I finished it, and I  
didn’t want to rewrite it.

> > print “Program read: \n”  
> > you like.  
> > Thanks, and thanks that you manage this whole quiz-thing and do the  
> > summaries and everything.

> James Edward G. II

Tom

---

<div class="post-metadata">

**Author:** ![tomrobinson](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/d78d45/32.png) [@tomrobinson](https://www.ruby-forum.com/u/tomrobinson)\
**Post date:** [July 31, 2006, 11:05pm UTC](https://www.ruby-forum.com/t/88-chip-8/100601/5 "2006-07-31T23:05:50Z")

</div>

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
