nonogamer9

BONZCH8: A Chip-8 Emulator For BonziWorld!

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