Created
January 3, 2015 14:51
-
-
Save marjinal1st/804584f5c49f7e91a4f8 to your computer and use it in GitHub Desktop.
CHIP-8 Ruby Emülatörü
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class CHIP8 | |
attr_accessor :memory, :gpio, :display_buffer, :stack, | |
:key_inputs, :fonts, :opcode, :index, :pc, | |
:delay_timer, :sound_timer, :should_draw, | |
:instructions, :vx, :vy | |
def initialize | |
@memory = [0] * 4096 # 4096 Baytlık belleğimiz | |
@gpio = [0] * 16 # 16 tane genel amaçlı register'ı tutan bir dizi | |
@display_buffer = [0] * 2048 # 64 * 32 = 2048 piksellik ekranımız | |
@stack = [] # Alt yordamları tutmamızı sağlayacak olan yığın | |
@key_inputs = [0] * 16 # 16 girdili klavyemiz | |
@fonts = [ # Her biri 5 bayt olarak şekilde, 16 tane fontumuz | |
0xF0, 0x90, 0x90, 0x90, 0xF0, # 0 | |
0x20, 0x60, 0x20, 0x20, 0x70, # 1 | |
0xF0, 0x10, 0xF0, 0x80, 0xF0, # 2 | |
0xF0, 0x10, 0xF0, 0x10, 0xF0, # 3 | |
0x90, 0x90, 0xF0, 0x10, 0x10, # 4 | |
0xF0, 0x80, 0xF0, 0x10, 0xF0, # 5 | |
0xF0, 0x80, 0xF0, 0x90, 0xF0, # 6 | |
0xF0, 0x10, 0x20, 0x40, 0x40, # 7 | |
0xF0, 0x90, 0xF0, 0x90, 0xF0, # 8 | |
0xF0, 0x90, 0xF0, 0x10, 0xF0, # 9 | |
0xF0, 0x90, 0xF0, 0x90, 0x90, # A | |
0xE0, 0x90, 0xE0, 0x90, 0xE0, # B | |
0xF0, 0x80, 0x80, 0x80, 0xF0, # C | |
0xE0, 0x90, 0x90, 0x90, 0xE0, # D | |
0xF0, 0x80, 0xF0, 0x80, 0xF0, # E | |
0xF0, 0x80, 0xF0, 0x80, 0x80 # F | |
] | |
@opcode = 0 # Çalıştırılacak olan işlemin kodu | |
@index = 0 # Index register'ımız | |
@pc = 0x200 # Program sayacımız. ilk 512 baytı es geçiyoruz. | |
@should_draw = false # Görüntü çizilmesi için bir boolean değişkeni | |
@vx, @vy = 0, 0 # Register'ları tutan değişkenler | |
@delay_timer = 0 | |
@sound_timer = 0 | |
# Fontları belleğe yüklüyoruz | |
(0...80).each { |i| @memory[i] = @fonts[i] } | |
# Ve en baba kısım: Talimatları bir Hash'e koyarak | |
# haritalama yapıyoruz. | |
@instructions = { | |
0x0000 => :_0ZZZ, | |
0x00e0 => :_0ZZ0, | |
0x00ee => :_0ZZE, | |
0x1000 => :_1ZZZ, | |
0x2000 => :_2ZZZ, | |
0x3000 => :_3ZZZ, | |
0x4000 => :_4ZZZ, | |
0x5000 => :_5ZZZ, | |
0x6000 => :_6ZZZ, | |
0x7000 => :_7ZZZ, | |
0x8000 => :_8ZZZ, | |
0x8FF0 => :_8ZZ0, | |
0x8FF1 => :_8ZZ1, | |
0x8FF2 => :_8ZZ2, | |
0x8FF3 => :_8ZZ3, | |
0x8FF4 => :_8ZZ4, | |
0x8FF5 => :_8ZZ5, | |
0x8FF6 => :_8ZZ6, | |
0x8FF7 => :_8ZZ7, | |
0x8FFE => :_8ZZE, | |
0x9000 => :_9ZZZ, | |
0xA000 => :_AZZZ, | |
0xB000 => :_BZZZ, | |
0xC000 => :_CZZZ, | |
0xD000 => :_DZZZ, | |
0xE000 => :_EZZZ, | |
0xE00E => :_EZZE, | |
0xE001 => :_EZZ1, | |
0xF000 => :_FZZZ, | |
0xF007 => :_FZ07, | |
0xF00A => :_FZ0A, | |
0xF015 => :_FZ15, | |
0xF018 => :_FZ18, | |
0xF01E => :_FZ1E, | |
0xF029 => :_FZ29, | |
0xF033 => :_FZ33, | |
0xF055 => :_FZ55, | |
0xF065 => :_FZ65 | |
} | |
end | |
def load_rom(path) | |
rom_file = File.open(path, 'rb') { |i| i.read } | |
rom_file.split('').each_with_index do |byte, index| | |
@memory[index + 0x200] = byte.ord | |
end | |
end | |
def cycle | |
@opcode = (@memory[@pc] << 8) | @memory[@pc + 1] # İşlem kodunu alıyoruz | |
@pc += 2 # Program sayacını arttırıyoruz | |
@vx = (@opcode & 0x0F00) >> 8 # İşlem kodunu maskeleyip | |
@vy = (@opcode & 0x00F0) >> 4 # register'ları değişkenlere atıyoruz | |
ext_op = @opcode & 0xF000 # İşlem kodunu maskeleyip, talimatı alıyoruz | |
puts "Running instruction: #{@instructions[ext_op].to_s[1..-1]}" rescue nil | |
run_instruction ext_op # Ve talimatı çalıştırıyoruz | |
@delay_timer -= 1 if @delay_timer > 0 # Gecikme ve ses sayaçlarını | |
@sound_timer -= 1 if @sound_timer > 0 # azaltıyoruz | |
end | |
def test | |
while true | |
sleep 0.1 | |
cycle | |
end | |
end | |
private | |
def run_instruction(ext_op) | |
method(@instructions[ext_op]).call rescue puts "Unknown instruction: #{ext_op.to_s(16)}" | |
end | |
def get_key | |
(0...16).each { |i| return i if @key_inputs[i] == 1 } | |
return -1 | |
end | |
def _0ZZZ | |
ext_op = @opcode & 0xF0FF | |
run_instruction ext_op | |
end | |
def _0ZZ0 | |
@display_buffer = [0] * 64 * 32 | |
@should_draw = true | |
end | |
def _0ZZE | |
@pc = @stack.pop | |
end | |
def _1ZZZ | |
@pc = @opcode & 0x0FFF | |
end | |
def _2ZZZ | |
@stack.push @pc | |
@pc = @opcode & 0x0FFF | |
end | |
def _3ZZZ | |
@pc += 2 if @gpio[@vx] == (@opcode & 0xFF) | |
end | |
def _4ZZZ | |
@pc += 2 if @gpio[@vx] != (@opcode & 0xFF) | |
end | |
def _5ZZZ | |
@pc += 2 if @gpio[@vx] == @gpio[@vy] | |
end | |
def _6ZZZ | |
@gpio[@vx] = @opcode & 0xFF | |
end | |
def _7ZZZ | |
@gpio[@vx] += (@opcode % 0xFF) | |
end | |
def _8ZZZ | |
ext_op = @opcode & 0xF00F | |
ext_op += 0xFF0 | |
run_instruction ext_op | |
end | |
def _8ZZ0 | |
@gpio[@vx] = @gpio[@vy] & 0xFF | |
end | |
def _8ZZ1 | |
@gpio[@vx] |= @gpio[@vy] | |
@gpio[@vx] &= 0xFF | |
end | |
def _8ZZ2 | |
@gpio[@vx] &= @gpio[@vy] | |
@gpio[@vx] &= 0xFF | |
end | |
def _8ZZ3 | |
@gpio[@vx] ^= @gpio[@vy] | |
@gpio[@vx] &= 0xFF | |
end | |
def _8ZZ4 | |
@gpio[0xF] = @gpio[@vx] + @gpio[@vy] > 0xFF ? 1 : 0 | |
@gpio[@vx] += @gpio[@vy] | |
@gpio[@vx] &= 0xFF | |
end | |
def _8ZZ5 | |
@gpio[0xF] = @gpio[@vx] < @gpio[@vy] ? 0 : 1 | |
@gpio[@vx] -= @gpio[@vy] | |
@gpio[@vx] &= 0xFF | |
end | |
def _8ZZ6 | |
@gpio[0xF] = @gpio[@vx] & 0x0001 | |
@gpio[@vx] >>= 1 | |
end | |
def _8ZZ7 | |
@gpio[0xF] = @gpio[@vx] > @gpio[@vy] ? 0 : 1 | |
@gpio[@vx] = @gpio[@vy] - @gpio[@vx] | |
end | |
def _8ZZE | |
@gpio[0xF] = (@gpio[@vx] & 0xF0) >> 7 | |
@gpio[@vx] <<= 1 | |
@gpio[@vx] &= 0xFF | |
end | |
def _9ZZZ | |
@pc += 2 if @gpio[@vx] != @gpio[@vy] | |
end | |
def _AZZZ | |
@index = @opcode & 0x0FFF | |
end | |
def _BZZZ | |
@pc = (@opcode & 0x0FFF) + @gpio[0] | |
end | |
def _CZZZ | |
r = (rand * 0xFF).to_i | |
@gpio[@vx] = r & (@opcode & 0xFF) | |
@gpio[@vx] &= 0xFF | |
end | |
def _DZZZ | |
@gpio[0xF] = 0 | |
x = @gpio[@vx] & 0xFF | |
y = @gpio[@vy] & 0xFF | |
height = @opcode & 0x000F | |
row = 0 | |
while row < height | |
current_row = @memory[row + @index] | |
pixel_offset = 0 | |
while pixel_offset < 8 | |
loc = x + pixel_offset + ((y + row) * 64) | |
pixel_offset += 1 | |
next if (y + row) >= 32 || (x + pixel_offset - 1) >= 64 | |
mask = 1 << 8 - pixel_offset | |
current_pixel = (current_row & mask) >> (8 - pixel_offset) | |
@display_buffer[loc] ^= current_pixel | |
@gpio[0xF] = @display_buffer[loc] == 0 ? 1 : 0 | |
end | |
row += 1 | |
end | |
@should_draw = true | |
end | |
def _EZZZ | |
ext_op = @opcode & 0xF00F | |
run_instruction ext_op | |
end | |
def _EZZE | |
key = @gpio[@vx] & 0xF | |
@pc += 2 if @key_inputs[key] == 1 | |
end | |
def _EZZ1 | |
key = @gpio[@vx] & 0xF | |
@pc += 2 if @key_inputs[key] == 1 | |
end | |
def _FZZZ | |
ext_op = @opcode & 0xF0FF | |
run_instruction ext_op | |
end | |
def _FZ07 | |
@gpio[@vx] = @delay_timer | |
end | |
def _FZ0A | |
ret = get_key | |
if ret >= 0 | |
@gpio[@vx] = ret | |
else | |
@pc -= 2 | |
end | |
end | |
def _FZ15 | |
@delay_timer = @gpio[@vx] | |
end | |
def _FZ18 | |
@sound_timer = @gpio[@vx] | |
end | |
def _FZ1E | |
@index += @gpio[@vx] | |
if @index > 0x0FFF | |
@gpio[0xF] = 1 | |
@index &= 0xFFF | |
else | |
@gpio[0xF] = 0 | |
end | |
end | |
def _FZ29 | |
@index = (5 * (@gpio[@vx])) & 0x0FFF | |
end | |
def _FZ33 | |
@memory[@index] = @gpio[@vx] / 100 | |
@memory[@index + 1] = (@gpio[@vx] % 100) / 10 | |
@memory[@index + 2] = @gpio[@vx] % 10 | |
end | |
def _FZ55 | |
(0..@vx).each { |i| @memory[@index + i] = @gpio[i] } | |
@index += @vx + 1 | |
end | |
def _FZ65 | |
(0..@vx).each { |i| @gpio[i] = @memory[@index + 1] } | |
@index += @vx + 1 | |
end | |
end | |
emulator = CHIP8.new | |
emulator.load_rom('PONG') | |
emulator.test |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment