Skip to content

Instantly share code, notes, and snippets.

@mikemar10
Created May 9, 2019 05:25
Show Gist options
  • Save mikemar10/c7cdd721769b8f4d60118834171aa152 to your computer and use it in GitHub Desktop.
Save mikemar10/c7cdd721769b8f4d60118834171aa152 to your computer and use it in GitHub Desktop.
#$rom = $dragon.ffi_file.loadfile('roms/space_invaders.ch8').bytes
#$rom = $dragon.ffi_file.loadfile('roms/pong.ch8').bytes
$rom = $dragon.ffi_file.loadfile('roms/breakout.ch8').bytes
#$rom = $dragon.ffi_file.loadfile('roms/maze.ch8').bytes
class Chip8
attr_accessor :game, :inputs, :outputs
def initialize
@ram = Array.new(4096, 0)
@pc = 0x200
@ram[@pc, $rom.length] = $rom
@ram[0,5] = [0xF0, 0x90, 0x90, 0x90, 0xF0] # digit zero
@ram[1*5,5] = [0x20, 0x60, 0x20, 0x20, 0x70] # digit one
@ram[2*5,5] = [0xF0, 0x10, 0xF0, 0x80, 0xF0] # digit two
@ram[3*5,5] = [0xF0, 0x10, 0xF0, 0x10, 0xF0] # digit three
@ram[4*5,5] = [0x90, 0x90, 0xF0, 0x10, 0x10] # digit four
@ram[5*5,5] = [0xF0, 0x80, 0xF0, 0x10, 0xF0] # digit five
@ram[6*5,5] = [0xF0, 0x80, 0xF0, 0x90, 0xF0] # digit six
@ram[7*5,5] = [0xF0, 0x10, 0x20, 0x40, 0x40] # digit seven
@ram[8*5,5] = [0xF0, 0x90, 0xF0, 0x90, 0xF0] # digit eight
@ram[9*5,5] = [0xF0, 0x90, 0xF0, 0x10, 0xF0] # digit nine
@ram[10*5,5] = [0xF0, 0x90, 0xF0, 0x90, 0x90] # digit A
@ram[11*5,5] = [0xE0, 0x90, 0xE0, 0x90, 0xE0] # digit B
@ram[12*5,5] = [0xF0, 0x80, 0x80, 0x80, 0xF0] # digit C
@ram[13*5,5] = [0xE0, 0x90, 0x90, 0x90, 0xE0] # digit D
@ram[14*5,5] = [0xF0, 0x80, 0xF0, 0x80, 0xF0] # digit E
@ram[15*5,5] = [0xF0, 0x80, 0xF0, 0x80, 0x80] # digit F
@sp = 0
@i = 0
@dt = 0
@st = 0
@stack = Array.new(16, 0)
@display = Array.new(2048, 0)
@registers = Array.new(16, 0)
@keymap = %i{x one two three q w e a s d z c four r f v}
puts $rom.map { |op| "%02X" % op }
end
def opx
(@opcode & 0x0F00) >> 8
end
def opy
(@opcode & 0x00F0) >> 4
end
def opkk
@opcode & 0x00FF
end
def opnnn
@opcode & 0x0FFF
end
def handle_timers
@dt -= 1 unless @dt == 0
@st -= 1 unless @st == 0
outputs.sounds << 'beep.wav' if @st == 1
end
def fetch_opcode
a, b = @ram[@pc, 2]
@opcode = (a << 8) | b
@pc += 2
end
def clear_screen
puts 'Clearing screen'
@display = Array.new(@display.length, 0)
end
def ret
@pc = @stack[@sp]
@sp -= 1 unless @sp == 0
end
def jmp1
@pc = opnnn
end
def call_subroutine
@sp += 1
@stack[@sp] = @pc
@pc = opnnn
end
def se3
@pc += 2 if @registers[opx] == opkk
end
def sne
@pc += 2 if @registers[opx] != opkk
end
def se5
@pc += 2 if @registers[opx] == @registers[opy]
end
def ld6
@registers[opx] = opkk
end
def add7
@registers[opx] += opkk
@registers[opx] = @registers[opx] & 0xFF
end
def ld8
@registers[opx] = @registers[opy]
end
def or8
@registers[opx] |= @registers[opy]
end
def and8
@registers[opx] &= @registers[opy]
end
def xor8
@registers[opx] ^= @registers[opy]
end
def add8
result = @registers[opx] + @registers[opy]
@registers[0xF] = result > 255 ? 1 : 0
@registers[opx] = result & 0xFF
end
def sub8
borrow = @registers[opx] > @registers[opy]
@registers[0xF] = borrow ? 1 : 0
@registers[opx] -= @registers[opy]
@registers[opx] = @registers[opx] & 0xFF
end
def shr8
@registers[0xF] = @registers[opx] & 0x000F ^ 0x0001 == 0 ? 1 : 0
@registers[opx] = @registers[opx].div(2)
end
def subn8
not_borrow = @registers[opx] < @registers[opy]
@registers[0xF] = not_borrow ? 1 : 0
@registers[opx] = (@registers[opy] - @registers[opx]) & 0xFF
end
def shl8
@registers[0xF] = @registers[opx] & 0xF000 ^ 0x1000 ? 1 : 0
@registers[opx] *= 2
end
def sne9
@pc += 2 if @registers[opx] != @registers[opy]
end
def ldi
@i = opnnn
end
def jmpb
@pc = opnnn + @registers[0]
end
def rnd
@registers[opx] = rand(256) & (opkk)
end
def skp
key = @registers[opx]
@pc += 2 if inputs.keyboard.key_down.send(@keymap[key])
end
def sknp
key = @registers[opx]
@pc += 2 if inputs.keyboard.key_up.send(@keymap[key])
end
def ldfx07
@registers[opx] = @dt
end
def ldfx0a
@blocked = true
key_state = @keymap.map { |key| inputs.keyboard.key_down.send(key) }
if key_state.index(true)
@blocked = false
@registers[opx] = key_state.index(true)
end
end
def ldfx15
@dt = @registers[opx]
end
def ldfx18
@st = @registers[opx]
end
def addfx1e
@i += @registers[opx]
@i = @i & 0xFFFF
end
def ldfx29
@i = @registers[opx] * 5
end
def ldfx33
value = @registers[opx]
@ram[@i] = value.div(100) % 10
@ram[@i + 1] = value.div(10) % 10
@ram[@i + 2] = value % 10
end
def ldfx55
@ram[@i,opx+1] = @registers[0..opx]
end
def ldfx65
@registers[0..opx] = @ram[@i,opx+1]
end
def drw
x, y = @registers[opx], @registers[opy]
n = @opcode & 0x000F
collision_occurred = false
n.times do |row|
current_sprite_byte = @ram[@i+row]
8.times do |bit|
current_pixel = @display[(x+bit) + (y+row)*64]
new_pixel = current_sprite_byte & (0x80 >> bit) != 0 ? 1 : 0
unless collision_occurred
collision_occurred = true if current_pixel == 1 && new_pixel == 0
end
@display[(x+bit) + (y+row)*64] = current_pixel ^ new_pixel
end
end
@registers[0xF] = collision_occurred ? 1 : 0
end
def debug_display
output = ""
32.times do |y|
output += "#{@display[y*64,64].join('')}\n"
end
puts output
end
def parse_opcode
if @opcode & 0xF000 ^ 0x000 == 0
# ignored on modern interpreters
elsif @opcode == 0x00E0
clear_screen
elsif @opcode == 0x00EE
ret
elsif @opcode & 0xF000 ^ 0x1000 == 0
jmp1
elsif @opcode & 0xF000 ^ 0x2000 == 0
call_subroutine
elsif @opcode & 0xF000 ^ 0x3000 == 0
se3
elsif @opcode & 0xF000 ^ 0x4000 == 0
sne
elsif @opcode & 0xF00F ^ 0x5000 == 0
se5
elsif @opcode & 0xF000 ^ 0x6000 == 0
ld6
elsif @opcode & 0xF000 ^ 0x7000 == 0
add7
elsif @opcode & 0xF00F ^ 0x8000 == 0
ld8
elsif @opcode & 0xF00F ^ 0x8001 == 0
or8
elsif @opcode & 0xF00F ^ 0x8002 == 0
and8
elsif @opcode & 0xF00F ^ 0x8003 == 0
xor8
elsif @opcode & 0xF00F ^ 0x8004 == 0
add8
elsif @opcode & 0xF00F ^ 0x8005 == 0
sub8
elsif @opcode & 0xF00F ^ 0x8006 == 0
shr8
elsif @opcode & 0xF00F ^ 0x8007 == 0
subn8
elsif @opcode & 0xF00F ^ 0x800E == 0
shl8
elsif @opcode & 0xF00F ^ 0x9000 == 0
sne9
elsif @opcode & 0xF000 ^ 0xA000 == 0
ldi
elsif @opcode & 0xF000 ^ 0xB000 == 0
jmpb
elsif @opcode & 0xF000 ^ 0xC000 == 0
rnd
elsif @opcode & 0xF000 ^ 0xD000 == 0
drw
elsif @opcode & 0xF0FF ^ 0xE09E == 0
skp
elsif @opcode & 0xF0FF ^ 0xE0A1 == 0
sknp
elsif @opcode & 0xF0FF ^ 0xF007 == 0
ldfx07
elsif @opcode & 0xF0FF ^ 0xF00A == 0
ldfx0a
elsif @opcode & 0xF0FF ^ 0xF015 == 0
ldfx15
elsif @opcode & 0xF0FF ^ 0xF018 == 0
ldfx18
elsif @opcode & 0xF0FF ^ 0xF01E == 0
addfx1e
elsif @opcode & 0xF0FF ^ 0xF029 == 0
ldfx29
elsif @opcode & 0xF0FF ^ 0xF033 == 0
ldfx33
elsif @opcode & 0xF0FF ^ 0xF055 == 0
ldfx55
elsif @opcode & 0xF0FF ^ 0xF065 == 0
ldfx65
else
puts "INVALID OPCODE: 0x#{@opcode.to_s(16)}"
@halt = true
end
end
def render
pixel_w = 20
pixel_h = 22
@display.each_with_index do |pixel, i|
outputs.solids << [(i % 64) * pixel_w, (720 - pixel_h - 8) - i.div(64) * pixel_h, pixel_w, pixel_h, 255, 255, 255, 255] if pixel == 1
end
end
def tick
if @blocked
ldfx0a
elsif @halt
unless @finished_halting
puts 'Program Halted'
puts "@pc #{@pc}"
puts "@opcode #{@opcode.to_s(16)}"
puts "@i #{@i}"
puts @registers
puts @display
puts outputs.solids
@finished_halting = true
end
else
handle_timers
fetch_opcode
parse_opcode
#puts "#{'%04X' % @opcode} #{@pc} #{@i} #{@registers.join(' ')}"
end
render
end
end
$chip8 = Chip8.new
def tick args
game, grid, inputs, outputs = args.game, args.grid, args.inputs, args.outputs
$chip8.inputs = inputs
$chip8.outputs = outputs
$chip8.game = game
args.dragon.reset if inputs.keyboard.key_up.enter
args.dragon.gridlines! if inputs.keyboard.key_up.tab
outputs.solids << [grid.rect, [0*3]]
$chip8.tick
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment