Advertisement
nonogamer9

OC-8 : An Chip-8 Emulator For OpenComputers (version 0.1)

Jul 21st, 2024 (edited)
135
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 9.17 KB | Gaming | 0 0
  1. local component = require("component")
  2. local computer = require("computer")
  3. local event = require("event")
  4. local gpu = component.gpu
  5. local filesystem = require("filesystem")
  6.  
  7. -- Bit operations
  8. local bit = bit32 or bit
  9.  
  10. -- Constants
  11. local DISPLAY_W, DISPLAY_H = 64, 32
  12. local MEM_SIZE = 0x1000
  13. local STACK_SIZE = 16
  14. local PROG_START = 0x200
  15. local V_COUNT = 16
  16. local KEYBOARD_COUNT = 16
  17. local INSTR_PER_CYCLE = 10
  18. local NEXT_INSTR = 2
  19. local FONT_HEIGHT = 5
  20. local BYTE = 8
  21. local MASK_INSTR = 0xF000
  22. local MSB = 0x80
  23. local SPRITE_W = 8
  24. local DIGIT = 4
  25. local DECIMAL = 10
  26.  
  27. -- Font sprites
  28. local MEM_FONT = {
  29.   0xF0, 0x90, 0x90, 0x90, 0xF0, -- 0
  30.   0x20, 0x60, 0x20, 0x20, 0x70, -- 1
  31.   0xF0, 0x10, 0xF0, 0x80, 0xF0, -- 2
  32.   0xF0, 0x10, 0xF0, 0x10, 0xF0, -- 3
  33.   0x90, 0x90, 0xF0, 0x10, 0x10, -- 4
  34.   0xF0, 0x80, 0xF0, 0x10, 0xF0, -- 5
  35.   0xF0, 0x80, 0xF0, 0x90, 0xF0, -- 6
  36.   0xF0, 0x10, 0x20, 0x40, 0x40, -- 7
  37.   0xF0, 0x90, 0xF0, 0x90, 0xF0, -- 8
  38.   0xF0, 0x90, 0xF0, 0x10, 0xF0, -- 9
  39.   0xF0, 0x90, 0xF0, 0x90, 0x90, -- A
  40.   0xE0, 0x90, 0xE0, 0x90, 0xE0, -- B
  41.   0xF0, 0x80, 0x80, 0x80, 0xF0, -- C
  42.   0xE0, 0x90, 0x90, 0x90, 0xE0, -- D
  43.   0xF0, 0x80, 0xF0, 0x80, 0xF0, -- E
  44.   0xF0, 0x80, 0xF0, 0x80, 0x80  -- F
  45. }
  46.  
  47. -- CHIP-8 object definition
  48. local CHIP8 = {}
  49. CHIP8.__index = CHIP8
  50.  
  51. function CHIP8.new()
  52.   local self = setmetatable({}, CHIP8)
  53.   self.V = {}
  54.   self.I = 0x0000
  55.   self.DT = 0x0
  56.   self.ST = 0x0
  57.   self.PC = 0x0000
  58.   self.SP = 0x0000
  59.   self.running = false
  60.   self.drawing = false
  61.   self.memory = {}
  62.   self.stack = {}
  63.   self.display = {}
  64.   self.keyboard = {}
  65.   return self
  66. end
  67.  
  68. function CHIP8:reset()
  69.   for i = 0, V_COUNT - 1 do self.V[i] = 0x00 end
  70.   for i = 0, STACK_SIZE - 1 do self.stack[i] = 0x0000 end
  71.   for i = 0, MEM_SIZE - 1 do
  72.     if (i < #MEM_FONT) then
  73.       self.memory[i] = MEM_FONT[i + 1]
  74.     else
  75.       self.memory[i] = 0x00
  76.     end
  77.   end
  78.   for i = 0, (DISPLAY_W * DISPLAY_H) - 1 do self.display[i] = 0x0 end
  79.   self.PC = PROG_START
  80.   self.running = true
  81. end
  82.  
  83. function CHIP8:setKeyDown(key, down)
  84.   self.keyboard[key] = down
  85. end
  86.  
  87. function CHIP8:cycle()
  88.   if (not self.running) then return end
  89.   self.drawing = false
  90.  
  91.   for i = 1, INSTR_PER_CYCLE do
  92.     if (not self.running) then return end
  93.     if (self.PC >= MEM_SIZE) then return "program counter out of range" end
  94.  
  95.     local op = self.memory[self.PC]
  96.     local op2 = self.memory[self.PC + 1]
  97.     local opcode = bit.bor(bit.lshift(op, BYTE), op2)
  98.     local instr = bit.band(opcode, MASK_INSTR)
  99.  
  100.     self.PC = self.PC + NEXT_INSTR
  101.     self:executeInstruction(instr, opcode)
  102.   end
  103.  
  104.   if (self.DT > 0) then self.DT = self.DT - 1 end
  105.   if (self.ST > 0) then self.ST = self.ST - 1 end
  106. end
  107.  
  108. function CHIP8:load(program)
  109.   for i = 1, #program do
  110.     self.memory[PROG_START + (i - 1)] = program[i]
  111.   end
  112. end
  113.  
  114. function CHIP8:executeInstruction(instr, opcode)
  115.   local x = bit.rshift(bit.band(opcode, 0x0F00), 8)
  116.   local y = bit.rshift(bit.band(opcode, 0x00F0), 4)
  117.   local nnn = bit.band(opcode, 0x0FFF)
  118.   local kk = bit.band(opcode, 0x00FF)
  119.   local n = bit.band(opcode, 0x000F)
  120.  
  121.   if instr == 0x0000 then
  122.     if nnn == 0x00E0 then
  123.       for i = 0, (DISPLAY_W * DISPLAY_H) - 1 do self.display[i] = 0 end
  124.       self.drawing = true
  125.     elseif nnn == 0x00EE then
  126.       self.SP = self.SP - 1
  127.       self.PC = self.stack[self.SP]
  128.     end
  129.   elseif instr == 0x1000 then
  130.     self.PC = nnn
  131.   elseif instr == 0x2000 then
  132.     self.stack[self.SP] = self.PC
  133.     self.SP = self.SP + 1
  134.     self.PC = nnn
  135.   elseif instr == 0x3000 then
  136.     if self.V[x] == kk then self.PC = self.PC + NEXT_INSTR end
  137.   elseif instr == 0x4000 then
  138.     if self.V[x] ~= kk then self.PC = self.PC + NEXT_INSTR end
  139.   elseif instr == 0x5000 then
  140.     if self.V[x] == self.V[y] then self.PC = self.PC + NEXT_INSTR end
  141.   elseif instr == 0x6000 then
  142.     self.V[x] = kk
  143.   elseif instr == 0x7000 then
  144.     self.V[x] = (self.V[x] + kk) % 256
  145.   elseif instr == 0x8000 then
  146.     if n == 0 then
  147.       self.V[x] = self.V[y]
  148.     elseif n == 1 then
  149.       self.V[x] = bit.bor(self.V[x], self.V[y])
  150.     elseif n == 2 then
  151.       self.V[x] = bit.band(self.V[x], self.V[y])
  152.     elseif n == 3 then
  153.       self.V[x] = bit.bxor(self.V[x], self.V[y])
  154.     elseif n == 4 then
  155.       local sum = self.V[x] + self.V[y]
  156.       self.V[0xF] = (sum > 255) and 1 or 0
  157.       self.V[x] = sum % 256
  158.     elseif n == 5 then
  159.       self.V[0xF] = (self.V[x] > self.V[y]) and 1 or 0
  160.       self.V[x] = (self.V[x] - self.V[y]) % 256
  161.     elseif n == 6 then
  162.       self.V[0xF] = bit.band(self.V[x], 1)
  163.       self.V[x] = bit.rshift(self.V[x], 1)
  164.     elseif n == 7 then
  165.       self.V[0xF] = (self.V[y] > self.V[x]) and 1 or 0
  166.       self.V[x] = (self.V[y] - self.V[x]) % 256
  167.     elseif n == 0xE then
  168.       self.V[0xF] = bit.rshift(self.V[x], 7)
  169.       self.V[x] = (self.V[x] * 2) % 256
  170.     end
  171.   elseif instr == 0x9000 then
  172.     if self.V[x] ~= self.V[y] then self.PC = self.PC + NEXT_INSTR end
  173.   elseif instr == 0xA000 then
  174.     self.I = nnn
  175.   elseif instr == 0xB000 then
  176.     self.PC = nnn + self.V[0]
  177.   elseif instr == 0xC000 then
  178.     self.V[x] = bit.band(math.random(0, 255), kk)
  179.   elseif instr == 0xD000 then
  180.     self.V[0xF] = 0
  181.     for yline = 0, n - 1 do
  182.       local pixel = self.memory[self.I + yline]
  183.       for xline = 0, 7 do
  184.         if bit.band(pixel, bit.rshift(0x80, xline)) ~= 0 then
  185.           local index = (self.V[x] + xline + ((self.V[y] + yline) * 64)) % (64 * 32)
  186.           if self.display[index] == 1 then self.V[0xF] = 1 end
  187.           self.display[index] = bit.bxor(self.display[index], 1)
  188.         end
  189.       end
  190.     end
  191.     self.drawing = true
  192.   elseif instr == 0xE000 then
  193.     if kk == 0x9E then
  194.       if self.keyboard[self.V[x]] then self.PC = self.PC + NEXT_INSTR end
  195.     elseif kk == 0xA1 then
  196.       if not self.keyboard[self.V[x]] then self.PC = self.PC + NEXT_INSTR end
  197.     end
  198.   elseif instr == 0xF000 then
  199.     if kk == 0x07 then
  200.       self.V[x] = self.DT
  201.     elseif kk == 0x0A then
  202.       self.running = false
  203.       event.listen("key_down", function(_, _, _, code)
  204.         self.V[x] = code
  205.         self.running = true
  206.         return false
  207.       end)
  208.     elseif kk == 0x15 then
  209.       self.DT = self.V[x]
  210.     elseif kk == 0x18 then
  211.       self.ST = self.V[x]
  212.     elseif kk == 0x1E then
  213.       self.I = self.I + self.V[x]
  214.     elseif kk == 0x29 then
  215.       self.I = self.V[x] * FONT_HEIGHT
  216.     elseif kk == 0x33 then
  217.       self.memory[self.I] = math.floor(self.V[x] / 100)
  218.       self.memory[self.I + 1] = math.floor((self.V[x] % 100) / 10)
  219.       self.memory[self.I + 2] = self.V[x] % 10
  220.     elseif kk == 0x55 then
  221.       for i = 0, x do
  222.         self.memory[self.I + i] = self.V[i]
  223.       end
  224.     elseif kk == 0x65 then
  225.       for i = 0, x do
  226.         self.V[i] = self.memory[self.I + i]
  227.       end
  228.     end
  229.   end
  230. end
  231.  
  232. local function loadROM(filename)
  233.   local file = io.open("/home/ROMS/" .. filename, "rb")
  234.   if not file then
  235.     error("Could not open file: " .. filename)
  236.   end
  237.  
  238.   local program = {}
  239.   local byte = file:read(1)
  240.   while byte do
  241.     table.insert(program, string.byte(byte))
  242.     byte = file:read(1)
  243.   end
  244.   file:close()
  245.  
  246.   return program
  247. end
  248.  
  249. local function listROMs()
  250.   local roms = {}
  251.   for file in filesystem.list("/home/ROMS/") do
  252.     if file:match("%.ch8$") then
  253.       table.insert(roms, file)
  254.     end
  255.   end
  256.   return roms
  257. end
  258.  
  259. local function drawGraphics(chip8)
  260.   local buffer = {}
  261.   for y = 0, DISPLAY_H - 1 do
  262.     local row = {}
  263.     for x = 0, DISPLAY_W - 1 do
  264.       local index = x + y * DISPLAY_W
  265.       row[x + 1] = chip8.display[index] == 1 and "#" or " "
  266.     end
  267.     buffer[y + 1] = table.concat(row)
  268.   end
  269.   gpu.fill(1, 1, DISPLAY_W, DISPLAY_H, " ")
  270.   for y, row in ipairs(buffer) do
  271.     gpu.set(1, y, row)
  272.   end
  273. end
  274.  
  275. -- Keyboard mapping
  276. local keyMap = {
  277.   ["1"] = 0x1, ["2"] = 0x2, ["3"] = 0x3, ["4"] = 0xC,
  278.   ["q"] = 0x4, ["w"] = 0x5, ["e"] = 0x6, ["r"] = 0xD,
  279.   ["a"] = 0x7, ["s"] = 0x8, ["d"] = 0x9, ["f"] = 0xE,
  280.   ["z"] = 0xA, ["x"] = 0x0, ["c"] = 0xB, ["v"] = 0xF
  281. }
  282.  
  283. -- Main program
  284. local chip8 = CHIP8.new()
  285. chip8:reset()
  286.  
  287. -- List available ROMs
  288. local roms = listROMs()
  289. print("Available ROMs:")
  290. for i, rom in ipairs(roms) do
  291.   print(i .. ". " .. rom)
  292. end
  293.  
  294. print("Enter the number of the ROM you want to run:")
  295. local choice = tonumber(io.read())
  296. if not choice or choice < 1 or choice > #roms then
  297.   error("Invalid selection")
  298. end
  299.  
  300. local program = loadROM(roms[choice])
  301. chip8:load(program)
  302.  
  303. gpu.setResolution(DISPLAY_W, DISPLAY_H)
  304. gpu.setBackground(0x000000)
  305. gpu.setForeground(0xFFFFFF)
  306.  
  307. -- Main loop
  308. local lastTime = computer.uptime()
  309. while true do
  310.   local currentTime = computer.uptime()
  311.   if currentTime - lastTime >= 0.002 then  -- Approximately 500 Hz
  312.     chip8:cycle()
  313.     if chip8.drawing then
  314.       drawGraphics(chip8)
  315.       chip8.drawing = false
  316.     end
  317.     lastTime = currentTime
  318.   end
  319.  
  320.   local event, _, char, code = event.pull(0.001)
  321.   if event == "key_down" then
  322.     local key = keyMap[string.char(char) or string.char(code)]
  323.     if key then
  324.       chip8:setKeyDown(key, true)
  325.     end
  326.   elseif event == "key_up" then
  327.     local key = keyMap[string.char(char) or string.char(code)]
  328.     if key then
  329.       chip8:setKeyDown(key, false)
  330.     end
  331.   end
  332. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement