Advertisement
nonogamer9

BONZCH8: A Chip-8 Emulator For BonziWorld!

May 15th, 2025 (edited)
595
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 12.71 KB | Software | 0 0
  1. const prefix = "!chip8 ";
  2. const botname = "BONZCH8 (!chip8)";
  3. let romList = [];
  4. const MEMORY_SIZE = 4096;
  5. const DISPLAY_WIDTH = 64;
  6. const DISPLAY_HEIGHT = 32;
  7. const PROGRAM_START = 0x200;
  8. const FONT_START = 0x50;
  9. let memory = new Uint8Array(MEMORY_SIZE);
  10. let V = new Uint8Array(16);
  11. let I = 0;
  12. let pc = PROGRAM_START;
  13. let stack = [];
  14. let delayTimer = 0;
  15. let soundTimer = 0;
  16. let display = new Uint8Array(DISPLAY_WIDTH * DISPLAY_HEIGHT);
  17. let keys = new Array(16).fill(false);
  18. let running = false;
  19. let autoRunInterval = null;
  20. let autoRunSpeed = 1000;
  21. const fontSet = [
  22.   0xF0,0x90,0x90,0x90,0xF0, 0x20,0x60,0x20,0x20,0x70, 0xF0,0x10,0xF0,0x80,0xF0, 0xF0,0x10,0xF0,0x10,0xF0,
  23.   0x90,0x90,0xF0,0x10,0x10, 0xF0,0x80,0xF0,0x10,0xF0, 0xF0,0x80,0xF0,0x90,0xF0, 0xF0,0x10,0x20,0x40,0x40,
  24.   0xF0,0x90,0xF0,0x90,0xF0, 0xF0,0x90,0xF0,0x10,0xF0, 0xF0,0x90,0xF0,0x90,0x90, 0xE0,0x90,0xE0,0x90,0xE0,
  25.   0xF0,0x80,0x80,0x80,0xF0, 0xE0,0x90,0x90,0x90,0xE0, 0xF0,0x80,0xF0,0x80,0xF0, 0xF0,0x80,0xF0,0x80,0x80
  26. ];
  27. for (let i = 0; i < fontSet.length; i++) memory[FONT_START + i] = fontSet[i];
  28. const exampleROM = [0x00, 0xE0, 0x60, 0x00, 0x61, 0x00, 0xA2, 0x02, 0xD0, 0x11, 0x70, 0x08, 0x30, 0x40, 0x12, 0x06];
  29.  
  30. function sendMsg(msg) {
  31.     setTimeout(() => {
  32.         socket.emit("talk", { text: msg });
  33.     }, 1100);
  34. }
  35.  
  36. setTimeout(() => { socket.emit("command", { list: ["name", botname] }) }, 1000);
  37. setTimeout(() => { socket.emit("command", { list: ["name", botname] }) }, 2100);
  38. setTimeout(() => {
  39.     sendMsg("BONZCH8 is online. The Only Emulator For BonziWorld! Type !chip8 help for commands.");
  40.     setInterval(() => { sendMsg("BONZCH8 is online. The Only Emulator For BonziWorld! Type !chip8 help for commands."); }, 60000);
  41. }, 3200);
  42.  
  43. function loadROM(rom) {
  44.     reset();
  45.     for (let i = 0; i < rom.length; i++) memory[PROGRAM_START + i] = rom[i];
  46.     running = true;
  47.     sendMsg("ROM loaded.");
  48. }
  49.  
  50. function reset() {
  51.     memory.fill(0);
  52.     for (let i = 0; i < fontSet.length; i++) memory[FONT_START + i] = fontSet[i];
  53.     V.fill(0);
  54.     I = 0;
  55.     pc = PROGRAM_START;
  56.     stack = [];
  57.     delayTimer = 0;
  58.     soundTimer = 0;
  59.     display.fill(0);
  60.     keys.fill(false);
  61.     running = false;
  62.     stopAutoRun();
  63. }
  64.  
  65. function stopAutoRun() {
  66.     if (autoRunInterval !== null) {
  67.         clearInterval(autoRunInterval);
  68.         autoRunInterval = null;
  69.     }
  70. }
  71.  
  72. function startAutoRun(intervalMs = autoRunSpeed) {
  73.     stopAutoRun();
  74.     autoRunInterval = setInterval(() => {
  75.         if (!running) {
  76.             stopAutoRun();
  77.             return;
  78.         }
  79.         for (let i = 0; i < 10; i++) cycle();
  80.         sendMsg(renderChip8Braille14x6());
  81.     }, intervalMs);
  82. }
  83.  
  84. function renderChip8Braille14x6() {
  85.     const BRAILLE_WIDTH = 14, BRAILLE_HEIGHT = 6;
  86.     const PIXEL_W = 2, PIXEL_H = 4;
  87.     const VIRTUAL_W = BRAILLE_WIDTH * PIXEL_W;
  88.     const VIRTUAL_H = BRAILLE_HEIGHT * PIXEL_H;
  89.     const scaleX = DISPLAY_WIDTH / VIRTUAL_W;
  90.     const scaleY = DISPLAY_HEIGHT / VIRTUAL_H;
  91.     let out = [];
  92.     for (let by = 0; by < BRAILLE_HEIGHT; by++) {
  93.         let line = "";
  94.         for (let bx = 0; bx < BRAILLE_WIDTH; bx++) {
  95.             let code = 0x2800;
  96.             for (let dy = 0; dy < PIXEL_H; dy++) {
  97.                 for (let dx = 0; dx < PIXEL_W; dx++) {
  98.                     let vx = bx * PIXEL_W + dx;
  99.                     let vy = by * PIXEL_H + dy;
  100.                     let chip8x = Math.floor(vx * scaleX);
  101.                     let chip8y = Math.floor(vy * scaleY);
  102.                     if (chip8x < DISPLAY_WIDTH && chip8y < DISPLAY_HEIGHT) {
  103.                         let dot = display[chip8x + chip8y * DISPLAY_WIDTH] ? 1 : 0;
  104.                         if (dot) {
  105.                             const dotIndex = [
  106.                                 [0, 1, 2, 6],
  107.                                 [3, 4, 5, 7]
  108.                             ][dx][dy];
  109.                             code |= (1 << dotIndex);
  110.                         }
  111.                     }
  112.                 }
  113.             }
  114.             line += String.fromCharCode(code);
  115.         }
  116.         out.push(line);
  117.     }
  118.     return out.join("\n");
  119. }
  120.  
  121. function cycle() {
  122.     let opcode = (memory[pc] << 8) | memory[pc + 1];
  123.     let x = (opcode & 0x0F00) >> 8;
  124.     let y = (opcode & 0x00F0) >> 4;
  125.     let n = opcode & 0x000F;
  126.     let nn = opcode & 0x00FF;
  127.     let nnn = opcode & 0x0FFF;
  128.     pc += 2;
  129.     switch(opcode & 0xF000) {
  130.         case 0x0000:
  131.             if (opcode === 0x00E0) { display.fill(0); }
  132.             else if (opcode === 0x00EE) { pc = stack.pop(); }
  133.             break;
  134.         case 0x1000: pc = nnn; break;
  135.         case 0x2000: stack.push(pc); pc = nnn; break;
  136.         case 0x3000: if (V[x] === nn) pc += 2; break;
  137.         case 0x4000: if (V[x] !== nn) pc += 2; break;
  138.         case 0x5000: if (V[x] === V[y]) pc += 2; break;
  139.         case 0x6000: V[x] = nn; break;
  140.         case 0x7000: V[x] = (V[x] + nn) & 0xFF; break;
  141.         case 0x8000:
  142.             switch (n) {
  143.                 case 0x0: V[x] = V[y]; break;
  144.                 case 0x1: V[x] |= V[y]; break;
  145.                 case 0x2: V[x] &= V[y]; break;
  146.                 case 0x3: V[x] ^= V[y]; break;
  147.                 case 0x4: {
  148.                     let sum = V[x] + V[y];
  149.                     V[0xF] = sum > 0xFF ? 1 : 0;
  150.                     V[x] = sum & 0xFF;
  151.                 } break;
  152.                 case 0x5: {
  153.                     V[0xF] = V[x] > V[y] ? 1 : 0;
  154.                     V[x] = (V[x] - V[y]) & 0xFF;
  155.                 } break;
  156.                 case 0x6: {
  157.                     V[0xF] = V[x] & 0x1;
  158.                     V[x] >>= 1;
  159.                 } break;
  160.                 case 0x7: {
  161.                     V[0xF] = V[y] > V[x] ? 1 : 0;
  162.                     V[x] = (V[y] - V[x]) & 0xFF;
  163.                 } break;
  164.                 case 0xE: {
  165.                     V[0xF] = (V[x] & 0x80) >> 7;
  166.                     V[x] = (V[x] << 1) & 0xFF;
  167.                 } break;
  168.             } break;
  169.         case 0x9000: if (V[x] !== V[y]) pc += 2; break;
  170.         case 0xA000: I = nnn; break;
  171.         case 0xB000: pc = nnn + V[0]; break;
  172.         case 0xC000: V[x] = (Math.floor(Math.random() * 0xFF)) & nn; break;
  173.         case 0xD000: {
  174.             let vx = V[x] % DISPLAY_WIDTH;
  175.             let vy = V[y] % DISPLAY_HEIGHT;
  176.             V[0xF] = 0;
  177.             for (let row = 0; row < n; row++) {
  178.                 let sprite = memory[I + row];
  179.                 for (let col = 0; col < 8; col++) {
  180.                     if ((sprite & (0x80 >> col)) !== 0) {
  181.                         let xi = vx + col;
  182.                         let yi = vy + row;
  183.                         if (xi >= DISPLAY_WIDTH || yi >= DISPLAY_HEIGHT) continue;
  184.                         let idx = xi + yi * DISPLAY_WIDTH;
  185.                         if (display[idx]) V[0xF] = 1;
  186.                         display[idx] ^= 1;
  187.                     }
  188.                 }
  189.             }
  190.         } break;
  191.         case 0xE000:
  192.             if (nn === 0x9E) { if (keys[V[x]]) pc += 2; }
  193.             else if (nn === 0xA1) { if (!keys[V[x]]) pc += 2; }
  194.             break;
  195.         case 0xF000:
  196.             switch (nn) {
  197.                 case 0x07: V[x] = delayTimer; break;
  198.                 case 0x0A: {
  199.                     let key = keys.findIndex(k => k);
  200.                     if (key === -1) { pc -= 2; } else { V[x] = key; }
  201.                 } break;
  202.                 case 0x15: delayTimer = V[x]; break;
  203.                 case 0x18: soundTimer = V[x]; break;
  204.                 case 0x1E: I = (I + V[x]) & 0xFFF; break;
  205.                 case 0x29: I = FONT_START + (V[x] * 5); break;
  206.                 case 0x33: {
  207.                     memory[I] = Math.floor(V[x] / 100);
  208.                     memory[I + 1] = Math.floor((V[x] % 100) / 10);
  209.                     memory[I + 2] = V[x] % 10;
  210.                 } break;
  211.                 case 0x55: for (let i = 0; i <= x; i++) memory[I + i] = V[i]; break;
  212.                 case 0x65: for (let i = 0; i <= x; i++) V[i] = memory[I + i]; break;
  213.             } break;
  214.     }
  215. }
  216.  
  217. const help = "__BONZCH8 Commands__\n" +
  218.     "!chip8 help - Show this help\n" +
  219.     "!chip8 load [url|number] - Download a ROM from URL and add it to the list, or load a ROM from the list (e.g. 1)\n" +
  220.     "!chip8 roms - List downloaded ROMs\n" +
  221.     "!chip8 listroms - List downloaded ROMs\n" +
  222.     "!chip8 reset - Reset emulator\n" +
  223.     "!chip8 step - Step one instruction\n" +
  224.     "!chip8 run - Run automatically, displaying every second\n" +
  225.     "!chip8 stop - Stop automatic run, clear display, and halt ROM\n" +
  226.     "!chip8 display - Show display buffer (14x6 braille)\n" +
  227.     "!chip8 key [0-F] [down|up] - Press or release a CHIP-8 key\n" +
  228.     "!chip8 speed <ms> - Set auto-run speed in milliseconds per frame (e.g. 500, 1500)";
  229.  
  230. socket.on("talk", function (message) {
  231.     if (!message.text.startsWith(prefix)) return;
  232.     const args = message.text.substring(prefix.length).split(" ");
  233.     const cmd = args[0];
  234.     if (cmd === "help") {
  235.         sendMsg(help);
  236.     } else if (cmd === "load") {
  237.         stopAutoRun();
  238.         if (args[1]) {
  239.             if (args[1].startsWith("http")) {
  240.                 fetch(args[1])
  241.                     .then(response => response.arrayBuffer())
  242.                     .then(buffer => {
  243.                         const rom = new Uint8Array(buffer);
  244.                         const name = args[1].split("/").pop() || `ROM${romList.length+1}`;
  245.                         romList.push({ name, data: rom, url: args[1] });
  246.                         sendMsg(`ROM downloaded and added as #${romList.length}: ${name}`);
  247.                     })
  248.                     .catch(() => sendMsg("Failed to download ROM from URL."));
  249.             } else if (!isNaN(args[1])) {
  250.                 const idx = parseInt(args[1], 10) - 1;
  251.                 if (romList[idx]) {
  252.                     loadROM(romList[idx].data);
  253.                     sendMsg(`Loaded ROM #${idx+1}: ${romList[idx].name}`);
  254.                 } else {
  255.                     sendMsg("No ROM at that number. Use !chip8 roms to list available ROMs.");
  256.                 }
  257.             } else {
  258.                 sendMsg("Invalid argument. Use a URL or a ROM number.");
  259.             }
  260.         } else {
  261.             loadROM(exampleROM);
  262.             sendMsg("Loaded default example ROM.");
  263.         }
  264.     } else if (cmd === "roms" || cmd === "listroms") {
  265.         if (romList.length === 0) {
  266.             sendMsg("No ROMs downloaded yet.");
  267.         } else {
  268.             let msg = "__ROM List__\n";
  269.             romList.forEach((rom, i) => {
  270.                 msg += `${i+1}. ${rom.name} (${rom.url})\n`;
  271.             });
  272.             sendMsg(msg.trim());
  273.         }
  274.     } else if (cmd === "reset") {
  275.         reset();
  276.         sendMsg("Emulator reset.");
  277.     } else if (cmd === "step") {
  278.         stopAutoRun();
  279.         if (!running) { sendMsg("No ROM loaded."); return; }
  280.         cycle();
  281.         sendMsg("Stepped one instruction.\n" + renderChip8Braille14x6());
  282.     } else if (cmd === "run") {
  283.         if (!running) { sendMsg("No ROM loaded."); return; }
  284.         sendMsg("Running automatically. Type !chip8 stop to halt.");
  285.         startAutoRun(autoRunSpeed);
  286.     } else if (cmd === "stop") {
  287.         stopAutoRun();
  288.         running = false;
  289.         display.fill(0);
  290.         sendMsg("Stopped automatic run, cleared display, and halted ROM.\n" + renderChip8Braille14x6());
  291.     } else if (cmd === "display") {
  292.         sendMsg(renderChip8Braille14x6());
  293.     } else if (cmd === "key") {
  294.         if (args.length < 3) {
  295.             sendMsg("Usage: !chip8 key [0-F] [down|up]");
  296.             return;
  297.         }
  298.         let keyStr = args[1].toUpperCase();
  299.         let action = args[2].toLowerCase();
  300.         let keyVal = parseInt(keyStr, 16);
  301.         if (isNaN(keyVal) || keyVal < 0 || keyVal > 0xF) {
  302.             sendMsg("Invalid key. Use 0-9 or A-F.");
  303.             return;
  304.         }
  305.         if (action === "down") {
  306.             keys[keyVal] = true;
  307.             sendMsg("Key " + keyStr + " pressed.");
  308.         } else if (action === "up") {
  309.             keys[keyVal] = false;
  310.             sendMsg("Key " + keyStr + " released.");
  311.         } else {
  312.             sendMsg("Invalid action. Use 'down' or 'up'.");
  313.         }
  314.     } else if (cmd === "speed") {
  315.         if (args.length < 2 || isNaN(args[1]) || parseInt(args[1],10) < 100) {
  316.             sendMsg("Usage: !chip8 speed <milliseconds> (minimum 100)");
  317.             return;
  318.         }
  319.         autoRunSpeed = parseInt(args[1], 10);
  320.         if (autoRunInterval !== null) {
  321.             startAutoRun(autoRunSpeed);
  322.             sendMsg(`Auto-run speed set to ${autoRunSpeed} ms per frame (applied immediately).`);
  323.         } else {
  324.             sendMsg(`Auto-run speed set to ${autoRunSpeed} ms per frame.`);
  325.         }
  326.     } else {
  327.         sendMsg("Unknown CHIP8 command. Type !chip8 help.");
  328.     }
  329. });
  330.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement