I implemented pretty much all the opcodes and used Tk to display the
graphics as curses didn't work on my machine.
It can run the games from the site James posted
(http://www.pdc.kth.se/%7Elfo/chip8/CHIP8/GAMES/).
I used one Win32 function (GetKeyState) as I didn't know a compatible
way to check if a key is pressed.

Controls: q for quit, normal controls mapped just like on the site
with the explanation of the opcodes.

I also put the code on Pastie, as Gmail will probably mess up my code
(again):  http://pastie.caboo.se/6634



require 'enumerator'
require 'tk'

require 'Win32API'
keystate = Win32API.new("user32", "GetKeyState", 'I', 'I')
# virtual keycodes, mapping keys like
http://www.pdc.kth.se/%7Elfo/chip8/CHIP8.htm does
VK = {'q' => 81, 0 => 110,  1 => 103, 2 => 104, 3 => 105,  4 => 100, 5
=> 101, 6 => 102, 7 => 97, 8 => 98, 9 => 99, 0xA => 96, 0xB => 13, 0xC
=> 111, 0xD => 106, 0xE => 109, 0xF => 107}

$key_pressed = proc{|key|
  keystate.call(VK[key]) & ~1 != 0  # higher bit set  = key currently pressed
}


class Integer
  def lo;  self & 0xF    end    # low nibble of byte
  def hi; (self >> 4).lo end    # high nibble of byte
  def bcd_byte
    [self / 100, (self/10) % 10, self % 10].map{|n| n.chr}.join
  end
end

class Chip8Emulator
  COLORS = ['#000000','#ffffff']
  FONT_TO_BIN = {' ' => 0,'#' => 1}
  FONT = ["####   #  #### #### #  # #### #### #### #### ####  ##  ###
 ### ###  #### #### ",
          "#  #  ##     #    # #  # #    #       # #  # #  # #  # #  #
#    #  # #    #    ",
          "#  #   #  ####  ### #### #### ####   #  #### #### #### ###
#    #  # ###  ###  ",
          "#  #   #  #       #    #    # #  #  #   #  #    # #  # #  #
#    #  # #    #    ",
          "####  ### #### ####    # #### ####  #   #### #### #  # ###
 ### ###  #### #    "
         ].map{|l| l.split('').enum_slice(5).to_a }.transpose.map{|lines|
             lines.map{|line| (line.map{|c| FONT_TO_BIN[c] }.join +
'000').to_i(2).chr }.join
          }.join # FONT is now the encoded font: 5 bytes for 0, 5
bytes for 1, etc   total 80 bytes

  def initialize(code)
    @ip = 0x200
    @mem  = FONT + "\0" * (@ip - 80)  + code + "\0" * 4096  # ensure
4kb mem + program at start
    @regs = "\0" * 160
    @I  = 0
    @stack = []
    init_screen
  end

  def +(a,b)
    a += b
    @regs[0xF] = a > 0xFF ? 1 : 0
    a & 0xFF
  end

  def -(a,b)
    a -= b
    @regs[0xF] = a < 0 ? 0 : 1
    (a + 0x100) & 0xFF
  end

  def get_keypress
    sleep 0.01 until k = (0..0xF).find{|k| $key_pressed.call(k) }
    k
  end

  def init_screen
    @screen = Array.new(32) {Array.new(64,0) }
    @img_screen = TkPhotoImage.new('width'=>64,'height'=>32)
    @img_screen.put( Array.new(32) {Array.new(64,COLORS[0]) } )
    update_screen
  end

  def draw_sprite(x,y,size)
    x %= 64
    y %= 32
    @regs[0xF] = 0
    img_update = Array.new(size){ Array.new(8) }
    @mem[@I,size].split('').each_with_index{|b,dy|
      ypos = (y+dy) % 32
      (0..7).each{|dx|
        chr = b[0][7-dx]
        xpos = (x+dx) % 64
        @regs[0xF] = 1 if(chr==1 && @screen[ypos][xpos]==1)  # collision
        col = @screen[ypos][xpos] ^= chr
        img_update[dy][dx] = COLORS[col]
      }
    }
    @img_screen.put(img_update, :to => [x,y] )
    update_screen
  end

  def update_screen
    $tkscreen.copy(@img_screen,:zoom=>10)
  end

  def fetch
    @instr = @mem[@ip,2]
    @ip += 2
  end

  def execute
    x   = @instr[0].lo
    y   = @instr[1].hi
    opt = @instr[1].lo
    kk  = @instr[1]
    nnn = @instr[0].lo << 8 | @instr[1]
    case @instr[0].hi
      when 0
        case kk
          when 0xE0 then init_screen
     #  00E0 Erase the screen
          when 0xEE then @ip = @stack.pop
     #   00EE Return from a CHIP-8 sub-routine
          else return nil
        end
      when 1   then @ip = nnn
     # 1NNN Jump to the address NNN of the file
      when 2
     #  2NNN Call CHIP-8 sub-routine at NNN
        @stack.push @ip
        @ip = nnn
      when 3   then @ip += 2 if @regs[x] == kk
     # 3XKK Skip next instruction if VX == KK
      when 4   then @ip += 2 if @regs[x] != kk
     # 4XKK Skip next instruction if VX != KK
      when 5   then @ip += 2 if @regs[x] ==  @regs[y]
     # 5XY0 Skip next instruction if VX == VY
      when 6   then @regs[x]  = kk
     #  6XKK VX = KK
      when 7   then @regs[x]  = self.+(@regs[x],kk)
     # 7XKK VX = VX + KK
      when 8
        case opt
          when 0   then @regs[x]  = @regs[y]
      #  8XY0 VX = VY
          when 1   then @regs[x] |= @regs[y]
      #  8XY1 VX = VX OR VY
          when 2   then @regs[x] &= @regs[y]
      #  8XY2 VX = VX AND VY
          when 3   then @regs[x] ^= @regs[y]
      #  8XY3 VX = VX XOR VY
          when 4   then @regs[x]  = self.+(@regs[x],@regs[y])
      # 8XY4 VX = VX + VY
          when 5   then @regs[x]  = self.-(@regs[x],@regs[y])
      # 8XY5 VX = VX - VY
          when 6   then @regs[0xF], @regs[x] = @regs[x][0], @regs[x]
>> 1    # 8X06 VX = VX SHIFT RIGHT 1 VF = least significant bit
          when 7   then @regs[x]  = self.-(@regs[y],@regs[x])
      # 8XY7 VX = VY - VX
          when 0xE then @regs[0xF], @regs[x] = @regs[x][7], @regs[x]
<< 1    # 8X0E VX = VX SHIFT LEFT 1, VF = most significant bit
          else return nil
        end
      when 9   then @ip += 2 if @regs[x] !=  @regs[y]
     # 9XY0 Skip next instruction if VX != VY
      when 0xA then @I   = nnn
     #  ANNN  I = NNN
      when 0xB then @ip  = nnn + @regs[0]
     #   BNNN Jump to NNN + V0
      when 0xC then @regs[x]  = kk & rand(0xFF)
     #   CXKK VX = Random number AND KK
      when 0xD then draw_sprite(@regs[x],@regs[y],opt)
     #   DXYN Draws a sprite at (VX,VY) starting at M(I). VF =
collision.
      when 0xE
        case kk
          when 0x9E  then  @ip +=2  if     $key_pressed.call @regs[x]
     #  EX9E Skip next instruction if key VX pressed
          when 0xA1  then  @ip +=2  unless $key_pressed.call @regs[x]
     #  EXA1 Skip next instruction if key VX not pressed
          else return nil
        end
      when 0xF
        case kk
          when 0x07 then @regs[x] = @delay_timer
     #  FX07 VX = Delay timer
          when 0x0A then @regs[x] = get_keypress
     #  FX0A Waits a keypress and stores it in VX
          when 0x15 then @delay_timer = @regs[x]
     #  FX15 Delay timer = VX
          when 0x18 then
     #  FX18 Sound timer = VX, not implemented as it doesn't do
anything except beep
          when 0x1E then @I += @regs[x]
     #  FX1E I = I + VX
          when 0x29 then @I = 5 * @regs[x]
     #  FX29 I points to the 4 x 5 font sprite of hex char in VX (
font at start of mem, 5 bytes per char)
          when 0x33 then @mem[@I,2] = @regs[x].bcd_byte
     #  FX33 Store BCD representation of VX in M(I)...M(I+2)
          when 0x55 then @mem[@I,x+1] = @regs[0..x]
     #  FX55 Save V0...VX in memory starting at M(I)
          when 0x65 then @regs[0..x]  = @mem[@I,x+1]
     #  FX65 Load V0...VX from memory starting at M(I)
          else return nil
        end
      else return nil
    end
    return true
  end

  def run
    Thread.new {
      @key_timer = @delay_timer = 0
      loop{
        sleep 1.0 / 60
        @delay_timer -= 1  if  @delay_timer > 0
        @key_timer   -= 1  if  @key_timer > 0
        @key_pressed = nil if  @key_timer == 0
        exit! if $key_pressed.call('q')
      }
    }

    loop {
      fetch
      break unless execute
    }
    puts "Halted at instruction %02X%02X " % [@instr[0],@instr[1]]
  end

end


$tkscreen = TkPhotoImage.new('width'=>640,'height'=>320)
TkLabel.new(nil, 'image' => $tkscreen ).pack
Thread.new{ Tk.mainloop }

Chip8Emulator.new( File.open(ARGV[0],'rb').read).run if ARGV[0]