Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local component = require("component")
- local computer = require("computer")
- local event = require("event")
- local gpu = component.gpu
- local filesystem = require("filesystem")
- -- Bit operations
- local bit = bit32 or bit
- -- Constants
- local DISPLAY_W, DISPLAY_H = 64, 32
- local MEM_SIZE = 0x1000
- local STACK_SIZE = 16
- local PROG_START = 0x200
- local V_COUNT = 16
- local KEYBOARD_COUNT = 16
- local INSTR_PER_CYCLE = 10
- local NEXT_INSTR = 2
- local FONT_HEIGHT = 5
- local BYTE = 8
- local MASK_INSTR = 0xF000
- local MSB = 0x80
- local SPRITE_W = 8
- local DIGIT = 4
- local DECIMAL = 10
- -- Font sprites
- local MEM_FONT = {
- 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
- }
- -- CHIP-8 object definition
- local CHIP8 = {}
- CHIP8.__index = CHIP8
- function CHIP8.new()
- local self = setmetatable({}, CHIP8)
- self.V = {}
- self.I = 0x0000
- self.DT = 0x0
- self.ST = 0x0
- self.PC = 0x0000
- self.SP = 0x0000
- self.running = false
- self.drawing = false
- self.memory = {}
- self.stack = {}
- self.display = {}
- self.keyboard = {}
- return self
- end
- function CHIP8:reset()
- for i = 0, V_COUNT - 1 do self.V[i] = 0x00 end
- for i = 0, STACK_SIZE - 1 do self.stack[i] = 0x0000 end
- for i = 0, MEM_SIZE - 1 do
- if (i < #MEM_FONT) then
- self.memory[i] = MEM_FONT[i + 1]
- else
- self.memory[i] = 0x00
- end
- end
- for i = 0, (DISPLAY_W * DISPLAY_H) - 1 do self.display[i] = 0x0 end
- self.PC = PROG_START
- self.running = true
- end
- function CHIP8:setKeyDown(key, down)
- self.keyboard[key] = down
- end
- function CHIP8:cycle()
- if (not self.running) then return end
- self.drawing = false
- for i = 1, INSTR_PER_CYCLE do
- if (not self.running) then return end
- if (self.PC >= MEM_SIZE) then return "program counter out of range" end
- local op = self.memory[self.PC]
- local op2 = self.memory[self.PC + 1]
- local opcode = bit.bor(bit.lshift(op, BYTE), op2)
- local instr = bit.band(opcode, MASK_INSTR)
- self.PC = self.PC + NEXT_INSTR
- self:executeInstruction(instr, opcode)
- end
- if (self.DT > 0) then self.DT = self.DT - 1 end
- if (self.ST > 0) then self.ST = self.ST - 1 end
- end
- function CHIP8:load(program)
- for i = 1, #program do
- self.memory[PROG_START + (i - 1)] = program[i]
- end
- end
- function CHIP8:executeInstruction(instr, opcode)
- local x = bit.rshift(bit.band(opcode, 0x0F00), 8)
- local y = bit.rshift(bit.band(opcode, 0x00F0), 4)
- local nnn = bit.band(opcode, 0x0FFF)
- local kk = bit.band(opcode, 0x00FF)
- local n = bit.band(opcode, 0x000F)
- if instr == 0x0000 then
- if nnn == 0x00E0 then
- for i = 0, (DISPLAY_W * DISPLAY_H) - 1 do self.display[i] = 0 end
- self.drawing = true
- elseif nnn == 0x00EE then
- self.SP = self.SP - 1
- self.PC = self.stack[self.SP]
- end
- elseif instr == 0x1000 then
- self.PC = nnn
- elseif instr == 0x2000 then
- self.stack[self.SP] = self.PC
- self.SP = self.SP + 1
- self.PC = nnn
- elseif instr == 0x3000 then
- if self.V[x] == kk then self.PC = self.PC + NEXT_INSTR end
- elseif instr == 0x4000 then
- if self.V[x] ~= kk then self.PC = self.PC + NEXT_INSTR end
- elseif instr == 0x5000 then
- if self.V[x] == self.V[y] then self.PC = self.PC + NEXT_INSTR end
- elseif instr == 0x6000 then
- self.V[x] = kk
- elseif instr == 0x7000 then
- self.V[x] = (self.V[x] + kk) % 256
- elseif instr == 0x8000 then
- if n == 0 then
- self.V[x] = self.V[y]
- elseif n == 1 then
- self.V[x] = bit.bor(self.V[x], self.V[y])
- elseif n == 2 then
- self.V[x] = bit.band(self.V[x], self.V[y])
- elseif n == 3 then
- self.V[x] = bit.bxor(self.V[x], self.V[y])
- elseif n == 4 then
- local sum = self.V[x] + self.V[y]
- self.V[0xF] = (sum > 255) and 1 or 0
- self.V[x] = sum % 256
- elseif n == 5 then
- self.V[0xF] = (self.V[x] > self.V[y]) and 1 or 0
- self.V[x] = (self.V[x] - self.V[y]) % 256
- elseif n == 6 then
- self.V[0xF] = bit.band(self.V[x], 1)
- self.V[x] = bit.rshift(self.V[x], 1)
- elseif n == 7 then
- self.V[0xF] = (self.V[y] > self.V[x]) and 1 or 0
- self.V[x] = (self.V[y] - self.V[x]) % 256
- elseif n == 0xE then
- self.V[0xF] = bit.rshift(self.V[x], 7)
- self.V[x] = (self.V[x] * 2) % 256
- end
- elseif instr == 0x9000 then
- if self.V[x] ~= self.V[y] then self.PC = self.PC + NEXT_INSTR end
- elseif instr == 0xA000 then
- self.I = nnn
- elseif instr == 0xB000 then
- self.PC = nnn + self.V[0]
- elseif instr == 0xC000 then
- self.V[x] = bit.band(math.random(0, 255), kk)
- elseif instr == 0xD000 then
- self.V[0xF] = 0
- for yline = 0, n - 1 do
- local pixel = self.memory[self.I + yline]
- for xline = 0, 7 do
- if bit.band(pixel, bit.rshift(0x80, xline)) ~= 0 then
- local index = (self.V[x] + xline + ((self.V[y] + yline) * 64)) % (64 * 32)
- if self.display[index] == 1 then self.V[0xF] = 1 end
- self.display[index] = bit.bxor(self.display[index], 1)
- end
- end
- end
- self.drawing = true
- elseif instr == 0xE000 then
- if kk == 0x9E then
- if self.keyboard[self.V[x]] then self.PC = self.PC + NEXT_INSTR end
- elseif kk == 0xA1 then
- if not self.keyboard[self.V[x]] then self.PC = self.PC + NEXT_INSTR end
- end
- elseif instr == 0xF000 then
- if kk == 0x07 then
- self.V[x] = self.DT
- elseif kk == 0x0A then
- self.running = false
- event.listen("key_down", function(_, _, _, code)
- self.V[x] = code
- self.running = true
- return false
- end)
- elseif kk == 0x15 then
- self.DT = self.V[x]
- elseif kk == 0x18 then
- self.ST = self.V[x]
- elseif kk == 0x1E then
- self.I = self.I + self.V[x]
- elseif kk == 0x29 then
- self.I = self.V[x] * FONT_HEIGHT
- elseif kk == 0x33 then
- self.memory[self.I] = math.floor(self.V[x] / 100)
- self.memory[self.I + 1] = math.floor((self.V[x] % 100) / 10)
- self.memory[self.I + 2] = self.V[x] % 10
- elseif kk == 0x55 then
- for i = 0, x do
- self.memory[self.I + i] = self.V[i]
- end
- elseif kk == 0x65 then
- for i = 0, x do
- self.V[i] = self.memory[self.I + i]
- end
- end
- end
- end
- local function loadROM(filename)
- local file = io.open("/home/ROMS/" .. filename, "rb")
- if not file then
- error("Could not open file: " .. filename)
- end
- local program = {}
- local byte = file:read(1)
- while byte do
- table.insert(program, string.byte(byte))
- byte = file:read(1)
- end
- file:close()
- return program
- end
- local function listROMs()
- local roms = {}
- for file in filesystem.list("/home/ROMS/") do
- if file:match("%.ch8$") then
- table.insert(roms, file)
- end
- end
- return roms
- end
- local function drawGraphics(chip8)
- local buffer = {}
- for y = 0, DISPLAY_H - 1 do
- local row = {}
- for x = 0, DISPLAY_W - 1 do
- local index = x + y * DISPLAY_W
- row[x + 1] = chip8.display[index] == 1 and "#" or " "
- end
- buffer[y + 1] = table.concat(row)
- end
- gpu.fill(1, 1, DISPLAY_W, DISPLAY_H, " ")
- for y, row in ipairs(buffer) do
- gpu.set(1, y, row)
- end
- end
- -- Keyboard mapping
- local keyMap = {
- ["1"] = 0x1, ["2"] = 0x2, ["3"] = 0x3, ["4"] = 0xC,
- ["q"] = 0x4, ["w"] = 0x5, ["e"] = 0x6, ["r"] = 0xD,
- ["a"] = 0x7, ["s"] = 0x8, ["d"] = 0x9, ["f"] = 0xE,
- ["z"] = 0xA, ["x"] = 0x0, ["c"] = 0xB, ["v"] = 0xF
- }
- -- Main program
- local chip8 = CHIP8.new()
- chip8:reset()
- -- List available ROMs
- local roms = listROMs()
- print("Available ROMs:")
- for i, rom in ipairs(roms) do
- print(i .. ". " .. rom)
- end
- print("Enter the number of the ROM you want to run:")
- local choice = tonumber(io.read())
- if not choice or choice < 1 or choice > #roms then
- error("Invalid selection")
- end
- local program = loadROM(roms[choice])
- chip8:load(program)
- gpu.setResolution(DISPLAY_W, DISPLAY_H)
- gpu.setBackground(0x000000)
- gpu.setForeground(0xFFFFFF)
- -- Main loop
- local lastTime = computer.uptime()
- while true do
- local currentTime = computer.uptime()
- if currentTime - lastTime >= 0.002 then -- Approximately 500 Hz
- chip8:cycle()
- if chip8.drawing then
- drawGraphics(chip8)
- chip8.drawing = false
- end
- lastTime = currentTime
- end
- local event, _, char, code = event.pull(0.001)
- if event == "key_down" then
- local key = keyMap[string.char(char) or string.char(code)]
- if key then
- chip8:setKeyDown(key, true)
- end
- elseif event == "key_up" then
- local key = keyMap[string.char(char) or string.char(code)]
- if key then
- chip8:setKeyDown(key, false)
- end
- end
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement