### JavaScript Setup for Chip-8 Emulator Frontend Source: https://github.com/aquova/chip8-book/blob/master/src/7-wasm.md Initializes constants for screen dimensions and scale, sets up the HTML canvas element, and gets references to necessary DOM elements like the canvas and file input. ```javascript import init, * as wasm from "./wasm.js" const WIDTH = 64 const HEIGHT = 32 const SCALE = 15 const TICKS_PER_FRAME = 10 let anim_frame = 0 const canvas = document.getElementById("canvas") canvas.width = WIDTH * SCALE canvas.height = HEIGHT * SCALE const ctx = canvas.getContext("2d") ctx.fillStyle = "black" ctx.fillRect(0, WIDTH * SCALE, HEIGHT * SCALE) const input = document.getElementById("fileinput") ``` -------------------------------- ### Basic Main Function Setup Source: https://github.com/aquova/chip8-book/blob/master/src/6-frontend.md Initializes the Rust program and checks for command-line arguments, expecting a path to a game file. ```rust use chip8_core::*; use std::env; fn main() { let args: Vec<_> = env::args().collect(); if args.len() != 2 { println!("Usage: cargo run path/to/game"); return; } } ``` -------------------------------- ### Start Python 3 HTTP server Source: https://github.com/aquova/chip8-book/blob/master/src/7-wasm.md Start a local web server using Python 3 to host the web frontend. Navigate to localhost in your browser to view the page. ```bash $ python3 -m http.server ``` -------------------------------- ### Install wasm-pack Source: https://github.com/aquova/chip8-book/blob/master/src/7-wasm.md Install the wasm-pack tool for easily compiling Rust to WebAssembly. ```bash $ cargo install wasm-pack ``` -------------------------------- ### Initialize Screen Drawing Function in WebAssembly Source: https://github.com/aquova/chip8-book/blob/master/src/7-wasm.md Placeholder function for drawing the screen in WebAssembly. The implementation will be completed after JavaScript setup. ```rust impl EmuWasm { #[wasm_bindgen] pub fn draw_screen(&mut self, scale: usize) { // TODO } } ``` -------------------------------- ### Initialize Emulator with Fontset Source: https://github.com/aquova/chip8-book/blob/master/src/4-methods.md Initializes a new emulator instance and copies the predefined fontset into the beginning of the emulator's RAM. This ensures that font sprites are available from the start. ```rust pub fn new() -> Self { let mut new_emu = Self { pc: START_ADDR, ram: [0; RAM_SIZE], screen: [false; SCREEN_WIDTH * SCREEN_HEIGHT], v_reg: [0; NUM_REGS], i_reg: 0, sp: 0, stack: [0; STACK_SIZE], keys: [false; NUM_KEYS], dt: 0, st: 0, }; new_emu.ram[..FONTSET_SIZE].copy_from_slice(&FONTSET); new_emu } ``` -------------------------------- ### Chip-8 Display Setup Source: https://github.com/aquova/chip8-book/blob/master/src/3-setup.md Defines constants for screen dimensions and initializes the screen buffer as an array of booleans within the Emu struct. Chip-8 uses a 64x32 monochrome display. ```Rust pub const SCREEN_WIDTH: usize = 64; pub const SCREEN_HEIGHT: usize = 32; const RAM_SIZE: usize = 4096; pub struct Emu { pc: u16, ram: [u8; RAM_SIZE], screen: [bool; SCREEN_WIDTH * SCREEN_HEIGHT], } ``` -------------------------------- ### Chip-8 I Register Setup Source: https://github.com/aquova/chip8-book/blob/master/src/3-setup.md Initializes the 16-bit I register within the Emu struct. This register is used for indexing into RAM for reads and writes. ```Rust pub const SCREEN_WIDTH: usize = 64; pub const SCREEN_HEIGHT: usize = 32; const RAM_SIZE: usize = 4096; const NUM_REGS: usize = 16; pub struct Emu { pc: u16, ram: [u8; RAM_SIZE], screen: [bool; SCREEN_WIDTH * SCREEN_HEIGHT], v_reg: [u8; NUM_REGS], i_reg: u16, } ``` -------------------------------- ### Chip-8 Emulator Initialization Source: https://github.com/aquova/chip8-book/blob/master/src/3-setup.md Implements the constructor for the Chip-8 emulator, initializing all registers, memory, and state to default values. The program counter is set to the standard starting address of 0x200. ```rust const START_ADDR: u16 = 0x200; impl Emu { pub fn new() -> Self { Self { pc: START_ADDR, ram: [0; RAM_SIZE], screen: [false; SCREEN_WIDTH * SCREEN_HEIGHT], v_reg: [0; NUM_REGS], i_reg: 0, sp: 0, stack: [0; STACK_SIZE], keys: [false; NUM_KEYS], dt: 0, st: 0, } } } ``` -------------------------------- ### Handling File Input for ROM Loading Source: https://github.com/aquova/chip8-book/blob/master/src/7-wasm.md Adds an event listener to the file input element to handle ROM loading. It stops any ongoing animation, reads the selected file as an ArrayBuffer, resets the emulator, loads the ROM, and starts the main emulation loop. ```javascript input.addEventListener("change", function(evt) { // Stop previous game from rendering, if one exists if (anim_frame != 0) { window.cancelAnimationFrame(anim_frame) } let file = evt.target.files[0] if (!file) { alert("Failed to read file") return } // Load in game as Uint8Array, send to .wasm, start main loop let fr = new FileReader() fr.onload = function(e) { let buffer = fr.result const rom = new Uint8Array(buffer) chip8.reset() chip8.load_game(rom) mainloop(chip8) } fr.readAsArrayBuffer(file) }, false) function mainloop(chip8) { } ``` -------------------------------- ### Load Game Data into RAM Source: https://github.com/aquova/chip8-book/blob/master/src/6-frontend.md Copies provided game data into the emulator's RAM starting at the address 0x200. Assumes the first 512 bytes of RAM are reserved. ```rust impl Emu { // -- Unchanged code omitted -- pub fn load(&mut self, data: &[u8]) { let start = START_ADDR as usize; let end = (START_ADDR as usize) + data.len(); self.ram[start..end].copy_from_slice(data); } // -- Unchanged code omitted -- } ``` -------------------------------- ### Implement FX33: Store BCD of VX in I Source: https://github.com/aquova/chip8-book/blob/master/src/5-instr.md Converts the value in VX to its Binary-Coded Decimal representation (hundreds, tens, ones) and stores these digits in memory starting at the address in the I register. This implementation uses floating-point arithmetic for clarity. ```rust match (digit1, digit2, digit3, digit4) { // -- Unchanged code omitted -- // BCD (0xF, _, 3, 3) => { let x = digit2 as usize; let vx = self.v_reg[x] as f32; // Fetch the hundreds digit by dividing by 100 and tossing the decimal let hundreds = (vx / 100.0).floor() as u8; // Fetch the tens digit by dividing by 10, tossing the ones digit and the decimal let tens = ((vx / 10.0) % 10.0).floor() as u8; // Fetch the ones digit by tossing the hundreds and the tens let ones = (vx % 10.0) as u8; self.ram[self.i_reg as usize] = hundreds; self.ram[(self.i_reg + 1) as usize] = tens; self.ram[(self.i_reg + 2) as usize] = ones; }, // -- Unchanged code omitted -- } ``` -------------------------------- ### Implement FX55: Store V0-VX into I Source: https://github.com/aquova/chip8-book/blob/master/src/5-instr.md Stores the values of Chip-8 registers V0 through VX (inclusive) into RAM, starting at the memory address specified by the I register. This operation increments the I register implicitly with each stored byte. ```rust match (digit1, digit2, digit3, digit4) { // -- Unchanged code omitted -- // STORE V0 - VX (0xF, _, 5, 5) => { let x = digit2 as usize; let i = self.i_reg as usize; for idx in 0..=x { self.ram[i + idx] = self.v_reg[idx]; } }, // -- Unchanged code omitted -- } ``` -------------------------------- ### Chip-8 V Registers Setup Source: https://github.com/aquova/chip8-book/blob/master/src/3-setup.md Defines the number of V registers and initializes them as an array of u8 within the Emu struct. Chip-8 has sixteen 8-bit V registers (V0 to VF). ```Rust pub const SCREEN_WIDTH: usize = 64; pub const SCREEN_HEIGHT: usize = 32; const RAM_SIZE: usize = 4096; const NUM_REGS: usize = 16; pub struct Emu { pc: u16, ram: [u8; RAM_SIZE], screen: [bool; SCREEN_WIDTH * SCREEN_HEIGHT], v_reg: [u8; NUM_REGS], } ``` -------------------------------- ### Set I Register to Font Sprite Address (FX29) Source: https://github.com/aquova/chip8-book/blob/master/src/5-instr.md Calculates and sets the I Register to the memory address of a font sprite corresponding to the value in VX. Assumes font sprites are 5 bytes each and stored contiguously starting from RAM address 0. ```rust match (digit1, digit2, digit3, digit4) { // -- Unchanged code omitted -- // I = FONT (0xF, _, 2, 9) => { let x = digit2 as usize; let c = self.v_reg[x] as u16; self.i_reg = c * 5; }, // -- Unchanged code omitted -- } ``` -------------------------------- ### Initialize SDL Window and Canvas Source: https://github.com/aquova/chip8-book/blob/master/src/6-frontend.md Sets up the SDL context, creates a window with specified dimensions and title, and prepares a canvas for drawing. ```rust let sdl_context = sdl2::init().unwrap(); let video_subsystem = sdl_context.video().unwrap(); let window = video_subsystem .window("Chip-8 Emulator", WINDOW_WIDTH, WINDOW_HEIGHT) .position_centered() .opengl() .build() .unwrap(); let mut canvas = window.into_canvas().present_vsync().build().unwrap(); canvas.clear(); canvas.present(); ``` -------------------------------- ### Initialize Desktop Frontend Executable Source: https://github.com/aquova/chip8-book/blob/master/src/3-setup.md Creates a new Rust executable crate for the desktop frontend. This crate will depend on the chip8_core library. ```bash cargo init desktop ``` -------------------------------- ### Main Emulation Run Function Source: https://github.com/aquova/chip8-book/blob/master/src/7-wasm.md Asynchronously initializes the WebAssembly module, creates an instance of the Chip-8 emulator, and sets up event listeners for keyboard input and file changes. It ensures the WebAssembly module is initialized before use. ```javascript async function run() { await init() let chip8 = new wasm.EmuWasm() document.addEventListener("keydown", function(evt) { chip8.keypress(evt, true) }) document.addEventListener("keyup", function(evt) { chip8.keypress(evt, false) }) input.addEventListener("change", function(evt) { // Handle file loading }, false) } run().catch(console.error) ``` -------------------------------- ### Run the Desktop Project Source: https://github.com/aquova/chip8-book/blob/master/src/3-setup.md Compiles and runs the desktop frontend project. This command should be executed from within the 'desktop' directory. ```bash cargo run ``` -------------------------------- ### Basic HTML boilerplate for web frontend Source: https://github.com/aquova/chip8-book/blob/master/src/7-wasm.md Create a basic index.html file to serve as the frontend for the WebAssembly application. ```html Chip-8 Emulator

