Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local WIDTH, HEIGHT = 64, 32
- local FONT_START = 0x50
- local PROGRAM_START = 0x200
- local VERSION = "V 1.0.5"
- local FONT = {
- 0xF0,0x90,0x90,0x90,0xF0, 0x20,0x60,0x20,0x20,0x70, 0xF0,0x10,0xF0,0x80,0xF0, 0xF0,0x10,0xF0,0x10,0xF0,
- 0x90,0x90,0xF0,0x10,0x10, 0xF0,0x80,0xF0,0x10,0xF0, 0xF0,0x80,0xF0,0x90,0xF0, 0xF0,0x10,0x20,0x40,0x40,
- 0xF0,0x90,0xF0,0x90,0xF0, 0xF0,0x90,0xF0,0x10,0xF0, 0xF0,0x90,0xF0,0x90,0x90, 0xE0,0x90,0xE0,0x90,0xE0,
- 0xF0,0x80,0x80,0x80,0xF0, 0xE0,0x90,0x90,0x90,0xE0, 0xF0,0x80,0xF0,0x80,0xF0, 0xF0,0x80,0xF0,0x80,0x80
- }
- local KEYPAD_MAP = {
- ["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
- }
- local COLOR_LIST = {colors.white, colors.lightGray, colors.gray, colors.black}
- local COLOR_NAMES = {"White", "LightGray", "Gray", "Black"}
- for _,c in ipairs{colors.red, colors.orange, colors.yellow, colors.lime, colors.green, colors.cyan, colors.lightBlue, colors.blue, colors.purple, colors.magenta, colors.pink, colors.brown} do
- table.insert(COLOR_LIST, c)
- end
- for _,n in ipairs{"Red","Orange","Yellow","Lime","Green","Cyan","LightBlue","Blue","Purple","Magenta","Pink","Brown"} do
- table.insert(COLOR_NAMES, n)
- end
- local function color_index(c)
- for i,v in ipairs(COLOR_LIST) do if v==c then return i end end
- return 1
- end
- local CC_PALETTE = {
- [colors.white] = {0xF0, 0xF0, 0xF0},
- [colors.orange] = {0xF2, 0xB2, 0x2E},
- [colors.magenta] = {0xC7, 0x4E, 0xBD},
- [colors.lightBlue] = {0x3A, 0xA2, 0xD8},
- [colors.yellow] = {0xDE, 0xD6, 0x2C},
- [colors.lime] = {0x41, 0xCD, 0x34},
- [colors.pink] = {0xD8, 0x4B, 0xA7},
- [colors.gray] = {0x4F, 0x4F, 0x4F},
- [colors.lightGray] = {0x99, 0x99, 0x99},
- [colors.cyan] = {0x16, 0x8A, 0x9D},
- [colors.purple] = {0x7E, 0x3E, 0x9D},
- [colors.blue] = {0x2E, 0x32, 0x99},
- [colors.brown] = {0x4F, 0x32, 0x14},
- [colors.green] = {0x35, 0x6E, 0x19},
- [colors.red] = {0xB2, 0x1A, 0x21},
- [colors.black] = {0x1D, 0x1D, 0x21},
- }
- local SOUND_MODES = {"Off", "Minecraft", "Beeper"}
- local function sound_mode_name(idx) return SOUND_MODES[idx] or "Off" end
- local SPEEDS = {500, 1000, 2000, 4000, 8000, 10000}
- local SPEED_NAMES = {"500Hz", "1000Hz", "2000Hz (Normal)", "4000Hz", "8000Hz", "10000Hz"}
- local function speed_name(idx) return SPEED_NAMES[idx] or tostring(SPEEDS[idx]).."Hz" end
- local PALETTE_MONO = {
- bg = colors.black, fg = colors.white, box = colors.gray,
- hilite = colors.white, hilitefg = colors.black, btn = colors.gray,
- }
- local PALETTE_COLOR = {
- bg = colors.blue,
- fg = colors.white,
- box = colors.cyan,
- hilite = colors.cyan,
- hilitefg = colors.black,
- btn = colors.cyan,
- }
- local function isColor()
- return term.isColor and term.isColor()
- end
- local function isVanillaCC()
- local ok, f = pcall(fs.open, "__cc_vanilla_test__", "rb")
- if ok and f then
- f.close()
- fs.delete("__cc_vanilla_test__")
- return false
- end
- if term.nativePaletteColor then return false end
- if os.version and tostring(os.version()):find("CC:Tweaked") then return false end
- return true
- end
- local PCM_BEEP_FILE = "2obgw2.raw"
- local PCM_BEEP_URL = "https://files.catbox.moe/2obgw2.raw"
- local pcm_beep_samples = nil
- local pcm_beep_pos = 1
- local pcm_beep_active = false
- local pcm_beep_chunk_size = 800
- local function ensure_pcm_beep()
- if not fs.exists(PCM_BEEP_FILE) then
- print("Downloading beeper PCM sound...")
- if http and http.get then
- local h = http.get(PCM_BEEP_URL)
- if h then
- local f = fs.open(PCM_BEEP_FILE, "wb")
- if f then
- while true do
- local b = h.read(32768)
- if not b then break end
- f.write(b)
- end
- f.close()
- h.close()
- print("Downloaded beeper sound.")
- else
- print("Failed to open file for writing: "..PCM_BEEP_FILE)
- end
- else
- print("Failed to download PCM beep from "..PCM_BEEP_URL)
- end
- else
- print("HTTP API not available! Cannot download PCM beep.")
- end
- end
- end
- local function load_pcm_beep()
- if pcm_beep_samples then return end
- local filename = PCM_BEEP_FILE
- local isUnsigned = true
- local inputSampleRate = 32000
- local outputSampleRate = 48000
- local volume = 1
- local removeDC = false
- local file = fs.open(filename, "rb")
- if not file then
- print("Could not open file: " .. filename)
- return
- end
- local samples = {}
- local sum, count = 0, 0
- while true do
- local byte = file.read()
- if not byte then break end
- if isUnsigned then
- byte = byte - 128
- end
- samples[#samples + 1] = byte
- sum = sum + byte
- count = count + 1
- end
- file.close()
- if #samples == 0 then
- print("PCM file is empty or unreadable.")
- return
- end
- if removeDC then
- local avg = sum / count
- for i = 1, #samples do
- samples[i] = samples[i] - avg
- end
- end
- if inputSampleRate ~= outputSampleRate then
- local resampled = {}
- local ratio = inputSampleRate / outputSampleRate
- for i = 1, math.floor(#samples / ratio) do
- local src = (i - 1) * ratio + 1
- local idx = math.floor(src)
- local frac = src - idx
- local s1 = samples[idx] or 0
- local s2 = samples[idx + 1] or s1
- resampled[i] = s1 + (s2 - s1) * frac
- end
- samples = resampled
- end
- for i = 1, #samples do
- local sample = math.floor(samples[i] * volume + 0.5)
- if sample > 127 then sample = 127 end
- if sample < -128 then sample = -128 end
- samples[i] = sample
- end
- pcm_beep_samples = samples
- end
- local function start_pcm_beep()
- load_pcm_beep()
- pcm_beep_pos = 1
- pcm_beep_active = true
- end
- local function stop_pcm_beep()
- pcm_beep_active = false
- end
- local function play_pcm_beep_chunk()
- if not pcm_beep_active or not pcm_beep_samples then return end
- local speaker = peripheral.find and peripheral.find("speaker")
- if not speaker then return end
- local chunk = {}
- for i = 1, pcm_beep_chunk_size do
- if pcm_beep_pos > #pcm_beep_samples then
- pcm_beep_active = false
- break
- end
- chunk[i] = pcm_beep_samples[pcm_beep_pos]
- pcm_beep_pos = pcm_beep_pos + 1
- end
- if #chunk > 0 then
- while not speaker.playAudio(chunk) do
- os.pullEvent("speaker_audio_empty")
- end
- end
- end
- local memory, V, I, pc, stack, delay_timer, sound_timer, display, keypad
- local function reset_state()
- memory = {}
- for i=0,4095 do memory[i]=0 end
- for i=1,#FONT do memory[FONT_START+i-1]=FONT[i] end
- V = {}
- for i=0,0xF do V[i]=0 end
- I, pc = 0, PROGRAM_START
- stack = {}
- delay_timer, sound_timer = 0, 0
- display = {}
- for y=1,HEIGHT do display[y]={} for x=1,WIDTH do display[y][x]=false end end
- keypad = {}
- for i=0,0xF do keypad[i]=false end
- end
- local function load_rom(path)
- local f = fs.open(path, "rb")
- if not f then error("ROM not found: "..path) end
- local i=0
- while true do
- local b = f.read()
- if not b then break end
- memory[PROGRAM_START+i] = b
- i = i+1
- end
- f.close()
- end
- local function get_mosaic_char(p1, p2, p3, p4, p5, p6)
- return string.char(128 + p1 + p2*2 + p3*4 + p4*8 + p5*16 + p6*32)
- end
- local function draw_rect(x, y, w, h, color)
- term.setBackgroundColor(color)
- for dy=0,h-1 do
- term.setCursorPos(x, y+dy)
- term.write((" "):rep(w))
- end
- end
- local function draw_text(x, y, text, fg, bg)
- term.setCursorPos(x, y)
- term.setTextColor(fg)
- term.setBackgroundColor(bg)
- term.write(text)
- end
- local function mouse_in(mx, my, x, y, w, h)
- return mx >= x and mx < x+w and my >= y and my < y+h
- end
- local function rom_menu_ui(rom_dir, use_mono)
- local palette = use_mono and PALETTE_MONO or PALETTE_COLOR
- while true do
- term.setBackgroundColor(palette.bg)
- term.setTextColor(palette.fg)
- term.clear()
- local files = fs.list(rom_dir)
- local roms = {}
- for _,f in ipairs(files) do
- if f:lower():match("%.ch8$") then table.insert(roms, f) end
- end
- table.sort(roms)
- local w, h = term.getSize()
- local menu_w, menu_h = 36, math.min(#roms, 12)+8
- local mx, my = math.floor((w-menu_w)/2)+1, math.floor((h-menu_h)/2)+1
- local sel = 1
- local scroll = 0
- local max_visible = menu_h-8
- local btn_settings = {mx, my+menu_h-5, menu_w, 1}
- local btn_quit = {mx, my+menu_h-4, menu_w, 1}
- local btn_about = {mx, my+menu_h-2, menu_w, 1}
- while true do
- draw_rect(mx, my, menu_w, menu_h, palette.box)
- draw_rect(mx+1, my+1, menu_w-2, menu_h-2, palette.bg)
- draw_text(mx+2, my+1, "CHIP-8 ROMs in: "..rom_dir, palette.fg, palette.bg)
- for i=1,max_visible do
- local idx = i+scroll
- if roms[idx] then
- local fg = (idx==sel) and palette.hilitefg or palette.fg
- local bg = (idx==sel) and palette.hilite or palette.bg
- draw_rect(mx+2, my+1+i, menu_w-4, 1, bg)
- draw_text(mx+3, my+1+i, roms[idx], fg, bg)
- end
- end
- draw_rect(btn_settings[1], btn_settings[2], btn_settings[3], btn_settings[4], palette.btn)
- draw_rect(btn_quit[1], btn_quit[2], btn_quit[3], btn_quit[4], palette.btn)
- draw_rect(btn_about[1], btn_about[2], btn_about[3], btn_about[4], palette.btn)
- draw_text(btn_settings[1]+math.floor((menu_w-11)/2), btn_settings[2], "[ Settings ]", palette.fg, palette.btn)
- draw_text(btn_quit[1]+math.floor((menu_w-7)/2), btn_quit[2], "[ Quit ]", palette.fg, palette.btn)
- draw_text(btn_about[1]+math.floor((menu_w-8)/2), btn_about[2], "[ About ]", palette.fg, palette.btn)
- local e = {os.pullEvent()}
- if e[1] == "mouse_click" then
- local click_x, click_y = e[3], e[4]
- for i=1,max_visible do
- local idx = i+scroll
- if roms[idx] and mouse_in(click_x, click_y, mx+2, my+1+i, menu_w-4, 1) then
- return fs.combine(rom_dir, roms[idx])
- end
- end
- if mouse_in(click_x, click_y, btn_settings[1], btn_settings[2], btn_settings[3], btn_settings[4]) then
- return "settings"
- end
- if mouse_in(click_x, click_y, btn_quit[1], btn_quit[2], btn_quit[3], btn_quit[4]) then
- return nil
- end
- if mouse_in(click_x, click_y, btn_about[1], btn_about[2], btn_about[3], btn_about[4]) then
- return "about"
- end
- elseif e[1] == "key" then
- if e[2] == keys.down then
- sel = math.min(sel+1, #roms)
- if sel > scroll+max_visible then scroll = scroll+1 end
- elseif e[2] == keys.up then
- sel = math.max(sel-1, 1)
- if sel < scroll+1 then scroll = math.max(sel-1, 0) end
- elseif e[2] == keys.enter then
- if roms[sel] then return fs.combine(rom_dir, roms[sel]) end
- elseif e[2] == keys.q then
- return nil
- elseif e[2] == keys.s then
- return "settings"
- elseif e[2] == keys.a then
- return "about"
- end
- end
- end
- end
- end
- local function read_bmp_mosaic(path, pxw, pxh, mono)
- local file = fs.open(path, "rb")
- if not file then return nil end
- local function readu8() return file.read() end
- local function readu16()
- local l = file.read() or 0
- local h = file.read() or 0
- return l + h*256
- end
- local function readu32()
- local l = file.read() or 0
- local h = file.read() or 0
- local i = file.read() or 0
- local j = file.read() or 0
- return l + h*256 + i*65536 + j*16777216
- end
- if file.read() ~= 0x42 or file.read() ~= 0x4D then file.close() return nil end
- file.read(); file.read(); file.read(); file.read()
- file.read(); file.read(); file.read(); file.read()
- local offset = readu32()
- local header_size = readu32()
- local width = readu32()
- local height = readu32()
- file.read(); file.read()
- local bpp = readu16()
- if bpp ~= 24 then file.close() return nil end
- file.read(); file.read(); file.read(); file.read()
- file.read(); file.read(); file.read(); file.read()
- file.read(); file.read(); file.read(); file.read()
- file.read(); file.read(); file.read(); file.read()
- file.read(); file.read(); file.read(); file.read()
- file.seek("set", offset)
- local img = {}
- local pad = (4 - (width*3)%4)%4
- for y=height-1,0,-1 do
- img[y+1] = {}
- for x=1,width do
- local b = readu8() or 0
- local g = readu8() or 0
- local r = readu8() or 0
- img[y+1][x] = {r,g,b}
- end
- for i=1,pad do file.read() end
- end
- file.close()
- local scale_x = width/pxw
- local scale_y = height/pxh
- local work = {}
- for y=1,pxh do
- work[y] = {}
- for x=1,pxw do
- local px = math.floor((x-1)*scale_x)+1
- local py = math.floor((y-1)*scale_y)+1
- local rgb = img[py][px]
- work[y][x] = {rgb[1], rgb[2], rgb[3]}
- end
- end
- local mosaic = {}
- for y=1,pxh do
- mosaic[y] = {}
- for x=1,pxw do
- local old = work[y][x]
- local r, g, b = old[1], old[2], old[3]
- local best, bestc, bestlum = math.huge, colors.white, 255
- if mono then
- local lum = (r+g+b)/3
- bestc = lum > 128 and colors.white or colors.black
- bestlum = bestc == colors.white and 255 or 0
- else
- for _,c in ipairs(COLOR_LIST) do
- local pal = CC_PALETTE[c] or {255,255,255}
- local cr, cg, cb = pal[1], pal[2], pal[3]
- local d = (r-cr)^2 + (g-cg)^2 + (b-cb)^2
- if d < best then best, bestc = d, c; bestlum = (cr+cg+cb)/3 end
- end
- end
- mosaic[y][x] = bestc
- local err_r, err_g, err_b
- if mono then
- local err = ((r+g+b)/3) - bestlum
- err_r, err_g, err_b = err, err, err
- else
- local pal = CC_PALETTE[bestc] or {255,255,255}
- err_r, err_g, err_b = r-pal[1], g-pal[2], b-pal[3]
- end
- local function dither(xo, yo, factor)
- local nx, ny = x+xo, y+yo
- if nx>=1 and nx<=pxw and ny>=1 and ny<=pxh then
- local n = work[ny][nx]
- n[1] = math.max(0, math.min(255, n[1] + err_r * factor))
- n[2] = math.max(0, math.min(255, n[2] + err_g * factor))
- n[3] = math.max(0, math.min(255, n[3] + err_b * factor))
- end
- end
- dither( 1, 0, 7/16)
- dither(-1, 1, 3/16)
- dither( 0, 1, 5/16)
- dither( 1, 1, 1/16)
- end
- end
- return mosaic
- end
- local function render_bmp_mosaic_highres(path, sx, sy, maxw, maxh, mono)
- local bmp = read_bmp_mosaic(path, maxw*2, maxh*3, mono)
- if not bmp then return end
- for y=1,maxh do
- term.setCursorPos(sx, sy+y-1)
- for x=1,maxw do
- local px = (x-1)*2+1
- local py = (y-1)*3+1
- local p1 = bmp[py] and bmp[py][px] or colors.black
- local p2 = bmp[py] and bmp[py][px+1] or colors.black
- local p3 = bmp[py+1] and bmp[py+1][px] or colors.black
- local p4 = bmp[py+1] and bmp[py+1][px+1] or colors.black
- local p5 = bmp[py+2] and bmp[py+2][px] or colors.black
- local p6 = bmp[py+2] and bmp[py+2][px+1] or colors.black
- if p1==p2 and p1==p3 and p1==p4 and p1==p5 and p1==p6 then
- term.setBackgroundColor(p1)
- term.write(" ")
- else
- local count = {}
- for _,v in ipairs{p1,p2,p3,p4,p5,p6} do count[v]=(count[v] or 0)+1 end
- local bgc, bgc_n = p1, 0
- for c,n in pairs(count) do if n>bgc_n then bgc, bgc_n = c, n end end
- local fgc, fgc_n = nil, 0
- for c,n in pairs(count) do if n<bgc_n and n>fgc_n then fgc, fgc_n = c, n end end
- local fg = fgc or p1
- local bg = bgc
- local bits = {}
- for i,v in ipairs{p1,p2,p3,p4,p5,p6} do bits[i]=(v==fg) and 1 or 0 end
- term.setTextColor(fg)
- term.setBackgroundColor(bg)
- term.write(string.char(128 + bits[1] + bits[2]*2 + bits[3]*4 + bits[4]*8 + bits[5]*16 + bits[6]*32))
- end
- end
- end
- end
- local function about_section(ver, use_mono)
- local palette = use_mono and PALETTE_MONO or PALETTE_COLOR
- term.setBackgroundColor(palette.bg)
- term.setTextColor(palette.fg)
- term.clear()
- local w, h = term.getSize()
- local imgw, imgh = 20, 12
- local logo_path = "logo.bmp"
- local vanilla = isVanillaCC()
- if not vanilla then
- if not fs.exists(logo_path) then
- local url = "https://files.catbox.moe/5k0smq.bmp"
- print("Downloading logo.bmp...")
- local ok = shell.run("wget", url, logo_path)
- if not ok then
- print("Failed to download logo.bmp! (HTTP must be enabled)")
- sleep(2)
- end
- end
- end
- draw_text(3, 2, "Bow to the fecal-lord", palette.fg, palette.bg)
- if not vanilla and fs.exists(logo_path) then
- render_bmp_mosaic_highres(logo_path, 3, 3, imgw, imgh, use_mono)
- elseif vanilla then
- draw_text(3, 3, "[BMP logo disabled on vanilla ComputerCraft]", palette.fg, palette.bg)
- end
- local tx = imgw+6
- local ty = 6
- draw_text(tx, ty, "3C8 Coded By", palette.fg, palette.bg)
- draw_text(tx, ty+2, "nonogamer9 ", palette.fg, palette.bg)
- local ver_color = use_mono and colors.gray or colors.red
- term.setTextColor(ver_color)
- draw_text(tx+11, ty+2, ver, ver_color, palette.bg)
- local btnw = 22
- local btnx, btny = tx, ty+6
- draw_rect(btnx, btny, btnw, 1, palette.btn)
- draw_text(btnx+math.floor((btnw-18)/2), btny, "Thanks Shitman!", palette.fg, palette.btn)
- while true do
- local e = {os.pullEvent()}
- if e[1] == "mouse_click" then
- local mx, my = e[3], e[4]
- if mx >= btnx and mx < btnx+btnw and my == btny then return end
- elseif e[1] == "key" and e[2] == keys.enter then
- return
- end
- end
- end
- local function settings_menu_ui(settings, use_mono)
- local palette = use_mono and PALETTE_MONO or PALETTE_COLOR
- while true do
- term.setBackgroundColor(palette.bg)
- term.setTextColor(palette.fg)
- term.clear()
- local w, h = term.getSize()
- local menu_w, menu_h = 36, 15
- local mx, my = math.floor((w-menu_w)/2)+1, math.floor((h-menu_h)/2)+1
- local btn_rom = {mx+2, my+2, menu_w-4, 1}
- local btn_mono = {mx+2, my+4, menu_w-4, 1}
- local btn_sound = {mx+2, my+6, menu_w-4, 1}
- local btn_speed = {mx+2, my+8, menu_w-4, 1}
- local btn_back = {mx+2, my+12, menu_w-4, 1}
- draw_rect(mx, my, menu_w, menu_h, palette.box)
- draw_rect(mx+1, my+1, menu_w-2, menu_h-2, palette.bg)
- draw_text(mx+2, my+1, "Settings", palette.fg, palette.bg)
- draw_rect(btn_rom[1], btn_rom[2], btn_rom[3], btn_rom[4], palette.btn)
- draw_text(btn_rom[1]+2, btn_rom[2], "ROM directory: "..settings.rom_dir, palette.fg, palette.btn)
- draw_rect(btn_mono[1], btn_mono[2], btn_mono[3], btn_mono[4], palette.btn)
- draw_text(btn_mono[1]+2, btn_mono[2], "Monochrome: "..(settings.monochrome and "ON" or "OFF"), palette.fg, palette.btn)
- draw_rect(btn_sound[1], btn_sound[2], btn_sound[3], btn_sound[4], palette.btn)
- draw_text(btn_sound[1]+2, btn_sound[2], "Sound: "..sound_mode_name(settings.sound_mode or 1), palette.fg, palette.btn)
- draw_rect(btn_speed[1], btn_speed[2], btn_speed[3], btn_speed[4], palette.btn)
- draw_text(btn_speed[1]+2, btn_speed[2], "Speed: "..speed_name(settings.speed or 1), palette.fg, palette.btn)
- draw_rect(btn_back[1], btn_back[2], btn_back[3], btn_back[4], palette.btn)
- draw_text(btn_back[1]+math.floor((btn_back[3]-4)/2), btn_back[2], "[ Back ]", palette.fg, palette.btn)
- local e = {os.pullEvent()}
- if e[1] == "mouse_click" then
- local x, y = e[3], e[4]
- if mouse_in(x, y, btn_rom[1], btn_rom[2], btn_rom[3], btn_rom[4]) then
- term.setCursorPos(mx+2, my+menu_h)
- term.setTextColor(palette.fg)
- term.setBackgroundColor(palette.bg)
- term.clearLine()
- term.write("New ROM dir: ")
- local newdir = read()
- if fs.exists(newdir) and fs.isDir(newdir) then
- settings.rom_dir = newdir
- else
- term.setCursorPos(mx+2, my+menu_h)
- term.write("Invalid directory!"); sleep(1)
- end
- elseif mouse_in(x, y, btn_mono[1], btn_mono[2], btn_mono[3], btn_mono[4]) then
- settings.monochrome = not settings.monochrome
- elseif mouse_in(x, y, btn_sound[1], btn_sound[2], btn_sound[3], btn_sound[4]) then
- settings.sound_mode = ((settings.sound_mode or 1) % #SOUND_MODES) + 1
- elseif mouse_in(x, y, btn_speed[1], btn_speed[2], btn_speed[3], btn_speed[4]) then
- settings.speed = ((settings.speed or 1) % #SPEEDS) + 1
- elseif mouse_in(x, y, btn_back[1], btn_back[2], btn_back[3], btn_back[4]) then
- return
- end
- elseif e[1] == "key" then
- if e[2] == keys.q or e[2] == keys.enter then
- return
- elseif e[2] == keys.m then
- settings.monochrome = not settings.monochrome
- elseif e[2] == keys.n then
- settings.sound_mode = ((settings.sound_mode or 1) % #SOUND_MODES) + 1
- elseif e[2] == keys.h then
- settings.speed = ((settings.speed or 1) % #SPEEDS) + 1
- end
- end
- end
- end
- local last_timer = os.clock()
- local function play_chip8_sound(mode)
- if mode == 2 then
- if commands and commands.execAsync then
- commands.execAsync("playsound block.note_block.pling @a ~ ~ ~ 0.5 2")
- elseif peripheral.find and peripheral.find("speaker") then
- local speaker = peripheral.find("speaker")
- if speaker then speaker.playNote("pling", 2, 1) end
- end
- elseif mode == 3 then
- local speaker = peripheral.find and peripheral.find("speaker")
- if speaker then
- speaker.playSound("minecraft:block.note_block.pling", 1, 2)
- elseif os.beep then
- os.beep(880, 0.05)
- elseif commands and commands.execAsync then
- commands.execAsync("playsound block.note_block.pling @a ~ ~ ~ 1 2")
- end
- end
- end
- local last_timer = os.clock()
- local function play_chip8_sound(mode)
- if mode == 2 then
- if commands and commands.execAsync then
- commands.execAsync("playsound block.note_block.pling @a ~ ~ ~ 0.5 2")
- elseif peripheral.find and peripheral.find("speaker") then
- local speaker = peripheral.find("speaker")
- if speaker then speaker.playNote("pling", 2, 1) end
- end
- elseif mode == 3 then
- play_pcm_beep()
- end
- end
- local function update_timers(settings)
- local now = os.clock()
- if now - last_timer >= 1/60 then
- if delay_timer > 0 then delay_timer = delay_timer - 1 end
- if sound_timer > 0 then
- sound_timer = sound_timer - 1
- if settings and settings.sound_mode and settings.sound_mode > 1 then
- play_chip8_sound(settings.sound_mode)
- end
- end
- last_timer = now
- end
- end
- local last_timer = os.clock()
- local function play_chip8_sound(mode)
- if mode == 2 then
- if commands and commands.execAsync then
- commands.execAsync("playsound block.note_block.pling @a ~ ~ ~ 0.5 2")
- elseif peripheral.find and peripheral.find("speaker") then
- local speaker = peripheral.find("speaker")
- if speaker then speaker.playNote("pling", 2, 1) end
- end
- elseif mode == 3 then
- end
- end
- local function update_timers(settings)
- local now = os.clock()
- if now - last_timer >= 1/60 then
- if delay_timer > 0 then delay_timer = delay_timer - 1 end
- if sound_timer > 0 then
- if settings and settings.sound_mode and settings.sound_mode == 3 then
- if not pcm_beep_active then
- start_pcm_beep()
- end
- play_pcm_beep_chunk()
- else
- if settings and settings.sound_mode and settings.sound_mode > 1 then
- play_chip8_sound(settings.sound_mode)
- end
- end
- sound_timer = sound_timer - 1
- else
- stop_pcm_beep()
- end
- last_timer = now
- end
- end
- local function draw_display(screenX, screenY, fg, bg)
- for j = 1, 12 do
- term.setCursorPos(screenX, screenY + j - 1)
- for i = 1, 32 do
- local y0 = (j - 1) * 3
- local x0 = (i - 1) * 2
- local p1 = display[y0 + 1] and display[y0 + 1][x0 + 1] and 1 or 0
- local p2 = display[y0 + 1] and display[y0 + 1][x0 + 2] and 1 or 0
- local p3 = display[y0 + 2] and display[y0 + 2][x0 + 1] and 1 or 0
- local p4 = display[y0 + 2] and display[y0 + 2][x0 + 2] and 1 or 0
- local p5 = display[y0 + 3] and display[y0 + 3][x0 + 1] and 1 or 0
- local p6 = display[y0 + 3] and display[y0 + 3][x0 + 2] and 1 or 0
- if p6 == 1 then
- p1, p2, p3, p4, p5 = 1 - p1, 1 - p2, 1 - p3, 1 - p4, 1 - p5
- term.setTextColor(bg)
- term.setBackgroundColor(fg)
- term.write(get_mosaic_char(p1, p2, p3, p4, p5, 0))
- term.setTextColor(fg)
- term.setBackgroundColor(bg)
- else
- term.write(get_mosaic_char(p1, p2, p3, p4, p5, 0))
- end
- end
- end
- end
- local function emulate(romfile, fg, bg, settings)
- reset_state()
- load_rom(romfile)
- local monochrome = settings.monochrome
- local running, paused = true, false
- local function center()
- local screenWidth, screenHeight = term.getSize()
- return math.floor((screenWidth - 32) / 2) + 1, math.floor((screenHeight - 12) / 2) + 1, screenHeight
- end
- local screenX, screenY, screenHeight = center()
- term.setTextColor(fg)
- term.setBackgroundColor(bg)
- term.clear()
- draw_display(screenX, screenY, fg, bg)
- local draw_flag = true
- local frameTimer = os.startTimer(1/60)
- local fg_idx, bg_idx = color_index(fg), color_index(bg)
- local waiting_key = false
- local waiting_reg = nil
- while running do
- local e = {os.pullEvent()}
- if e[1] == "terminate" then
- return "quit"
- elseif e[1] == "key" then
- local k = keys.getName(e[2])
- if k then
- k = k:lower()
- if KEYPAD_MAP[k] then
- keypad[KEYPAD_MAP[k]] = true
- if waiting_key then
- V[waiting_reg] = KEYPAD_MAP[k]
- waiting_key = false
- waiting_reg = nil
- end
- end
- if not waiting_key then
- if e[2] == keys.enter then
- return "menu"
- elseif e[2] == keys.space then
- paused = not paused
- elseif not monochrome then
- if e[2] == keys.up then
- bg_idx = (bg_idx % #COLOR_LIST) + 1
- bg = COLOR_LIST[bg_idx]
- term.setBackgroundColor(bg)
- draw_flag = true
- elseif e[2] == keys.down then
- bg_idx = (bg_idx - 2) % #COLOR_LIST + 1
- bg = COLOR_LIST[bg_idx]
- term.setBackgroundColor(bg)
- draw_flag = true
- elseif e[2] == keys.right then
- fg_idx = (fg_idx % #COLOR_LIST) + 1
- fg = COLOR_LIST[fg_idx]
- term.setTextColor(fg)
- draw_flag = true
- elseif e[2] == keys.left then
- fg_idx = (fg_idx - 2) % #COLOR_LIST + 1
- fg = COLOR_LIST[fg_idx]
- term.setTextColor(fg)
- draw_flag = true
- end
- end
- end
- end
- elseif e[1] == "key_up" then
- local k = keys.getName(e[2])
- if k and KEYPAD_MAP[k:lower()] then
- keypad[KEYPAD_MAP[k:lower()]] = false
- end
- elseif e[1] == "term_resize" then
- screenX, screenY, screenHeight = center()
- term.clear()
- draw_flag = true
- elseif e[1] == "timer" and e[2] == frameTimer then
- frameTimer = os.startTimer(1/60)
- if not paused then
- local hz = SPEEDS[settings.speed or 1] or 500
- for i=1,math.floor(hz/60) do
- if waiting_key then break end
- local opcode = bit32.lshift(memory[pc],8) + memory[pc+1]
- pc = (pc + 2) % 4096
- local nnn = bit32.band(opcode, 0x0FFF)
- local nn = bit32.band(opcode, 0x00FF)
- local n = bit32.band(opcode, 0x000F)
- local x = bit32.band(bit32.rshift(opcode,8),0xF)
- local y = bit32.band(bit32.rshift(opcode,4),0xF)
- if opcode == 0x00E0 then
- for y1=1,HEIGHT do for x1=1,WIDTH do display[y1][x1]=false end end
- draw_flag = true
- elseif opcode == 0x00EE then
- pc = table.remove(stack)
- elseif bit32.band(opcode,0xF000) == 0x1000 then
- pc = nnn
- elseif bit32.band(opcode,0xF000) == 0x2000 then
- table.insert(stack, pc)
- pc = nnn
- elseif bit32.band(opcode,0xF000) == 0x3000 then
- if V[x]==nn then pc=pc+2 end
- elseif bit32.band(opcode,0xF000) == 0x4000 then
- if V[x]~=nn then pc=pc+2 end
- elseif bit32.band(opcode,0xF00F) == 0x5000 then
- if V[x]==V[y] then pc=pc+2 end
- elseif bit32.band(opcode,0xF000) == 0x6000 then
- V[x]=nn
- elseif bit32.band(opcode,0xF000) == 0x7000 then
- V[x]=(V[x]+nn)%256
- elseif bit32.band(opcode,0xF00F)==0x8000 then
- V[x]=V[y]
- elseif bit32.band(opcode,0xF00F)==0x8001 then
- V[x]=bit32.bor(V[x],V[y])
- elseif bit32.band(opcode,0xF00F)==0x8002 then
- V[x]=bit32.band(V[x],V[y])
- elseif bit32.band(opcode,0xF00F)==0x8003 then
- V[x]=bit32.bxor(V[x],V[y])
- elseif bit32.band(opcode,0xF00F)==0x8004 then
- local sum = V[x]+V[y]
- V[0xF] = sum>255 and 1 or 0
- V[x]=sum%256
- elseif bit32.band(opcode,0xF00F)==0x8005 then
- V[0xF] = (V[x] >= V[y]) and 1 or 0
- V[x]=(V[x]-V[y])%256
- elseif bit32.band(opcode,0xF00F)==0x8006 then
- V[0xF] = bit32.band(V[x],1)
- V[x]=bit32.rshift(V[x],1)
- elseif bit32.band(opcode,0xF00F)==0x8007 then
- V[0xF] = (V[y] >= V[x]) and 1 or 0
- V[x]=(V[y]-V[x])%256
- elseif bit32.band(opcode,0xF00F)==0x800E then
- V[0xF] = bit32.band(bit32.rshift(V[x],7),1)
- V[x]=(bit32.lshift(V[x],1)%256)
- elseif bit32.band(opcode,0xF00F)==0x9000 then
- if V[x]~=V[y] then pc=pc+2 end
- elseif bit32.band(opcode,0xF000)==0xA000 then
- I = nnn
- elseif bit32.band(opcode,0xF000)==0xB000 then
- pc = nnn + V[0]
- elseif bit32.band(opcode,0xF000)==0xC000 then
- V[x]=bit32.band(math.random(0,255),nn)
- elseif bit32.band(opcode,0xF000)==0xD000 then
- local vx, vy = V[x]%WIDTH, V[y]%HEIGHT
- V[0xF]=0
- for row=0,n-1 do
- if vy+row>=HEIGHT then break end
- local sprite = memory[I+row]
- for col=0,7 do
- if vx+col>=WIDTH then break end
- local bit = bit32.band(sprite, bit32.rshift(0x80,col)) ~= 0
- if bit then
- if display[vy+row+1][vx+col+1] then
- V[0xF]=1
- end
- display[vy+row+1][vx+col+1] = bit32.bxor(display[vy+row+1][vx+col+1] and 1 or 0, 1)==1
- end
- end
- end
- draw_flag = true
- elseif bit32.band(opcode,0xF0FF)==0xE09E then
- if keypad[V[x]] then pc=pc+2 end
- elseif bit32.band(opcode,0xF0FF)==0xE0A1 then
- if not keypad[V[x]] then pc=pc+2 end
- elseif bit32.band(opcode,0xF0FF)==0xF007 then
- V[x]=delay_timer
- elseif bit32.band(opcode,0xF0FF)==0xF00A then
- waiting_key = true
- waiting_reg = x
- elseif bit32.band(opcode,0xF0FF)==0xF015 then
- delay_timer=V[x]
- elseif bit32.band(opcode,0xF0FF)==0xF018 then
- sound_timer=V[x]
- elseif bit32.band(opcode,0xF0FF)==0xF01E then
- I = (I+V[x])%4096
- elseif bit32.band(opcode,0xF0FF)==0xF029 then
- I = FONT_START + (V[x]%16)*5
- elseif bit32.band(opcode,0xF0FF)==0xF033 then
- local val = V[x]
- memory[I] = math.floor(val/100)
- memory[I+1] = math.floor((val%100)/10)
- memory[I+2] = val%10
- elseif bit32.band(opcode,0xF0FF)==0xF055 then
- for idx=0,x do memory[I+idx]=V[idx] end
- elseif bit32.band(opcode,0xF0FF)==0xF065 then
- for idx=0,x do V[idx]=memory[I+idx] end
- end
- end
- update_timers(settings)
- if draw_flag then
- draw_display(screenX, screenY, fg, bg)
- draw_flag = false
- end
- end
- term.setCursorPos(1, screenHeight)
- term.clearLine()
- local status =
- "Space: Pause Enter: Menu " ..
- (monochrome and "[Monochrome]" or ("FG: "..COLOR_NAMES[fg_idx].." BG: "..COLOR_NAMES[bg_idx])) ..
- " Sound: "..sound_mode_name(settings.sound_mode or 1) ..
- " Speed: "..speed_name(settings.speed or 1)
- local w = term.getSize()
- if #status > w then
- status = status:sub(1, w-1) .. "…"
- end
- term.write(status)
- end
- end
- end
- local function main()
- ensure_pcm_beep()
- local settings = { rom_dir = "roms", monochrome = false, sound_mode = 1, speed = 3 }
- local color_capable = isColor()
- while true do
- local romfile
- while not romfile do
- local use_mono = settings.monochrome or not color_capable
- local sel = rom_menu_ui(settings.rom_dir, use_mono)
- if sel == "settings" then
- settings_menu_ui(settings, use_mono)
- elseif sel == "about" then
- about_section(VERSION, use_mono)
- elseif sel == nil then
- term.clear()
- term.setCursorPos(1,1)
- print("Goodbye!")
- return
- else
- romfile = sel
- end
- end
- local fg, bg = colors.white, colors.black
- local result = emulate(romfile, fg, bg, settings)
- if result == "quit" then return end
- if result == "menu" then romfile = nil end
- end
- end
- main()
Add Comment
Please, Sign In to add comment