### 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
```
--------------------------------
### 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,
}
```