nonogamer9

OC-8 : A Chip-8 Emulator For OpenComputers (version 1.2)

Jul 21st, 2024 (edited)
638
1
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 12.87 KB | Gaming | 1 0
  1. local comp = require("component")
  2. local ev = require("event")
  3. local bit = require("bit32")
  4. local fs = require("filesystem")
  5. local gpu = comp.gpu
  6. local shell = require("shell")
  7. local unicode = require("unicode")
  8. local computer = require("computer")
  9.  
  10. local b_and, b_shl, b_shr, b_xor, b_or = bit.band, bit.lshift, bit.rshift, bit.bxor, bit.bor
  11. local floor, random = math.floor, math.random
  12. local s_char, s_sub, s_lower = string.char, string.sub, string.lower
  13. local os_clock = os.clock
  14.  
  15. -- Sound card detection
  16. local hasSoundCard = comp.isAvailable("sound")
  17. local sound = nil
  18. local soundChannel = 1
  19. local soundEnabled = true
  20.  
  21. if hasSoundCard then
  22.     sound = comp.sound
  23.     -- Initialize sound card
  24.     sound.open(soundChannel)
  25.     sound.setWave(soundChannel, sound.modes.square)
  26.     sound.setVolume(soundChannel, 0.8)
  27.     sound.setADSR(soundChannel, 1, 100, 0, 1)
  28.     print("Sound card detected and initialized!")
  29. else
  30.     print("No sound card detected, using computer.beep() for audio")
  31. end
  32.  
  33. local isTier1 = (gpu.maxDepth() == 1)
  34.  
  35. local hires = false          
  36. local shiftQuirk = true      
  37. local loadStoreQuirk = true  
  38. local screenW, screenH = gpu.getResolution()
  39. local instructionsPerLoop = 150
  40.  
  41. local dotMap = {
  42.   {0x1, 0x8},
  43.   {0x2, 0x10},
  44.   {0x4, 0x20},
  45.   {0x40, 0x80}
  46. }
  47.  
  48. local mem = {} for i=0,4095 do mem[i]=0 end
  49. local V = {} for i=0,15 do V[i]=0 end
  50. local RPL = {} for i=0,7 do RPL[i]=0 end
  51. local stack = {}
  52. local I, PC, SP = 0, 0x200, 0
  53. local delayT, soundT = 0, 0
  54. local soundActive = false
  55. local keys = {}
  56. local keyMap = "1234qwerasdfzxcv"
  57. local keyPending, keyWaitReg, keyWaitAddr = nil, nil, nil
  58.  
  59. local disp, lastDispChar = {}, {}
  60. local function clearScreen()
  61.   for x=0,127 do
  62.     disp[x] = disp[x] or {}
  63.     for y=0,63 do disp[x][y] = false end
  64.   end
  65.   for i=1,screenW do lastDispChar[i] = {} end
  66.   gpu.setBackground(0x000000)
  67.   gpu.fill(1, 1, screenW, screenH, " ")
  68. end
  69.  
  70. local function drawStatus()
  71.   local status = string.format(" SPEED: %d Cycles/Loop | Arrows to Adjust | Sound: %s",
  72.     instructionsPerLoop,
  73.     (hasSoundCard and soundEnabled and "CARD" or (hasSoundCard and "MUTED" or "BEEP")))
  74.   gpu.setForeground(0x000000)
  75.   gpu.setBackground(0xFFFFFF)
  76.   gpu.set(1, screenH, status .. string.rep(" ", screenW - #status))
  77.   gpu.setBackground(0x000000)
  78.   gpu.setForeground(0xFFFFFF)
  79. end
  80.  
  81. -- Sound control functions
  82. local function startSound()
  83.   if not soundEnabled then return end
  84.   if hasSoundCard then
  85.     -- Use the sound card for better audio
  86.     sound.setFrequency(soundChannel, 440) -- 440Hz is a good beep frequency
  87.     sound.setVolume(soundChannel, 0.8)
  88.     -- Re-open channel if needed
  89.     sound.close(soundChannel)
  90.     sound.open(soundChannel)
  91.     sound.setWave(soundChannel, sound.modes.square)
  92.     sound.setADSR(soundChannel, 1, 100, 0, 1)
  93.     sound.delay(16) -- 16ms beep
  94.     sound.process()
  95.   else
  96.     -- Fallback to computer.beep
  97.     computer.beep(440, 0.016)
  98.   end
  99. end
  100.  
  101. local function stopSound()
  102.   if hasSoundCard and soundEnabled then
  103.     sound.setVolume(soundChannel, 0)
  104.     sound.process()
  105.   end
  106. end
  107.  
  108. local function toggleSound()
  109.   if hasSoundCard then
  110.     soundEnabled = not soundEnabled
  111.     if not soundEnabled then
  112.       sound.setVolume(soundChannel, 0)
  113.       sound.process()
  114.     else
  115.       sound.setVolume(soundChannel, 0.8)
  116.     end
  117.     drawStatus()
  118.     return true
  119.   end
  120.   return false
  121. end
  122.  
  123. local function draw()
  124.   local curW = hires and 128 or 64
  125.   local curH = hires and 64 or 32
  126.   local charW = curW / 2
  127.   local charH = curH / 4
  128.   local offX = floor((screenW - charW) / 2)
  129.   local offY = floor((screenH - charH) / 2)
  130.  
  131.   for py = 0, charH - 1 do
  132.     for px = 0, charW - 1 do
  133.       local charCode = 0x2800
  134.       for dy = 0, 3 do
  135.         for dx = 0, 1 do
  136.           if disp[px * 2 + dx][py * 4 + dy] then
  137.             charCode = charCode + dotMap[dy + 1][dx + 1]
  138.           end
  139.         end
  140.       end
  141.       local char = unicode.char(charCode)
  142.       if (py + offY + 1) < screenH then
  143.         if char ~= lastDispChar[px + offX][py + offY] then
  144.           gpu.set(px + offX + 1, py + offY + 1, char)
  145.           lastDispChar[px + offX][py + offY] = char
  146.         end
  147.       end
  148.     end
  149.   end
  150. end
  151.  
  152. local function loadFonts()
  153.   local fonts = {
  154.     0xF0,0x90,0x90,0x90,0xF0, 0x20,0x60,0x20,0x20,0x70, 0xF0,0x10,0xF0,0x80,0xF0, 0xF0,0x10,0xF0,0x10,0xF0,
  155.     0x90,0x90,0xF0,0x10,0x10, 0xF0,0x80,0xF0,0x10,0xF0, 0xF0,0x80,0xF0,0x90,0xF0, 0xF0,0x10,0x20,0x40,0x40,
  156.     0xF0,0x90,0xF0,0x90,0xF0, 0xF0,0x90,0xF0,0x10,0xF0, 0xF0,0x90,0xF0,0x90,0x90, 0xE0,0x90,0xE0,0x90,0xE0,
  157.     0xF0,0x80,0x80,0x80,0xF0, 0xE0,0x90,0x90,0x90,0xE0, 0xF0,0x80,0xF0,0x80,0xF0, 0xF0,0x80,0xF0,0x80,0x80,
  158.     0xFF,0xFF,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xFF,0xFF, 0x18,0x38,0x78,0x18,0x18,0x18,0x18,0x18,0x7E,0x7E,
  159.     0xFF,0xFF,0x03,0x03,0xFF,0xFF,0xC0,0xC0,0xFF,0xFF, 0xFF,0xFF,0x03,0x03,0xFF,0xFF,0x03,0x03,0xFF,0xFF,
  160.     0xC3,0xC3,0xC3,0xC3,0xFF,0xFF,0x03,0x03,0x03,0x03, 0xFF,0xFF,0xC0,0xC0,0xFF,0xFF,0x03,0x03,0xFF,0xFF,
  161.     0xFF,0xFF,0xC0,0xC0,0xFF,0xFF,0xC3,0xC3,0xFF,0xFF, 0xFF,0xFF,0x03,0x03,0x06,0x0C,0x18,0x18,0x18,0x18,
  162.     0xFF,0xFF,0xC3,0xC3,0xFF,0xFF,0xC3,0xC3,0xFF,0xFF, 0xFF,0xFF,0xC3,0xC3,0xFF,0xFF,0x03,0x03,0xFF,0xFF,
  163.     0x7E,0x7E,0xC3,0xC3,0xC3,0xFF,0xFF,0xC3,0xC3,0xC3, 0xFC,0xFC,0xC3,0xC3,0xFC,0xFC,0xC3,0xC3,0xFC,0xFC,
  164.     0x3C,0x3C,0x60,0x60,0x60,0x60,0x60,0x60,0x3C,0x3C, 0xFC,0xFC,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xFC,0xFC,
  165.     0xFF,0xFF,0xC0,0xC0,0xF8,0xF8,0xC0,0xC0,0xFF,0xFF, 0xFF,0xFF,0xC0,0xC0,0xF8,0xF8,0xC0,0xC0,0xC0,0xC0
  166.   }
  167.   for i=0,#fonts-1 do mem[i] = fonts[i+1] end
  168. end
  169.  
  170. local function selectRom()
  171.   local path = shell.getWorkingDirectory()
  172.   local files = {}
  173.   for f in fs.list(path) do if f:match("%.ch8$") then files[#files+1] = f end end
  174.   if #files == 0 then print("No .ch8 files found!") return nil end
  175.  
  176.   gpu.setBackground(0x000000)
  177.   gpu.setForeground(0xFFFFFF)
  178.   gpu.fill(1,1,screenW,screenH," ")
  179.  
  180.   gpu.set(2, 2, "8\"\"\"88 8\"\"\"\"8       eeeee  8 ")
  181.   gpu.set(2, 3, "8    8 8    \"       8   8  88")
  182.   gpu.set(2, 4, "8    8 8e           8eee8  88")
  183.   gpu.set(2, 5, "8    8 88     eeee 88   88 88")
  184.   gpu.set(2, 6, "8    8 88   e      88   88   ")
  185.   gpu.set(2, 7, "8eeee8 88eee8      88eee88 88")
  186.   gpu.set(2, 8, "OC-8: CH8/SCH8 Emulator for OC by nonogamer9")
  187.  
  188.   if hasSoundCard then
  189.     gpu.set(2, 9, "Sound Card: ENABLED (Press S to toggle)")
  190.   else
  191.     gpu.set(2, 9, "Sound: Using computer.beep() (no sound card detected)")
  192.   end
  193.  
  194.   gpu.set(2, 10, "Select a ROM (1-"..#files.."):")
  195.   for i, f in ipairs(files) do gpu.set(4, i+11, i..". "..f) end
  196.  
  197.   local choice = nil
  198.   while true do
  199.     local e = {ev.pull("key_down")}
  200.     local n = tonumber(s_char(e[3]))
  201.     if n and files[n] then choice = path.."/"..files[n] break end
  202.     if e[4] == 28 then break end
  203.   end
  204.   return choice
  205. end
  206.  
  207. local function step()
  208.   if keyPending then return false end
  209.   local op = b_or(b_shl(mem[PC], 8), mem[PC+1])
  210.   PC = PC + 2
  211.   local n1, x, y, n4 = b_shr(op, 12), b_and(b_shr(op, 8), 0xF), b_and(b_shr(op, 4), 0xF), b_and(op, 0xF)
  212.   local nn, nnn = b_and(op, 0xFF), b_and(op, 0xFFF)
  213.  
  214.   if n1 == 0 then
  215.     if op == 0x00E0 then clearScreen()
  216.     elseif op == 0x00EE then PC = stack[SP] SP = SP - 1
  217.     elseif op == 0x00FF then if not isTier1 then hires = true; clearScreen() end
  218.     elseif op == 0x00FE then hires = false; clearScreen()
  219.     elseif op == 0x00FD then
  220.       -- Cleanup sound card on exit
  221.       if hasSoundCard then
  222.         sound.close(soundChannel)
  223.       end
  224.       os.exit()
  225.     elseif op == 0x00FB then
  226.       for sx=127,4,-1 do disp[sx] = disp[sx-4] end
  227.       for sx=0,3 do disp[sx] = {}; for sy=0,63 do disp[sx][sy]=false end end
  228.     elseif op == 0x00FC then
  229.       for sx=0,123 do disp[sx] = disp[sx+4] end
  230.       for sx=124,127 do disp[sx] = {}; for sy=0,63 do disp[sx][sy]=false end end
  231.     elseif b_and(op, 0xFFF0) == 0x00C0 then
  232.       local n = b_and(op, 0xF)
  233.       for sx=0,127 do
  234.         for sy=63,n,-1 do disp[sx][sy] = disp[sx][sy-n] end
  235.         for sy=0,n-1 do disp[sx][sy] = false end
  236.       end
  237.     end
  238.   elseif n1 == 1 then PC = nnn
  239.   elseif n1 == 2 then SP = SP + 1; stack[SP] = PC; PC = nnn
  240.   elseif n1 == 3 then if V[x] == nn then PC = PC + 2 end
  241.   elseif n1 == 4 then if V[x] ~= nn then PC = PC + 2 end
  242.   elseif n1 == 5 then if V[x] == V[y] then PC = PC + 2 end
  243.   elseif n1 == 6 then V[x] = nn
  244.   elseif n1 == 7 then V[x] = b_and(V[x] + nn, 0xFF)
  245.   elseif n1 == 8 then
  246.     if n4 == 0 then V[x] = V[y]
  247.     elseif n4 == 1 then V[x] = b_or(V[x], V[y])
  248.     elseif n4 == 2 then V[x] = b_and(V[x], V[y])
  249.     elseif n4 == 3 then V[x] = b_xor(V[x], V[y])
  250.     elseif n4 == 4 then local s = V[x]+V[y] V[x]=b_and(s,0xFF) V[15]=(s>0xFF) and 1 or 0
  251.     elseif n4 == 5 then V[15]=(V[x]>=V[y]) and 1 or 0 V[x]=b_and(V[x]-V[y],0xFF)
  252.     elseif n4 == 6 then
  253.       if shiftQuirk then V[15]=b_and(V[x],1) V[x]=b_shr(V[x],1)
  254.       else V[15]=b_and(V[y],1) V[x]=b_shr(V[y],1) end
  255.     elseif n4 == 7 then V[15]=(V[y]>=V[x]) and 1 or 0 V[x]=b_and(V[y]-V[x],0xFF)
  256.     elseif n4 == 14 then
  257.       if shiftQuirk then V[15]=b_shr(V[x],7) V[x]=b_and(b_shl(V[x],1),0xFF)
  258.       else V[15]=b_shr(V[y],7) V[x]=b_and(b_shl(V[y],1),0xFF) end
  259.     end
  260.   elseif n1 == 9 then if V[x] ~= V[y] then PC = PC + 2 end
  261.   elseif n1 == 0xA then I = nnn
  262.   elseif n1 == 0xB then PC = nnn + V[0]
  263.   elseif n1 == 0xC then V[x] = b_and(random(0,255), nn)
  264.   elseif n1 == 0xD then
  265.     V[15] = 0
  266.     local w, h = (n4 == 0 and 16 or 8), (n4 == 0 and 16 or n4)
  267.     local limX, limY = (hires and 128 or 64), (hires and 64 or 32)
  268.     for r=0, h-1 do
  269.       local line = (n4 == 0) and b_or(b_shl(mem[I+r*2], 8), mem[I+r*2+1]) or mem[I+r]
  270.       for c=0, w-1 do
  271.         if b_and(line, b_shl(1, (w-1)-c)) ~= 0 then
  272.           local tx, ty = b_and(V[x]+c, limX-1), b_and(V[y]+r, limY-1)
  273.           if disp[tx][ty] then V[15] = 1 end
  274.           disp[tx][ty] = not disp[tx][ty]
  275.         end
  276.       end
  277.     end
  278.     return true
  279.   elseif n1 == 0xE then
  280.     if nn == 0x9E then if keys[V[x]] then PC = PC + 2 end
  281.     elseif nn == 0xA1 then if not keys[V[x]] then PC = PC + 2 end end
  282.   elseif n1 == 0xF then
  283.     if nn == 0x07 then V[x] = delayT
  284.     elseif nn == 0x0A then keyPending, keyWaitReg, keyWaitAddr = true, x, PC
  285.     elseif nn == 0x15 then delayT = V[x]
  286.     elseif nn == 0x18 then
  287.       soundT = V[x]
  288.       -- Start sound immediately if sound timer is set
  289.       if soundT > 0 and not soundActive then
  290.         startSound()
  291.         soundActive = true
  292.       end
  293.     elseif nn == 0x1E then I = b_and(I + V[x], 0xFFFF)
  294.     elseif nn == 0x29 then I = V[x] * 5
  295.     elseif nn == 0x30 then I = 80 + (V[x] * 10)
  296.     elseif nn == 0x33 then mem[I]=floor(V[x]/100) mem[I+1]=floor((V[x]%100)/10) mem[I+2]=V[x]%10
  297.     elseif nn == 0x55 then
  298.       for i=0,x do mem[I+i]=V[i] end
  299.       if not loadStoreQuirk then I = I + x + 1 end
  300.     elseif nn == 0x65 then
  301.       for i=0,x do V[i]=mem[I+i] end
  302.       if not loadStoreQuirk then I = I + x + 1 end
  303.     elseif nn == 0x75 then for i=0,math.min(x,7) do RPL[i]=V[i] end
  304.     elseif nn == 0x85 then for i=0,math.min(x,7) do V[i]=RPL[i] end end
  305.   end
  306.   return false
  307. end
  308.  
  309. loadFonts()
  310. local romPath = selectRom()
  311. if not romPath then return end
  312.  
  313. local f = io.open(romPath, "rb")
  314. local d = f:read("*a")
  315. f:close()
  316. for i=1, #d do mem[0x200+i-1] = d:byte(i) end
  317.  
  318. clearScreen()
  319. drawStatus()
  320. local nextTimer = os_clock()
  321.  
  322. while true do
  323.   local now = os_clock()
  324.   while true do
  325.     local ed = {ev.pull(0)}
  326.     if not ed[1] then break end
  327.     if ed[1] == "key_down" then
  328.       local code = ed[4]
  329.       local char = ed[3]
  330.       local k = s_lower(s_char(char))
  331.      
  332.       -- Sound toggle with 'S' key
  333.       if k == "s" and hasSoundCard then
  334.         toggleSound()
  335.       end
  336.      
  337.       if code == 200 then
  338.         instructionsPerLoop = instructionsPerLoop + 25
  339.         drawStatus()
  340.       elseif code == 208 then
  341.         instructionsPerLoop = math.max(1, instructionsPerLoop - 25)
  342.         drawStatus()
  343.       end
  344.       for i=0,15 do if k == s_sub(keyMap, i+1, i+1) then
  345.         keys[i] = true
  346.         if keyPending then V[keyWaitReg], keyPending = i, nil end
  347.       end end
  348.     elseif ed[1] == "key_up" then
  349.       local k = s_lower(s_char(ed[3]))
  350.       for i=0,15 do if k == s_sub(keyMap, i+1, i+1) then keys[i] = false end end
  351.     elseif ed[1] == "interrupted" then
  352.       -- Cleanup on interrupt
  353.       if hasSoundCard then
  354.         sound.close(soundChannel)
  355.       end
  356.       return
  357.     end
  358.   end
  359.  
  360.   local redraw = false
  361.   for _=1, instructionsPerLoop do if step() then redraw = true end end
  362.   if redraw then draw() end
  363.  
  364.   if now >= nextTimer then
  365.     if delayT > 0 then delayT = delayT - 1 end
  366.     if soundT > 0 then
  367.       soundT = soundT - 1
  368.       if not soundActive then
  369.         startSound()
  370.         soundActive = true
  371.       end
  372.     else
  373.       if soundActive then
  374.         stopSound()
  375.         soundActive = false
  376.       end
  377.     end
  378.     nextTimer = now + 0.016
  379.   end
  380. end
Advertisement
Add Comment
Please, Sign In to add comment