My Chip-8 Emulator

``` -------------------------------- ### Initialize Chip-8 Core Library Source: https://github.com/aquova/chip8-book/blob/master/src/3-setup.md Creates a new Rust library crate for the core emulation logic. Use this command in your project's root directory. ```bash cargo init chip8_core --lib ``` -------------------------------- ### Initialize Emulator Source: https://github.com/aquova/chip8-book/blob/master/src/6-frontend.md Instantiate the CHIP-8 emulator object before the main game loop. ```rust fn main() { // -- Unchanged code omitted -- let mut chip8 = Emu::new(); 'gameloop: loop { // -- Unchanged code omitted -- } } ``` -------------------------------- ### Deploy WASM to Web Directory Source: https://github.com/aquova/chip8-book/blob/master/src/7-wasm.md Copies the compiled WASM binary and JavaScript glue code from the 'pkg' directory to the 'web' directory for serving. ```bash wasm-pack build --target web mv pkg/wasm_bg.wasm ../web mv pkg/wasm.js ../web ``` -------------------------------- ### Configure C dynamic library for WASM Source: https://github.com/aquova/chip8-book/blob/master/src/7-wasm.md Set the crate type to 'cdylib' in Cargo.toml to enable dynamic linking for WebAssembly. ```toml [lib] crate-type = ["cdylib"] ``` -------------------------------- ### SDL2 Initialization for Drawing Source: https://github.com/aquova/chip8-book/blob/master/src/6-frontend.md Imports necessary SDL2 components for pixel manipulation, rectangle drawing, and canvas rendering. ```rust use sdl2::pixels::Color; use sdl2::rect::Rect; use sdl2::render::Canvas; use sdl2::video::Window; ``` -------------------------------- ### HTML Structure for Chip-8 Emulator Source: https://github.com/aquova/chip8-book/blob/master/src/7-wasm.md Sets up the basic HTML for the emulator, including a title, heading, file input for ROMs, and an HTML5 canvas for rendering. It also links to the main JavaScript module. ```html Chip-8 Emulator

My Chip-8 Emulator


If you see this message, then your browser doesn't support HTML5 ``` -------------------------------- ### Import SDL Keyboard Support Source: https://github.com/aquova/chip8-book/blob/master/src/6-frontend.md Bring SDL keyboard functionality into the main Rust file. This is required before using SDL keycodes. ```rust use sdl2::keyboard::Keycode; ``` -------------------------------- ### Load Game ROM Source: https://github.com/aquova/chip8-book/blob/master/src/6-frontend.md Open a file, read its contents into a buffer, and load the data into the emulator. Handles file not found errors. ```rust fn main() { // -- Unchanged code omitted -- let mut chip8 = Emu::new(); let mut rom = File::open(&args[1]).expect("Unable to open file"); let mut buffer = Vec::new(); rom.read_to_end(&mut buffer).unwrap(); chip8.load(&buffer); // -- Unchanged code omitted -- } ``` -------------------------------- ### Import File I/O Modules Source: https://github.com/aquova/chip8-book/blob/master/src/6-frontend.md Include necessary modules from the Rust standard library for file operations. ```rust use std::fs::File; use std::io::Read; ``` -------------------------------- ### Add SDL2 Dependency Source: https://github.com/aquova/chip8-book/blob/master/src/6-frontend.md Add the sdl2 crate to your project's Cargo.toml file to include SDL2 functionality. ```toml [dependencies] chip8_core = { path = "../chip8_core" } sdl2 = "^0.34.3 ``` -------------------------------- ### Implement FX65: Load I into V0-VX Source: https://github.com/aquova/chip8-book/blob/master/src/5-instr.md Loads values from RAM into Chip-8 registers V0 through VX (inclusive). The loading begins at the memory address specified by the I register and proceeds sequentially. ```rust match (digit1, digit2, digit3, digit4) { // -- Unchanged code omitted -- // LOAD V0 - VX (0xF, _, 6, 5) => { let x = digit2 as usize; let i = self.i_reg as usize; for idx in 0..=x { self.v_reg[idx] = self.ram[i + idx]; } }, // -- Unchanged code omitted -- } ``` -------------------------------- ### Add Chip-8 Core Dependency to Desktop Frontend Source: https://github.com/aquova/chip8-book/blob/master/src/3-setup.md Configures the desktop frontend's Cargo.toml to include the chip8_core library as a dependency. This allows the frontend to use the core emulation logic. ```toml chip8_core = { path = "../chip8_core" } ``` -------------------------------- ### Importing WebAssembly Module Source: https://github.com/aquova/chip8-book/blob/master/src/7-wasm.md Imports the necessary initialization function and all exported items from the WebAssembly module generated by wasm-pack. This must be called before using any other functions from the module. ```javascript import init, * as wasm from "./wasm.js" ``` -------------------------------- ### Chip-8 Emulation Main Loop Source: https://github.com/aquova/chip8-book/blob/master/src/7-wasm.md This function handles the core emulation loop, ticking the Chip-8 state, managing timers, clearing the canvas, and drawing the screen. It uses `requestAnimationFrame` for smooth 60 FPS rendering. ```javascript function mainloop(chip8) { // Only draw every few ticks for (let i = 0; i < TICKS_PER_FRAME; i++) { chip8.tick() } chip8.tick_timers() // Clear the canvas before drawing ctx.fillStyle = "black" ctx.fillRect(0, 0, WIDTH * SCALE, HEIGHT * SCALE) // Set the draw color back to white before we render our frame ctx.fillStyle = "white" chip8.draw_screen(SCALE) anim_frame = window.requestAnimationFrame(() => { mainloop(chip8) }) } ``` -------------------------------- ### Implement WASM Keyboard Input Handling Source: https://github.com/aquova/chip8-book/blob/master/src/7-wasm.md Implement the `keypress` function to receive keyboard events from JavaScript, translate the key, and update the CHIP-8 core state. ```rust use web_sys::KeyboardEvent; // -- Unchanged code omitted -- impl EmuWasm { // -- Unchanged code omitted -- #[wasm_bindgen] pub fn keypress(&mut self, evt: KeyboardEvent, pressed: bool) { let key = evt.key(); if let Some(k) = key2btn(&key) { self.chip8.keypress(k, pressed); } } } fn key2btn(key: &str) -> Option { match key { "1" => Some(0x1), "2" => Some(0x2), "3" => Some(0x3), "4" => Some(0xC), "q" => Some(0x4), "w" => Some(0x5), "e" => Some(0x6), "r" => Some(0xD), "a" => Some(0x7), ``` -------------------------------- ### Chip-8 Emulator Structure with Timers Source: https://github.com/aquova/chip8-book/blob/master/src/3-setup.md Further expands the Chip-8 emulator structure to include 8-bit delay and sound timer registers. ```rust pub const SCREEN_WIDTH: usize = 64; pub const SCREEN_HEIGHT: usize = 32; const RAM_SIZE: usize = 4096; const NUM_REGS: usize = 16; const STACK_SIZE: usize = 16; const NUM_KEYS: usize = 16; pub struct Emu { pc: u16, ram: [u8; RAM_SIZE], screen: [bool; SCREEN_WIDTH * SCREEN_HEIGHT], v_reg: [u8; NUM_REGS], i_reg: u16, sp: u16, stack: [u16; STACK_SIZE], keys: [bool; NUM_KEYS], dt: u8, st: u8, } ``` -------------------------------- ### Implement Game Loop with Event Handling Source: https://github.com/aquova/chip8-book/blob/master/src/6-frontend.md Adds a main game loop that polls for SDL events, specifically handling the Quit event to allow graceful program termination. ```rust let mut event_pump = sdl_context.event_pump().unwrap(); 'gameloop: loop { for evt in event_pump.poll_iter() { match evt { Event::Quit{..} => { break 'gameloop; }, _ => () } } } ``` -------------------------------- ### Main Game Loop with Screen Drawing Integration Source: https://github.com/aquova/chip8-book/blob/master/src/6-frontend.md Calls the draw_screen function after each emulator tick to update the display in the main game loop. ```rust fn main() { // -- Unchanged code omitted -- 'gameloop: loop { for event in event_pump.poll_iter() { // -- Unchanged code omitted -- } chip8.tick(); draw_screen(&chip8, &mut canvas); } } ``` -------------------------------- ### Implement WASM Tick Methods Source: https://github.com/aquova/chip8-book/blob/master/src/7-wasm.md Create wrapper functions for ticking the CHIP-8 CPU and timers, exposed to JavaScript via `#[wasm_bindgen]`. ```rust #[wasm_bindgen] impl EmuWasm { // -- Unchanged code omitted -- #[wasm_bindgen] pub fn tick(&mut self) { self.chip8.tick(); } #[wasm_bindgen] pub fn tick_timers(&mut self) { self.chip8.tick_timers(); } } ``` -------------------------------- ### Load Game Data in WebAssembly Source: https://github.com/aquova/chip8-book/blob/master/src/7-wasm.md Loads game data into the CHIP-8 emulator from a JavaScript Uint8Array. The data is converted to a Vec for the emulator. ```rust use js_sys::Uint8Array; impl EmuWasm { #[wasm_bindgen] pub fn load_game(&mut self, data: Uint8Array) { self.chip8.load(&data.to_vec()); } } ``` -------------------------------- ### Handle Key Presses Source: https://github.com/aquova/chip8-book/blob/master/src/6-frontend.md Allows the frontend to update the emulator's internal key state. Ensure the index is within bounds (0-15) to avoid panics. ```rust impl Emu { // -- Unchanged code omitted -- pub fn keypress(&mut self, idx: usize, pressed: bool) { self.keys[idx] = pressed; } // -- Unchanged code omitted -- } ``` -------------------------------- ### Chip-8 Emulator Structure with Key Input Source: https://github.com/aquova/chip8-book/blob/master/src/3-setup.md Extends the Chip-8 emulator structure to include an array for tracking the state of 16 keys. ```rust pub const SCREEN_WIDTH: usize = 64; pub const SCREEN_HEIGHT: usize = 32; const RAM_SIZE: usize = 4096; const NUM_REGS: usize = 16; const STACK_SIZE: usize = 16; const NUM_KEYS: usize = 16; pub struct Emu { pc: u16, ram: [u8; RAM_SIZE], screen: [bool; SCREEN_WIDTH * SCREEN_HEIGHT], v_reg: [u8; NUM_REGS], i_reg: u16, sp: u16, stack: [u16; STACK_SIZE], keys: [bool; NUM_KEYS], } ``` -------------------------------- ### Handle Key Down and Key Up Events Source: https://github.com/aquova/chip8-book/blob/master/src/6-frontend.md Integrates keyboard input handling into the main event loop. Processes KeyDown and KeyUp events, converting valid keys using `key2btn` and updating the emulator state accordingly. This allows for interactive control of the Chip-8 emulation. ```rust fn main() { // -- Unchanged code omitted -- 'gameloop: loop { for evt in event_pump.poll_iter() { match evt { Event::Quit{..} => { break 'gameloop; }, Event::KeyDown{keycode: Some(key), ..} => { if let Some(k) = key2btn(key) { chip8.keypress(k, true); } }, Event::KeyUp{keycode: Some(key), ..} => { if let Some(k) = key2btn(key) { chip8.keypress(k, false); } }, _ => () } for _ in 0..TICKS_PER_FRAME { chip8.tick(); } chip8.tick_timers(); draw_screen(&chip8, &mut canvas); } // -- Unchanged code omitted -- } ``` -------------------------------- ### Implement Stack Push and Pop Methods Source: https://github.com/aquova/chip8-book/blob/master/src/4-methods.md Adds push and pop methods to the Emu struct for managing the CPU's stack. The push method adds a value to the stack and increments the stack pointer. The pop method decrements the stack pointer and returns the value from the stack. Panics on stack underflow. ```Rust impl Emu { // -- Unchanged code omitted -- fn push(&mut self, val: u16) { self.stack[self.sp as usize] = val; self.sp += 1; } fn pop(&mut self) -> u16 { self.sp -= 1; self.stack[self.sp as usize] } // -- Unchanged code omitted -- } ``` -------------------------------- ### Implement WASM Reset Method Source: https://github.com/aquova/chip8-book/blob/master/src/7-wasm.md Provide a wrapper function for the `reset` method of the CHIP-8 core, making it callable from JavaScript. ```rust #[wasm_bindgen] pub fn reset(&mut self) { self.chip8.reset(); } ``` -------------------------------- ### Optimized Game Loop with Multiple Ticks Per Frame Source: https://github.com/aquova/chip8-book/blob/master/src/6-frontend.md Configures the emulator to tick multiple times (e.g., 10) before redrawing the screen, improving perceived performance. Requires TICKS_PER_FRAME constant. ```rust const TICKS_PER_FRAME: usize = 10; // -- Unchanged code omitted -- fn main() { // -- Unchanged code omitted -- 'gameloop: loop { for event in event_pump.poll_iter() { // -- Unchanged code omitted -- } for _ in 0..TICKS_PER_FRAME { chip8.tick(); } draw_screen(&chip8, &mut canvas); } } ``` -------------------------------- ### Build WASM with wasm-pack Source: https://github.com/aquova/chip8-book/blob/master/src/7-wasm.md Compile Rust code to a WebAssembly binary targeting the web. This command generates necessary JavaScript glue code and the WASM module in the 'pkg' directory. ```bash wasm-pack build --target web ``` -------------------------------- ### Implement VX = rand() & NN Source: https://github.com/aquova/chip8-book/blob/master/src/5-instr.md Assigns a random 8-bit number, bitwise ANDed with NN (the lower 8 bits of the opcode), to register VX. Requires the 'rand' crate. ```rust use rand::random; // ... match (digit1, digit2, digit3, digit4) { // -- Unchanged code omitted -- // VX = rand() & NN (0xC, _, _, _) => { let x = digit2 as usize; let nn = (op & 0xFF) as u8; let rng: u8 = random(); self.v_reg[x] = rng & nn; }, // -- Unchanged code omitted -- } ``` -------------------------------- ### Wait for Key Press (FX0A) Source: https://github.com/aquova/chip8-book/blob/master/src/5-instr.md This instruction blocks execution until a key is pressed. It stores the index of the pressed key into VX. If no key is pressed, the opcode is re-executed. ```rust match (digit1, digit2, digit3, digit4) { // -- Unchanged code omitted -- // WAIT KEY (0xF, _, 0, 0xA) => { let x = digit2 as usize; let mut pressed = false; for i in 0..self.keys.len() { if self.keys[i] { self.v_reg[x] = i as u8; pressed = true; break; } } if !pressed { // Redo opcode self.pc -= 2; } }, // -- Unchanged code omitted -- } ``` -------------------------------- ### Chip-8 Opcode Fetch Implementation Source: https://github.com/aquova/chip8-book/blob/master/src/4-methods.md Fetches a 16-bit opcode from RAM at the current program counter and increments the PC. ```rust fn fetch(&mut self) -> u16 { let higher_byte = self.ram[self.pc as usize] as u16; let lower_byte = self.ram[(self.pc + 1) as usize] as u16; let op = (higher_byte << 8) | lower_byte; self.pc += 2; op } ``` -------------------------------- ### Define Scaled Window Dimensions Source: https://github.com/aquova/chip8-book/blob/master/src/6-frontend.md Defines constants for scaling the CHIP-8's native screen resolution to a larger window size. ```rust const SCALE: u32 = 15; const WINDOW_WIDTH: u32 = (SCREEN_WIDTH as u32) * SCALE; const WINDOW_HEIGHT: u32 = (SCREEN_HEIGHT as u32) * SCALE; ``` -------------------------------- ### Draw Screen Pixels to Canvas Source: https://github.com/aquova/chip8-book/blob/master/src/7-wasm.md Renders the Chip-8 display buffer to the HTML canvas. Pixels are drawn as scaled rectangles based on the display state and a provided scale factor. ```rust #[wasm_bindgen] pub fn draw_screen(&mut self, scale: usize) { let disp = self.chip8.get_display(); for i in 0..(SCREEN_WIDTH * SCREEN_HEIGHT) { if disp[i] { let x = i % SCREEN_WIDTH; let y = i / SCREEN_WIDTH; self.ctx.fill_rect( (x * scale) as f64, (y * scale) as f64, scale as f64, scale as f64 ); } } } ``` -------------------------------- ### Implementing Clear Screen (CLS) opcode Source: https://github.com/aquova/chip8-book/blob/master/src/5-instr.md Implementation of the 00E0 opcode (CLS - Clear Screen) which resets the screen buffer to all false (off). ```rust match (digit1, digit2, digit3, digit4) { // -- Unchanged code omitted -- // CLS (0, 0, 0xE, 0) => { self.screen = [false; SCREEN_WIDTH * SCREEN_HEIGHT]; }, // -- Unchanged code omitted -- } ``` -------------------------------- ### Add WASM Dependencies to Cargo.toml Source: https://github.com/aquova/chip8-book/blob/master/src/7-wasm.md Include necessary crates for WebAssembly communication and the CHIP-8 core. Ensure to specify features for web-sys. ```toml [dependencies] chip8_core = { path = "../chip8_core" } js-sys = "^0.3.46" wasm-bindgen = "^0.2.69" [dependencies.web-sys] version = "^0.3.46" features = [] ``` -------------------------------- ### Implement WASM Constructor for Emulator Source: https://github.com/aquova/chip8-book/blob/master/src/7-wasm.md Define a constructor for the `EmuWasm` struct using `#[wasm_bindgen(constructor)]` to allow instantiation from JavaScript. ```rust use chip8_core::* use wasm_bindgen::prelude::* #[wasm_bindgen] pub struct EmuWasm { chip8: Emu, } #[wasm_bindgen] impl EmuWasm { #[wasm_bindgen(constructor)] pub fn new() -> EmuWasm { EmuWasm { chip8: Emu::new(), } } } ``` -------------------------------- ### Add chip8_core dependency to wasm project Source: https://github.com/aquova/chip8-book/blob/master/src/7-wasm.md Edit wasm/Cargo.toml to include chip8_core as a dependency, specifying its local path. ```toml [dependencies] chip8_core = { path = "../chip8_core" } ``` -------------------------------- ### Import SDL Event Type Source: https://github.com/aquova/chip8-book/blob/master/src/6-frontend.md Imports the necessary Event enum from the sdl2 crate to handle user input and window events. ```rust use sdl2::event::Event; ``` -------------------------------- ### Set Sound Timer from VX (FX18) Source: https://github.com/aquova/chip8-book/blob/master/src/5-instr.md Copies the value from a V Register (VX) to the Sound Timer (ST). This instruction controls the duration of sound output. ```rust match (digit1, digit2, digit3, digit4) { // -- Unchanged code omitted -- // ST = VX (0xF, _, 1, 8) => { let x = digit2 as usize; self.st = self.v_reg[x]; }, // -- Unchanged code omitted -- } ``` -------------------------------- ### Convert SDL Keycode to Chip-8 Button Source: https://github.com/aquova/chip8-book/blob/master/src/6-frontend.md Maps SDL Keycode values to the 16-bit Chip-8 button representation (0-F). Returns None for keys not mapped to Chip-8. This function is crucial for translating user input into a format the emulator understands. ```rust fn key2btn(key: Keycode) -> Option { match key { Keycode::Num1 => Some(0x1), Keycode::Num2 => Some(0x2), Keycode::Num3 => Some(0x3), Keycode::Num4 => Some(0xC), Keycode::Q => Some(0x4), Keycode::W => Some(0x5), Keycode::E => Some(0x6), Keycode::R => Some(0xD), Keycode::A => Some(0x7), Keycode::S => Some(0x8), Keycode::D => Some(0x9), Keycode::F => Some(0xE), Keycode::Z => Some(0xA), Keycode::X => Some(0x0), Keycode::C => Some(0xB), Keycode::V => Some(0xF), _ => None, } } ``` -------------------------------- ### Initialize EmuWasm with Canvas Context Source: https://github.com/aquova/chip8-book/blob/master/src/7-wasm.md Rust code to initialize the EmuWasm struct, obtaining the HTML canvas element and its 2D rendering context from the browser's document. ```rust use wasm_bindgen::JsCast; use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, KeyboardEvent}; #[wasm_bindgen] pub struct EmuWasm { chip8: Emu, ctx: CanvasRenderingContext2d, } #[wasm_bindgen] impl EmuWasm { #[wasm_bindgen(constructor)] pub fn new() -> Result { let chip8 = Emu::new(); let document = web_sys::window().unwrap().document().unwrap(); let canvas = document.get_element_by_id("canvas").unwrap(); let canvas: HtmlCanvasElement = canvas .dyn_into::() .map_err(|_| ()) .unwrap(); let ctx = canvas.get_context("2d") .unwrap().unwrap() .dyn_into::() .unwrap(); Ok(EmuWasm{chip8, ctx}) } // -- Unchanged code omitted -- } ``` -------------------------------- ### Implementing NOP opcode Source: https://github.com/aquova/chip8-book/blob/master/src/5-instr.md Implementation of the 0000 opcode (NOP - No Operation) which does nothing and proceeds to the next opcode. ```rust match (digit1, digit2, digit3, digit4) { // NOP (0, 0, 0, 0) => return, (_, _, _, _) => unimplemented!("Unimplemented opcode: {}", op), } ``` -------------------------------- ### Read Command Line Arguments Source: https://github.com/aquova/chip8-book/blob/master/src/6-frontend.md Collects command line arguments into a vector. Exits if the number of arguments is not exactly two (program name + ROM path). ```rust use std::env; fn main() { let args: Vec<_> = env::args().collect(); if args.len() != 2 { println!("Usage: cargo run path/to/game"); return; } } ``` -------------------------------- ### Function to Draw Emulator Screen Buffer Source: https://github.com/aquova/chip8-book/blob/master/src/6-frontend.md Clears the canvas, iterates through the emulator's display buffer, and draws white rectangles for active pixels. Assumes SCREEN_WIDTH and SCALE constants are defined. ```rust fn draw_screen(emu: &Emu, canvas: &mut Canvas) { // Clear canvas as black canvas.set_draw_color(Color::RGB(0, 0, 0)); canvas.clear(); let screen_buf = emu.get_display(); // Now set draw color to white, iterate through each point and see if it should be drawn canvas.set_draw_color(Color::RGB(255, 255, 255)); for (i, pixel) in screen_buf.iter().enumerate() { if *pixel { // Convert our 1D array's index into a 2D (x,y) position let x = (i % SCREEN_WIDTH) as u32; let y = (i / SCREEN_WIDTH) as u32; // Draw a rectangle at (x,y), scaled up by our SCALE value let rect = Rect::new((x * SCALE) as i32, (y * SCALE) as i32, SCALE, SCALE); canvas.fill_rect(rect).unwrap(); } } canvas.present(); } ``` -------------------------------- ### Implement Skip if VX != NN (4XNN) Source: https://github.com/aquova/chip8-book/blob/master/src/5-instr.md Compares the value in register VX with NN. If they are not equal, the program counter is incremented by 2 to skip the next instruction. ```Rust match (digit1, digit2, digit3, digit4) { // -- Unchanged code omitted -- // SKIP VX != NN (4, _, _, _) => { let x = digit2 as usize; let nn = (op & 0xFF) as u8; if self.v_reg[x] != nn { self.pc += 2; } }, // -- Unchanged code omitted -- } ``` -------------------------------- ### Implement Call Subroutine (CALL NNN) Source: https://github.com/aquova/chip8-book/blob/master/src/5-instr.md Pushes the current program counter onto the stack and then jumps to the specified address NNN. This is the counterpart to the RET instruction. ```Rust match (digit1, digit2, digit3, digit4) { // -- Unchanged code omitted -- // CALL NNN (2, _, _, _) => { let nnn = op & 0xFFF; self.push(self.pc); self.pc = nnn; }, // -- Unchanged code omitted -- } ``` -------------------------------- ### Add KeyboardEvent Feature to web-sys Source: https://github.com/aquova/chip8-book/blob/master/src/7-wasm.md Enable the 'KeyboardEvent' feature for the 'web-sys' dependency in Cargo.toml to handle keyboard events from JavaScript. ```toml [dependencies.web-sys] version = "^0.3.46" features = [ "KeyboardEvent" ] ``` -------------------------------- ### Skip if Key Not Pressed Opcode (EXA1) Source: https://github.com/aquova/chip8-book/blob/master/src/5-instr.md Skips the next instruction if the key corresponding to the value in VX is not pressed. Requires the 'keys' array to be populated with input states. ```rust match (digit1, digit2, digit3, digit4) { // -- Unchanged code omitted -- // SKIP KEY RELEASE (0xE, _, 0xA, 1) => { let x = digit2 as usize; let vx = self.v_reg[x]; let key = self.keys[vx as usize]; if !key { self.pc += 2; } }, // -- Unchanged code omitted -- } ``` -------------------------------- ### Implement Skip if VX == NN (3XNN) Source: https://github.com/aquova/chip8-book/blob/master/src/5-instr.md Compares the value in register VX with NN. If they are equal, the program counter is incremented by 2 to skip the next instruction. ```Rust match (digit1, digit2, digit3, digit4) { // -- Unchanged code omitted -- // SKIP VX == NN (3, _, _, _) => { let x = digit2 as usize; let nn = (op & 0xFF) as u8; if self.v_reg[x] == nn { self.pc += 2; } }, // -- Unchanged code omitted -- } ``` -------------------------------- ### Implement Return from Subroutine (RET) Source: https://github.com/aquova/chip8-book/blob/master/src/5-instr.md Pops the return address from the stack and sets the program counter to it. This is used to return from a subroutine call. ```Rust match (digit1, digit2, digit3, digit4) { // -- Unchanged code omitted -- // RET (0, 0, 0xE, 0xE) => { let ret_addr = self.pop(); self.pc = ret_addr; }, // -- Unchanged code omitted -- } ``` -------------------------------- ### Define WASM-compatible Emulator Struct Source: https://github.com/aquova/chip8-book/blob/master/src/7-wasm.md Create a Rust struct annotated with `#[wasm_bindgen]` to hold the CHIP-8 emulator core and expose it to JavaScript. ```rust use chip8_core::* use wasm_bindgen::prelude::* #[wasm_bindgen] pub struct EmuWasm { chip8: Emu, } ``` -------------------------------- ### Expose Screen Buffer Source: https://github.com/aquova/chip8-book/blob/master/src/6-frontend.md Provides read-only access to the emulator's screen buffer for rendering. ```rust impl Emu { // -- Unchanged code omitted -- pub fn get_display(&self) -> &[bool] { &self.screen } // -- Unchanged code omitted -- } ``` -------------------------------- ### WASM Dependencies in Cargo.toml Source: https://github.com/aquova/chip8-book/blob/master/src/7-wasm.md Specifies the necessary web-sys crate features for interacting with browser APIs like CanvasRenderingContext2d, Document, and Window. ```toml [dependencies.web-sys] version = "^0.3.46" features = [ "CanvasRenderingContext2d", "Document", "Element", "HtmlCanvasElement", "ImageData", "KeyboardEvent", "Window" ] ``` -------------------------------- ### Implement Jump to V0 + NNN Source: https://github.com/aquova/chip8-book/blob/master/src/5-instr.md Sets the program counter (PC) to the sum of the value in register V0 and the 12-bit value NNN from the opcode. This instruction always uses V0. ```rust match (digit1, digit2, digit3, digit4) { // -- Unchanged code omitted -- // JMP V0 + NNN (0xB, _, _, _) => { let nnn = op & 0xFFF; self.pc = (self.v_reg[0] as u16) + nnn; }, // -- Unchanged code omitted -- } ``` -------------------------------- ### Chip-8 Emulator Structure Source: https://github.com/aquova/chip8-book/blob/master/src/3-setup.md Defines the main structure for the Chip-8 emulator, including program counter, RAM, screen buffer, general-purpose registers, index register, stack pointer, stack, and constants for sizes. ```rust pub const SCREEN_WIDTH: usize = 64; pub const SCREEN_HEIGHT: usize = 32; const RAM_SIZE: usize = 4096; const NUM_REGS: usize = 16; const STACK_SIZE: usize = 16; pub struct Emu { pc: u16, ram: [u8; RAM_SIZE], screen: [bool; SCREEN_WIDTH * SCREEN_HEIGHT], v_reg: [u8; NUM_REGS], i_reg: u16, sp: u16, stack: [u16; STACK_SIZE], } ``` -------------------------------- ### Main Game Loop with Emulator Tick Source: https://github.com/aquova/chip8-book/blob/master/src/6-frontend.md Integrates the emulator's tick function into the main game loop to process instructions on each cycle. ```rust fn main() { // -- Unchanged code omitted -- 'gameloop: loop { for event in event_pump.poll_iter() { // -- Unchanged code omitted -- } chip8.tick(); } } ``` -------------------------------- ### Define Emulator Struct Source: https://github.com/aquova/chip8-book/blob/master/src/3-setup.md Defines the main struct for the emulator backend in Rust. This struct will manage the game state and interact with the frontend. ```rust pub struct Emu { } ``` -------------------------------- ### Basic match statement for opcodes Source: https://github.com/aquova/chip8-book/blob/master/src/5-instr.md A basic Rust match statement structure to handle different opcode patterns, with a fallback for unimplemented opcodes. ```rust fn execute(&mut self, op: u16) { let digit1 = (op & 0xF000) >> 12; let digit2 = (op & 0x0F00) >> 8; let digit3 = (op & 0x00F0) >> 4; let digit4 = op & 0x000F; match (digit1, digit2, digit3, digit4) { (_, _, _, _) => unimplemented!("Unimplemented opcode: {}", op), } } ``` -------------------------------- ### Add Program Counter to Emulator Struct Source: https://github.com/aquova/chip8-book/blob/master/src/3-setup.md Adds the program counter (pc) field to the Emu struct. The PC tracks the current instruction being executed in the game program. ```rust pub struct Emu { pc: u16, } ```